start implementing bitmask-based change tracking (#1943)

pull/3945/head
Richard Harris 6 years ago
parent 669101dfc1
commit 7118318224

@ -889,48 +889,8 @@ export default class Component {
return null; return null;
} }
invalidate(name, value?) { invalidate(_name, _value?) {
const variable = this.var_lookup.get(name); throw new Error(`invalidate method now belongs to Renderer`);
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}}`);
} }
rewrite_props(get_insert: (variable: Var) => Node[]) { rewrite_props(get_insert: (variable: Var) => Node[]) {
@ -1325,23 +1285,8 @@ export default class Component {
}); });
} }
qualify(name) { qualify(_name) {
if (name === `$$props`) return x`#ctx.$$props`; throw new Error(`component.qualify is now renderer.reference`);
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}`);
} }
warn_if_undefined(name: string, node, template_scope: TemplateScope) { warn_if_undefined(name: string, node, template_scope: TemplateScope) {

@ -14,7 +14,7 @@ export default class Action extends Node {
component.warn_if_undefined(info.name, info, scope); component.warn_if_undefined(info.name, info, scope);
this.name = info.name; this.name = info.name;
component.qualify(info.name); component.add_reference(info.name.split('.')[0]);
this.expression = info.expression this.expression = info.expression
? new Expression(component, this, scope, info.expression) ? new Expression(component, this, scope, info.expression)

@ -13,7 +13,7 @@ export default class Animation extends Node {
component.warn_if_undefined(info.name, info, scope); component.warn_if_undefined(info.name, info, scope);
this.name = info.name; this.name = info.name;
component.qualify(info.name); component.add_reference(info.name.split('.')[0]);
if (parent.animation) { if (parent.animation) {
component.error(this, { component.error(this, {

@ -15,7 +15,7 @@ export default class Transition extends Node {
component.warn_if_undefined(info.name, info, scope); component.warn_if_undefined(info.name, info, scope);
this.name = info.name; 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.directive = info.intro && info.outro ? 'transition' : info.intro ? 'in' : 'out';
this.is_local = info.modifiers.includes('local'); this.is_local = info.modifiers.includes('local');

@ -9,9 +9,9 @@ import TemplateScope from './TemplateScope';
import get_object from '../../utils/get_object'; import get_object from '../../utils/get_object';
import Block from '../../render_dom/Block'; import Block from '../../render_dom/Block';
import is_dynamic from '../../render_dom/wrappers/shared/is_dynamic'; import is_dynamic from '../../render_dom/wrappers/shared/is_dynamic';
import { x, b, p } from 'code-red'; import { x, b } from 'code-red';
import { invalidate } from '../../utils/invalidate'; import { invalidate } from '../../render_dom/invalidate';
import { Node, FunctionExpression } from 'estree'; import { Node, FunctionExpression, Identifier } from 'estree';
import { TemplateNode } from '../../../interfaces'; import { TemplateNode } from '../../../interfaces';
type Owner = Wrapper | TemplateNode; type Owner = Wrapper | TemplateNode;
@ -213,7 +213,9 @@ export default class Expression {
component.add_reference(name); // TODO is this redundant/misplaced? component.add_reference(name); // TODO is this redundant/misplaced?
} }
} else if (is_contextual(component, template_scope, name)) { } 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(); this.skip();
@ -260,42 +262,37 @@ export default class Expression {
// function can be hoisted inside the component init // function can be hoisted inside the component init
component.partly_hoisted.push(declaration); component.partly_hoisted.push(declaration);
this.replace(x`#ctx.${id}` as any); const i = block.renderer.add_to_context(id.name);
this.replace(x`#ctx[${i}]` as any);
component.add_var({
name: id.name,
internal: true,
referenced: true
});
} }
else { else {
// we need a combo block/init recipe // we need a combo block/init recipe
(node as FunctionExpression).params.unshift({ const deps = Array.from(contextual_dependencies);
type: 'ObjectPattern',
properties: Array.from(contextual_dependencies).map(name => p`${name}` as any) (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); component.partly_hoisted.push(declaration);
this.replace(id as any); const i = block.renderer.add_to_context(id.name);
component.add_var({ this.replace(id as any);
name: id.name,
internal: true,
referenced: true
});
if ((node as FunctionExpression).params.length > 0) { if ((node as FunctionExpression).params.length > 0) {
declarations.push(b` declarations.push(b`
function ${id}(...args) { function ${id}(...args) {
return #ctx.${id}(#ctx, ...args); return #ctx[${i}](${context_args}, ...args);
} }
`); `);
} else { } else {
declarations.push(b` declarations.push(b`
function ${id}() { 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));
} }
} }
}); });

@ -302,7 +302,7 @@ export default class Block {
properties.update = noop; properties.update = noop;
} else { } else {
const ctx = this.maintain_context ? x`#new_ctx` : x`#ctx`; 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.maintain_context && b`#ctx = ${ctx};`}
${this.chunks.update} ${this.chunks.update}
}`; }`;

@ -9,6 +9,8 @@ export default class Renderer {
component: Component; // TODO Maybe Renderer shouldn't know about Component? component: Component; // TODO Maybe Renderer shouldn't know about Component?
options: CompileOptions; options: CompileOptions;
context: string[] = [];
context_lookup: Map<string, number> = new Map();
blocks: Array<Block | Node | Node[]> = []; blocks: Array<Block | Node | Node[]> = [];
readonly: Set<string> = new Set(); readonly: Set<string> = new Set();
meta_bindings: Array<Node | Node[]> = []; // initial values for e.g. window.innerWidth, if there's a <svelte:window> meta tag meta_bindings: Array<Node | Node[]> = []; // initial values for e.g. window.innerWidth, if there's a <svelte:window> meta tag
@ -27,6 +29,11 @@ export default class Renderer {
this.file_var = options.dev && this.component.get_unique_name('file'); 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 // main block
this.block = new Block({ this.block = new Block({
renderer: this, renderer: this,
@ -61,4 +68,93 @@ export default class Renderer {
this.fragment.render(this.block, null, x`#nodes` as Identifier); 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}`);
}
} }

@ -5,9 +5,9 @@ import { CompileOptions } from '../../interfaces';
import { walk } from 'estree-walker'; import { walk } from 'estree-walker';
import add_to_set from '../utils/add_to_set'; import add_to_set from '../utils/add_to_set';
import { extract_names } from '../utils/scope'; import { extract_names } from '../utils/scope';
import { invalidate } from '../utils/invalidate'; import { invalidate } from './invalidate';
import Block from './Block'; 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( export default function dom(
component: Component, component: Component,
@ -80,12 +80,12 @@ export default function dom(
const set = (uses_props || writable_props.length > 0 || component.slots.size > 0) const set = (uses_props || writable_props.length > 0 || component.slots.size > 0)
? x` ? x`
${$$props} => { ${$$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 => ${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 && ${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; : null;
@ -105,7 +105,7 @@ export default function dom(
kind: 'get', kind: 'get',
key: { type: 'Identifier', name: prop.export_name }, key: { type: 'Identifier', name: prop.export_name },
value: x`function() { 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) { } 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); const writable_vars = component.vars.filter(variable => !variable.module && variable.writable);
inject_state = (uses_props || writable_vars.length > 0) ? x` inject_state = (uses_props || writable_vars.length > 0) ? x`
${$$props} => { ${$$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` ${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` ` : x`
@ -216,17 +216,18 @@ export default function dom(
// onto the initial function call // onto the initial function call
const names = new Set(extract_names(assignee)); 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 }) => { component.rewrite_props(({ name, reassigned, export_name }) => {
const value = `$${name}`; const value = `$${name}`;
const i = renderer.context_lookup.get(name);
const insert = (reassigned || export_name) const insert = (reassigned || export_name)
? b`${`$$subscribe_${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) { if (component.compile_options.dev) {
return b`@validate_store(${name}, '${name}'); ${insert}`; return b`@validate_store(${name}, '${name}'); ${insert}`;
@ -256,11 +257,13 @@ export default function dom(
${component.fully_hoisted} ${component.fully_hoisted}
`); `);
const filtered_declarations = component.vars const filtered_declarations = renderer.context
.filter(v => ((v.referenced || v.export_name) && !v.hoistable)) .map(name => name ? ({
.map(v => p`${v.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 filtered_props = props.filter(prop => {
const variable = component.var_lookup.get(prop.name); 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] !== '$'); const reactive_stores = component.vars.filter(variable => variable.name[0] === '$' && variable.name[1] !== '$');
if (component.slots.size > 0) { 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) { 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); const instance_javascript = component.extract_javascript(component.ast.instance);
@ -307,7 +310,7 @@ export default function dom(
}) })
.map(({ name }) => b` .map(({ name }) => b`
${component.compile_options.dev && b`@validate_store(${name.slice(1)}, '${name.slice(1)}');`} ${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 const resubscribable_reactive_store_unsubscribers = reactive_stores
@ -330,9 +333,7 @@ export default function dom(
return variable && (variable.writable || variable.mutated); return variable && (variable.writable || variable.mutated);
}); });
const condition = !uses_props && writable.length > 0 && (writable const condition = !uses_props && writable.length > 0 && renderer.changed(writable);
.map(n => x`#changed.${n}`)
.reduce((lhs, rhs) => x`${lhs} || ${rhs}`));
let statement = d.node; // TODO remove label (use d.node.body) if it's not referenced 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)) { if (store && (store.reassigned || store.export_name)) {
const unsubscribe = `$$unsubscribe_${name}`; const unsubscribe = `$$unsubscribe_${name}`;
const subscribe = `$$subscribe_${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};`; return b`let ${$name};`;
@ -375,8 +378,8 @@ export default function dom(
} }
const return_value = { const return_value = {
type: 'ObjectExpression', type: 'ArrayExpression',
properties: filtered_declarations elements: filtered_declarations
}; };
const reactive_dependencies = { const reactive_dependencies = {

@ -1,10 +1,12 @@
import Component from '../Component';
import { nodes_match } from '../../utils/nodes_match'; import { nodes_match } from '../../utils/nodes_match';
import { Scope } from './scope'; import { Scope } from '../utils/scope';
import { x } from 'code-red'; import { x } from 'code-red';
import { Node } from 'estree'; import { Node } from 'estree';
import Renderer from './Renderer';
export function invalidate(renderer: Renderer, scope: Scope, node: Node, names: Set<string>) {
const { component } = renderer;
export function invalidate(component: Component, scope: Scope, node: Node, names: Set<string>) {
const [head, ...tail] = Array.from(names).filter(name => { const [head, ...tail] = Array.from(names).filter(name => {
const owner = scope.find_owner(name); const owner = scope.find_owner(name);
if (owner && owner !== component.instance_scope) return false; 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; component.has_reactive_assignments = true;
if (node.type === 'AssignmentExpression' && node.operator === '=' && nodes_match(node.left, node.right) && tail.length === 0) { if (node.type === 'AssignmentExpression' && node.operator === '=' && nodes_match(node.left, node.right) && tail.length === 0) {
return component.invalidate(head); return renderer.invalidate(head);
} else { } else {
const is_store_value = head[0] === '$'; const is_store_value = head[0] === '$';
const variable = component.var_lookup.get(head); 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 = ( const pass_value = (
extra_args.length > 0 || 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 = is_store_value
let invalidate = x`${callee}(${is_store_value ? head.slice(1) : x`"${head}"`}, ${node}, ${extra_args})`; ? 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) { if (variable.subscribable && variable.reassigned) {
const subscribe = `$$subscribe_${head}`; const subscribe = `$$subscribe_${head}`;

@ -8,7 +8,6 @@ import FragmentWrapper from './Fragment';
import PendingBlock from '../../nodes/PendingBlock'; import PendingBlock from '../../nodes/PendingBlock';
import ThenBlock from '../../nodes/ThenBlock'; import ThenBlock from '../../nodes/ThenBlock';
import CatchBlock from '../../nodes/CatchBlock'; import CatchBlock from '../../nodes/CatchBlock';
import { changed } from './shared/changed';
import { Identifier } from 'estree'; import { Identifier } from 'estree';
class AwaitBlockBranch extends Wrapper { class AwaitBlockBranch extends Wrapper {
@ -187,7 +186,7 @@ export default class AwaitBlockWrapper extends Wrapper {
if (dependencies.length > 0) { if (dependencies.length > 0) {
const condition = x` const condition = x`
${changed(dependencies)} && ${block.renderer.changed(dependencies)} &&
${promise} !== (${promise} = ${snippet}) && ${promise} !== (${promise} = ${snippet}) &&
@handle_promise(${promise}, ${info})`; @handle_promise(${promise}, ${info})`;
@ -200,7 +199,7 @@ export default class AwaitBlockWrapper extends Wrapper {
if (${condition}) { if (${condition}) {
// nothing // nothing
} else { } else {
${info}.block.p(#changed, @assign(@assign({}, #ctx), ${info}.resolved)); ${info}.block.p(@assign(@assign({}, #ctx), ${info}.resolved), #changed);
} }
`); `);
} else { } else {
@ -211,7 +210,7 @@ export default class AwaitBlockWrapper extends Wrapper {
} else { } else {
if (this.pending.block.has_update_method) { if (this.pending.block.has_update_method) {
block.chunks.update.push(b` block.chunks.update.push(b`
${info}.block.p(#changed, @assign(@assign({}, #ctx), ${info}.resolved)); ${info}.block.p(@assign(@assign({}, #ctx), ${info}.resolved), #changed);
`); `);
} }
} }

@ -5,7 +5,6 @@ import DebugTag from '../../nodes/DebugTag';
import add_to_set from '../../utils/add_to_set'; import add_to_set from '../../utils/add_to_set';
import { b, p } from 'code-red'; import { b, p } from 'code-red';
import { Identifier, DebuggerStatement } from 'estree'; import { Identifier, DebuggerStatement } from 'estree';
import { changed } from './shared/changed';
export default class DebugTagWrapper extends Wrapper { export default class DebugTagWrapper extends Wrapper {
node: DebugTag; node: DebugTag;
@ -70,7 +69,7 @@ export default class DebugTagWrapper extends Wrapper {
debugger;`; debugger;`;
if (dependencies.size) { if (dependencies.size) {
const condition = changed(Array.from(dependencies)); const condition = renderer.changed(Array.from(dependencies));
block.chunks.update.push(b` block.chunks.update.push(b`
if (${condition}) { if (${condition}) {

@ -7,7 +7,6 @@ import FragmentWrapper from './Fragment';
import { b, x } from 'code-red'; import { b, x } from 'code-red';
import ElseBlock from '../../nodes/ElseBlock'; import ElseBlock from '../../nodes/ElseBlock';
import { Identifier, Node } from 'estree'; import { Identifier, Node } from 'estree';
import { changed } from './shared/changed';
export class ElseBlockWrapper extends Wrapper { export class ElseBlockWrapper extends Wrapper {
node: ElseBlock; node: ElseBlock;
@ -81,6 +80,10 @@ export default class EachBlockWrapper extends Wrapper {
const { dependencies } = node.expression; const { dependencies } = node.expression;
block.add_dependencies(dependencies); block.add_dependencies(dependencies);
this.node.contexts.forEach(context => {
renderer.add_to_context(context.key.name, true);
});
this.block = block.child({ this.block = block.child({
comment: create_debugging_comment(this.node, this.renderer.component), comment: create_debugging_comment(this.node, this.renderer.component),
name: renderer.component.get_unique_name('create_each_block'), 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 each_block_value = renderer.component.get_unique_name(`${this.var.name}_value`);
const iterations = block.get_unique_name(`${this.var.name}_blocks`); 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 = { this.vars = {
create_each_block: this.block.name, create_each_block: this.block.name,
each_block_value, each_block_value,
@ -190,18 +196,19 @@ export default class EachBlockWrapper extends Wrapper {
? !this.next.is_dom_node() : ? !this.next.is_dom_node() :
!parent_node || !this.parent.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.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.${this.index_name} = i;`); 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); const snippet = this.node.expression.manipulate(block);
block.chunks.init.push(b`let ${this.vars.each_block_value} = ${snippet};`); 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` renderer.blocks.push(b`
function ${this.vars.get_each_context}(#ctx, list, i) { function ${this.vars.get_each_context}(#ctx, list, i) {
const child_ctx = @_Object.create(#ctx); const child_ctx = #ctx.slice();
${this.context_props} ${this.context_props}
return child_ctx; return child_ctx;
} }
@ -270,7 +277,7 @@ export default class EachBlockWrapper extends Wrapper {
if (this.else.block.has_update_method) { if (this.else.block.has_update_method) {
block.chunks.update.push(b` block.chunks.update.push(b`
if (!${this.vars.data_length} && ${each_block_else}) { 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}) { } 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}.c();
@ -481,7 +488,7 @@ export default class EachBlockWrapper extends Wrapper {
const for_loop_body = this.block.has_update_method const for_loop_body = this.block.has_update_method
? b` ? b`
if (${iterations}[#i]) { 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);`} ${has_transitions && b`@transition_in(${this.vars.iterations}[#i], 1);`}
} else { } else {
${iterations}[#i] = ${create_each_block}(child_ctx); ${iterations}[#i] = ${create_each_block}(child_ctx);
@ -554,7 +561,7 @@ export default class EachBlockWrapper extends Wrapper {
`; `;
block.chunks.update.push(b` block.chunks.update.push(b`
if (${changed(Array.from(all_dependencies))}) { if (${block.renderer.changed(Array.from(all_dependencies))}) {
${update} ${update}
} }
`); `);

@ -6,7 +6,6 @@ import { string_literal } from '../../../utils/stringify';
import { b, x } from 'code-red'; import { b, x } from 'code-red';
import Expression from '../../../nodes/shared/Expression'; import Expression from '../../../nodes/shared/Expression';
import Text from '../../../nodes/Text'; import Text from '../../../nodes/Text';
import { changed } from '../shared/changed';
export default class AttributeWrapper { export default class AttributeWrapper {
node: Attribute; node: Attribute;
@ -140,7 +139,7 @@ export default class AttributeWrapper {
} }
if (dependencies.length > 0) { if (dependencies.length > 0) {
let condition = changed(dependencies); let condition = block.renderer.changed(dependencies);
if (should_cache) { if (should_cache) {
condition = is_src condition = is_src
@ -197,8 +196,8 @@ export default class AttributeWrapper {
} }
let value = this.node.name === 'class' let value = this.node.name === 'class'
? this.get_class_name_text() ? this.get_class_name_text(block)
: this.render_chunks().reduce((lhs, rhs) => x`${lhs} + ${rhs}`); : this.render_chunks(block).reduce((lhs, rhs) => x`${lhs} + ${rhs}`);
// '{foo} {bar}' — treat as string concatenation // '{foo} {bar}' — treat as string concatenation
if (this.node.chunks[0].type !== 'Text') { if (this.node.chunks[0].type !== 'Text') {
@ -208,9 +207,9 @@ export default class AttributeWrapper {
return value; return value;
} }
get_class_name_text() { get_class_name_text(block) {
const scoped_css = this.node.chunks.some((chunk: Text) => chunk.synthetic); 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) { if (scoped_css && rendered.length === 2) {
// we have a situation like class={possiblyUndefined} // we have a situation like class={possiblyUndefined}
@ -220,13 +219,13 @@ export default class AttributeWrapper {
return rendered.reduce((lhs, rhs) => x`${lhs} + ${rhs}`); return rendered.reduce((lhs, rhs) => x`${lhs} + ${rhs}`);
} }
render_chunks() { render_chunks(block: Block) {
return this.node.chunks.map((chunk) => { return this.node.chunks.map((chunk) => {
if (chunk.type === 'Text') { if (chunk.type === 'Text') {
return string_literal(chunk.data); return string_literal(chunk.data);
} }
return chunk.manipulate(); return chunk.manipulate(block);
}); });
} }

@ -6,7 +6,6 @@ import Block from '../../Block';
import Renderer from '../../Renderer'; import Renderer from '../../Renderer';
import flatten_reference from '../../../utils/flatten_reference'; import flatten_reference from '../../../utils/flatten_reference';
import EachBlock from '../../../nodes/EachBlock'; import EachBlock from '../../../nodes/EachBlock';
import { changed } from '../shared/changed';
import { Node, Identifier } from 'estree'; import { Node, Identifier } from 'estree';
export default class BindingWrapper { export default class BindingWrapper {
@ -91,7 +90,7 @@ export default class BindingWrapper {
const dependency_array = [...this.node.expression.dependencies]; const dependency_array = [...this.node.expression.dependencies];
if (dependency_array.length > 0) { if (dependency_array.length > 0) {
update_conditions.push(changed(dependency_array)); update_conditions.push(block.renderer.changed(dependency_array));
} }
if (parent.node.name === 'input') { if (parent.node.name === 'input') {

@ -15,6 +15,7 @@ export default class EventHandlerWrapper {
this.parent = parent; this.parent = parent;
if (!node.expression) { if (!node.expression) {
// TODO use renderer.add_to_context
this.parent.renderer.component.add_var({ this.parent.renderer.component.add_var({
name: node.handler_name.name, name: node.handler_name.name,
internal: true, internal: true,
@ -22,10 +23,10 @@ export default class EventHandlerWrapper {
}); });
this.parent.renderer.component.partly_hoisted.push(b` this.parent.renderer.component.partly_hoisted.push(b`
function ${node.handler_name.name}(event) { function ${node.handler_name.name}(event) {
@bubble($$self, event); @bubble($$self, event);
} }
`); `);
} }
} }

@ -7,7 +7,6 @@ import { string_literal } from '../../../utils/stringify';
import add_to_set from '../../../utils/add_to_set'; import add_to_set from '../../../utils/add_to_set';
import Expression from '../../../nodes/shared/Expression'; import Expression from '../../../nodes/shared/Expression';
import Text from '../../../nodes/Text'; import Text from '../../../nodes/Text';
import { changed } from '../shared/changed';
export interface StyleProp { export interface StyleProp {
key: string; key: string;
@ -46,7 +45,7 @@ export default class StyleAttributeWrapper extends AttributeWrapper {
// } // }
if (prop_dependencies.size) { if (prop_dependencies.size) {
let condition = changed(Array.from(prop_dependencies)); let condition = block.renderer.changed(Array.from(prop_dependencies));
if (block.has_outros) { if (block.has_outros) {
condition = x`!#current || ${condition}`; condition = x`!#current || ${condition}`;

@ -21,7 +21,6 @@ import add_actions from '../shared/add_actions';
import create_debugging_comment from '../shared/create_debugging_comment'; import create_debugging_comment from '../shared/create_debugging_comment';
import { get_context_merger } from '../shared/get_context_merger'; import { get_context_merger } from '../shared/get_context_merger';
import bind_this from '../shared/bind_this'; import bind_this from '../shared/bind_this';
import { changed } from '../shared/changed';
import { is_head } from '../shared/is_head'; import { is_head } from '../shared/is_head';
import { Identifier } from 'estree'; import { Identifier } from 'estree';
import EventHandler from './EventHandler'; 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})`; const children = x`@children(${this.node.name === 'template' ? x`${node}.content` : node})`;
block.add_variable(node); block.add_variable(node);
const render_statement = this.get_render_statement(); const render_statement = this.get_render_statement(block);
block.chunks.create.push( block.chunks.create.push(
b`${node} = ${render_statement};` 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'); 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; const { name, namespace } = this.node;
if (namespace === 'http://www.w3.org/2000/svg') { 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'); const is = this.attributes.find(attr => attr.node.name === 'is');
if (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}")`; return x`@element("${name}")`;
@ -455,18 +454,13 @@ export default class ElementWrapper extends Wrapper {
groups.forEach(group => { groups.forEach(group => {
const handler = renderer.component.get_unique_name(`${this.var.name}_${group.events.join('_')}_handler`); const handler = renderer.component.get_unique_name(`${this.var.name}_${group.events.join('_')}_handler`);
const i = renderer.add_to_context(handler.name);
renderer.component.add_var({
name: handler.name,
internal: true,
referenced: true
});
// TODO figure out how to handle locks // TODO figure out how to handle locks
const needs_lock = group.bindings.some(binding => binding.needs_lock); const needs_lock = group.bindings.some(binding => binding.needs_lock);
const dependencies = new Set(); const dependencies: Set<string> = new Set();
const contextual_dependencies = new Set(); const contextual_dependencies: Set<string> = new Set();
group.bindings.forEach(binding => { group.bindings.forEach(binding => {
// TODO this is a mess // TODO this is a mess
@ -501,21 +495,21 @@ export default class ElementWrapper extends Wrapper {
${animation_frame} = @raf(${handler}); ${animation_frame} = @raf(${handler});
${needs_lock && b`${lock} = true;`} ${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 { } else {
block.chunks.init.push(b` block.chunks.init.push(b`
function ${handler}() { function ${handler}() {
${needs_lock && b`${lock} = true;`} ${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; callee = handler;
} else { } else {
callee = x`#ctx.${handler}`; callee = x`#ctx[${i}]`;
} }
const arg = contextual_dependencies.size > 0 && { const arg = contextual_dependencies.size > 0 && {
@ -537,7 +531,7 @@ export default class ElementWrapper extends Wrapper {
${Array.from(dependencies) ${Array.from(dependencies)
.filter(dep => dep[0] !== '$') .filter(dep => dep[0] !== '$')
.filter(dep => !contextual_dependencies.has(dep)) .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 this.attributes
.forEach(attr => { .forEach(attr => {
const condition = attr.node.dependencies.size > 0 const condition = attr.node.dependencies.size > 0
? changed(Array.from(attr.node.dependencies)) ? block.renderer.changed(Array.from(attr.node.dependencies))
: null; : null;
if (attr.node.is_spread) { if (attr.node.is_spread) {
@ -685,8 +679,6 @@ export default class ElementWrapper extends Wrapper {
const { intro, outro } = this.node; const { intro, outro } = this.node;
if (!intro && !outro) return; if (!intro && !outro) return;
const { component } = this.renderer;
if (intro === outro) { if (intro === outro) {
// bidirectional transition // bidirectional transition
const name = block.get_unique_name(`${this.var.name}_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); block.add_variable(name);
const fn = component.qualify(intro.name); const fn = this.renderer.reference(intro.name);
const intro_block = b` const intro_block = b`
@add_render_callback(() => { @add_render_callback(() => {
@ -740,7 +732,7 @@ export default class ElementWrapper extends Wrapper {
? intro.expression.manipulate(block) ? intro.expression.manipulate(block)
: x`{}`; : x`{}`;
const fn = component.qualify(intro.name); const fn = this.renderer.reference(intro.name);
let intro_block; let intro_block;
@ -782,7 +774,7 @@ export default class ElementWrapper extends Wrapper {
? outro.expression.manipulate(block) ? outro.expression.manipulate(block)
: x`{}`; : x`{}`;
const fn = component.qualify(outro.name); const fn = this.renderer.reference(outro.name);
if (!intro) { if (!intro) {
block.chunks.intro.push(b` block.chunks.intro.push(b`
@ -814,7 +806,6 @@ export default class ElementWrapper extends Wrapper {
add_animation(block: Block) { add_animation(block: Block) {
if (!this.node.animation) return; if (!this.node.animation) return;
const { component } = this.renderer;
const { outro } = this.node; const { outro } = this.node;
const rect = block.get_unique_name('rect'); 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 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` block.chunks.animate.push(b`
${stop_animation}(); ${stop_animation}();
@ -844,7 +835,7 @@ export default class ElementWrapper extends Wrapper {
} }
add_actions(block: Block) { 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) { add_classes(block: Block) {
@ -868,7 +859,7 @@ export default class ElementWrapper extends Wrapper {
block.chunks.update.push(updater); block.chunks.update.push(updater);
} else if ((dependencies && dependencies.size > 0) || this.class_dependencies.length) { } else if ((dependencies && dependencies.size > 0) || this.class_dependencies.length) {
const all_dependencies = this.class_dependencies.concat(...dependencies); 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` block.chunks.update.push(b`
if (${condition}) { if (${condition}) {

@ -10,7 +10,6 @@ import { b, x } from 'code-red';
import { walk } from 'estree-walker'; import { walk } from 'estree-walker';
import { is_head } from './shared/is_head'; import { is_head } from './shared/is_head';
import { Identifier, Node } from 'estree'; import { Identifier, Node } from 'estree';
import { changed } from './shared/changed';
function is_else_if(node: ElseBlock) { function is_else_if(node: ElseBlock) {
return ( return (
@ -272,7 +271,7 @@ export default class IfBlockWrapper extends Wrapper {
? b` ? b`
${snippet && ( ${snippet && (
dependencies.length > 0 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}` : b`if (${condition} == null) ${condition} = !!${snippet}`
)} )}
if (${condition}) return ${block.name};` if (${condition}) return ${block.name};`
@ -323,21 +322,21 @@ export default class IfBlockWrapper extends Wrapper {
if (dynamic) { if (dynamic) {
block.chunks.update.push(b` block.chunks.update.push(b`
if (${current_block_type} === (${current_block_type} = ${select_block_type}(#changed, #ctx)) && ${name}) { if (${current_block_type} === (${current_block_type} = ${select_block_type}(#ctx, #changed)) && ${name}) {
${name}.p(#changed, #ctx); ${name}.p(#ctx, #changed);
} else { } else {
${change_block} ${change_block}
} }
`); `);
} else { } else {
block.chunks.update.push(b` 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} ${change_block}
} }
`); `);
} }
} else if (dynamic) { } 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) { if (if_exists_condition) {
@ -391,7 +390,7 @@ export default class IfBlockWrapper extends Wrapper {
? b` ? b`
${snippet && ( ${snippet && (
dependencies.length > 0 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}` : b`if (${condition} == null) ${condition} = !!${snippet}`
)} )}
if (${condition}) return ${i};` if (${condition}) return ${i};`
@ -476,7 +475,7 @@ export default class IfBlockWrapper extends Wrapper {
let ${previous_block_index} = ${current_block_type_index}; let ${previous_block_index} = ${current_block_type_index};
${current_block_type_index} = ${select_block_type}(#changed, #ctx); ${current_block_type_index} = ${select_block_type}(#changed, #ctx);
if (${current_block_type_index} === ${previous_block_index}) { 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 { } else {
${change_block} ${change_block}
} }
@ -491,7 +490,7 @@ export default class IfBlockWrapper extends Wrapper {
`); `);
} }
} else if (dynamic) { } 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( block.chunks.destroy.push(
@ -528,7 +527,7 @@ export default class IfBlockWrapper extends Wrapper {
const enter = dynamic const enter = dynamic
? b` ? b`
if (${name}) { if (${name}) {
${name}.p(#changed, #ctx); ${name}.p(#ctx, #changed);
${has_transitions && b`@transition_in(${name}, 1);`} ${has_transitions && b`@transition_in(${name}, 1);`}
} else { } else {
${name} = ${branch.block.name}(#ctx); ${name} = ${branch.block.name}(#ctx);
@ -549,7 +548,7 @@ export default class IfBlockWrapper extends Wrapper {
`; `;
if (branch.snippet) { 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, // no `p()` here — we don't want to update outroing nodes,
@ -578,7 +577,7 @@ export default class IfBlockWrapper extends Wrapper {
} }
} else if (dynamic) { } else if (dynamic) {
block.chunks.update.push(b` block.chunks.update.push(b`
if (${branch.condition}) ${name}.p(#changed, #ctx); if (${branch.condition}) ${name}.p(#ctx, #changed);
`); `);
} }

@ -14,7 +14,6 @@ import EachBlock from '../../../nodes/EachBlock';
import TemplateScope from '../../../nodes/shared/TemplateScope'; import TemplateScope from '../../../nodes/shared/TemplateScope';
import is_dynamic from '../shared/is_dynamic'; import is_dynamic from '../shared/is_dynamic';
import bind_this from '../shared/bind_this'; import bind_this from '../shared/bind_this';
import { changed } from '../shared/changed';
import { Node, Identifier, ObjectExpression } from 'estree'; import { Node, Identifier, ObjectExpression } from 'estree';
import EventHandler from '../Element/EventHandler'; import EventHandler from '../Element/EventHandler';
@ -206,7 +205,7 @@ export default class InlineComponentWrapper extends Wrapper {
const { name, dependencies } = attr; const { name, dependencies } = attr;
const condition = dependencies.size > 0 && (dependencies.size !== all_dependencies.size) const condition = dependencies.size > 0 && (dependencies.size !== all_dependencies.size)
? changed(Array.from(dependencies)) ? renderer.changed(Array.from(dependencies))
: null; : null;
if (attr.is_spread) { if (attr.is_spread) {
@ -239,7 +238,7 @@ export default class InlineComponentWrapper extends Wrapper {
`); `);
if (all_dependencies.size) { if (all_dependencies.size) {
const condition = changed(Array.from(all_dependencies)); const condition = renderer.changed(Array.from(all_dependencies));
updates.push(b` updates.push(b`
const ${name_changes} = ${condition} ? @get_spread_update(${levels}, [ const ${name_changes} = ${condition} ? @get_spread_update(${levels}, [
@ -255,7 +254,7 @@ export default class InlineComponentWrapper extends Wrapper {
dynamic_attributes.forEach((attribute: Attribute) => { dynamic_attributes.forEach((attribute: Attribute) => {
const dependencies = attribute.get_dependencies(); const dependencies = attribute.get_dependencies();
if (dependencies.length > 0) { if (dependencies.length > 0) {
const condition = changed(dependencies); const condition = renderer.changed(dependencies);
updates.push(b` updates.push(b`
if (${condition}) ${name_changes}.${attribute.name} = ${attribute.get_value(block)}; 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) { if (non_let_dependencies.length > 0) {
updates.push(b` updates.push(b`
if (${changed(non_let_dependencies)}) { if (${renderer.changed(non_let_dependencies)}) {
${name_changes}.$$scope = { changed: #changed, ctx: #ctx }; ${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`); const id = component.get_unique_name(`${this.var.name}_${binding.name}_binding`);
const i = renderer.add_to_context(id.name);
component.add_var({
name: id.name,
internal: true,
referenced: true
});
const updating = block.get_unique_name(`updating_${binding.name}`); const updating = block.get_unique_name(`updating_${binding.name}`);
block.add_variable(updating); block.add_variable(updating);
@ -299,7 +293,7 @@ export default class InlineComponentWrapper extends Wrapper {
); );
updates.push(b` updates.push(b`
if (!${updating} && ${changed(Array.from(binding.expression.dependencies))}) { if (!${updating} && ${renderer.changed(Array.from(binding.expression.dependencies))}) {
${updating} = true; ${updating} = true;
${name_changes}.${binding.name} = ${snippet}; ${name_changes}.${binding.name} = ${snippet};
@add_flush_callback(() => ${updating} = false); @add_flush_callback(() => ${updating} = false);
@ -338,7 +332,7 @@ export default class InlineComponentWrapper extends Wrapper {
block.chunks.init.push(b` block.chunks.init.push(b`
function ${id}(${value}) { 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 { } else {
block.chunks.init.push(b` block.chunks.init.push(b`
function ${id}(${value}) { 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` const body = b`
function ${id}(${args}) { function ${id}(${args}) {
${lhs} = ${value}; ${lhs} = ${value};
${component.invalidate(dependencies[0])}; ${renderer.invalidate(dependencies[0])};
} }
`; `;
@ -460,7 +454,7 @@ export default class InlineComponentWrapper extends Wrapper {
} else { } else {
const expression = this.node.name === 'svelte:self' const expression = this.node.name === 'svelte:self'
? component.name ? component.name
: component.qualify(this.node.name); : this.renderer.reference(this.node.name);
block.chunks.init.push(b` block.chunks.init.push(b`
${(this.node.attributes.length > 0 || this.node.bindings.length > 0) && b` ${(this.node.attributes.length > 0 || this.node.bindings.length > 0) && b`

@ -10,7 +10,6 @@ import get_slot_data from '../../utils/get_slot_data';
import Expression from '../../nodes/shared/Expression'; import Expression from '../../nodes/shared/Expression';
import is_dynamic from './shared/is_dynamic'; import is_dynamic from './shared/is_dynamic';
import { Identifier, ObjectExpression } from 'estree'; import { Identifier, ObjectExpression } from 'estree';
import { changed } from './shared/changed';
export default class SlotWrapper extends Wrapper { export default class SlotWrapper extends Wrapper {
node: Slot; node: Slot;
@ -184,10 +183,10 @@ export default class SlotWrapper extends Wrapper {
}); });
block.chunks.update.push(b` block.chunks.update.push(b`
if (${slot} && ${slot}.p && ${changed(dynamic_dependencies)}) { if (${slot} && ${slot}.p && ${renderer.changed(dynamic_dependencies)}) {
${slot}.p( ${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})
); );
} }
`); `);

@ -7,7 +7,6 @@ import { string_literal } from '../../utils/stringify';
import add_to_set from '../../utils/add_to_set'; import add_to_set from '../../utils/add_to_set';
import Text from '../../nodes/Text'; import Text from '../../nodes/Text';
import { Identifier } from 'estree'; import { Identifier } from 'estree';
import { changed } from './shared/changed';
import MustacheTag from '../../nodes/MustacheTag'; import MustacheTag from '../../nodes/MustacheTag';
export default class TitleWrapper extends Wrapper { export default class TitleWrapper extends Wrapper {
@ -76,7 +75,7 @@ export default class TitleWrapper extends Wrapper {
if (all_dependencies.size) { if (all_dependencies.size) {
const dependencies = Array.from(all_dependencies); const dependencies = Array.from(all_dependencies);
let condition = changed(dependencies); let condition = block.renderer.changed(dependencies);
if (block.has_outros) { if (block.has_outros) {
condition = x`!#current || ${condition}`; condition = x`!#current || ${condition}`;

@ -5,7 +5,6 @@ import { b, x } from 'code-red';
import add_event_handlers from './shared/add_event_handlers'; import add_event_handlers from './shared/add_event_handlers';
import Window from '../../nodes/Window'; import Window from '../../nodes/Window';
import add_actions from './shared/add_actions'; import add_actions from './shared/add_actions';
import { changed } from './shared/changed';
import { Identifier } from 'estree'; import { Identifier } from 'estree';
import { TemplateNode } from '../../../interfaces'; import { TemplateNode } from '../../../interfaces';
import EventHandler from './Element/EventHandler'; import EventHandler from './Element/EventHandler';
@ -49,7 +48,7 @@ export default class WindowWrapper extends Wrapper {
const events = {}; const events = {};
const bindings: Record<string, string> = {}; const bindings: Record<string, string> = {};
add_actions(component, block, '@_window', this.node.actions); add_actions(block, '@_window', this.node.actions);
add_event_handlers(block, '@_window', this.handlers); add_event_handlers(block, '@_window', this.handlers);
this.node.bindings.forEach(binding => { this.node.bindings.forEach(binding => {
@ -122,20 +121,16 @@ export default class WindowWrapper extends Wrapper {
`); `);
} }
component.add_var({ const i = renderer.add_to_context(id.name);
name: id.name,
internal: true,
referenced: true
});
component.partly_hoisted.push(b` component.partly_hoisted.push(b`
function ${id}() { 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` block.chunks.init.push(b`
@add_render_callback(#ctx.${id}); @add_render_callback(#ctx[${i}]);
`); `);
component.has_reactive_assignments = true; 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 // special case... might need to abstract this out if we add more special cases
if (bindings.scrollX || bindings.scrollY) { 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 scrollX = bindings.scrollX ? x`#ctx.${bindings.scrollX}` : x`@_window.pageXOffset`;
const scrollY = bindings.scrollY ? x`#ctx.${bindings.scrollY}` : x`@_window.pageYOffset`; 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 id = block.get_unique_name(`onlinestatuschanged`);
const name = bindings.online; const name = bindings.online;
component.add_var({ const i = renderer.add_to_context(id.name);
name: id.name,
internal: true,
referenced: true
});
component.partly_hoisted.push(b` component.partly_hoisted.push(b`
function ${id}() { function ${id}() {
${component.invalidate(name, x`${name} = @_navigator.onLine`)} ${renderer.invalidate(name, x`${name} = @_navigator.onLine`)}
} }
`); `);
block.chunks.init.push(b` block.chunks.init.push(b`
@add_render_callback(#ctx.${id}); @add_render_callback(#ctx[${i}]);
`); `);
block.event_listeners.push( block.event_listeners.push(
x`@listen(@_window, "online", #ctx.${id})`, x`@listen(@_window, "online", #ctx[${i}])`,
x`@listen(@_window, "offline", #ctx.${id})` x`@listen(@_window, "offline", #ctx[${i}])`
); );
component.has_reactive_assignments = true; component.has_reactive_assignments = true;

@ -5,7 +5,6 @@ import Block from '../../Block';
import MustacheTag from '../../../nodes/MustacheTag'; import MustacheTag from '../../../nodes/MustacheTag';
import RawMustacheTag from '../../../nodes/RawMustacheTag'; import RawMustacheTag from '../../../nodes/RawMustacheTag';
import { Node } from 'estree'; import { Node } from 'estree';
import { changed } from './changed';
export default class Tag extends Wrapper { export default class Tag extends Wrapper {
node: MustacheTag | RawMustacheTag; 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 (this.node.should_cache) block.add_variable(value, snippet); // TODO may need to coerce snippet to string
if (dependencies.length > 0) { if (dependencies.length > 0) {
let condition = changed(dependencies); let condition = block.renderer.changed(dependencies);
if (block.has_outros) { if (block.has_outros) {
condition = x`!#current || ${condition}`; condition = x`!#current || ${condition}`;

@ -1,10 +1,8 @@
import { b, x } from 'code-red'; import { b, x } from 'code-red';
import Block from '../../Block'; import Block from '../../Block';
import Action from '../../../nodes/Action'; import Action from '../../../nodes/Action';
import Component from '../../../Component';
export default function add_actions( export default function add_actions(
component: Component,
block: Block, block: Block,
target: string, target: string,
actions: Action[] actions: Action[]
@ -25,7 +23,7 @@ export default function add_actions(
block.add_variable(id); block.add_variable(id);
const fn = component.qualify(action.name); const fn = block.renderer.reference(action.name);
block.chunks.mount.push( block.chunks.mount.push(
b`${id} = ${fn}.call(null, ${target}, ${snippet}) || {};` b`${id} = ${fn}.call(null, ${target}, ${snippet}) || {};`
@ -34,14 +32,8 @@ export default function add_actions(
if (dependencies && dependencies.length > 0) { if (dependencies && dependencies.length > 0) {
let condition = x`@is_function(${id}.update)`; let condition = x`@is_function(${id}.update)`;
// TODO can this case be handled more elegantly?
if (dependencies.length > 0) { if (dependencies.length > 0) {
let changed = x`#changed.${dependencies[0]}`; condition = x`${condition} && ${block.renderer.changed(dependencies)}`;
for (let i = 1; i < dependencies.length; i += 1) {
changed = x`${changed} || #changed.${dependencies[i]}`;
}
condition = x`${condition} && ${changed}`;
} }
block.chunks.update.push( block.chunks.update.push(

@ -7,12 +7,7 @@ import { Identifier } from 'estree';
export default function bind_this(component: Component, block: Block, binding: Binding, variable: Identifier) { export default function bind_this(component: Component, block: Block, binding: Binding, variable: Identifier) {
const fn = component.get_unique_name(`${variable.name}_binding`); const fn = component.get_unique_name(`${variable.name}_binding`);
const i = block.renderer.add_to_context(fn.name);
component.add_var({
name: fn.name,
internal: true,
referenced: true
});
let lhs; let lhs;
let object; let object;
@ -32,11 +27,11 @@ export default function bind_this(component: Component, block: Block, binding: B
body = binding.raw_expression.type === 'Identifier' body = binding.raw_expression.type === 'Identifier'
? b` ? b`
${component.invalidate(object, x`${lhs} = $$value`)}; ${block.renderer.invalidate(object, x`${lhs} = $$value`)};
` `
: b` : b`
${lhs} = $$value; ${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}`); const unassign = block.get_unique_name(`unassign_${variable.name}`);
block.chunks.init.push(b` block.chunks.init.push(b`
const ${assign} = () => #ctx.${fn}(${variable}, ${args}); const ${assign} = () => #ctx[${i}](${variable}, ${args});
const ${unassign} = () => #ctx.${fn}(null, ${args}); const ${unassign} = () => #ctx[${i}](null, ${args});
`); `);
const condition = Array.from(contextual_dependencies) 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}`); .reduce((lhs, rhs) => x`${lhs} || ${rhs}`);
// we push unassign and unshift assign so that references are // 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);`); block.chunks.destroy.push(b`#ctx[${i}](null);`);
return b`#ctx.${fn}(${variable});`; return b`#ctx[${i}](${variable});`;
} }

@ -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}`);
}

@ -21,7 +21,7 @@ interface Fragment {
} }
// eslint-disable-next-line @typescript-eslint/class-name-casing // eslint-disable-next-line @typescript-eslint/class-name-casing
interface T$$ { interface T$$ {
dirty: null; dirty: number;
ctx: null|any; ctx: null|any;
bound: any; bound: any;
update: () => void; update: () => void;
@ -87,13 +87,13 @@ export function destroy_component(component, detaching) {
} }
} }
function make_dirty(component, key) { function make_dirty(component, i) {
if (!component.$$.dirty) { if (component.$$.dirty === -1) {
dirty_components.push(component); dirty_components.push(component);
schedule_update(); 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) { 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 // everything else
callbacks: blank_object(), callbacks: blank_object(),
dirty: null dirty: -1
}; };
let ready = false; let ready = false;
$$.ctx = instance $$.ctx = instance
? instance(component, prop_values, (key, ret, value = ret) => { ? instance(component, prop_values, (i, ret, value = ret) => {
if ($$.ctx && not_equal($$.ctx[key], $$.ctx[key] = value)) { if ($$.ctx && not_equal($$.ctx[i], $$.ctx[i] = value)) {
if ($$.bound[key]) $$.bound[key](value); if ($$.bound[i]) $$.bound[i](value);
if (ready) make_dirty(component, key); if (ready) make_dirty(component, i);
} }
return ret; return ret;
}) })

@ -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 = create_each_block(key, child_ctx);
block.c(); block.c();
} else if (dynamic) { } else if (dynamic) {
block.p(changed, child_ctx); block.p(child_ctx, changed);
} }
new_lookup.set(key, new_blocks[i] = block); new_lookup.set(key, new_blocks[i] = block);

@ -73,8 +73,8 @@ function update($$) {
if ($$.fragment !== null) { if ($$.fragment !== null) {
$$.update($$.dirty); $$.update($$.dirty);
run_all($$.before_update); run_all($$.before_update);
$$.fragment && $$.fragment.p($$.dirty, $$.ctx); $$.fragment && $$.fragment.p($$.ctx, $$.dirty);
$$.dirty = null; $$.dirty = -1;
$$.after_update.forEach(add_render_callback); $$.after_update.forEach(add_render_callback);
} }

@ -23,8 +23,8 @@ function create_fragment(ctx) {
insert(target, button, anchor); insert(target, button, anchor);
foo_action = foo.call(null, button, ctx[1]) || ({}); foo_action = foo.call(null, button, ctx[1]) || ({});
}, },
p(ctx) { p(ctx, changed) {
if (is_function(foo_action.update)) foo_action.update.call(null, ctx[1]); if (is_function(foo_action.update) && changed.bar) foo_action.update.call(null, ctx[1]);
}, },
i: noop, i: noop,
o: noop, o: noop,

@ -26,7 +26,7 @@ process.on('unhandledRejection', err => {
unhandled_rejection = err; unhandled_rejection = err;
}); });
describe("runtime", () => { describe.only("runtime", () => {
before(() => { before(() => {
svelte = loadSvelte(false); svelte = loadSvelte(false);
svelte$ = loadSvelte(true); svelte$ = loadSvelte(true);

@ -1,7 +1,7 @@
export default { export default {
html: '<button>0, 0</button>', html: '<button>0, 0</button>',
async test({ assert, component, target, window }) { async test({ assert, target, window }) {
const event = new window.MouseEvent('click', { const event = new window.MouseEvent('click', {
clientX: 42, clientX: 42,
clientY: 42 clientY: 42

Loading…
Cancel
Save