diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index 4443c2679d..7f61df306a 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -300,8 +300,13 @@ export default class Component { walk(program, { enter: (node) => { if (node.type === 'Identifier' && node.name[0] === '@') { - const alias = this.helper(node.name.slice(1)); - node.name = alias.name; + if (node.name[1] === '_') { + const alias = this.global(node.name.slice(2)); + node.name = alias.name; + } else { + const alias = this.helper(node.name.slice(1)); + node.name = alias.name; + } } } }); @@ -1223,7 +1228,7 @@ export default class Component { } qualify(name) { - if (name === `$$props`) return `ctx.$$props`; + if (name === `$$props`) return `#ctx.$$props`; const variable = this.var_lookup.get(name); @@ -1233,7 +1238,7 @@ export default class Component { if (variable.hoistable) return name; - return `ctx.${name}`; + return `#ctx.${name}`; } warn_if_undefined(name: string, node, template_scope: TemplateScope) { diff --git a/src/compiler/compile/create_module.ts b/src/compiler/compile/create_module.ts index 2e25258835..73ab84bdc1 100644 --- a/src/compiler/compile/create_module.ts +++ b/src/compiler/compile/create_module.ts @@ -58,10 +58,11 @@ function esm( source: { type: 'Literal', value: internal_path } } - const internal_globals = globals.length > 0 &&{ + const internal_globals = globals.length > 0 && { type: 'VariableDeclaration', kind: 'const', declarations: [{ + type: 'VariableDeclarator', id: { type: 'ObjectPattern', properties: globals.sort((a, b) => a.name < b.name ? -1 : 1).map(g => ({ @@ -70,10 +71,11 @@ function esm( shorthand: false, computed: false, key: { type: 'Identifier', name: g.name }, - value: { type: 'Identifier', name: g.alias } + value: g.alias, + kind: 'init' })) }, - init: { type: 'Identifier', name: helpers.find(({ name }) => name === 'globals').alias } + init: helpers.find(({ name }) => name === 'globals').alias }] }; @@ -135,7 +137,7 @@ function cjs( }] }; - const internal_globals = globals.length > 0 &&{ + const internal_globals = globals.length > 0 && { type: 'VariableDeclaration', kind: 'const', declarations: [{ @@ -148,10 +150,11 @@ function cjs( shorthand: false, computed: false, key: { type: 'Identifier', name: g.name }, - value: { type: 'Identifier', name: g.alias } + value: g.alias, + kind: 'init' })) }, - init: { type: 'Identifier', name: helpers.find(({ name }) => name === 'globals').alias } + init: helpers.find(({ name }) => name === 'globals').alias }] }; @@ -182,7 +185,7 @@ function cjs( program.body = b` "use strict"; ${internal_requires} - // ${internal_globals} + ${internal_globals} ${user_requires} ${program.body} diff --git a/src/compiler/compile/nodes/Binding.ts b/src/compiler/compile/nodes/Binding.ts index e099f4405a..949b0c657c 100644 --- a/src/compiler/compile/nodes/Binding.ts +++ b/src/compiler/compile/nodes/Binding.ts @@ -68,7 +68,7 @@ export default class Binding extends Node { if (!this.expression.node.computed) prop = `'${prop}'`; obj = `[✂${this.expression.node.object.start}-${this.expression.node.object.end}✂]`; } else { - obj = 'ctx'; + obj = '#ctx'; prop = `'${name}'`; } diff --git a/src/compiler/compile/nodes/EventHandler.ts b/src/compiler/compile/nodes/EventHandler.ts index a1adfd345b..3abb2cff54 100644 --- a/src/compiler/compile/nodes/EventHandler.ts +++ b/src/compiler/compile/nodes/EventHandler.ts @@ -66,6 +66,6 @@ export default class EventHandler extends Node { if (this.expression) this.expression.manipulate(block); // this.component.add_reference(this.handler_name); - return `ctx.${this.handler_name}`; + return `#ctx.${this.handler_name}`; } } diff --git a/src/compiler/compile/nodes/shared/Expression.ts b/src/compiler/compile/nodes/shared/Expression.ts index 900a0ba976..7b8ea6a925 100644 --- a/src/compiler/compile/nodes/shared/Expression.ts +++ b/src/compiler/compile/nodes/shared/Expression.ts @@ -325,6 +325,16 @@ export default class Expression { else { // we need a combo block/init recipe + node.params.unshift({ + type: 'ObjectPattern', + properties: Array.from(contextual_dependencies).map(name => ({ + type: 'Property', + kind: 'init', + key: { type: 'Identifier', name }, + value: { type: 'Identifier', name } + })) + }); + component.partly_hoisted.push(declaration); node.name = id; diff --git a/src/compiler/compile/render_dom/Block.ts b/src/compiler/compile/render_dom/Block.ts index cde43a8956..f50b412f38 100644 --- a/src/compiler/compile/render_dom/Block.ts +++ b/src/compiler/compile/render_dom/Block.ts @@ -1,6 +1,5 @@ import Renderer from './Renderer'; import Wrapper from './wrappers/shared/Wrapper'; -import { escape } from '../utils/stringify'; import { b, x } from 'code-red'; import { Node, Identifier } from '../../interfaces'; @@ -397,16 +396,15 @@ export default class Block { render() { const key = this.key && { type: 'Identifier', name: this.get_unique_name('key') }; - const id = { type: 'Identifier', name: this.name }; const args: any[] = [x`#ctx`]; if (key) args.unshift(key); // TODO include this.comment + // ${this.comment && `// ${escape(this.comment, { only_escape_at_symbol: true })}`} return b` - ${this.comment && `// ${escape(this.comment, { only_escape_at_symbol: true })}`} - function ${id}(${args}) { + function ${this.name}(${args}) { ${this.get_contents(key)} } `; diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index c35d3d317d..7d57a48f91 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -9,6 +9,7 @@ import { stringify_props } from '../utils/stringify_props'; import add_to_set from '../utils/add_to_set'; import { extract_names } from '../utils/scope'; import { invalidate } from '../utils/invalidate'; +import Block from './Block'; export default function dom( component: Component, @@ -52,7 +53,12 @@ export default function dom( // TODO the deconflicted names of blocks are reversed... should set them here const blocks = renderer.blocks.slice().reverse(); - body.push(...blocks); + body.push(...blocks.map(block => { + // TODO this is a horrible mess — renderer.blocks + // contains a mixture of Blocks and Nodes + if ((block as Block).render) return (block as Block).render(); + return block; + })); if (options.dev && !options.hydratable) { block.chunks.claim.push( @@ -230,7 +236,7 @@ export default function dom( } body.push(b` - function create_fragment(ctx) { + function create_fragment(#ctx) { ${block.get_contents()} } diff --git a/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts b/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts index 9eceea691d..793b9935ab 100644 --- a/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts @@ -137,7 +137,7 @@ export default class AwaitBlockWrapper extends Wrapper { block.maintain_context = true; const info_props = [ - 'ctx', + '#ctx', 'current: null', 'token: null', this.pending.block.name && `pending: ${this.pending.block.name}`, @@ -197,7 +197,7 @@ export default class AwaitBlockWrapper extends Wrapper { ); block.chunks.update.push( - b`${info}.ctx = ctx;` + b`${info}.ctx = #ctx;` ); if (this.pending.block.has_update_method) { @@ -205,7 +205,7 @@ export default class AwaitBlockWrapper extends Wrapper { if (${conditions.join(' && ')}) { // nothing } else { - ${info}.block.p(changed, @assign(@assign({}, ctx), ${info}.resolved)); + ${info}.block.p(changed, @assign(@assign({}, #ctx), ${info}.resolved)); } `); } else { @@ -216,7 +216,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(changed, @assign(@assign({}, #ctx), ${info}.resolved)); `); } } diff --git a/src/compiler/compile/render_dom/wrappers/EachBlock.ts b/src/compiler/compile/render_dom/wrappers/EachBlock.ts index 35f6dc6e8d..afc9f678bd 100644 --- a/src/compiler/compile/render_dom/wrappers/EachBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/EachBlock.ts @@ -59,7 +59,6 @@ export default class EachBlockWrapper extends Wrapper { fixed_length: number; data_length: string; view_length: string; - length: string; } context_props: string[]; @@ -104,27 +103,32 @@ export default class EachBlockWrapper extends Wrapper { ? node.expression.node.elements.length : null; - // TODO // hack the sourcemap, so that if data is missing the bug // is easy to find let c = this.node.start + 2; while (renderer.component.source[c] !== 'e') c += 1; + const length = { + type: 'Identifier', + name: 'length', + // TODO this format may be incorrect + start: c, + end: c + 4 + }; // renderer.component.code.overwrite(c, c + 4, 'length'); const each_block_value = renderer.component.get_unique_name(`${this.var.name}_value`); - const iterations = block.get_unique_name(`${this.var}_blocks`); + const iterations = block.get_unique_name(`${this.var.name}_blocks`); this.vars = { create_each_block: this.block.name, each_block_value, - get_each_context: renderer.component.get_unique_name(`get_${this.var}_context`), + get_each_context: renderer.component.get_unique_name(`get_${this.var.name}_context`), iterations, - length: `[✂${c}-${c+4}✂]`, // optimisation for array literal fixed_length, - data_length: fixed_length === null ? `${each_block_value}.[✂${c}-${c+4}✂]` : fixed_length, - view_length: fixed_length === null ? `${iterations}.[✂${c}-${c+4}✂]` : fixed_length + data_length: fixed_length === null ? x`${each_block_value}.${length}` : fixed_length, + view_length: fixed_length === null ? x`${iterations}.length` : fixed_length }; const store = @@ -185,18 +189,18 @@ 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 => `child_ctx.${prop.key.name} = ${attach_head('list[i]', prop.tail)};`); + this.context_props = this.node.contexts.map(prop => b`child_ctx.${prop.key.name} = ${attach_head('list[i]', prop.tail)};`); - if (this.node.has_binding) this.context_props.push(`child_ctx.${this.vars.each_block_value} = list;`); - if (this.node.has_binding || this.node.index) this.context_props.push(`child_ctx.${this.index_name} = 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;`); const snippet = this.node.expression.manipulate(block); block.chunks.init.push(b`let ${this.vars.each_block_value} = ${snippet};`); renderer.blocks.push(b` - function ${this.vars.get_each_context}(ctx, list, i) { - const child_ctx = @_Object.create(ctx); + function ${this.vars.get_each_context}(#ctx, list, i) { + const child_ctx = @_Object.create(#ctx); ${this.context_props} return child_ctx; } @@ -251,7 +255,7 @@ export default class EachBlockWrapper extends Wrapper { // TODO neaten this up... will end up with an empty line in the block block.chunks.init.push(b` if (!${this.vars.data_length}) { - ${each_block_else} = ${this.else.block.name}(ctx); + ${each_block_else} = ${this.else.block.name}(#ctx); ${each_block_else}.c(); } `); @@ -265,9 +269,9 @@ 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(changed, #ctx); } else if (!${this.vars.data_length}) { - ${each_block_else} = ${this.else.block.name}(ctx); + ${each_block_else} = ${this.else.block.name}(#ctx); ${each_block_else}.c(); ${each_block_else}.m(${update_mount_node}, ${update_anchor_node}); } else if (${each_block_else}) { @@ -283,7 +287,7 @@ export default class EachBlockWrapper extends Wrapper { ${each_block_else} = null; } } else if (!${each_block_else}) { - ${each_block_else} = ${this.else.block.name}(ctx); + ${each_block_else} = ${this.else.block.name}(#ctx); ${each_block_else}.c(); ${each_block_else}.m(${update_mount_node}, ${update_anchor_node}); } @@ -323,8 +327,8 @@ export default class EachBlockWrapper extends Wrapper { }) { const { create_each_block, - length, iterations, + data_length, view_length } = this.vars; @@ -347,12 +351,12 @@ export default class EachBlockWrapper extends Wrapper { } block.chunks.init.push(b` - const ${get_key} = ctx => ${ + const ${get_key} = #ctx => ${ // @ts-ignore todo: probably error this.node.key.render()}; - for (let #i = 0; #i < ${this.vars.each_block_value}.${length}; #i += 1) { - let child_ctx = ${this.vars.get_each_context}(ctx, ${this.vars.each_block_value}, #i); + for (let #i = 0; #i < ${data_length}; #i += 1) { + let child_ctx = ${this.vars.get_each_context}(#ctx, ${this.vars.each_block_value}, #i); let key = ${get_key}(child_ctx); ${lookup}.set(key, ${iterations}[#i] = ${create_each_block}(key, child_ctx)); } @@ -393,7 +397,7 @@ export default class EachBlockWrapper extends Wrapper { ${this.block.has_outros && `@group_outros();`} ${this.node.has_animation && `for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].r();`} - ${iterations} = @update_keyed_each(${iterations}, changed, ${get_key}, ${dynamic ? '1' : '0'}, ctx, ${this.vars.each_block_value}, ${lookup}, ${update_mount_node}, ${destroy}, ${create_each_block}, ${update_anchor_node}, ${this.vars.get_each_context}); + ${iterations} = @update_keyed_each(${iterations}, changed, ${get_key}, ${dynamic ? '1' : '0'}, #ctx, ${this.vars.each_block_value}, ${lookup}, ${update_mount_node}, ${destroy}, ${create_each_block}, ${update_anchor_node}, ${this.vars.get_each_context}); ${this.node.has_animation && `for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].a();`} ${this.block.has_outros && `@check_outros();`} `); @@ -432,7 +436,6 @@ export default class EachBlockWrapper extends Wrapper { }) { const { create_each_block, - length, iterations, fixed_length, data_length, @@ -443,7 +446,7 @@ export default class EachBlockWrapper extends Wrapper { let ${iterations} = []; for (let #i = 0; #i < ${data_length}; #i += 1) { - ${iterations}[#i] = ${create_each_block}(${this.vars.get_each_context}(ctx, ${this.vars.each_block_value}, #i)); + ${iterations}[#i] = ${create_each_block}(${this.vars.get_each_context}(#ctx, ${this.vars.each_block_value}, #i)); } `); @@ -473,13 +476,19 @@ export default class EachBlockWrapper extends Wrapper { all_dependencies.add(dependency); }); - const condition = Array.from(all_dependencies) - .map(dependency => `changed.${dependency}`) - .join(' || '); + let condition; + if (all_dependencies.size > 0) { + // TODO make this more elegant somehow? + const array = Array.from(all_dependencies); + let condition = x`#changed.${array[0]}`; + for (let i = 1; i < array.length; i += 1) { + condition = x`${condition} || #changed.${array[i]}`; + } + } const has_transitions = !!(this.block.has_intro_method || this.block.has_outro_method); - if (condition !== '') { + if (condition) { const for_loop_body = this.block.has_update_method ? b` if (${iterations}[#i]) { @@ -525,17 +534,17 @@ export default class EachBlockWrapper extends Wrapper { `); remove_old_blocks = b` @group_outros(); - for (#i = ${this.vars.each_block_value}.${length}; #i < ${view_length}; #i += 1) { + for (#i = ${data_length}; #i < ${view_length}; #i += 1) { ${out}(#i); } @check_outros(); `; } else { remove_old_blocks = b` - for (${this.block.has_update_method ? `` : `#i = ${this.vars.each_block_value}.${length}`}; #i < ${this.block.has_update_method ? view_length : '#old_length'}; #i += 1) { + for (${this.block.has_update_method ? `` : `#i = ${data_length}`}; #i < ${this.block.has_update_method ? view_length : '#old_length'}; #i += 1) { ${iterations}[#i].d(1); } - ${!fixed_length && `${view_length} = ${this.vars.each_block_value}.${length};`} + ${!fixed_length && `${view_length} = ${data_length};`} `; } @@ -546,8 +555,8 @@ export default class EachBlockWrapper extends Wrapper { ${this.vars.each_block_value} = ${snippet}; let #i; - for (#i = ${start}; #i < ${this.vars.each_block_value}.${length}; #i += 1) { - const child_ctx = ${this.vars.get_each_context}(ctx, ${this.vars.each_block_value}, #i); + for (#i = ${start}; #i < ${data_length}; #i += 1) { + const child_ctx = ${this.vars.get_each_context}(#ctx, ${this.vars.each_block_value}, #i); ${for_loop_body} } diff --git a/src/compiler/compile/render_dom/wrappers/shared/Tag.ts b/src/compiler/compile/render_dom/wrappers/shared/Tag.ts index 902074aab7..38ba3f2411 100644 --- a/src/compiler/compile/render_dom/wrappers/shared/Tag.ts +++ b/src/compiler/compile/render_dom/wrappers/shared/Tag.ts @@ -1,4 +1,4 @@ -import { b } from 'code-red'; +import { b, x } from 'code-red'; import Wrapper from './Wrapper'; import Renderer from '../../Renderer'; import Block from '../../Block'; @@ -29,16 +29,20 @@ 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) { - const changed_check = ( - (block.has_outros ? `!#current || ` : '') + - dependencies.map((dependency: string) => `changed.${dependency}`).join(' || ') - ); + let condition = x`#changed.${dependencies[0]}`; + for (let i = 1; i < dependencies.length; i += 1) { + condition = x`${condition} || #changed.${dependencies[i]}`; + } - const update_cached_value = `${value} !== (${value} = ${snippet} + "")`; + if (block.has_outros) { + condition = x`!#current || ${condition}`; + } - const condition = this.node.should_cache - ? `(${changed_check}) && ${update_cached_value}` - : changed_check; + const update_cached_value = x`${value} !== (${value} = ${snippet} + "")`; + + if (this.node.should_cache) { + condition = x`${condition} && ${update_cached_value}`; + } block.chunks.update.push(b`if (${condition}) ${update(content as Node)}`); } diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts index ae80ae38c1..5b9007b6bc 100644 --- a/src/runtime/internal/Component.ts +++ b/src/runtime/internal/Component.ts @@ -102,6 +102,7 @@ export function init(component, options, instance, create_fragment, not_equal, p $$.ctx = instance ? instance(component, props, (key, ret, value = ret) => { + console.log(`invalidating`, key, ret, value); if ($$.ctx && not_equal($$.ctx[key], $$.ctx[key] = value)) { if ($$.bound[key]) $$.bound[key](value); if (ready) make_dirty(component, key);