init store values in situ - fixes #2015

pull/2099/head
Richard Harris 6 years ago
parent 7cde8d06be
commit 534f6e5425

@ -750,7 +750,7 @@ export default class Component {
});
}
rewrite_props() {
rewrite_props(get_insert: (name: string) => string) {
const component = this;
const { code, instance_scope, instance_scope_map: map, componentOptions } = this;
let scope = instance_scope;
@ -771,73 +771,97 @@ export default class Component {
if (node.type === 'VariableDeclaration') {
if (node.kind === 'var' || scope === instance_scope) {
let has_exports = false;
let has_only_exports = true;
node.declarations.forEach((declarator, i) => {
const next = node.declarations[i + 1];
node.declarations.forEach(declarator => {
extractNames(declarator.id).forEach(name => {
const variable = component.var_lookup.get(name);
if (declarator.id.type !== 'Identifier') {
const inserts = [];
if (name === componentOptions.props_object) {
if (variable.export_name) {
component.error(declarator, {
code: 'exported-options-props',
message: `Cannot export props binding`
});
}
extractNames(declarator.id).forEach(name => {
const variable = component.var_lookup.get(name);
if (declarator.id.type !== 'Identifier') {
if (variable.export_name || name === componentOptions.props_object) {
component.error(declarator, {
code: 'destructured-prop',
message: `props binding in destructured declaration is not yet supported`
message: `Cannot declare props in destructured declaration`
});
}
// can't use the @ trick here, because we're
// manipulating the underlying magic string
const exclude_internal_props = component.helper('exclude_internal_props');
const suffix = code.original[declarator.end] === ';'
? ` = ${exclude_internal_props}($$props)`
: ` = ${exclude_internal_props}($$props);`
if (variable.subscribable) {
inserts.push(get_insert(name));
}
});
if (declarator.id.end === declarator.end) {
code.appendLeft(declarator.end, suffix);
if (inserts.length > 0) {
if (next) {
code.overwrite(declarator.end, next.start, `; ${inserts.join('; ')}; ${node.kind} `);
} else {
code.overwrite(declarator.id.end, declarator.end, suffix);
code.appendLeft(declarator.end, `; ${inserts.join('; ')}`);
}
}
return;
}
const { name } = declarator.id;
const variable = component.var_lookup.get(name);
if (name === componentOptions.props_object) {
if (variable.export_name) {
has_exports = true;
component.error(declarator, {
code: 'exported-options-props',
message: `Cannot export props binding`
});
}
if (!variable.export_name || variable.subscribable) {
has_only_exports = false;
// can't use the @ trick here, because we're
// manipulating the underlying magic string
const exclude_internal_props = component.helper('exclude_internal_props');
const suffix = code.original[declarator.end] === ';'
? ` = ${exclude_internal_props}($$props)`
: ` = ${exclude_internal_props}($$props);`
if (declarator.id.end === declarator.end) {
code.appendLeft(declarator.end, suffix);
} else {
code.overwrite(declarator.id.end, declarator.end, suffix);
}
});
});
}
if (variable.export_name) {
if (variable.subscribable) {
coalesced_declarations.push({
kind: node.kind,
declarators: [declarator],
insert: get_insert(name)
});
} else {
if (current_group && current_group.kind !== node.kind) {
current_group = null;
}
if (!current_group) {
current_group = { kind: node.kind, declarators: [], insert: null };
coalesced_declarations.push(current_group);
}
if (has_only_exports) {
if (current_group && current_group[current_group.length - 1].kind !== node.kind) {
current_group.declarators.push(declarator);
}
} else {
current_group = null;
}
// rewrite as a group, later
if (!current_group) {
current_group = [];
coalesced_declarations.push(current_group);
}
if (variable.subscribable) {
let insert = get_insert(name);
current_group.push(node);
} else {
if (has_exports) {
// rewrite in place
throw new Error('TODO rewrite prop declaration in place');
if (next) {
code.overwrite(declarator.end, next.start, `; ${insert}; ${node.kind} `);
} else {
code.appendLeft(declarator.end, `; ${insert}`);
}
}
}
current_group = null;
}
});
}
} else {
if (node.type !== 'ExportNamedDeclaration') {
@ -858,31 +882,25 @@ export default class Component {
let combining = false;
group.forEach(node => {
node.declarations.forEach(declarator => {
const { id, init } = declarator;
if (id.type === 'Identifier') {
const value = init
? this.code.slice(id.start, init.end)
: this.code.slice(id.start, id.end);
group.declarators.forEach(declarator => {
const { id } = declarator;
if (combining) {
code.overwrite(c, id.start, ', ');
} else {
code.appendLeft(id.start, '{ ');
combining = true;
}
} else {
throw new Error('TODO destructured declarations');
}
if (combining) {
code.overwrite(c, id.start, ', ');
} else {
code.appendLeft(id.start, '{ ');
combining = true;
}
c = declarator.end;
});
c = declarator.end;
});
if (combining) {
const suffix = code.original[c] === ';' ? ` } = $$props` : ` } = $$props;`;
const insert = group.insert
? `; ${group.insert}`
: '';
const suffix = code.original[c] === ';' ? ` } = $$props${insert}` : ` } = $$props${insert};`;
code.appendLeft(c, suffix);
}
});

@ -202,38 +202,6 @@ export default function dom(
component.has_reactive_assignments = true;
}
else if (node.type === 'VariableDeclarator') {
const names = extractNames(node.id);
names.forEach(name => {
const owner = scope.findOwner(name);
if (owner && owner !== component.instance_scope) return;
const variable = component.var_lookup.get(name);
if (variable && variable.subscribable) {
const value = `$${name}`;
const subscribe = component.helper('subscribe');
const index = parent.declarations.indexOf(node);
const next = parent.declarations[index + 1];
let insert = `${subscribe}($$self, ${name}, $$value => { ${value} = $$value; $$invalidate('${value}', ${value}) })`;
if (component.compileOptions.dev) {
const validate_store = component.helper('validate_store');
insert = `${validate_store}(${name}, '${name}'); ${insert}`;
}
// initialise store value here
if (next) {
code.overwrite(node.end, next.start, `; ${insert}; ${parent.kind} `);
} else {
// final (or only) declarator
code.appendLeft(node.end, `; ${insert}`);
}
}
});
}
if (pending_assignments.size > 0) {
if (node.type === 'ArrowFunctionExpression') {
const insert = Array.from(pending_assignments).map(name => `$$invalidate('${name}', ${name})`).join(';');
@ -272,7 +240,19 @@ export default function dom(
throw new Error(`TODO this should not happen!`);
}
component.rewrite_props();
component.rewrite_props(name => {
const value = `$${name}`;
const subscribe = component.helper('subscribe');
let insert = `${subscribe}($$self, ${name}, $$value => { ${value} = $$value; $$invalidate('${value}', ${value}) })`;
if (component.compileOptions.dev) {
const validate_store = component.helper('validate_store');
insert = `${validate_store}(${name}, '${name}'); ${insert}`;
}
return insert;
});
}
const args = ['$$self'];
@ -333,17 +313,34 @@ export default function dom(
addToSet(all_reactive_dependencies, d.dependencies);
});
const user_code = component.javascript || (
!component.ast.instance && !component.ast.module && (filtered_props.length > 0 || component.componentOptions.props)
? [
component.componentOptions.props && `let ${component.componentOptions.props} = $$props;`,
filtered_props.length > 0 && `let { ${filtered_props.map(x => x.name).join(', ')} } = $$props;`
].filter(Boolean).join('\n')
: null
);
let user_code;
if (component.javascript) {
user_code = component.javascript;
} else {
if (!component.ast.instance && !component.ast.module && (filtered_props.length > 0 || component.componentOptions.props)) {
const statements = [];
if (component.componentOptions.props) statements.push(`let ${component.componentOptions.props} = $$props;`);
if (filtered_props.length > 0) statements.push(`let { ${filtered_props.map(x => x.name).join(', ')} } = $$props;`);
reactive_stores.forEach(({ name }) => {
if (component.compileOptions.dev) {
statements.push(`${component.compileOptions.dev && `@validate_store(${name.slice(1)}, '${name.slice(1)}');`}`);
}
statements.push(`@subscribe($$self, ${name.slice(1)}, $$value => { ${name} = $$value; $$invalidate('${name}', ${name}); });`);
});
user_code = statements.join('\n');
}
}
const reactive_store_subscriptions = reactive_stores
.filter(store => component.var_lookup.get(store.name).hoistable)
.filter(store => {
const variable = component.var_lookup.get(store.name.slice(1));
return variable.hoistable;
})
.map(({ name }) => deindent`
${component.compileOptions.dev && `@validate_store(${name.slice(1)}, '${name.slice(1)}');`}
@subscribe($$self, ${name.slice(1)}, $$value => { ${name} = $$value; $$invalidate('${name}', ${name}); });

@ -24,57 +24,16 @@ export default function ssr(
{ code: null, map: null } :
component.stylesheet.render(options.filename, true);
// insert store values
if (component.ast.instance) {
let scope = component.instance_scope;
let map = component.instance_scope_map;
walk(component.ast.instance.content, {
enter: (node, parent) => {
if (map.has(node)) {
scope = map.get(node);
}
},
leave(node, parent) {
if (map.has(node)) {
scope = scope.parent;
}
if (node.type === 'VariableDeclarator') {
const names = extractNames(node.id);
names.forEach(name => {
const owner = scope.findOwner(name);
if (owner && owner !== component.instance_scope) return;
const variable = component.var_lookup.get(name);
if (variable && variable.subscribable) {
const value = `$${name}`;
const get_store_value = component.helper('get_store_value');
const index = parent.declarations.indexOf(node);
const next = parent.declarations[index + 1];
let insert = `const ${value} = ${get_store_value}(${name});`;
if (component.compileOptions.dev) {
const validate_store = component.helper('validate_store');
insert = `${validate_store}(${name}, '${name}'); ${insert}`;
}
// initialise store value here
if (next) {
component.code.overwrite(node.end, next.start, `; ${insert}; ${parent.kind} `);
} else {
// final (or only) declarator
component.code.appendLeft(node.end, `; ${insert}`);
}
}
});
}
}
const reactive_stores = component.vars.filter(variable => variable.name[0] === '$');
const reactive_store_values = reactive_stores
.filter(store => component.var_lookup.get(store.name).hoistable)
.map(({ name }) => {
const assignment = `const ${name} = @get_store_value(${name.slice(1)});`;
return component.compileOptions.dev
? `@validate_store(${name.slice(1)}, '${name.slice(1)}'); ${assignment}`
: assignment;
});
}
// TODO remove this, just use component.vars everywhere
const props = component.vars.filter(variable => !variable.module && variable.export_name && variable.export_name !== component.componentOptions.props_object);
@ -82,26 +41,38 @@ export default function ssr(
let user_code;
if (component.javascript) {
component.rewrite_props();
component.rewrite_props(name => {
const value = `$${name}`;
const get_store_value = component.helper('get_store_value');
let insert = `const ${value} = ${get_store_value}(${name})`;
if (component.compileOptions.dev) {
const validate_store = component.helper('validate_store');
insert = `${validate_store}(${name}, '${name}'); ${insert}`;
}
return insert;
});
user_code = component.javascript;
} else if (!component.ast.instance && !component.ast.module && (props.length > 0 || component.componentOptions.props)) {
user_code = [
component.componentOptions.props && `let ${component.componentOptions.props} = $$props;`,
props.length > 0 && `let { ${props.map(prop => prop.export_name).join(', ')} } = $$props;`
].filter(Boolean).join('\n');
}
const statements = [];
const reactive_stores = component.vars.filter(variable => variable.name[0] === '$');
const reactive_store_values = reactive_stores
.filter(store => component.var_lookup.get(store.name).hoistable)
.map(({ name }) => {
const assignment = `const ${name} = @get_store_value(${name.slice(1)});`;
if (component.componentOptions.props) statements.push(`let ${component.componentOptions.props} = $$props;`);
if (props.length > 0) statements.push(`let { ${props.map(x => x.name).join(', ')} } = $$props;`);
return component.compileOptions.dev
? `@validate_store(${name.slice(1)}, '${name.slice(1)}'); ${assignment}`
: assignment;
reactive_stores.forEach(({ name }) => {
if (component.compileOptions.dev) {
statements.push(`${component.compileOptions.dev && `@validate_store(${name.slice(1)}, '${name.slice(1)}');`}`);
}
statements.push(`const ${name} = @get_store_value(${name.slice(1)});`);
});
user_code = statements.join('\n');
}
// TODO only do this for props with a default value
const parent_bindings = component.javascript
? props.map(prop => {

Loading…
Cancel
Save