diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index 2f8874de7acb..44fd3bea94c7 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -38,6 +38,7 @@ import compiler_warnings from './compiler_warnings'; import compiler_errors from './compiler_errors'; import { extract_ignores_above_position, extract_svelte_ignore_from_comments } from '../utils/extract_svelte_ignore'; import check_enable_sourcemap from './utils/check_enable_sourcemap'; +import is_dynamic from './render_dom/wrappers/shared/is_dynamic'; interface ComponentOptions { namespace?: string; @@ -1340,12 +1341,11 @@ export default class Component { module_dependencies.add(name); } } - const is_writable_or_mutated = - variable && (variable.writable || variable.mutated); + if ( should_add_as_dependency && (!owner || owner === component.instance_scope) && - (name[0] === '$' || is_writable_or_mutated) + (name[0] === '$' || variable) ) { dependencies.add(name); } @@ -1369,6 +1369,17 @@ export default class Component { const { expression } = node.body as ExpressionStatement; const declaration = expression && (expression as AssignmentExpression).left; + const is_dependency_static = Array.from(dependencies).every(dependency => dependency !== '$$props' && !is_dynamic(this.var_lookup.get(dependency))); + + if (is_dependency_static) { + assignees.forEach(assignee => { + const variable = component.var_lookup.get(assignee); + if (variable) { + variable.is_reactive_static = true; + } + }); + } + unsorted_reactive_declarations.push({ assignees, dependencies, diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index 173d93d5dda7..b300017daf01 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -385,11 +385,21 @@ export default function dom( ${component.compile_options.dev && b`@validate_store(${name.slice(1)}, '${name.slice(1)}');`} @component_subscribe($$self, ${name.slice(1)}, $$value => $$invalidate(${renderer.context_lookup.get(name).index}, ${name} = $$value)); `); + + // const reactive_store_non_hoistable_subscriptions = reactive_stores + // .filter(store => { + // const variable = component.var_lookup.get(store.name.slice(1)); + // return !variable || variable.hoistable; + // }) + // .map(({ name }) => b` + // ${component.compile_options.dev && b`@validate_store(${name.slice(1)}, '${name.slice(1)}');`} + // @component_subscribe($$self, ${name.slice(1)}, $$value => $$invalidate(${renderer.context_lookup.get(name).index}, ${name} = $$value)); + // `); const resubscribable_reactive_store_unsubscribers = reactive_stores .filter(store => { const variable = component.var_lookup.get(store.name.slice(1)); - return variable && (variable.reassigned || variable.export_name); + return variable && (variable.reassigned || variable.export_name) && !variable.is_reactive_static; }) .map(({ name }) => b`$$self.$$.on_destroy.push(() => ${`$$unsubscribe_${name.slice(1)}`}());`); @@ -416,6 +426,15 @@ export default function dom( reactive_declarations.push(statement); } else { fixed_reactive_declarations.push(statement); + for (const assignee of d.assignees) { + const variable = component.var_lookup.get(assignee); + if (variable && variable.subscribable) { + fixed_reactive_declarations.push(b` + ${component.compile_options.dev && b`@validate_store(${assignee}, '${assignee}');`} + @component_subscribe($$self, ${assignee}, $$value => $$invalidate(${renderer.context_lookup.get('$' + assignee).index}, ${'$' + assignee} = $$value)); + `); + } + } } }); @@ -429,7 +448,7 @@ export default function dom( const name = $name.slice(1); const store = component.var_lookup.get(name); - if (store && (store.reassigned || store.export_name)) { + if (store && (store.reassigned || store.export_name) && !store.is_reactive_static) { const unsubscribe = `$$unsubscribe_${name}`; const subscribe = `$$subscribe_${name}`; const i = renderer.context_lookup.get($name).index; diff --git a/src/compiler/compile/render_dom/invalidate.ts b/src/compiler/compile/render_dom/invalidate.ts index 7eeb34a89400..acd521462046 100644 --- a/src/compiler/compile/render_dom/invalidate.ts +++ b/src/compiler/compile/render_dom/invalidate.ts @@ -19,6 +19,7 @@ export function invalidate(renderer: Renderer, scope: Scope, node: Node, names: !variable.hoistable && !variable.global && !variable.module && + !variable.is_reactive_static && ( variable.referenced || variable.subscribable || diff --git a/src/compiler/interfaces.ts b/src/compiler/interfaces.ts index 248175da5995..da276039af9c 100644 --- a/src/compiler/interfaces.ts +++ b/src/compiler/interfaces.ts @@ -222,6 +222,7 @@ export interface Var { subscribable?: boolean; is_reactive_dependency?: boolean; imported?: boolean; + is_reactive_static?: boolean; } export interface CssResult { diff --git a/test/js/samples/reactive-class-optimized/expected.js b/test/js/samples/reactive-class-optimized/expected.js index 1d0606ad60a2..f75a4015b0e3 100644 --- a/test/js/samples/reactive-class-optimized/expected.js +++ b/test/js/samples/reactive-class-optimized/expected.js @@ -9,7 +9,6 @@ import { noop, safe_not_equal, space, - subscribe, toggle_class } from "svelte/internal"; @@ -133,13 +132,8 @@ let reactiveModuleVar = Math.random(); function instance($$self, $$props, $$invalidate) { let reactiveDeclaration; let $reactiveStoreVal; - - let $reactiveDeclaration, - $$unsubscribe_reactiveDeclaration = noop, - $$subscribe_reactiveDeclaration = () => ($$unsubscribe_reactiveDeclaration(), $$unsubscribe_reactiveDeclaration = subscribe(reactiveDeclaration, $$value => $$invalidate(3, $reactiveDeclaration = $$value)), reactiveDeclaration); - + let $reactiveDeclaration; component_subscribe($$self, reactiveStoreVal, $$value => $$invalidate(2, $reactiveStoreVal = $$value)); - $$self.$$.on_destroy.push(() => $$unsubscribe_reactiveDeclaration()); nonReactiveGlobal = Math.random(); const reactiveConst = { x: Math.random() }; reactiveModuleVar += 1; @@ -148,7 +142,8 @@ function instance($$self, $$props, $$invalidate) { reactiveConst.x += 1; } - $: $$subscribe_reactiveDeclaration($$invalidate(1, reactiveDeclaration = reactiveModuleVar * 2)); + $: reactiveDeclaration = reactiveModuleVar * 2; + component_subscribe($$self, reactiveDeclaration, $$value => $$invalidate(3, $reactiveDeclaration = $$value)); return [reactiveConst, reactiveDeclaration, $reactiveStoreVal, $reactiveDeclaration]; } diff --git a/test/js/samples/reactive-values/expected.js b/test/js/samples/reactive-values/expected.js new file mode 100644 index 000000000000..7ed435d6ad99 --- /dev/null +++ b/test/js/samples/reactive-values/expected.js @@ -0,0 +1,60 @@ +/* generated by Svelte vX.Y.Z */ +import { + SvelteComponent, + detach, + element, + init, + insert, + noop, + safe_not_equal, + set_data, + space, + text +} from "svelte/internal"; + +function create_fragment(ctx) { + let h1; + let t3; + let t4; + + return { + c() { + h1 = element("h1"); + h1.textContent = `Hello ${name}!`; + t3 = space(); + t4 = text(/*foo*/ ctx[0]); + }, + m(target, anchor) { + insert(target, h1, anchor); + insert(target, t3, anchor); + insert(target, t4, anchor); + }, + p(ctx, [dirty]) { + if (dirty & /*foo*/ 1) set_data(t4, /*foo*/ ctx[0]); + }, + i: noop, + o: noop, + d(detaching) { + if (detaching) detach(h1); + if (detaching) detach(t3); + if (detaching) detach(t4); + } + }; +} + +let name = 'world'; + +function instance($$self) { + let foo; + $: foo = name + name; + return [foo]; +} + +class Component extends SvelteComponent { + constructor(options) { + super(); + init(this, options, instance, create_fragment, safe_not_equal, {}); + } +} + +export default Component; \ No newline at end of file diff --git a/test/js/samples/reactive-values/input.svelte b/test/js/samples/reactive-values/input.svelte new file mode 100644 index 000000000000..d71321777123 --- /dev/null +++ b/test/js/samples/reactive-values/input.svelte @@ -0,0 +1,7 @@ + + +