diff --git a/src/compile/nodes/CatchBlock.ts b/src/compile/nodes/CatchBlock.ts index 6486f4a3a8..db202b9517 100644 --- a/src/compile/nodes/CatchBlock.ts +++ b/src/compile/nodes/CatchBlock.ts @@ -12,7 +12,7 @@ export default class CatchBlock extends Node { super(component, parent, scope, info); this.scope = scope.child(); - this.scope.add(parent.error, parent.expression.dependencies); + this.scope.add(parent.error, parent.expression.dependencies, this); this.children = mapChildren(component, parent, this.scope, info.children); this.warnIfEmptyBlock(); diff --git a/src/compile/nodes/EachBlock.ts b/src/compile/nodes/EachBlock.ts index 6770ab8739..5ea1d5e033 100644 --- a/src/compile/nodes/EachBlock.ts +++ b/src/compile/nodes/EachBlock.ts @@ -20,6 +20,7 @@ export default class EachBlock extends Node { scope: TemplateScope; contexts: Array<{ name: string, tail: string }>; hasAnimation: boolean; + has_binding = false; children: Node[]; else?: ElseBlock; @@ -38,7 +39,7 @@ export default class EachBlock extends Node { unpackDestructuring(this.contexts, info.context, ''); this.contexts.forEach(context => { - this.scope.add(context.key.name, this.expression.dependencies); + this.scope.add(context.key.name, this.expression.dependencies, this); }); this.key = info.key @@ -48,7 +49,7 @@ export default class EachBlock extends Node { if (this.index) { // index can only change if this is a keyed each block const dependencies = this.key ? this.expression.dependencies : []; - this.scope.add(this.index, dependencies); + this.scope.add(this.index, dependencies, this); } this.hasAnimation = false; diff --git a/src/compile/nodes/InlineComponent.ts b/src/compile/nodes/InlineComponent.ts index 8a075d0ce9..4d3b7332a6 100644 --- a/src/compile/nodes/InlineComponent.ts +++ b/src/compile/nodes/InlineComponent.ts @@ -82,7 +82,7 @@ export default class InlineComponent extends Node { const dependencies = new Set([l.name]); l.names.forEach(name => { - this.scope.add(name, dependencies); + this.scope.add(name, dependencies, this); }); }); } else { diff --git a/src/compile/nodes/ThenBlock.ts b/src/compile/nodes/ThenBlock.ts index 9921eb77c7..6df8259273 100644 --- a/src/compile/nodes/ThenBlock.ts +++ b/src/compile/nodes/ThenBlock.ts @@ -12,7 +12,7 @@ export default class ThenBlock extends Node { super(component, parent, scope, info); this.scope = scope.child(); - this.scope.add(parent.value, parent.expression.dependencies); + this.scope.add(parent.value, parent.expression.dependencies, this); this.children = mapChildren(component, parent, this.scope, info.children); this.warnIfEmptyBlock(); diff --git a/src/compile/nodes/shared/Expression.ts b/src/compile/nodes/shared/Expression.ts index 852065d98d..9ec0c92e84 100644 --- a/src/compile/nodes/shared/Expression.ts +++ b/src/compile/nodes/shared/Expression.ts @@ -113,7 +113,10 @@ export default class Expression { // function, and it only applies if the dependency is writable // or a sub-path of a non-writable if (component.instance_script) { - if (component.writable_declarations.has(name) || name[0] === '$' || (component.userVars.has(name) && deep)) { + const owner = template_scope.getOwner(name); + const is_let = owner && (owner.type === 'InlineComponent' || owner.type === 'Element'); + + if (is_let || component.writable_declarations.has(name) || name[0] === '$' || (component.userVars.has(name) && deep)) { dynamic_dependencies.add(name); } } else { diff --git a/src/compile/nodes/shared/TemplateScope.ts b/src/compile/nodes/shared/TemplateScope.ts index 8d31e848e6..df48cc57f9 100644 --- a/src/compile/nodes/shared/TemplateScope.ts +++ b/src/compile/nodes/shared/TemplateScope.ts @@ -1,19 +1,28 @@ +import Node from './Node'; +import EachBlock from '../EachBlock'; +import ThenBlock from '../ThenBlock'; +import CatchBlock from '../CatchBlock'; +import InlineComponent from '../InlineComponent'; + +type NodeWithScope = EachBlock | ThenBlock | CatchBlock | InlineComponent | Element; + export default class TemplateScope { names: Set; dependenciesForName: Map>; - mutables: Set; + mutables: Set = new Set(); + owners: Map = new Map(); parent?: TemplateScope; constructor(parent?: TemplateScope) { this.parent = parent; this.names = new Set(parent ? parent.names : []); this.dependenciesForName = new Map(parent ? parent.dependenciesForName : []); - this.mutables = new Set(); } - add(name, dependencies: Set) { + add(name, dependencies: Set, owner) { this.names.add(name); this.dependenciesForName.set(name, dependencies); + this.owners.set(name, owner); return this; } @@ -32,6 +41,10 @@ export default class TemplateScope { containsMutable(names: Iterable) { for (const name of names) { + const owner = this.getOwner(name); + const is_let = owner && (owner.type === 'InlineComponent' || owner.type === 'Element'); + if (is_let) return true; + if (name[0] === '$') return true; if (this.mutables.has(name)) return true; else if (this.dependenciesForName.has(name) && this.containsMutable(this.dependenciesForName.get(name))) return true; @@ -44,4 +57,8 @@ export default class TemplateScope { isTopLevel(name: string) { return !this.parent || !this.names.has(name) && this.parent.isTopLevel(name); } + + getOwner(name: string): NodeWithScope { + return this.owners.get(name) || (this.parent && this.parent.getOwner(name)); + } } \ No newline at end of file diff --git a/src/compile/render-dom/Block.ts b/src/compile/render-dom/Block.ts index 299ba637c5..bf4823f863 100644 --- a/src/compile/render-dom/Block.ts +++ b/src/compile/render-dom/Block.ts @@ -1,9 +1,10 @@ import CodeBuilder from '../../utils/CodeBuilder'; import deindent from '../../utils/deindent'; -import { escape } from '../../utils/stringify'; import Renderer from './Renderer'; import Wrapper from './wrappers/shared/Wrapper'; import EachBlockWrapper from './wrappers/EachBlock'; +import InlineComponentWrapper from './wrappers/InlineComponent'; +import ElementWrapper from './wrappers/Element'; export interface BlockOptions { parent?: Block; @@ -12,7 +13,6 @@ export interface BlockOptions { comment?: string; key?: string; bindings?: Map { object: string, property: string, snippet: string }>; - contextOwners?: Map; dependencies?: Set; } @@ -30,7 +30,6 @@ export default class Block { dependencies: Set; bindings: Map; - contextOwners: Map; builders: { init: CodeBuilder; @@ -79,7 +78,6 @@ export default class Block { this.dependencies = new Set(); this.bindings = options.bindings; - this.contextOwners = options.contextOwners; this.builders = { init: new CodeBuilder(), diff --git a/src/compile/render-dom/Renderer.ts b/src/compile/render-dom/Renderer.ts index 28a8a90dfb..d2bbcb3f53 100644 --- a/src/compile/render-dom/Renderer.ts +++ b/src/compile/render-dom/Renderer.ts @@ -44,7 +44,6 @@ export default class Renderer { key: null, bindings: new Map(), - contextOwners: new Map(), dependencies: new Set(), }); diff --git a/src/compile/render-dom/wrappers/EachBlock.ts b/src/compile/render-dom/wrappers/EachBlock.ts index 490b0929a1..b2741bfe3a 100644 --- a/src/compile/render-dom/wrappers/EachBlock.ts +++ b/src/compile/render-dom/wrappers/EachBlock.ts @@ -62,7 +62,6 @@ export default class EachBlockWrapper extends Wrapper { indexName: string; var = 'each'; - hasBinding = false; constructor( renderer: Renderer, @@ -83,8 +82,7 @@ export default class EachBlockWrapper extends Wrapper { name: renderer.component.getUniqueName('create_each_block'), key: node.key, // TODO... - bindings: new Map(block.bindings), - contextOwners: new Map(block.contextOwners) + bindings: new Map(block.bindings) }); // TODO this seems messy @@ -110,8 +108,6 @@ export default class EachBlockWrapper extends Wrapper { }; node.contexts.forEach(prop => { - this.block.contextOwners.set(prop.key.name, this); - this.block.bindings.set(prop.key.name, { object: this.vars.each_block_value, property: this.indexName, @@ -167,8 +163,8 @@ export default class EachBlockWrapper extends Wrapper { this.contextProps = this.node.contexts.map(prop => `child_ctx.${prop.key.name} = list[i]${prop.tail};`); - if (this.hasBinding) this.contextProps.push(`child_ctx.${this.vars.each_block_value} = list;`); - if (this.hasBinding || this.node.index) this.contextProps.push(`child_ctx.${this.indexName} = i;`); + if (this.node.has_binding) this.contextProps.push(`child_ctx.${this.vars.each_block_value} = list;`); + if (this.node.has_binding || this.node.index) this.contextProps.push(`child_ctx.${this.indexName} = i;`); const snippet = this.node.expression.render(block); diff --git a/src/compile/render-dom/wrappers/Element/Binding.ts b/src/compile/render-dom/wrappers/Element/Binding.ts index 7fd65c2e07..cec3283322 100644 --- a/src/compile/render-dom/wrappers/Element/Binding.ts +++ b/src/compile/render-dom/wrappers/Element/Binding.ts @@ -7,6 +7,7 @@ import Node from '../../../nodes/shared/Node'; import Renderer from '../../Renderer'; import flattenReference from '../../../../utils/flattenReference'; import { get_tail } from '../../../../utils/get_tail_snippet'; +import EachBlock from '../../../nodes/EachBlock'; // TODO this should live in a specific binding const readOnlyMediaAttributes = new Set([ @@ -52,9 +53,9 @@ export default class BindingWrapper { // we need to ensure that the each block creates a context including // the list and the index, if they're not otherwise referenced const { name } = getObject(this.node.expression.node); - const eachBlock = block.contextOwners.get(name); + const eachBlock = this.parent.node.scope.getOwner(name); - eachBlock.hasBinding = true; + (eachBlock).has_binding = true; } this.object = getObject(this.node.expression.node).name; diff --git a/src/compile/render-dom/wrappers/Element/index.ts b/src/compile/render-dom/wrappers/Element/index.ts index 0345026fe6..21b0053fb5 100644 --- a/src/compile/render-dom/wrappers/Element/index.ts +++ b/src/compile/render-dom/wrappers/Element/index.ts @@ -21,6 +21,7 @@ import addEventHandlers from '../shared/addEventHandlers'; import addActions from '../shared/addActions'; import createDebuggingComment from '../../../../utils/createDebuggingComment'; import sanitize from '../../../../utils/sanitize'; +import { get_context_merger } from '../shared/get_context_merger'; const events = [ { @@ -136,7 +137,7 @@ export default class ElementWrapper extends Wrapper { name: this.renderer.component.getUniqueName(`create_${sanitize(name)}_slot`) }); - const fn = `({ thing }) => ({ thing })`; + const fn = get_context_merger(this.node.lets); (owner).slots.set(name, { block: child_block, diff --git a/src/compile/render-dom/wrappers/Fragment.ts b/src/compile/render-dom/wrappers/Fragment.ts index aad4c74c80..fcefae9ebb 100644 --- a/src/compile/render-dom/wrappers/Fragment.ts +++ b/src/compile/render-dom/wrappers/Fragment.ts @@ -63,6 +63,10 @@ export default class FragmentWrapper { while (i--) { const child = nodes[i]; + if (!child.type) { + throw new Error(`missing type`) + } + if (!(child.type in wrappers)) { throw new Error(`TODO implement ${child.type}`); } diff --git a/src/compile/render-dom/wrappers/InlineComponent/index.ts b/src/compile/render-dom/wrappers/InlineComponent/index.ts index 643dba89fb..42b962c0bd 100644 --- a/src/compile/render-dom/wrappers/InlineComponent/index.ts +++ b/src/compile/render-dom/wrappers/InlineComponent/index.ts @@ -14,6 +14,7 @@ import flattenReference from '../../../../utils/flattenReference'; import createDebuggingComment from '../../../../utils/createDebuggingComment'; import sanitize from '../../../../utils/sanitize'; import { get_context_merger } from '../shared/get_context_merger'; +import EachBlock from '../../../nodes/EachBlock'; export default class InlineComponentWrapper extends Wrapper { var: string; @@ -46,9 +47,9 @@ export default class InlineComponentWrapper extends Wrapper { // we need to ensure that the each block creates a context including // the list and the index, if they're not otherwise referenced const { name } = getObject(binding.expression.node); - const eachBlock = block.contextOwners.get(name); + const eachBlock = this.node.scope.getOwner(name); - eachBlock.hasBinding = true; + (eachBlock).has_binding = true; } block.addDependencies(binding.expression.dynamic_dependencies); @@ -71,6 +72,7 @@ export default class InlineComponentWrapper extends Wrapper { comment: createDebuggingComment(node, renderer.component), name: renderer.component.getUniqueName(`create_default_slot`) }); + this.renderer.blocks.push(default_slot); const fn = get_context_merger(this.node.lets); diff --git a/src/compile/render-dom/wrappers/Slot.ts b/src/compile/render-dom/wrappers/Slot.ts index e8be2caead..42b028af01 100644 --- a/src/compile/render-dom/wrappers/Slot.ts +++ b/src/compile/render-dom/wrappers/Slot.ts @@ -2,16 +2,17 @@ import Wrapper from './shared/Wrapper'; import Renderer from '../Renderer'; import Block from '../Block'; import Slot from '../../nodes/Slot'; -import { quotePropIfNecessary } from '../../../utils/quoteIfNecessary'; import FragmentWrapper from './Fragment'; import deindent from '../../../utils/deindent'; import sanitize from '../../../utils/sanitize'; +import addToSet from '../../../utils/addToSet'; export default class SlotWrapper extends Wrapper { node: Slot; fragment: FragmentWrapper; var = 'slot'; + dependencies: Set = new Set(['$$scope']); constructor( renderer: Renderer, @@ -33,7 +34,11 @@ export default class SlotWrapper extends Wrapper { nextSibling ); - block.addDependencies(new Set(['$$scope'])); + this.node.attributes.forEach(attribute => { + addToSet(this.dependencies, attribute.dependencies); + }); + + block.addDependencies(this.dependencies); } render( @@ -90,9 +95,14 @@ export default class SlotWrapper extends Wrapper { } `); - block.builders.update.addLine( - `if (${slot} && changed.$$scope) ${slot}.p(ctx.$$scope.changed, ctx.$$scope.ctx);` - ); + let update_conditions = [...this.dependencies].map(name => `changed.${name}`).join(' || '); + if (this.dependencies.size > 1) update_conditions = `(${update_conditions})`; + + block.builders.update.addBlock(deindent` + if (${slot} && ${update_conditions}) { + ${slot}.p(@assign(@assign({}, changed), ctx.$$scope.changed), @get_slot_context(ctx.$$slot_${sanitize(slot_name)}, ctx)); + } + `); block.builders.destroy.addLine( `if (${slot}) ${slot}.d(detach);` diff --git a/src/internal/utils.js b/src/internal/utils.js index 4f6d94ec5e..1ce5f10cd3 100644 --- a/src/internal/utils.js +++ b/src/internal/utils.js @@ -49,10 +49,14 @@ export function validate_store(store, name) { export function create_slot(definition, ctx) { if (definition) { - const slot_ctx = definition[1] - ? assign({}, assign(ctx.$$scope.ctx, definition[1](ctx))) - : ctx.$$scope.ctx; - + const slot_ctx = get_slot_context(definition, ctx); return definition[0](slot_ctx); } -} \ No newline at end of file +} + +export function get_slot_context(definition, ctx) { + return definition[1] + ? assign({}, assign(ctx.$$scope.ctx, definition[1](ctx))) + : ctx.$$scope.ctx +} +