ugh so close

pull/3945/head
Rich Harris 6 years ago
parent 4338d2e64d
commit 3d8bd5ddad

@ -209,7 +209,7 @@ export default class Component {
});
const subscribable_name = name.slice(1);
this.add_reference(subscribable_name);
// this.add_reference(subscribable_name);
const variable = this.var_lookup.get(subscribable_name);
if (variable) variable.subscribable = true;

@ -34,7 +34,7 @@ export default class Block {
key: Identifier;
first: Identifier;
dependencies: Set<string>;
dependencies: Set<string> = new Set();
bindings: Map<string, {
object: Identifier;
@ -90,8 +90,6 @@ export default class Block {
this.key = options.key;
this.first = null;
this.dependencies = new Set();
this.bindings = options.bindings;
this.chunks = {

@ -1,17 +1,26 @@
import Block from './Block';
import { CompileOptions } from '../../interfaces';
import { CompileOptions, Var } from '../../interfaces';
import Component from '../Component';
import FragmentWrapper from './wrappers/Fragment';
import { x } from 'code-red';
import { Node, Identifier, MemberExpression } from 'estree';
import { Node, Identifier, MemberExpression, Literal } from 'estree';
import flatten_reference from '../utils/flatten_reference';
type ContextMember = {
name: string;
index: Literal;
is_contextual: boolean;
is_non_contextual: boolean;
variable: Var;
priority: number;
};
export default class Renderer {
component: Component; // TODO Maybe Renderer shouldn't know about Component?
options: CompileOptions;
context: string[] = [];
context_lookup: Map<string, number> = new Map();
context: ContextMember[] = [];
context_lookup: Map<string, ContextMember> = new Map();
blocks: Array<Block | Node | Node[]> = [];
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
@ -31,22 +40,18 @@ export default class Renderer {
this.file_var = options.dev && this.component.get_unique_name('file');
// TODO sort vars, most frequently referenced first?
component.vars
.filter(v => ((v.referenced || v.export_name) && !v.hoistable))
.forEach(v => this.add_to_context(v.name));
component.vars.filter(v => !v.hoistable || (v.export_name && !v.module)).forEach(v => this.add_to_context(v.name));
// ensure store values are included in context
component.vars
.filter(v => v.subscribable)
.forEach(v => this.add_to_context(`$${v.name}`));
component.vars.filter(v => v.subscribable).forEach(v => this.add_to_context(`$${v.name}`));
if (component.var_lookup.has('$$props')) {
this.add_to_context('$$props');
}
if (component.slots.size > 0) {
this.add_to_context('$$slots');
this.add_to_context('$$scope');
this.add_to_context('$$slots');
}
if (this.binding_groups.length > 0) {
@ -86,25 +91,62 @@ export default class Renderer {
this.block.assign_variable_names();
this.fragment.render(this.block, null, x`#nodes` as Identifier);
this.context.forEach(member => {
const { variable } = member;
if (variable) {
member.priority += 2;
if (variable.mutated || variable.reassigned) member.priority += 4;
// these determine whether variable is included in initial context
// array, so must have the highest priority
if (variable.export_name) member.priority += 8;
if (variable.referenced) member.priority += 16;
}
if (!member.is_contextual) {
member.priority += 1;
}
});
this.context.sort((a, b) => b.priority - a.priority);
this.context.forEach((member, i) => member.index.value = i);
}
add_to_context(name: string, contextual = false) {
if (!this.context_lookup.has(name)) {
const i = this.context.length;
const member: ContextMember = {
name,
index: { type: 'Literal', value: -1 }, // set later
is_contextual: false,
is_non_contextual: false, // shadowed vars could be contextual and non-contextual
variable: null,
priority: 0
};
this.context_lookup.set(name, member);
this.context.push(member);
}
const member = this.context_lookup.get(name);
this.context_lookup.set(name, i);
this.context.push(contextual ? null : name);
if (contextual) {
member.is_contextual = true;
} else {
member.is_non_contextual = true;
const variable = this.component.var_lookup.get(name);
member.variable = variable;
}
return this.context_lookup.get(name);
return member;
}
invalidate(name: string, value?) {
const variable = this.component.var_lookup.get(name);
const i = this.context_lookup.get(name);
const member = this.context_lookup.get(name);
if (variable && (variable.subscribable && (variable.reassigned || variable.export_name))) {
return x`${`$$subscribe_${name}`}($$invalidate(${i}, ${value || name}))`;
return x`${`$$subscribe_${name}`}($$invalidate(${member.index}, ${value || name}))`;
}
if (name[0] === '$' && name[1] !== '$') {
@ -122,7 +164,7 @@ export default class Renderer {
}
if (value) {
return x`$$invalidate(${i}, ${value})`;
return x`$$invalidate(${member.index}, ${value})`;
}
// if this is a reactive declaration, invalidate dependencies recursively
@ -140,16 +182,47 @@ export default class Renderer {
});
return Array.from(deps)
.map(n => x`$$invalidate(${this.context_lookup.get(n)}, ${n})`)
.map(n => x`$$invalidate(${this.context_lookup.get(n).index}, ${n})`)
.reduce((lhs, rhs) => x`${lhs}, ${rhs}}`);
}
get_bitmask(names) {
const { context_lookup } = this;
// names.forEach(name => {
// if (!context_lookup.has(name)) {
// console.log({ names });
// throw new Error(`wut ${name}`);
// }
// });
return {
type: 'Literal',
// we need to use a getter so that bitmasks can be determined
// lazily, once context has fully shaken out. TODO would be nice
// to do everything in a different order so that this isn't necessary
get value() {
return names.reduce((bits, name) => {
const bit = 1 << this.context_lookup.get(name);
const member = context_lookup.get(name);
if (!member) return bits;
if (member.index.value === -1) {
throw new Error(`unset index`);
}
// if (!member) {
// console.log({ names });
// throw new Error(`wut ${name}`);
// }
const bit = 1 << (member.index.value as number);
return bits | bit;
}, 0);
}
};
}
changed(names, is_reactive_declaration = false) {
const bitmask = this.get_bitmask(names);
@ -165,17 +238,17 @@ export default class Renderer {
}
const { name, nodes } = flatten_reference(node);
const i = this.context_lookup.get(name);
const member = this.context_lookup.get(name);
// TODO is this correct?
if (this.component.var_lookup.get(name)) {
this.component.add_reference(name);
}
if (i !== undefined) {
const replacement = x`#ctx[${i}]` as MemberExpression;
if (member !== undefined) {
const replacement = x`#ctx[${member.index}]` as MemberExpression;
replacement.object.loc = nodes[0].loc;
if (nodes[0].loc) replacement.object.loc = nodes[0].loc;
nodes[0] = replacement;
return nodes.reduce((lhs, rhs) => x`${lhs}.${rhs}`);

@ -104,7 +104,7 @@ export default function dom(
kind: 'get',
key: { type: 'Identifier', name: prop.export_name },
value: x`function() {
return ${prop.hoistable ? prop.name : x`this.$$.ctx[${renderer.context_lookup.get(prop.name)}]`}
return ${prop.hoistable ? prop.name : x`this.$$.ctx[${renderer.context_lookup.get(prop.name).index}]`}
}`
});
} else if (component.compile_options.dev) {
@ -222,7 +222,7 @@ export default function dom(
component.rewrite_props(({ name, reassigned, export_name }) => {
const value = `$${name}`;
const i = renderer.context_lookup.get(`$${name}`);
const i = renderer.context_lookup.get(`$${name}`).index;
const insert = (reassigned || export_name)
? b`${`$$subscribe_${name}`}()`
@ -268,12 +268,31 @@ export default function dom(
const instance_javascript = component.extract_javascript(component.ast.instance);
let i = renderer.context.length;
while (i--) {
const member = renderer.context[i];
if (member.variable) {
if (member.variable.referenced || member.variable.export_name) break;
} else if (member.is_non_contextual) {
break;
}
}
const initial_context = renderer.context.slice(0, i + 1);
// const initial_context = renderer.context.filter(member => {
// // return member.variable
// // ? (member.variable.referenced || member.variable.export_name)
// // : !member.is_contextual;
// return member.variable || !member.is_contextual;
// });
const has_definition = (
(instance_javascript && instance_javascript.length > 0) ||
filtered_props.length > 0 ||
uses_props ||
component.partly_hoisted.length > 0 ||
renderer.context.length > 0 ||
initial_context.length > 0 ||
component.reactive_declarations.length > 0
);
@ -288,7 +307,7 @@ export default function dom(
})
.map(({ name }) => b`
${component.compile_options.dev && b`@validate_store(${name.slice(1)}, '${name.slice(1)}');`}
@component_subscribe($$self, ${name.slice(1)}, $$value => $$invalidate(${renderer.context_lookup.get(name)}, ${name} = $$value));
@component_subscribe($$self, ${name.slice(1)}, $$value => $$invalidate(${renderer.context_lookup.get(name).index}, ${name} = $$value));
`);
const resubscribable_reactive_store_unsubscribers = reactive_stores
@ -308,7 +327,7 @@ export default function dom(
const writable = dependencies.filter(n => {
const variable = component.var_lookup.get(n);
return variable && (variable.writable || variable.mutated);
return variable && (variable.export_name || variable.mutated || variable.reassigned);
});
const condition = !uses_props && writable.length > 0 && renderer.changed(writable, true);
@ -337,7 +356,7 @@ export default function dom(
if (store && (store.reassigned || store.export_name)) {
const unsubscribe = `$$unsubscribe_${name}`;
const subscribe = `$$subscribe_${name}`;
const i = renderer.context_lookup.get($name);
const i = renderer.context_lookup.get($name).index;
return b`let ${$name}, ${unsubscribe} = @noop, ${subscribe} = () => (${unsubscribe}(), ${unsubscribe} = @subscribe(${name}, $$value => $$invalidate(${i}, ${$name} = $$value)), ${name})`;
}
@ -357,11 +376,10 @@ export default function dom(
const return_value = {
type: 'ArrayExpression',
elements: renderer.context
.map(name => name ? ({
elements: initial_context.map(member => ({
type: 'Identifier',
name
}) as Expression : x`null`)
name: member.name
}) as Expression)
};
body.push(b`
@ -406,7 +424,7 @@ export default function dom(
}
const prop_indexes = x`{
${props.filter(v => !v.hoistable).map(v => p`${v.export_name}: ${renderer.context_lookup.get(v.name)}`)}
${props.filter(v => v.export_name && !v.module).map(v => p`${v.export_name}: ${renderer.context_lookup.get(v.name).index}`)}
}` as ObjectExpression;
if (options.customElement) {

@ -52,7 +52,7 @@ export function invalidate(renderer: Renderer, scope: Scope, node: Node, names:
let invalidate = is_store_value
? x`@set_store_value(${head.slice(1)}, ${node}, ${extra_args})`
: x`$$invalidate(${renderer.context_lookup.get(head)}, ${node}, ${extra_args})`;
: x`$$invalidate(${renderer.context_lookup.get(head).index}, ${node}, ${extra_args})`;
if (variable.subscribable && variable.reassigned) {
const subscribe = `$$subscribe_${head}`;

@ -118,6 +118,9 @@ export default class AwaitBlockWrapper extends Wrapper {
if (has_outros) {
block.add_outro();
}
if (this.node.value) block.renderer.add_to_context(this.node.value, true);
if (this.node.error) block.renderer.add_to_context(this.node.error, true);
}
render(
@ -137,8 +140,8 @@ export default class AwaitBlockWrapper extends Wrapper {
block.maintain_context = true;
const value_index = this.node.value && block.renderer.add_to_context(this.node.value, true);
const error_index = this.node.error && block.renderer.add_to_context(this.node.error, true);
const value_index = this.node.value && block.renderer.context_lookup.get(this.node.value).index;
const error_index = this.node.error && block.renderer.context_lookup.get(this.node.error).index;
const info_props: any = x`{
ctx: #ctx,

@ -196,10 +196,10 @@ export default class EachBlockWrapper extends Wrapper {
? !this.next.is_dom_node() :
!parent_node || !this.parent.is_dom_node();
this.context_props = this.node.contexts.map(prop => b`child_ctx[${renderer.context_lookup.get(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).index}] = ${prop.modifier(x`list[i]`)};`);
if (this.node.has_binding) this.context_props.push(b`child_ctx[${renderer.context_lookup.get(this.vars.each_block_value.name)}] = list;`);
if (this.node.has_binding || this.node.index) this.context_props.push(b`child_ctx[${renderer.context_lookup.get(this.index_name.name)}] = i;`);
if (this.node.has_binding) this.context_props.push(b`child_ctx[${renderer.context_lookup.get(this.vars.each_block_value.name).index}] = list;`);
if (this.node.has_binding || this.node.index) this.context_props.push(b`child_ctx[${renderer.context_lookup.get(this.index_name.name).index}] = i;`);
const snippet = this.node.expression.manipulate(block);
@ -476,9 +476,8 @@ export default class EachBlockWrapper extends Wrapper {
}
`);
const all_dependencies = new Set(this.block.dependencies);
const { dependencies } = this.node.expression;
dependencies.forEach((dependency: string) => {
const all_dependencies = new Set(this.block.dependencies); // TODO should be dynamic deps only
this.node.expression.dynamic_dependencies().forEach((dependency: string) => {
all_dependencies.add(dependency);
});

@ -156,6 +156,7 @@ export default class InlineComponentWrapper extends Wrapper {
}
if (this.fragment) {
this.renderer.add_to_context('$$scope', true);
const default_slot = this.slots.get('default');
this.fragment.nodes.forEach((child) => {

@ -124,9 +124,6 @@ export default class WindowWrapper extends Wrapper {
`);
}
renderer.add_to_context(id.name);
const reference = renderer.reference(id.name);
component.partly_hoisted.push(b`
function ${id}() {
${props.map(prop => renderer.invalidate(prop.name, x`${prop.name} = @_window.${prop.value}`))}
@ -134,7 +131,7 @@ export default class WindowWrapper extends Wrapper {
`);
block.chunks.init.push(b`
@add_render_callback(${reference});
@add_render_callback(${fn});
`);
component.has_reactive_assignments = true;

@ -25,13 +25,22 @@ export function get_slot_definition(block: Block, scope: TemplateScope, lets: Le
const context = {
type: 'ObjectExpression',
properties: Array.from(names).map(name => p`${block.renderer.context_lookup.get(name)}: ${name}`)
properties: Array.from(names).map(name => p`${block.renderer.context_lookup.get(name).index}: ${name}`)
};
const changes = Array.from(names)
.map(name => {
const i = block.renderer.context_lookup.get(name);
return x`${name} ? ${1 << i} : 0`;
const { context_lookup } = block.renderer;
const literal = {
type: 'Literal',
get value() {
const i = context_lookup.get(name).index.value;
return 1 << i;
}
};
return x`${name} ? ${literal} : 0`;
})
.reduce((lhs, rhs) => x`${lhs} | ${rhs}`);

@ -83,7 +83,7 @@ export function destroy_component(component, detaching) {
// TODO null out other refs, including component.$$ (but need to
// preserve final state?)
$$.on_destroy = $$.fragment = null;
$$.ctx = {};
$$.ctx = [];
}
}
@ -134,7 +134,7 @@ export function init(component, options, instance, create_fragment, not_equal, p
}
return ret;
})
: prop_values;
: [];
$$.update();
ready = true;

@ -22,14 +22,14 @@ function create_fragment(ctx) {
return {
c() {
h1 = element("h1");
t = text(ctx[1]);
t = text(ctx[0]);
},
m(target, anchor) {
insert(target, h1, anchor);
append(h1, t);
},
p(ctx, changed) {
if (changed & 2) set_data(t, ctx[1]);
if (changed & 1) set_data(t, ctx[0]);
},
i: noop,
o: noop,
@ -42,8 +42,8 @@ function create_fragment(ctx) {
function instance($$self, $$props, $$invalidate) {
let $foo;
const foo = writable(0);
component_subscribe($$self, foo, value => $$invalidate(1, $foo = value));
return [foo, $foo];
component_subscribe($$self, foo, value => $$invalidate(0, $foo = value));
return [$foo];
}
class Component extends SvelteComponent {

@ -194,7 +194,7 @@ function instance($$self, $$props, $$invalidate) {
if ("baz" in $$props) $$invalidate(3, baz = $$props.baz);
};
return [things, foo, bar, baz, null, null, null];
return [things, foo, bar, baz];
}
class Component extends SvelteComponentDev {

@ -182,7 +182,7 @@ function instance($$self, $$props, $$invalidate) {
if ("foo" in $$props) $$invalidate(1, foo = $$props.foo);
};
return [things, foo, null, null, null];
return [things, foo];
}
class Component extends SvelteComponentDev {

@ -89,7 +89,7 @@ function create_fragment(ctx) {
insert_dev(target, each_1_anchor, anchor);
},
p: function update(ctx, changed) {
if (changed & 1) {
if (changed & 0) {
each_value = things;
let i;

@ -108,7 +108,7 @@ function instance($$self, $$props, $$invalidate) {
if ("createElement" in $$props) $$invalidate(0, createElement = $$props.createElement);
};
return [createElement, null, null, null];
return [createElement];
}
class Component extends SvelteComponent {

@ -114,7 +114,7 @@ function instance($$self, $$props, $$invalidate) {
if ("e" in $$props) $$invalidate(4, e = $$props.e);
};
return [a, b, c, d, e, null, null, null];
return [a, b, c, d, e];
}
class Component extends SvelteComponent {

@ -159,7 +159,7 @@ function instance($$self, $$props, $$invalidate) {
if ("foo" in $$props) $$invalidate(3, foo = $$props.foo);
};
return [comments, elapsed, time, foo, null, null, null];
return [comments, elapsed, time, foo];
}
class Component extends SvelteComponent {

@ -130,7 +130,7 @@ function instance($$self, $$props, $$invalidate) {
if ("things" in $$props) $$invalidate(0, things = $$props.things);
};
return [things, null, null, null];
return [things];
}
class Component extends SvelteComponent {

@ -99,7 +99,7 @@ function instance($$self, $$props, $$invalidate) {
if ("things" in $$props) $$invalidate(0, things = $$props.things);
};
return [things, null, null, null];
return [things];
}
class Component extends SvelteComponent {

@ -12,10 +12,10 @@ function instance($$self, $$props, $$invalidate) {
$$self.$$.update = () => {
if ($$self.$$.dirty & 1) {
$: $$invalidate("b", b = x);
$: $$invalidate(2, b = x);
}
if ($$self.$$.dirty & 2) {
if ($$self.$$.dirty & 4) {
$: a = b;
}
};

@ -7,10 +7,14 @@ function foo(bar) {
console.log(bar);
}
function instance($$self, $$props, $$invalidate) {
return [foo];
}
class Component extends SvelteComponent {
constructor(options) {
super();
init(this, options, null, null, safe_not_equal, { foo: 0 });
init(this, options, instance, null, safe_not_equal, { foo: 0 });
}
get foo() {

@ -39,7 +39,7 @@ function create_fragment(ctx) {
}
function instance($$self, $$props, $$invalidate) {
let a, b, c;
let a = 1, b = 2, c = 3;
onMount(() => {
const interval = setInterval(
@ -58,15 +58,12 @@ function instance($$self, $$props, $$invalidate) {
let y;
$$self.$$.update = () => {
if ($$self.$$.dirty === -1) {
$: x = a * 2;
}
if ($$self.$$.dirty & 2) {
$: $$invalidate(0, y = b * 2);
}
};
$: x = a * 2;
return [y];
}

@ -1,7 +1,7 @@
<script>
import { onMount } from 'svelte';
let a, b, c;
let a = 1, b = 2, c = 3;
onMount(() => {
const interval = setInterval(() => {

Loading…
Cancel
Save