From 71183182246e9f4738abdca60a2d121effa39910 Mon Sep 17 00:00:00 2001 From: Richard Harris Date: Sun, 17 Nov 2019 17:09:24 -0500 Subject: [PATCH] start implementing bitmask-based change tracking (#1943) --- src/compiler/compile/Component.ts | 63 +----------- src/compiler/compile/nodes/Action.ts | 2 +- src/compiler/compile/nodes/Animation.ts | 2 +- src/compiler/compile/nodes/Transition.ts | 2 +- .../compile/nodes/shared/Expression.ts | 45 ++++----- src/compiler/compile/render_dom/Block.ts | 2 +- src/compiler/compile/render_dom/Renderer.ts | 96 +++++++++++++++++++ src/compiler/compile/render_dom/index.ts | 49 +++++----- .../{utils => render_dom}/invalidate.ts | 17 ++-- .../compile/render_dom/wrappers/AwaitBlock.ts | 7 +- .../compile/render_dom/wrappers/DebugTag.ts | 3 +- .../compile/render_dom/wrappers/EachBlock.ts | 23 +++-- .../render_dom/wrappers/Element/Attribute.ts | 15 ++- .../render_dom/wrappers/Element/Binding.ts | 3 +- .../wrappers/Element/EventHandler.ts | 11 ++- .../wrappers/Element/StyleAttribute.ts | 3 +- .../render_dom/wrappers/Element/index.ts | 43 ++++----- .../compile/render_dom/wrappers/IfBlock.ts | 23 +++-- .../wrappers/InlineComponent/index.ts | 26 ++--- .../compile/render_dom/wrappers/Slot.ts | 7 +- .../compile/render_dom/wrappers/Title.ts | 3 +- .../compile/render_dom/wrappers/Window.ts | 29 ++---- .../compile/render_dom/wrappers/shared/Tag.ts | 3 +- .../render_dom/wrappers/shared/add_actions.ts | 12 +-- .../render_dom/wrappers/shared/bind_this.ts | 21 ++-- .../render_dom/wrappers/shared/changed.ts | 7 -- src/runtime/internal/Component.ts | 22 ++--- src/runtime/internal/keyed_each.ts | 2 +- src/runtime/internal/scheduler.ts | 4 +- .../action-custom-event-handler/expected.js | 4 +- test/runtime/index.js | 2 +- .../action-custom-event-handler/_config.js | 2 +- 32 files changed, 276 insertions(+), 277 deletions(-) rename src/compiler/compile/{utils => render_dom}/invalidate.ts (73%) delete mode 100644 src/compiler/compile/render_dom/wrappers/shared/changed.ts diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index 2d696ad306..ff63e7718e 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -889,48 +889,8 @@ export default class Component { return null; } - invalidate(name, value?) { - const variable = this.var_lookup.get(name); - - if (variable && (variable.subscribable && (variable.reassigned || variable.export_name))) { - return x`${`$$subscribe_${name}`}($$invalidate('${name}', ${value || name}))`; - } - - if (name[0] === '$' && name[1] !== '$') { - return x`${name.slice(1)}.set(${value || name})`; - } - - if ( - variable && - !variable.referenced && - !variable.is_reactive_dependency && - !variable.export_name && - !name.startsWith('$$') - ) { - return value || name; - } - - if (value) { - return x`$$invalidate('${name}', ${value})`; - } - - // if this is a reactive declaration, invalidate dependencies recursively - const deps = new Set([name]); - - deps.forEach(name => { - const reactive_declarations = this.reactive_declarations.filter(x => - x.assignees.has(name) - ); - reactive_declarations.forEach(declaration => { - declaration.dependencies.forEach(name => { - deps.add(name); - }); - }); - }); - - return Array.from(deps) - .map(n => x`$$invalidate('${n}', ${n})`) - .reduce((lhs, rhs) => x`${lhs}, ${rhs}}`); + invalidate(_name, _value?) { + throw new Error(`invalidate method now belongs to Renderer`); } rewrite_props(get_insert: (variable: Var) => Node[]) { @@ -1325,23 +1285,8 @@ export default class Component { }); } - qualify(name) { - if (name === `$$props`) return x`#ctx.$$props`; - - let [head, ...tail] = name.split('.'); - - const variable = this.var_lookup.get(head); - - if (variable) { - this.add_reference(name); // TODO we can probably remove most other occurrences of this - - if (!variable.hoistable) { - tail.unshift(head); - head = '#ctx'; - } - } - - return [head, ...tail].reduce((lhs, rhs) => x`${lhs}.${rhs}`); + qualify(_name) { + throw new Error(`component.qualify is now renderer.reference`); } warn_if_undefined(name: string, node, template_scope: TemplateScope) { diff --git a/src/compiler/compile/nodes/Action.ts b/src/compiler/compile/nodes/Action.ts index 77b9e3c846..86aefa0ced 100644 --- a/src/compiler/compile/nodes/Action.ts +++ b/src/compiler/compile/nodes/Action.ts @@ -14,7 +14,7 @@ export default class Action extends Node { component.warn_if_undefined(info.name, info, scope); this.name = info.name; - component.qualify(info.name); + component.add_reference(info.name.split('.')[0]); this.expression = info.expression ? new Expression(component, this, scope, info.expression) diff --git a/src/compiler/compile/nodes/Animation.ts b/src/compiler/compile/nodes/Animation.ts index ef48b4d745..10cc9364a0 100644 --- a/src/compiler/compile/nodes/Animation.ts +++ b/src/compiler/compile/nodes/Animation.ts @@ -13,7 +13,7 @@ export default class Animation extends Node { component.warn_if_undefined(info.name, info, scope); this.name = info.name; - component.qualify(info.name); + component.add_reference(info.name.split('.')[0]); if (parent.animation) { component.error(this, { diff --git a/src/compiler/compile/nodes/Transition.ts b/src/compiler/compile/nodes/Transition.ts index ceb5880e64..a680fde46e 100644 --- a/src/compiler/compile/nodes/Transition.ts +++ b/src/compiler/compile/nodes/Transition.ts @@ -15,7 +15,7 @@ export default class Transition extends Node { component.warn_if_undefined(info.name, info, scope); this.name = info.name; - component.qualify(info.name); + component.add_reference(info.name.split('.')[0]); this.directive = info.intro && info.outro ? 'transition' : info.intro ? 'in' : 'out'; this.is_local = info.modifiers.includes('local'); diff --git a/src/compiler/compile/nodes/shared/Expression.ts b/src/compiler/compile/nodes/shared/Expression.ts index ad4a1bc24d..5d8ee18c0f 100644 --- a/src/compiler/compile/nodes/shared/Expression.ts +++ b/src/compiler/compile/nodes/shared/Expression.ts @@ -9,9 +9,9 @@ import TemplateScope from './TemplateScope'; import get_object from '../../utils/get_object'; import Block from '../../render_dom/Block'; import is_dynamic from '../../render_dom/wrappers/shared/is_dynamic'; -import { x, b, p } from 'code-red'; -import { invalidate } from '../../utils/invalidate'; -import { Node, FunctionExpression } from 'estree'; +import { x, b } from 'code-red'; +import { invalidate } from '../../render_dom/invalidate'; +import { Node, FunctionExpression, Identifier } from 'estree'; import { TemplateNode } from '../../../interfaces'; type Owner = Wrapper | TemplateNode; @@ -213,7 +213,9 @@ export default class Expression { component.add_reference(name); // TODO is this redundant/misplaced? } } else if (is_contextual(component, template_scope, name)) { - this.replace(x`#ctx.${node}`); + if (block) { // TODO not sure what's going on here — DOM only, maybe? + this.replace(block.renderer.reference(name)); + } } this.skip(); @@ -260,42 +262,37 @@ export default class Expression { // function can be hoisted inside the component init component.partly_hoisted.push(declaration); - this.replace(x`#ctx.${id}` as any); - - component.add_var({ - name: id.name, - internal: true, - referenced: true - }); + const i = block.renderer.add_to_context(id.name); + this.replace(x`#ctx[${i}]` as any); } else { // we need a combo block/init recipe - (node as FunctionExpression).params.unshift({ - type: 'ObjectPattern', - properties: Array.from(contextual_dependencies).map(name => p`${name}` as any) - }); + const deps = Array.from(contextual_dependencies); + + (node as FunctionExpression).params = [ + ...deps.map(name => ({ type: 'Identifier', name } as Identifier)), + ...(node as FunctionExpression).params + ]; + + const context_args = deps.map(name => block.renderer.reference(name)); component.partly_hoisted.push(declaration); - this.replace(id as any); + const i = block.renderer.add_to_context(id.name); - component.add_var({ - name: id.name, - internal: true, - referenced: true - }); + this.replace(id as any); if ((node as FunctionExpression).params.length > 0) { declarations.push(b` function ${id}(...args) { - return #ctx.${id}(#ctx, ...args); + return #ctx[${i}](${context_args}, ...args); } `); } else { declarations.push(b` function ${id}() { - return #ctx.${id}(#ctx); + return #ctx[${i}](${context_args}); } `); } @@ -329,7 +326,7 @@ export default class Expression { } }); - this.replace(invalidate(component, scope, node, traced)); + this.replace(invalidate(block.renderer, scope, node, traced)); } } }); diff --git a/src/compiler/compile/render_dom/Block.ts b/src/compiler/compile/render_dom/Block.ts index 74822ef9be..01359b7c18 100644 --- a/src/compiler/compile/render_dom/Block.ts +++ b/src/compiler/compile/render_dom/Block.ts @@ -302,7 +302,7 @@ export default class Block { properties.update = noop; } else { const ctx = this.maintain_context ? x`#new_ctx` : x`#ctx`; - properties.update = x`function #update(#changed, ${ctx}) { + properties.update = x`function #update(${ctx}, #changed) { ${this.maintain_context && b`#ctx = ${ctx};`} ${this.chunks.update} }`; diff --git a/src/compiler/compile/render_dom/Renderer.ts b/src/compiler/compile/render_dom/Renderer.ts index 624886c2c7..40c2d9234e 100644 --- a/src/compiler/compile/render_dom/Renderer.ts +++ b/src/compiler/compile/render_dom/Renderer.ts @@ -9,6 +9,8 @@ export default class Renderer { component: Component; // TODO Maybe Renderer shouldn't know about Component? options: CompileOptions; + context: string[] = []; + context_lookup: Map = new Map(); blocks: Array = []; readonly: Set = new Set(); meta_bindings: Array = []; // initial values for e.g. window.innerWidth, if there's a meta tag @@ -27,6 +29,11 @@ export default class Renderer { this.file_var = options.dev && this.component.get_unique_name('file'); + // TODO sort vars, most frequently referenced first? + component.vars + .filter(v => ((v.referenced || v.export_name) && !v.hoistable)) + .forEach(v => this.add_to_context(v.name)); + // main block this.block = new Block({ renderer: this, @@ -61,4 +68,93 @@ export default class Renderer { this.fragment.render(this.block, null, x`#nodes` as Identifier); } + + add_to_context(name: string, contextual = false) { + if (!this.context_lookup.has(name)) { + const i = this.context.length; + + this.context_lookup.set(name, i); + this.context.push(contextual ? null : name); + } + + return this.context_lookup.get(name); + } + + invalidate(name: string, value?) { + const variable = this.component.var_lookup.get(name); + const i = this.context_lookup.get(name); + + if (variable && (variable.subscribable && (variable.reassigned || variable.export_name))) { + return x`${`$$subscribe_${name}`}($$invalidate(${i}, ${value || name}))`; + } + + if (name[0] === '$' && name[1] !== '$') { + return x`${name.slice(1)}.set(${value || name})`; + } + + if ( + variable && + !variable.referenced && + !variable.is_reactive_dependency && + !variable.export_name && + !name.startsWith('$$') + ) { + return value || name; + } + + if (value) { + return x`$$invalidate(${i}, ${value})`; + } + + // if this is a reactive declaration, invalidate dependencies recursively + const deps = new Set([name]); + + deps.forEach(name => { + const reactive_declarations = this.component.reactive_declarations.filter(x => + x.assignees.has(name) + ); + reactive_declarations.forEach(declaration => { + declaration.dependencies.forEach(name => { + deps.add(name); + }); + }); + }); + + return Array.from(deps) + .map(n => x`$$invalidate(${i}, ${n})`) + .reduce((lhs, rhs) => x`${lhs}, ${rhs}}`); + } + + changed(names) { + const bitmask = names.reduce((bits, name) => { + const bit = 1 << this.context_lookup.get(name); + return bits | bit; + }, 0); + + return x`#changed & ${bitmask}`; + } + + reference(name) { + const i = this.context_lookup.get(name); + + if (name === `$$props`) return x`#ctx[${i}]`; + + let [head, ...tail] = name.split('.'); + + const variable = this.component.var_lookup.get(head); + + // TODO this feels woolly. might encounter false positive + // if each context shadows top-level var + if (variable) { + this.component.add_reference(name); // TODO we can probably remove most other occurrences of this + + if (!variable.hoistable) { + head = x`#ctx[${i}]`; + } + } else { + head = x`#ctx[${i}]`; + } + + return [head, ...tail].reduce((lhs, rhs) => x`${lhs}.${rhs}`); + } } diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index cc0c7dfe31..18f21e276a 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -5,9 +5,9 @@ import { CompileOptions } from '../../interfaces'; import { walk } from 'estree-walker'; import add_to_set from '../utils/add_to_set'; import { extract_names } from '../utils/scope'; -import { invalidate } from '../utils/invalidate'; +import { invalidate } from './invalidate'; import Block from './Block'; -import { ClassDeclaration, FunctionExpression, Node, Statement, ObjectExpression } from 'estree'; +import { ClassDeclaration, FunctionExpression, Node, Statement, ObjectExpression, Expression } from 'estree'; export default function dom( component: Component, @@ -80,12 +80,12 @@ export default function dom( const set = (uses_props || writable_props.length > 0 || component.slots.size > 0) ? x` ${$$props} => { - ${uses_props && component.invalidate('$$props', x`$$props = @assign(@assign({}, $$props), $$new_props)`)} + ${uses_props && renderer.invalidate('$$props', x`$$props = @assign(@assign({}, $$props), $$new_props)`)} ${writable_props.map(prop => - b`if ('${prop.export_name}' in ${$$props}) ${component.invalidate(prop.name, x`${prop.name} = ${$$props}.${prop.export_name}`)};` + b`if ('${prop.export_name}' in ${$$props}) ${renderer.invalidate(prop.name, x`${prop.name} = ${$$props}.${prop.export_name}`)};` )} ${component.slots.size > 0 && - b`if ('$$scope' in ${$$props}) ${component.invalidate('$$scope', x`$$scope = ${$$props}.$$scope`)};`} + b`if ('$$scope' in ${$$props}) ${renderer.invalidate('$$scope', x`$$scope = ${$$props}.$$scope`)};`} } ` : null; @@ -105,7 +105,7 @@ export default function dom( kind: 'get', key: { type: 'Identifier', name: prop.export_name }, value: x`function() { - return ${prop.hoistable ? prop.name : x`this.$$.ctx.${prop.name}`} + return ${prop.hoistable ? prop.name : x`this.$$.ctx[${renderer.context_lookup.get(prop.name)}]`} }` }); } else if (component.compile_options.dev) { @@ -180,9 +180,9 @@ export default function dom( const writable_vars = component.vars.filter(variable => !variable.module && variable.writable); inject_state = (uses_props || writable_vars.length > 0) ? x` ${$$props} => { - ${uses_props && component.invalidate('$$props', x`$$props = @assign(@assign({}, $$props), $$new_props)`)} + ${uses_props && renderer.invalidate('$$props', x`$$props = @assign(@assign({}, $$props), $$new_props)`)} ${writable_vars.map(prop => b` - if ('${prop.name}' in $$props) ${component.invalidate(prop.name, x`${prop.name} = ${$$props}.${prop.name}`)}; + if ('${prop.name}' in $$props) ${renderer.invalidate(prop.name, x`${prop.name} = ${$$props}.${prop.name}`)}; `)} } ` : x` @@ -216,17 +216,18 @@ export default function dom( // onto the initial function call const names = new Set(extract_names(assignee)); - this.replace(invalidate(component, scope, node, names)); + this.replace(invalidate(renderer, scope, node, names)); } } }); component.rewrite_props(({ name, reassigned, export_name }) => { const value = `$${name}`; + const i = renderer.context_lookup.get(name); const insert = (reassigned || export_name) ? b`${`$$subscribe_${name}`}()` - : b`@component_subscribe($$self, ${name}, #value => $$invalidate('${value}', ${value} = #value))`; + : b`@component_subscribe($$self, ${name}, #value => $$invalidate(${i}, ${value} = #value))`; if (component.compile_options.dev) { return b`@validate_store(${name}, '${name}'); ${insert}`; @@ -256,11 +257,13 @@ export default function dom( ${component.fully_hoisted} `); - const filtered_declarations = component.vars - .filter(v => ((v.referenced || v.export_name) && !v.hoistable)) - .map(v => p`${v.name}`); + const filtered_declarations = renderer.context + .map(name => name ? ({ + type: 'Identifier', + name + }) as Expression : x`null`); - if (uses_props) filtered_declarations.push(p`$$props: $$props = @exclude_internal_props($$props)`); + if (uses_props) filtered_declarations.push(x`$$props = @exclude_internal_props($$props)`); const filtered_props = props.filter(prop => { const variable = component.var_lookup.get(prop.name); @@ -273,11 +276,11 @@ export default function dom( const reactive_stores = component.vars.filter(variable => variable.name[0] === '$' && variable.name[1] !== '$'); if (component.slots.size > 0) { - filtered_declarations.push(p`$$slots`, p`$$scope`); + filtered_declarations.push(x`$$slots`, x`$$scope`); } if (renderer.binding_groups.length > 0) { - filtered_declarations.push(p`$$binding_groups`); + filtered_declarations.push(x`$$binding_groups`); } const instance_javascript = component.extract_javascript(component.ast.instance); @@ -307,7 +310,7 @@ export default function dom( }) .map(({ name }) => b` ${component.compile_options.dev && b`@validate_store(${name.slice(1)}, '${name.slice(1)}');`} - @component_subscribe($$self, ${name.slice(1)}, $$value => $$invalidate('${name}', ${name} = $$value)); + @component_subscribe($$self, ${name.slice(1)}, $$value => $$invalidate(${renderer.context_lookup.get(name)}, ${name} = $$value)); `); const resubscribable_reactive_store_unsubscribers = reactive_stores @@ -330,9 +333,7 @@ export default function dom( return variable && (variable.writable || variable.mutated); }); - const condition = !uses_props && writable.length > 0 && (writable - .map(n => x`#changed.${n}`) - .reduce((lhs, rhs) => x`${lhs} || ${rhs}`)); + const condition = !uses_props && writable.length > 0 && renderer.changed(writable); let statement = d.node; // TODO remove label (use d.node.body) if it's not referenced @@ -358,7 +359,9 @@ export default function dom( if (store && (store.reassigned || store.export_name)) { const unsubscribe = `$$unsubscribe_${name}`; const subscribe = `$$subscribe_${name}`; - return b`let ${$name}, ${unsubscribe} = @noop, ${subscribe} = () => (${unsubscribe}(), ${unsubscribe} = @subscribe(${name}, $$value => $$invalidate('${$name}', ${$name} = $$value)), ${name})`; + const i = renderer.context_lookup.get($name); + + return b`let ${$name}, ${unsubscribe} = @noop, ${subscribe} = () => (${unsubscribe}(), ${unsubscribe} = @subscribe(${name}, $$value => $$invalidate(${i}, ${$name} = $$value)), ${name})`; } return b`let ${$name};`; @@ -375,8 +378,8 @@ export default function dom( } const return_value = { - type: 'ObjectExpression', - properties: filtered_declarations + type: 'ArrayExpression', + elements: filtered_declarations }; const reactive_dependencies = { diff --git a/src/compiler/compile/utils/invalidate.ts b/src/compiler/compile/render_dom/invalidate.ts similarity index 73% rename from src/compiler/compile/utils/invalidate.ts rename to src/compiler/compile/render_dom/invalidate.ts index f386991d04..d51d7fe787 100644 --- a/src/compiler/compile/utils/invalidate.ts +++ b/src/compiler/compile/render_dom/invalidate.ts @@ -1,10 +1,12 @@ -import Component from '../Component'; import { nodes_match } from '../../utils/nodes_match'; -import { Scope } from './scope'; +import { Scope } from '../utils/scope'; import { x } from 'code-red'; import { Node } from 'estree'; +import Renderer from './Renderer'; + +export function invalidate(renderer: Renderer, scope: Scope, node: Node, names: Set) { + const { component } = renderer; -export function invalidate(component: Component, scope: Scope, node: Node, names: Set) { const [head, ...tail] = Array.from(names).filter(name => { const owner = scope.find_owner(name); if (owner && owner !== component.instance_scope) return false; @@ -28,12 +30,12 @@ export function invalidate(component: Component, scope: Scope, node: Node, names component.has_reactive_assignments = true; if (node.type === 'AssignmentExpression' && node.operator === '=' && nodes_match(node.left, node.right) && tail.length === 0) { - return component.invalidate(head); + return renderer.invalidate(head); } else { const is_store_value = head[0] === '$'; const variable = component.var_lookup.get(head); - const extra_args = tail.map(name => component.invalidate(name)); + const extra_args = tail.map(name => renderer.invalidate(name)); const pass_value = ( extra_args.length > 0 || @@ -48,8 +50,9 @@ export function invalidate(component: Component, scope: Scope, node: Node, names }); } - const callee = is_store_value ? `@set_store_value` : `$$invalidate`; - let invalidate = x`${callee}(${is_store_value ? head.slice(1) : x`"${head}"`}, ${node}, ${extra_args})`; + let invalidate = is_store_value + ? x`@set_store_value(${head.slice(1)}, ${node}, ${extra_args})` + : x`$$invalidate(${renderer.context_lookup.get(head)}, ${node}, ${extra_args})`; if (variable.subscribable && variable.reassigned) { const subscribe = `$$subscribe_${head}`; diff --git a/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts b/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts index d20d8f6f94..e6dac09a11 100644 --- a/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts @@ -8,7 +8,6 @@ import FragmentWrapper from './Fragment'; import PendingBlock from '../../nodes/PendingBlock'; import ThenBlock from '../../nodes/ThenBlock'; import CatchBlock from '../../nodes/CatchBlock'; -import { changed } from './shared/changed'; import { Identifier } from 'estree'; class AwaitBlockBranch extends Wrapper { @@ -187,7 +186,7 @@ export default class AwaitBlockWrapper extends Wrapper { if (dependencies.length > 0) { const condition = x` - ${changed(dependencies)} && + ${block.renderer.changed(dependencies)} && ${promise} !== (${promise} = ${snippet}) && @handle_promise(${promise}, ${info})`; @@ -200,7 +199,7 @@ export default class AwaitBlockWrapper extends Wrapper { if (${condition}) { // nothing } else { - ${info}.block.p(#changed, @assign(@assign({}, #ctx), ${info}.resolved)); + ${info}.block.p(@assign(@assign({}, #ctx), ${info}.resolved), #changed); } `); } else { @@ -211,7 +210,7 @@ export default class AwaitBlockWrapper extends Wrapper { } else { if (this.pending.block.has_update_method) { block.chunks.update.push(b` - ${info}.block.p(#changed, @assign(@assign({}, #ctx), ${info}.resolved)); + ${info}.block.p(@assign(@assign({}, #ctx), ${info}.resolved), #changed); `); } } diff --git a/src/compiler/compile/render_dom/wrappers/DebugTag.ts b/src/compiler/compile/render_dom/wrappers/DebugTag.ts index dc3a2f2857..4e8fe1e07d 100644 --- a/src/compiler/compile/render_dom/wrappers/DebugTag.ts +++ b/src/compiler/compile/render_dom/wrappers/DebugTag.ts @@ -5,7 +5,6 @@ import DebugTag from '../../nodes/DebugTag'; import add_to_set from '../../utils/add_to_set'; import { b, p } from 'code-red'; import { Identifier, DebuggerStatement } from 'estree'; -import { changed } from './shared/changed'; export default class DebugTagWrapper extends Wrapper { node: DebugTag; @@ -70,7 +69,7 @@ export default class DebugTagWrapper extends Wrapper { debugger;`; if (dependencies.size) { - const condition = changed(Array.from(dependencies)); + const condition = renderer.changed(Array.from(dependencies)); block.chunks.update.push(b` if (${condition}) { diff --git a/src/compiler/compile/render_dom/wrappers/EachBlock.ts b/src/compiler/compile/render_dom/wrappers/EachBlock.ts index 33b4a9cbd5..244e879e81 100644 --- a/src/compiler/compile/render_dom/wrappers/EachBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/EachBlock.ts @@ -7,7 +7,6 @@ import FragmentWrapper from './Fragment'; import { b, x } from 'code-red'; import ElseBlock from '../../nodes/ElseBlock'; import { Identifier, Node } from 'estree'; -import { changed } from './shared/changed'; export class ElseBlockWrapper extends Wrapper { node: ElseBlock; @@ -81,6 +80,10 @@ export default class EachBlockWrapper extends Wrapper { const { dependencies } = node.expression; block.add_dependencies(dependencies); + this.node.contexts.forEach(context => { + renderer.add_to_context(context.key.name, true); + }); + this.block = block.child({ comment: create_debugging_comment(this.node, this.renderer.component), name: renderer.component.get_unique_name('create_each_block'), @@ -119,6 +122,9 @@ export default class EachBlockWrapper extends Wrapper { const each_block_value = renderer.component.get_unique_name(`${this.var.name}_value`); const iterations = block.get_unique_name(`${this.var.name}_blocks`); + renderer.add_to_context(each_block_value.name, true); + renderer.add_to_context(this.index_name.name, true); + this.vars = { create_each_block: this.block.name, each_block_value, @@ -190,18 +196,19 @@ export default class EachBlockWrapper extends Wrapper { ? !this.next.is_dom_node() : !parent_node || !this.parent.is_dom_node(); - this.context_props = this.node.contexts.map(prop => b`child_ctx.${prop.key.name} = ${prop.modifier(x`list[i]`)};`); + this.context_props = this.node.contexts.map(prop => b`child_ctx[${renderer.context_lookup.get(prop.key.name)}] = ${prop.modifier(x`list[i]`)};`); - if (this.node.has_binding) this.context_props.push(b`child_ctx.${this.vars.each_block_value} = list;`); - if (this.node.has_binding || this.node.index) this.context_props.push(b`child_ctx.${this.index_name} = i;`); + if (this.node.has_binding) this.context_props.push(b`child_ctx[${renderer.context_lookup.get(this.vars.each_block_value.name)}] = list;`); + if (this.node.has_binding || this.node.index) this.context_props.push(b`child_ctx[${renderer.context_lookup.get(this.index_name.name)}] = i;`); const snippet = this.node.expression.manipulate(block); block.chunks.init.push(b`let ${this.vars.each_block_value} = ${snippet};`); + // TODO which is better — Object.create(array) or array.slice()? renderer.blocks.push(b` function ${this.vars.get_each_context}(#ctx, list, i) { - const child_ctx = @_Object.create(#ctx); + const child_ctx = #ctx.slice(); ${this.context_props} return child_ctx; } @@ -270,7 +277,7 @@ export default class EachBlockWrapper extends Wrapper { if (this.else.block.has_update_method) { block.chunks.update.push(b` if (!${this.vars.data_length} && ${each_block_else}) { - ${each_block_else}.p(#changed, #ctx); + ${each_block_else}.p(#ctx, #changed); } else if (!${this.vars.data_length}) { ${each_block_else} = ${this.else.block.name}(#ctx); ${each_block_else}.c(); @@ -481,7 +488,7 @@ export default class EachBlockWrapper extends Wrapper { const for_loop_body = this.block.has_update_method ? b` if (${iterations}[#i]) { - ${iterations}[#i].p(#changed, child_ctx); + ${iterations}[#i].p(child_ctx, #changed); ${has_transitions && b`@transition_in(${this.vars.iterations}[#i], 1);`} } else { ${iterations}[#i] = ${create_each_block}(child_ctx); @@ -554,7 +561,7 @@ export default class EachBlockWrapper extends Wrapper { `; block.chunks.update.push(b` - if (${changed(Array.from(all_dependencies))}) { + if (${block.renderer.changed(Array.from(all_dependencies))}) { ${update} } `); diff --git a/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts b/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts index db3cf7a662..eb9dfe8e47 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts @@ -6,7 +6,6 @@ import { string_literal } from '../../../utils/stringify'; import { b, x } from 'code-red'; import Expression from '../../../nodes/shared/Expression'; import Text from '../../../nodes/Text'; -import { changed } from '../shared/changed'; export default class AttributeWrapper { node: Attribute; @@ -140,7 +139,7 @@ export default class AttributeWrapper { } if (dependencies.length > 0) { - let condition = changed(dependencies); + let condition = block.renderer.changed(dependencies); if (should_cache) { condition = is_src @@ -197,8 +196,8 @@ export default class AttributeWrapper { } let value = this.node.name === 'class' - ? this.get_class_name_text() - : this.render_chunks().reduce((lhs, rhs) => x`${lhs} + ${rhs}`); + ? this.get_class_name_text(block) + : this.render_chunks(block).reduce((lhs, rhs) => x`${lhs} + ${rhs}`); // '{foo} {bar}' — treat as string concatenation if (this.node.chunks[0].type !== 'Text') { @@ -208,9 +207,9 @@ export default class AttributeWrapper { return value; } - get_class_name_text() { + get_class_name_text(block) { const scoped_css = this.node.chunks.some((chunk: Text) => chunk.synthetic); - const rendered = this.render_chunks(); + const rendered = this.render_chunks(block); if (scoped_css && rendered.length === 2) { // we have a situation like class={possiblyUndefined} @@ -220,13 +219,13 @@ export default class AttributeWrapper { return rendered.reduce((lhs, rhs) => x`${lhs} + ${rhs}`); } - render_chunks() { + render_chunks(block: Block) { return this.node.chunks.map((chunk) => { if (chunk.type === 'Text') { return string_literal(chunk.data); } - return chunk.manipulate(); + return chunk.manipulate(block); }); } diff --git a/src/compiler/compile/render_dom/wrappers/Element/Binding.ts b/src/compiler/compile/render_dom/wrappers/Element/Binding.ts index 9618ad3c2c..50436f3588 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/Binding.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/Binding.ts @@ -6,7 +6,6 @@ import Block from '../../Block'; import Renderer from '../../Renderer'; import flatten_reference from '../../../utils/flatten_reference'; import EachBlock from '../../../nodes/EachBlock'; -import { changed } from '../shared/changed'; import { Node, Identifier } from 'estree'; export default class BindingWrapper { @@ -91,7 +90,7 @@ export default class BindingWrapper { const dependency_array = [...this.node.expression.dependencies]; if (dependency_array.length > 0) { - update_conditions.push(changed(dependency_array)); + update_conditions.push(block.renderer.changed(dependency_array)); } if (parent.node.name === 'input') { diff --git a/src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts b/src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts index 37089e7493..d76b4ce54d 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts @@ -15,6 +15,7 @@ export default class EventHandlerWrapper { this.parent = parent; if (!node.expression) { + // TODO use renderer.add_to_context this.parent.renderer.component.add_var({ name: node.handler_name.name, internal: true, @@ -22,10 +23,10 @@ export default class EventHandlerWrapper { }); this.parent.renderer.component.partly_hoisted.push(b` - function ${node.handler_name.name}(event) { - @bubble($$self, event); - } - `); + function ${node.handler_name.name}(event) { + @bubble($$self, event); + } + `); } } @@ -45,7 +46,7 @@ export default class EventHandlerWrapper { if (this.node.modifiers.has('preventDefault')) snippet = x`@prevent_default(${snippet})`; if (this.node.modifiers.has('stopPropagation')) snippet = x`@stop_propagation(${snippet})`; if (this.node.modifiers.has('self')) snippet = x`@self(${snippet})`; - + const args = []; const opts = ['passive', 'once', 'capture'].filter(mod => this.node.modifiers.has(mod)); diff --git a/src/compiler/compile/render_dom/wrappers/Element/StyleAttribute.ts b/src/compiler/compile/render_dom/wrappers/Element/StyleAttribute.ts index c60a89f66f..828df56965 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/StyleAttribute.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/StyleAttribute.ts @@ -7,7 +7,6 @@ import { string_literal } from '../../../utils/stringify'; import add_to_set from '../../../utils/add_to_set'; import Expression from '../../../nodes/shared/Expression'; import Text from '../../../nodes/Text'; -import { changed } from '../shared/changed'; export interface StyleProp { key: string; @@ -46,7 +45,7 @@ export default class StyleAttributeWrapper extends AttributeWrapper { // } if (prop_dependencies.size) { - let condition = changed(Array.from(prop_dependencies)); + let condition = block.renderer.changed(Array.from(prop_dependencies)); if (block.has_outros) { condition = x`!#current || ${condition}`; diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 0c3b05a6bf..a0859cfced 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -21,7 +21,6 @@ import add_actions from '../shared/add_actions'; import create_debugging_comment from '../shared/create_debugging_comment'; import { get_context_merger } from '../shared/get_context_merger'; import bind_this from '../shared/bind_this'; -import { changed } from '../shared/changed'; import { is_head } from '../shared/is_head'; import { Identifier } from 'estree'; import EventHandler from './EventHandler'; @@ -282,7 +281,7 @@ export default class ElementWrapper extends Wrapper { const children = x`@children(${this.node.name === 'template' ? x`${node}.content` : node})`; block.add_variable(node); - const render_statement = this.get_render_statement(); + const render_statement = this.get_render_statement(block); block.chunks.create.push( b`${node} = ${render_statement};` ); @@ -398,7 +397,7 @@ export default class ElementWrapper extends Wrapper { return this.is_static_content && this.fragment.nodes.every(node => node.node.type === 'Text' || node.node.type === 'MustacheTag'); } - get_render_statement() { + get_render_statement(block: Block) { const { name, namespace } = this.node; if (namespace === 'http://www.w3.org/2000/svg') { @@ -411,7 +410,7 @@ export default class ElementWrapper extends Wrapper { const is = this.attributes.find(attr => attr.node.name === 'is'); if (is) { - return x`@element_is("${name}", ${is.render_chunks().reduce((lhs, rhs) => x`${lhs} + ${rhs}`)});`; + return x`@element_is("${name}", ${is.render_chunks(block).reduce((lhs, rhs) => x`${lhs} + ${rhs}`)});`; } return x`@element("${name}")`; @@ -455,18 +454,13 @@ export default class ElementWrapper extends Wrapper { groups.forEach(group => { const handler = renderer.component.get_unique_name(`${this.var.name}_${group.events.join('_')}_handler`); - - renderer.component.add_var({ - name: handler.name, - internal: true, - referenced: true - }); + const i = renderer.add_to_context(handler.name); // TODO figure out how to handle locks const needs_lock = group.bindings.some(binding => binding.needs_lock); - const dependencies = new Set(); - const contextual_dependencies = new Set(); + const dependencies: Set = new Set(); + const contextual_dependencies: Set = new Set(); group.bindings.forEach(binding => { // TODO this is a mess @@ -501,21 +495,21 @@ export default class ElementWrapper extends Wrapper { ${animation_frame} = @raf(${handler}); ${needs_lock && b`${lock} = true;`} } - #ctx.${handler}.call(${this.var}, ${contextual_dependencies.size > 0 ? '#ctx' : null}); + #ctx[${i}].call(${this.var}, ${contextual_dependencies.size > 0 ? '#ctx' : null}); } `); } else { block.chunks.init.push(b` function ${handler}() { ${needs_lock && b`${lock} = true;`} - #ctx.${handler}.call(${this.var}, ${contextual_dependencies.size > 0 ? '#ctx' : null}); + #ctx[${i}].call(${this.var}, ${contextual_dependencies.size > 0 ? '#ctx' : null}); } `); } callee = handler; } else { - callee = x`#ctx.${handler}`; + callee = x`#ctx[${i}]`; } const arg = contextual_dependencies.size > 0 && { @@ -537,7 +531,7 @@ export default class ElementWrapper extends Wrapper { ${Array.from(dependencies) .filter(dep => dep[0] !== '$') .filter(dep => !contextual_dependencies.has(dep)) - .map(dep => b`${this.renderer.component.invalidate(dep)};`)} + .map(dep => b`${this.renderer.invalidate(dep)};`)} } `); @@ -632,7 +626,7 @@ export default class ElementWrapper extends Wrapper { this.attributes .forEach(attr => { const condition = attr.node.dependencies.size > 0 - ? changed(Array.from(attr.node.dependencies)) + ? block.renderer.changed(Array.from(attr.node.dependencies)) : null; if (attr.node.is_spread) { @@ -685,8 +679,6 @@ export default class ElementWrapper extends Wrapper { const { intro, outro } = this.node; if (!intro && !outro) return; - const { component } = this.renderer; - if (intro === outro) { // bidirectional transition const name = block.get_unique_name(`${this.var.name}_transition`); @@ -696,7 +688,7 @@ export default class ElementWrapper extends Wrapper { block.add_variable(name); - const fn = component.qualify(intro.name); + const fn = this.renderer.reference(intro.name); const intro_block = b` @add_render_callback(() => { @@ -740,7 +732,7 @@ export default class ElementWrapper extends Wrapper { ? intro.expression.manipulate(block) : x`{}`; - const fn = component.qualify(intro.name); + const fn = this.renderer.reference(intro.name); let intro_block; @@ -782,7 +774,7 @@ export default class ElementWrapper extends Wrapper { ? outro.expression.manipulate(block) : x`{}`; - const fn = component.qualify(outro.name); + const fn = this.renderer.reference(outro.name); if (!intro) { block.chunks.intro.push(b` @@ -814,7 +806,6 @@ export default class ElementWrapper extends Wrapper { add_animation(block: Block) { if (!this.node.animation) return; - const { component } = this.renderer; const { outro } = this.node; const rect = block.get_unique_name('rect'); @@ -835,7 +826,7 @@ export default class ElementWrapper extends Wrapper { const params = this.node.animation.expression ? this.node.animation.expression.manipulate(block) : x`{}`; - const name = component.qualify(this.node.animation.name); + const name = this.renderer.reference(this.node.animation.name); block.chunks.animate.push(b` ${stop_animation}(); @@ -844,7 +835,7 @@ export default class ElementWrapper extends Wrapper { } add_actions(block: Block) { - add_actions(this.renderer.component, block, this.var, this.node.actions); + add_actions(block, this.var, this.node.actions); } add_classes(block: Block) { @@ -868,7 +859,7 @@ export default class ElementWrapper extends Wrapper { block.chunks.update.push(updater); } else if ((dependencies && dependencies.size > 0) || this.class_dependencies.length) { const all_dependencies = this.class_dependencies.concat(...dependencies); - const condition = changed(all_dependencies); + const condition = block.renderer.changed(all_dependencies); block.chunks.update.push(b` if (${condition}) { diff --git a/src/compiler/compile/render_dom/wrappers/IfBlock.ts b/src/compiler/compile/render_dom/wrappers/IfBlock.ts index 6ef505cd3b..4d61aa006d 100644 --- a/src/compiler/compile/render_dom/wrappers/IfBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/IfBlock.ts @@ -10,7 +10,6 @@ import { b, x } from 'code-red'; import { walk } from 'estree-walker'; import { is_head } from './shared/is_head'; import { Identifier, Node } from 'estree'; -import { changed } from './shared/changed'; function is_else_if(node: ElseBlock) { return ( @@ -272,7 +271,7 @@ export default class IfBlockWrapper extends Wrapper { ? b` ${snippet && ( dependencies.length > 0 - ? b`if (${condition} == null || ${changed(dependencies)}) ${condition} = !!${snippet}` + ? b`if (${condition} == null || ${block.renderer.changed(dependencies)}) ${condition} = !!${snippet}` : b`if (${condition} == null) ${condition} = !!${snippet}` )} if (${condition}) return ${block.name};` @@ -323,21 +322,21 @@ export default class IfBlockWrapper extends Wrapper { if (dynamic) { block.chunks.update.push(b` - if (${current_block_type} === (${current_block_type} = ${select_block_type}(#changed, #ctx)) && ${name}) { - ${name}.p(#changed, #ctx); + if (${current_block_type} === (${current_block_type} = ${select_block_type}(#ctx, #changed)) && ${name}) { + ${name}.p(#ctx, #changed); } else { ${change_block} } `); } else { block.chunks.update.push(b` - if (${current_block_type} !== (${current_block_type} = ${select_block_type}(#changed, #ctx))) { + if (${current_block_type} !== (${current_block_type} = ${select_block_type}(#ctx, #changed))) { ${change_block} } `); } } else if (dynamic) { - block.chunks.update.push(b`${name}.p(#changed, #ctx);`); + block.chunks.update.push(b`${name}.p(#ctx, #changed);`); } if (if_exists_condition) { @@ -391,7 +390,7 @@ export default class IfBlockWrapper extends Wrapper { ? b` ${snippet && ( dependencies.length > 0 - ? b`if (${condition} == null || ${changed(dependencies)}) ${condition} = !!${snippet}` + ? b`if (${condition} == null || ${block.renderer.changed(dependencies)}) ${condition} = !!${snippet}` : b`if (${condition} == null) ${condition} = !!${snippet}` )} if (${condition}) return ${i};` @@ -476,7 +475,7 @@ export default class IfBlockWrapper extends Wrapper { let ${previous_block_index} = ${current_block_type_index}; ${current_block_type_index} = ${select_block_type}(#changed, #ctx); if (${current_block_type_index} === ${previous_block_index}) { - ${if_current_block_type_index(b`${if_blocks}[${current_block_type_index}].p(#changed, #ctx);`)} + ${if_current_block_type_index(b`${if_blocks}[${current_block_type_index}].p(#ctx, #changed);`)} } else { ${change_block} } @@ -491,7 +490,7 @@ export default class IfBlockWrapper extends Wrapper { `); } } else if (dynamic) { - block.chunks.update.push(b`${name}.p(#changed, #ctx);`); + block.chunks.update.push(b`${name}.p(#ctx, #changed);`); } block.chunks.destroy.push( @@ -528,7 +527,7 @@ export default class IfBlockWrapper extends Wrapper { const enter = dynamic ? b` if (${name}) { - ${name}.p(#changed, #ctx); + ${name}.p(#ctx, #changed); ${has_transitions && b`@transition_in(${name}, 1);`} } else { ${name} = ${branch.block.name}(#ctx); @@ -549,7 +548,7 @@ export default class IfBlockWrapper extends Wrapper { `; if (branch.snippet) { - block.chunks.update.push(b`if (${changed(branch.dependencies)}) ${branch.condition} = ${branch.snippet}`); + block.chunks.update.push(b`if (${block.renderer.changed(branch.dependencies)}) ${branch.condition} = ${branch.snippet}`); } // no `p()` here — we don't want to update outroing nodes, @@ -578,7 +577,7 @@ export default class IfBlockWrapper extends Wrapper { } } else if (dynamic) { block.chunks.update.push(b` - if (${branch.condition}) ${name}.p(#changed, #ctx); + if (${branch.condition}) ${name}.p(#ctx, #changed); `); } diff --git a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts index cc02c171ed..bc6d32c524 100644 --- a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts +++ b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts @@ -14,7 +14,6 @@ import EachBlock from '../../../nodes/EachBlock'; import TemplateScope from '../../../nodes/shared/TemplateScope'; import is_dynamic from '../shared/is_dynamic'; import bind_this from '../shared/bind_this'; -import { changed } from '../shared/changed'; import { Node, Identifier, ObjectExpression } from 'estree'; import EventHandler from '../Element/EventHandler'; @@ -206,7 +205,7 @@ export default class InlineComponentWrapper extends Wrapper { const { name, dependencies } = attr; const condition = dependencies.size > 0 && (dependencies.size !== all_dependencies.size) - ? changed(Array.from(dependencies)) + ? renderer.changed(Array.from(dependencies)) : null; if (attr.is_spread) { @@ -239,7 +238,7 @@ export default class InlineComponentWrapper extends Wrapper { `); if (all_dependencies.size) { - const condition = changed(Array.from(all_dependencies)); + const condition = renderer.changed(Array.from(all_dependencies)); updates.push(b` const ${name_changes} = ${condition} ? @get_spread_update(${levels}, [ @@ -255,7 +254,7 @@ export default class InlineComponentWrapper extends Wrapper { dynamic_attributes.forEach((attribute: Attribute) => { const dependencies = attribute.get_dependencies(); if (dependencies.length > 0) { - const condition = changed(dependencies); + const condition = renderer.changed(dependencies); updates.push(b` if (${condition}) ${name_changes}.${attribute.name} = ${attribute.get_value(block)}; @@ -267,7 +266,7 @@ export default class InlineComponentWrapper extends Wrapper { if (non_let_dependencies.length > 0) { updates.push(b` - if (${changed(non_let_dependencies)}) { + if (${renderer.changed(non_let_dependencies)}) { ${name_changes}.$$scope = { changed: #changed, ctx: #ctx }; }`); } @@ -280,12 +279,7 @@ export default class InlineComponentWrapper extends Wrapper { } const id = component.get_unique_name(`${this.var.name}_${binding.name}_binding`); - - component.add_var({ - name: id.name, - internal: true, - referenced: true - }); + const i = renderer.add_to_context(id.name); const updating = block.get_unique_name(`updating_${binding.name}`); block.add_variable(updating); @@ -299,7 +293,7 @@ export default class InlineComponentWrapper extends Wrapper { ); updates.push(b` - if (!${updating} && ${changed(Array.from(binding.expression.dependencies))}) { + if (!${updating} && ${renderer.changed(Array.from(binding.expression.dependencies))}) { ${updating} = true; ${name_changes}.${binding.name} = ${snippet}; @add_flush_callback(() => ${updating} = false); @@ -338,7 +332,7 @@ export default class InlineComponentWrapper extends Wrapper { block.chunks.init.push(b` function ${id}(${value}) { - #ctx.${id}.call(null, ${value}, #ctx); + #ctx[${i}].call(null, ${value}, #ctx); } `); @@ -346,7 +340,7 @@ export default class InlineComponentWrapper extends Wrapper { } else { block.chunks.init.push(b` function ${id}(${value}) { - #ctx.${id}.call(null, ${value}); + #ctx[${i}].call(null, ${value}); } `); } @@ -354,7 +348,7 @@ export default class InlineComponentWrapper extends Wrapper { const body = b` function ${id}(${args}) { ${lhs} = ${value}; - ${component.invalidate(dependencies[0])}; + ${renderer.invalidate(dependencies[0])}; } `; @@ -460,7 +454,7 @@ export default class InlineComponentWrapper extends Wrapper { } else { const expression = this.node.name === 'svelte:self' ? component.name - : component.qualify(this.node.name); + : this.renderer.reference(this.node.name); block.chunks.init.push(b` ${(this.node.attributes.length > 0 || this.node.bindings.length > 0) && b` diff --git a/src/compiler/compile/render_dom/wrappers/Slot.ts b/src/compiler/compile/render_dom/wrappers/Slot.ts index 4d575d21d3..9568010732 100644 --- a/src/compiler/compile/render_dom/wrappers/Slot.ts +++ b/src/compiler/compile/render_dom/wrappers/Slot.ts @@ -10,7 +10,6 @@ import get_slot_data from '../../utils/get_slot_data'; import Expression from '../../nodes/shared/Expression'; import is_dynamic from './shared/is_dynamic'; import { Identifier, ObjectExpression } from 'estree'; -import { changed } from './shared/changed'; export default class SlotWrapper extends Wrapper { node: Slot; @@ -184,10 +183,10 @@ export default class SlotWrapper extends Wrapper { }); block.chunks.update.push(b` - if (${slot} && ${slot}.p && ${changed(dynamic_dependencies)}) { + if (${slot} && ${slot}.p && ${renderer.changed(dynamic_dependencies)}) { ${slot}.p( - @get_slot_changes(${slot_definition}, #ctx, #changed, ${get_slot_changes}), - @get_slot_context(${slot_definition}, #ctx, ${get_slot_context}) + @get_slot_context(${slot_definition}, #ctx, ${get_slot_context}), + @get_slot_changes(${slot_definition}, #ctx, #changed, ${get_slot_changes}) ); } `); diff --git a/src/compiler/compile/render_dom/wrappers/Title.ts b/src/compiler/compile/render_dom/wrappers/Title.ts index 2a02f1bc08..610575a479 100644 --- a/src/compiler/compile/render_dom/wrappers/Title.ts +++ b/src/compiler/compile/render_dom/wrappers/Title.ts @@ -7,7 +7,6 @@ import { string_literal } from '../../utils/stringify'; import add_to_set from '../../utils/add_to_set'; import Text from '../../nodes/Text'; import { Identifier } from 'estree'; -import { changed } from './shared/changed'; import MustacheTag from '../../nodes/MustacheTag'; export default class TitleWrapper extends Wrapper { @@ -76,7 +75,7 @@ export default class TitleWrapper extends Wrapper { if (all_dependencies.size) { const dependencies = Array.from(all_dependencies); - let condition = changed(dependencies); + let condition = block.renderer.changed(dependencies); if (block.has_outros) { condition = x`!#current || ${condition}`; diff --git a/src/compiler/compile/render_dom/wrappers/Window.ts b/src/compiler/compile/render_dom/wrappers/Window.ts index 7594adee67..d34af65829 100644 --- a/src/compiler/compile/render_dom/wrappers/Window.ts +++ b/src/compiler/compile/render_dom/wrappers/Window.ts @@ -5,7 +5,6 @@ import { b, x } from 'code-red'; import add_event_handlers from './shared/add_event_handlers'; import Window from '../../nodes/Window'; import add_actions from './shared/add_actions'; -import { changed } from './shared/changed'; import { Identifier } from 'estree'; import { TemplateNode } from '../../../interfaces'; import EventHandler from './Element/EventHandler'; @@ -49,7 +48,7 @@ export default class WindowWrapper extends Wrapper { const events = {}; const bindings: Record = {}; - add_actions(component, block, '@_window', this.node.actions); + add_actions(block, '@_window', this.node.actions); add_event_handlers(block, '@_window', this.handlers); this.node.bindings.forEach(binding => { @@ -122,20 +121,16 @@ export default class WindowWrapper extends Wrapper { `); } - component.add_var({ - name: id.name, - internal: true, - referenced: true - }); + const i = renderer.add_to_context(id.name); component.partly_hoisted.push(b` function ${id}() { - ${props.map(prop => component.invalidate(prop.name, x`${prop.name} = @_window.${prop.value}`))} + ${props.map(prop => renderer.invalidate(prop.name, x`${prop.name} = @_window.${prop.value}`))} } `); block.chunks.init.push(b` - @add_render_callback(#ctx.${id}); + @add_render_callback(#ctx[${i}]); `); component.has_reactive_assignments = true; @@ -143,7 +138,7 @@ export default class WindowWrapper extends Wrapper { // special case... might need to abstract this out if we add more special cases if (bindings.scrollX || bindings.scrollY) { - const condition = changed([bindings.scrollX, bindings.scrollY].filter(Boolean)); + const condition = renderer.changed([bindings.scrollX, bindings.scrollY].filter(Boolean)); const scrollX = bindings.scrollX ? x`#ctx.${bindings.scrollX}` : x`@_window.pageXOffset`; const scrollY = bindings.scrollY ? x`#ctx.${bindings.scrollY}` : x`@_window.pageYOffset`; @@ -162,25 +157,21 @@ export default class WindowWrapper extends Wrapper { const id = block.get_unique_name(`onlinestatuschanged`); const name = bindings.online; - component.add_var({ - name: id.name, - internal: true, - referenced: true - }); + const i = renderer.add_to_context(id.name); component.partly_hoisted.push(b` function ${id}() { - ${component.invalidate(name, x`${name} = @_navigator.onLine`)} + ${renderer.invalidate(name, x`${name} = @_navigator.onLine`)} } `); block.chunks.init.push(b` - @add_render_callback(#ctx.${id}); + @add_render_callback(#ctx[${i}]); `); block.event_listeners.push( - x`@listen(@_window, "online", #ctx.${id})`, - x`@listen(@_window, "offline", #ctx.${id})` + x`@listen(@_window, "online", #ctx[${i}])`, + x`@listen(@_window, "offline", #ctx[${i}])` ); component.has_reactive_assignments = true; diff --git a/src/compiler/compile/render_dom/wrappers/shared/Tag.ts b/src/compiler/compile/render_dom/wrappers/shared/Tag.ts index ccb7c6ea40..e49bdc0982 100644 --- a/src/compiler/compile/render_dom/wrappers/shared/Tag.ts +++ b/src/compiler/compile/render_dom/wrappers/shared/Tag.ts @@ -5,7 +5,6 @@ import Block from '../../Block'; import MustacheTag from '../../../nodes/MustacheTag'; import RawMustacheTag from '../../../nodes/RawMustacheTag'; import { Node } from 'estree'; -import { changed } from './changed'; export default class Tag extends Wrapper { node: MustacheTag | RawMustacheTag; @@ -40,7 +39,7 @@ export default class Tag extends Wrapper { if (this.node.should_cache) block.add_variable(value, snippet); // TODO may need to coerce snippet to string if (dependencies.length > 0) { - let condition = changed(dependencies); + let condition = block.renderer.changed(dependencies); if (block.has_outros) { condition = x`!#current || ${condition}`; diff --git a/src/compiler/compile/render_dom/wrappers/shared/add_actions.ts b/src/compiler/compile/render_dom/wrappers/shared/add_actions.ts index 538bfc7a95..57453cb0f2 100644 --- a/src/compiler/compile/render_dom/wrappers/shared/add_actions.ts +++ b/src/compiler/compile/render_dom/wrappers/shared/add_actions.ts @@ -1,10 +1,8 @@ import { b, x } from 'code-red'; import Block from '../../Block'; import Action from '../../../nodes/Action'; -import Component from '../../../Component'; export default function add_actions( - component: Component, block: Block, target: string, actions: Action[] @@ -25,7 +23,7 @@ export default function add_actions( block.add_variable(id); - const fn = component.qualify(action.name); + const fn = block.renderer.reference(action.name); block.chunks.mount.push( b`${id} = ${fn}.call(null, ${target}, ${snippet}) || {};` @@ -34,14 +32,8 @@ export default function add_actions( if (dependencies && dependencies.length > 0) { let condition = x`@is_function(${id}.update)`; - // TODO can this case be handled more elegantly? if (dependencies.length > 0) { - let changed = x`#changed.${dependencies[0]}`; - for (let i = 1; i < dependencies.length; i += 1) { - changed = x`${changed} || #changed.${dependencies[i]}`; - } - - condition = x`${condition} && ${changed}`; + condition = x`${condition} && ${block.renderer.changed(dependencies)}`; } block.chunks.update.push( diff --git a/src/compiler/compile/render_dom/wrappers/shared/bind_this.ts b/src/compiler/compile/render_dom/wrappers/shared/bind_this.ts index 504e209763..6462522590 100644 --- a/src/compiler/compile/render_dom/wrappers/shared/bind_this.ts +++ b/src/compiler/compile/render_dom/wrappers/shared/bind_this.ts @@ -7,12 +7,7 @@ import { Identifier } from 'estree'; export default function bind_this(component: Component, block: Block, binding: Binding, variable: Identifier) { const fn = component.get_unique_name(`${variable.name}_binding`); - - component.add_var({ - name: fn.name, - internal: true, - referenced: true - }); + const i = block.renderer.add_to_context(fn.name); let lhs; let object; @@ -32,11 +27,11 @@ export default function bind_this(component: Component, block: Block, binding: B body = binding.raw_expression.type === 'Identifier' ? b` - ${component.invalidate(object, x`${lhs} = $$value`)}; + ${block.renderer.invalidate(object, x`${lhs} = $$value`)}; ` : b` ${lhs} = $$value; - ${component.invalidate(object)}; + ${block.renderer.invalidate(object)}; `; } @@ -65,12 +60,12 @@ export default function bind_this(component: Component, block: Block, binding: B const unassign = block.get_unique_name(`unassign_${variable.name}`); block.chunks.init.push(b` - const ${assign} = () => #ctx.${fn}(${variable}, ${args}); - const ${unassign} = () => #ctx.${fn}(null, ${args}); + const ${assign} = () => #ctx[${i}](${variable}, ${args}); + const ${unassign} = () => #ctx[${i}](null, ${args}); `); const condition = Array.from(contextual_dependencies) - .map(name => x`${name} !== #ctx.${name}`) + .map(name => x`${name} !== #ctx.${name}`) // TODO figure out contextual deps .reduce((lhs, rhs) => x`${lhs} || ${rhs}`); // we push unassign and unshift assign so that references are @@ -96,6 +91,6 @@ export default function bind_this(component: Component, block: Block, binding: B } `); - block.chunks.destroy.push(b`#ctx.${fn}(null);`); - return b`#ctx.${fn}(${variable});`; + block.chunks.destroy.push(b`#ctx[${i}](null);`); + return b`#ctx[${i}](${variable});`; } \ No newline at end of file diff --git a/src/compiler/compile/render_dom/wrappers/shared/changed.ts b/src/compiler/compile/render_dom/wrappers/shared/changed.ts deleted file mode 100644 index b7dc12e53f..0000000000 --- a/src/compiler/compile/render_dom/wrappers/shared/changed.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { x } from 'code-red'; - -export function changed(dependencies: string[]) { - return dependencies - .map(d => x`#changed.${d}`) - .reduce((lhs, rhs) => x`${lhs} || ${rhs}`); -} \ No newline at end of file diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts index 0ce3b3209b..b035d958ed 100644 --- a/src/runtime/internal/Component.ts +++ b/src/runtime/internal/Component.ts @@ -21,7 +21,7 @@ interface Fragment { } // eslint-disable-next-line @typescript-eslint/class-name-casing interface T$$ { - dirty: null; + dirty: number; ctx: null|any; bound: any; update: () => void; @@ -87,13 +87,13 @@ export function destroy_component(component, detaching) { } } -function make_dirty(component, key) { - if (!component.$$.dirty) { +function make_dirty(component, i) { + if (component.$$.dirty === -1) { dirty_components.push(component); schedule_update(); - component.$$.dirty = blank_object(); + component.$$.dirty = 0; } - component.$$.dirty[key] = true; + component.$$.dirty |= (1 << i); } export function init(component, options, instance, create_fragment, not_equal, props) { @@ -121,16 +121,16 @@ export function init(component, options, instance, create_fragment, not_equal, p // everything else callbacks: blank_object(), - dirty: null + dirty: -1 }; let ready = false; $$.ctx = instance - ? instance(component, prop_values, (key, ret, value = ret) => { - if ($$.ctx && not_equal($$.ctx[key], $$.ctx[key] = value)) { - if ($$.bound[key]) $$.bound[key](value); - if (ready) make_dirty(component, key); + ? instance(component, prop_values, (i, ret, value = ret) => { + if ($$.ctx && not_equal($$.ctx[i], $$.ctx[i] = value)) { + if ($$.bound[i]) $$.bound[i](value); + if (ready) make_dirty(component, i); } return ret; }) @@ -139,7 +139,7 @@ export function init(component, options, instance, create_fragment, not_equal, p $$.update(); ready = true; run_all($$.before_update); - + // `false` as a special case of no DOM component $$.fragment = create_fragment ? create_fragment($$.ctx) : false; diff --git a/src/runtime/internal/keyed_each.ts b/src/runtime/internal/keyed_each.ts index 76708c93db..d3a161c6b4 100644 --- a/src/runtime/internal/keyed_each.ts +++ b/src/runtime/internal/keyed_each.ts @@ -43,7 +43,7 @@ export function update_keyed_each(old_blocks, changed, get_key, dynamic, ctx, li block = create_each_block(key, child_ctx); block.c(); } else if (dynamic) { - block.p(changed, child_ctx); + block.p(child_ctx, changed); } new_lookup.set(key, new_blocks[i] = block); diff --git a/src/runtime/internal/scheduler.ts b/src/runtime/internal/scheduler.ts index 7cb00c085b..99feb14152 100644 --- a/src/runtime/internal/scheduler.ts +++ b/src/runtime/internal/scheduler.ts @@ -73,8 +73,8 @@ function update($$) { if ($$.fragment !== null) { $$.update($$.dirty); run_all($$.before_update); - $$.fragment && $$.fragment.p($$.dirty, $$.ctx); - $$.dirty = null; + $$.fragment && $$.fragment.p($$.ctx, $$.dirty); + $$.dirty = -1; $$.after_update.forEach(add_render_callback); } diff --git a/test/js/samples/action-custom-event-handler/expected.js b/test/js/samples/action-custom-event-handler/expected.js index 917cbf6cb2..5ed38de87c 100644 --- a/test/js/samples/action-custom-event-handler/expected.js +++ b/test/js/samples/action-custom-event-handler/expected.js @@ -23,8 +23,8 @@ function create_fragment(ctx) { insert(target, button, anchor); foo_action = foo.call(null, button, ctx[1]) || ({}); }, - p(ctx) { - if (is_function(foo_action.update)) foo_action.update.call(null, ctx[1]); + p(ctx, changed) { + if (is_function(foo_action.update) && changed.bar) foo_action.update.call(null, ctx[1]); }, i: noop, o: noop, diff --git a/test/runtime/index.js b/test/runtime/index.js index fc989d352f..79dfd9e050 100644 --- a/test/runtime/index.js +++ b/test/runtime/index.js @@ -26,7 +26,7 @@ process.on('unhandledRejection', err => { unhandled_rejection = err; }); -describe("runtime", () => { +describe.only("runtime", () => { before(() => { svelte = loadSvelte(false); svelte$ = loadSvelte(true); diff --git a/test/runtime/samples/action-custom-event-handler/_config.js b/test/runtime/samples/action-custom-event-handler/_config.js index ad13d900b2..b132223e32 100644 --- a/test/runtime/samples/action-custom-event-handler/_config.js +++ b/test/runtime/samples/action-custom-event-handler/_config.js @@ -1,7 +1,7 @@ export default { html: '', - async test({ assert, component, target, window }) { + async test({ assert, target, window }) { const event = new window.MouseEvent('click', { clientX: 42, clientY: 42