start moving everything into component.vars

pull/2011/head
Richard Harris 7 years ago
parent af6167a998
commit d8dc61a047

@ -11,7 +11,7 @@ import Stylesheet from './css/Stylesheet';
import { test } from '../config'; import { test } from '../config';
import Fragment from './nodes/Fragment'; import Fragment from './nodes/Fragment';
import internal_exports from './internal-exports'; import internal_exports from './internal-exports';
import { Node, Ast, CompileOptions } from '../interfaces'; import { Node, Ast, CompileOptions, Var } from '../interfaces';
import error from '../utils/error'; import error from '../utils/error';
import getCodeFrame from '../utils/getCodeFrame'; import getCodeFrame from '../utils/getCodeFrame';
import flattenReference from '../utils/flattenReference'; import flattenReference from '../utils/flattenReference';
@ -56,6 +56,9 @@ export default class Component {
namespace: string; namespace: string;
tag: string; tag: string;
vars: Var[] = [];
var_lookup: Map<string, Var> = new Map();
imports: Node[] = []; imports: Node[] = [];
module_javascript: string; module_javascript: string;
javascript: string; javascript: string;
@ -90,7 +93,6 @@ export default class Component {
stylesheet: Stylesheet; stylesheet: Stylesheet;
userVars: Set<string> = new Set();
aliases: Map<string, string> = new Map(); aliases: Map<string, string> = new Map();
usedNames: Set<string> = new Set(); usedNames: Set<string> = new Set();
@ -152,18 +154,57 @@ export default class Component {
this.declarations.push(...props); this.declarations.push(...props);
addToSet(this.mutable_props, this.template_references); addToSet(this.mutable_props, this.template_references);
addToSet(this.writable_declarations, this.template_references); addToSet(this.writable_declarations, this.template_references);
addToSet(this.userVars, this.template_references);
this.props = props.map(name => ({ props.forEach(name => {
this.add_var({
name, name,
as: name kind: 'injected',
})); import_type: null,
imported_as: null,
exported_as: name,
source: null,
mutated: false,
referenced: true,
module: false
});
});
// TODO remove this
this.props = props.map(name => ({ name, as: name }));
} }
// tell the root fragment scope about all of the mutable names we know from the script // tell the root fragment scope about all of the mutable names we know from the script
this.mutable_props.forEach(name => this.fragment.scope.mutables.add(name)); this.mutable_props.forEach(name => this.fragment.scope.mutables.add(name));
} }
add_var(variable: Var) {
this.vars.push(variable);
this.var_lookup.set(variable.name, variable);
}
add_reference(name: string) {
const variable = this.var_lookup.get(name);
if (variable) {
variable.referenced = true;
} else if (!this.ast.instance) {
this.add_var({
name,
kind: 'injected',
import_type: null,
imported_as: null,
source: null,
exported_as: null,
module: false,
mutated: true,
referenced: true
});
}
// TODO remove this
this.template_references.add(name);
}
addSourcemapLocations(node: Node) { addSourcemapLocations(node: Node) {
walk(node, { walk(node, {
enter: (node: Node) => { enter: (node: Node) => {
@ -290,7 +331,7 @@ export default class Component {
for ( for (
let i = 1; let i = 1;
reservedNames.has(alias) || reservedNames.has(alias) ||
this.userVars.has(alias) || this.var_lookup.has(alias) ||
this.usedNames.has(alias); this.usedNames.has(alias);
alias = `${name}_${i++}` alias = `${name}_${i++}`
); );
@ -306,7 +347,7 @@ export default class Component {
} }
reservedNames.forEach(add); reservedNames.forEach(add);
this.userVars.forEach(add); this.var_lookup.forEach((value, key) => add(key));
return (name: string) => { return (name: string) => {
if (test) name = `${name}$`; if (test) name = `${name}$`;
@ -373,7 +414,54 @@ export default class Component {
}); });
} }
extract_imports_and_exports(content, imports, exports) { extract_imports(content, is_module: boolean) {
const { code } = this;
content.body.forEach(node => {
if (node.type === 'ImportDeclaration') {
// imports need to be hoisted out of the IIFE
removeNode(code, content.start, content.end, content.body, node);
this.imports.push(node);
node.specifiers.forEach((specifier: Node) => {
if (specifier.local.name[0] === '$') {
this.error(specifier.local, {
code: 'illegal-declaration',
message: `The $ prefix is reserved, and cannot be used for variable and import names`
});
}
const imported_as = specifier.imported
? specifier.imported.name
: specifier.type === 'ImportDefaultSpecifier'
? 'default'
: '*';
const import_type = specifier.type === 'ImportSpecifier'
? 'named'
: specifier.type === 'ImportDefaultSpecifier'
? 'default'
: 'namespace';
this.add_var({
name: specifier.local.name,
kind: 'import',
imported_as,
import_type,
source: node.source.value,
exported_as: null,
module: is_module,
mutated: false,
referenced: false
});
this.imported_declarations.add(specifier.local.name);
});
}
});
}
extract_exports(content, is_module: boolean) {
const { code } = this; const { code } = this;
content.body.forEach(node => { content.body.forEach(node => {
@ -389,44 +477,60 @@ export default class Component {
if (node.declaration.type === 'VariableDeclaration') { if (node.declaration.type === 'VariableDeclaration') {
node.declaration.declarations.forEach(declarator => { node.declaration.declarations.forEach(declarator => {
extractNames(declarator.id).forEach(name => { extractNames(declarator.id).forEach(name => {
exports.push({ name, as: name }); this.add_var({
this.mutable_props.add(name); name,
kind: node.declaration.kind,
import_type: null,
imported_as: null,
exported_as: name,
source: null,
module: is_module,
mutated: false,
referenced: false
});
if (!is_module) this.mutable_props.add(name);
}); });
}); });
} else { } else {
const { name } = node.declaration.id; const { name } = node.declaration.id;
exports.push({ name, as: name });
const kind = node.declaration.type === 'ClassDeclaration'
? 'class'
: node.declaration.type === 'FunctionDeclaration'
? 'function'
: null;
// sanity check
if (!kind) throw new Error(`Unknown declaration type ${node.declaration.type}`);
this.add_var({
name,
kind,
import_type: null,
imported_as: null,
exported_as: name,
source: null,
module: is_module,
mutated: false,
referenced: false
});
} }
code.remove(node.start, node.declaration.start); code.remove(node.start, node.declaration.start);
} else { } else {
removeNode(code, content.start, content.end, content.body, node); removeNode(code, content.start, content.end, content.body, node);
node.specifiers.forEach(specifier => { node.specifiers.forEach(specifier => {
exports.push({ const variable = this.var_lookup.get(specifier.local.name);
name: specifier.local.name,
as: specifier.exported.name
});
});
}
}
// imports need to be hoisted out of the IIFE if (variable) {
else if (node.type === 'ImportDeclaration') { variable.exported_as = specifier.exported.name;
removeNode(code, content.start, content.end, content.body, node); } else {
imports.push(node); // TODO what happens with `export { Math }` or some other global?
node.specifiers.forEach((specifier: Node) => {
if (specifier.local.name[0] === '$') {
this.error(specifier.local, {
code: 'illegal-declaration',
message: `The $ prefix is reserved, and cannot be used for variable and import names`
});
} }
this.userVars.add(specifier.local.name);
this.imported_declarations.add(specifier.local.name);
}); });
} }
}
}); });
} }
@ -485,9 +589,16 @@ export default class Component {
} }
}); });
this.extract_imports_and_exports(script.content, this.imports, this.module_exports); this.extract_imports(script.content, true);
this.extract_exports(script.content, true);
remove_indentation(this.code, script.content); remove_indentation(this.code, script.content);
this.module_javascript = this.extract_javascript(script); this.module_javascript = this.extract_javascript(script);
// TODO remove this
this.module_exports = this.vars.filter(variable => variable.module && variable.exported_as).map(variable => ({
name: variable.name,
as: variable.exported_as
}));
} }
walk_instance_js_pre_template() { walk_instance_js_pre_template() {
@ -507,11 +618,33 @@ export default class Component {
message: `The $ prefix is reserved, and cannot be used for variable and import names` message: `The $ prefix is reserved, and cannot be used for variable and import names`
}); });
} }
if (!/Import/.test(node.type)) {
const kind = node.type === 'VariableDeclaration'
? node.kind
: node.type === 'ClassDeclaration'
? 'class'
: node.type === 'FunctionDeclaration'
? 'function'
: null;
// sanity check
if (!kind) throw new Error(`Unknown declaration type ${node.type}`);
this.add_var({
name,
kind,
import_type: null,
imported_as: null,
exported_as: null,
source: null,
module: false,
mutated: false,
referenced: false
}); });
instance_scope.declarations.forEach((node, name) => {
this.userVars.add(name);
this.declarations.push(name); this.declarations.push(name);
}
this.node_for_declaration.set(name, node); this.node_for_declaration.set(name, node);
}); });
@ -520,11 +653,28 @@ export default class Component {
this.initialised_declarations = instance_scope.initialised_declarations; this.initialised_declarations = instance_scope.initialised_declarations;
globals.forEach(name => { globals.forEach(name => {
this.userVars.add(name); this.add_var({
name,
kind: 'global',
import_type: null,
imported_as: null,
source: null,
exported_as: null,
module: false,
mutated: false,
referenced: false
});
}); });
this.track_mutations(); this.track_mutations();
this.extract_imports_and_exports(script.content, this.imports, this.props); this.extract_imports(script.content, false);
this.extract_exports(script.content, false);
// TODO remove this, just use component.symbols everywhere
this.props = this.vars.filter(variable => !variable.module && variable.exported_as).map(variable => ({
name: variable.name,
as: variable.exported_as
}));
} }
walk_instance_js_post_template() { walk_instance_js_post_template() {
@ -586,7 +736,7 @@ export default class Component {
component.warn_if_undefined(object, null); component.warn_if_undefined(object, null);
// cheeky hack // cheeky hack
component.template_references.add(name); component.add_reference(name);
} }
} }
}, },
@ -963,7 +1113,7 @@ export default class Component {
if (this.imported_declarations.has(name)) return name; if (this.imported_declarations.has(name)) return name;
if (this.declarations.indexOf(name) === -1) return name; if (this.declarations.indexOf(name) === -1) return name;
this.template_references.add(name); // TODO we can probably remove most other occurrences of this this.add_reference(name); // TODO we can probably remove most other occurrences of this
return `ctx.${name}`; return `ctx.${name}`;
} }

@ -57,7 +57,7 @@ export default class EventHandler extends Node {
render(block: Block) { render(block: Block) {
if (this.expression) return this.expression.render(block); if (this.expression) return this.expression.render(block);
this.component.template_references.add(this.handler_name); this.component.add_reference(this.handler_name);
return `ctx.${this.handler_name}`; return `ctx.${this.handler_name}`;
} }
} }

@ -24,7 +24,7 @@ export default class InlineComponent extends Node {
if (info.name !== 'svelte:component' && info.name !== 'svelte:self') { if (info.name !== 'svelte:component' && info.name !== 'svelte:self') {
component.warn_if_undefined(info, scope); component.warn_if_undefined(info, scope);
component.template_references.add(info.name); component.add_reference(info.name);
} }
this.name = info.name; this.name = info.name;

@ -116,7 +116,7 @@ export default class Expression {
const owner = template_scope.getOwner(name); const owner = template_scope.getOwner(name);
const is_let = owner && (owner.type === 'InlineComponent' || owner.type === 'Element'); const is_let = owner && (owner.type === 'InlineComponent' || owner.type === 'Element');
if (is_let || component.writable_declarations.has(name) || name[0] === '$' || (component.userVars.has(name) && deep)) { if (is_let || component.writable_declarations.has(name) || name[0] === '$' || (component.var_lookup.has(name) && deep)) {
dynamic_dependencies.add(name); dynamic_dependencies.add(name);
} }
} else { } else {
@ -153,7 +153,7 @@ export default class Expression {
template_scope.dependenciesForName.get(name).forEach(name => add_dependency(name, true)); template_scope.dependenciesForName.get(name).forEach(name => add_dependency(name, true));
} else { } else {
add_dependency(name, nodes.length > 1); add_dependency(name, nodes.length > 1);
component.template_references.add(name); component.add_reference(name);
component.warn_if_undefined(nodes[0], template_scope, true); component.warn_if_undefined(nodes[0], template_scope, true);
} }
@ -241,7 +241,7 @@ export default class Expression {
}); });
} else { } else {
dependencies.add(name); dependencies.add(name);
component.template_references.add(name); component.add_reference(name);
} }
} else if (!is_synthetic && isContextual(component, template_scope, name)) { } else if (!is_synthetic && isContextual(component, template_scope, name)) {
code.prependRight(node.start, key === 'key' && parent.shorthand code.prependRight(node.start, key === 'key' && parent.shorthand
@ -365,7 +365,7 @@ export default class Expression {
// function can be hoisted inside the component init // function can be hoisted inside the component init
component.partly_hoisted.push(fn); component.partly_hoisted.push(fn);
component.declarations.push(name); component.declarations.push(name);
component.template_references.add(name); component.add_reference(name);
code.overwrite(node.start, node.end, `ctx.${name}`); code.overwrite(node.start, node.end, `ctx.${name}`);
} }
@ -373,7 +373,7 @@ export default class Expression {
// we need a combo block/init recipe // we need a combo block/init recipe
component.partly_hoisted.push(fn); component.partly_hoisted.push(fn);
component.declarations.push(name); component.declarations.push(name);
component.template_references.add(name); component.add_reference(name);
code.overwrite(node.start, node.end, name); code.overwrite(node.start, node.end, name);
declarations.push(deindent` declarations.push(deindent`

@ -405,7 +405,7 @@ export default class ElementWrapper extends Wrapper {
groups.forEach(group => { groups.forEach(group => {
const handler = renderer.component.getUniqueName(`${this.var}_${group.events.join('_')}_handler`); const handler = renderer.component.getUniqueName(`${this.var}_${group.events.join('_')}_handler`);
renderer.component.declarations.push(handler); renderer.component.declarations.push(handler);
renderer.component.template_references.add(handler); renderer.component.add_reference(handler);
// TODO figure out how to handle locks // TODO figure out how to handle locks
const needsLock = group.bindings.some(binding => binding.needsLock); const needsLock = group.bindings.some(binding => binding.needsLock);
@ -506,7 +506,7 @@ export default class ElementWrapper extends Wrapper {
if (this_binding) { if (this_binding) {
const name = renderer.component.getUniqueName(`${this.var}_binding`); const name = renderer.component.getUniqueName(`${this.var}_binding`);
renderer.component.declarations.push(name); renderer.component.declarations.push(name);
renderer.component.template_references.add(name); renderer.component.add_reference(name);
const { handler, object } = this_binding; const { handler, object } = this_binding;

@ -234,7 +234,7 @@ export default class InlineComponentWrapper extends Wrapper {
if (binding.name === 'this') { if (binding.name === 'this') {
const fn = component.getUniqueName(`${this.var}_binding`); const fn = component.getUniqueName(`${this.var}_binding`);
component.declarations.push(fn); component.declarations.push(fn);
component.template_references.add(fn); component.add_reference(fn);
let lhs; let lhs;
let object; let object;
@ -265,7 +265,7 @@ export default class InlineComponentWrapper extends Wrapper {
const name = component.getUniqueName(`${this.var}_${binding.name}_binding`); const name = component.getUniqueName(`${this.var}_${binding.name}_binding`);
component.declarations.push(name); component.declarations.push(name);
component.template_references.add(name); component.add_reference(name);
const updating = block.getUniqueName(`updating_${binding.name}`); const updating = block.getUniqueName(`updating_${binding.name}`);
block.addVariable(updating); block.addVariable(updating);

@ -119,7 +119,7 @@ export default class WindowWrapper extends Wrapper {
} }
component.declarations.push(handler_name); component.declarations.push(handler_name);
component.template_references.add(handler_name); component.add_reference(handler_name);
component.partly_hoisted.push(deindent` component.partly_hoisted.push(deindent`
function ${handler_name}() { function ${handler_name}() {
${props.map(prop => `${prop.name} = window.${prop.value}; $$invalidate('${prop.name}', ${prop.name});`)} ${props.map(prop => `${prop.name} = window.${prop.value}; $$invalidate('${prop.name}', ${prop.name});`)}

@ -27,7 +27,7 @@ export default function addActions(
? action.name ? action.name
: `ctx.${action.name}`; : `ctx.${action.name}`;
component.template_references.add(action.name); component.add_reference(action.name);
block.builders.mount.addLine( block.builders.mount.addLine(
`${name} = ${fn}.call(null, ${target}${snippet ? `, ${snippet}` : ''}) || {};` `${name} = ${fn}.call(null, ${target}${snippet ? `, ${snippet}` : ''}) || {};`

@ -78,3 +78,15 @@ export interface AppendTarget {
slots: Record<string, string>; slots: Record<string, string>;
slotStack: string[] slotStack: string[]
} }
export interface Var {
name: string;
kind: 'let' | 'var' | 'const' | 'class' | 'function' | 'import' | 'injected' | 'global';
import_type: 'default' | 'named' | 'namespace';
imported_as: string; // the `foo` in `import { foo as bar }`
exported_as: string; // the `bar` in `export { foo as bar }`
source: string;
module: boolean;
mutated: boolean;
referenced: boolean;
}

@ -1,17 +1,23 @@
export default { export default {
test(assert, stats) { test(assert, stats) {
assert.deepEqual(stats.imports, [ assert.deepEqual(stats.vars, [
{ {
source: 'x', kind: 'import',
specifiers: [{ name: 'default', as: 'x' }] imported: 'x',
default: true,
source: 'x'
}, },
{ {
source: 'y', kind: 'import',
specifiers: [{ name: 'y', as: 'y' }] imported: 'y',
named: true,
source: 'y'
}, },
{ {
source: 'z', kind: 'import',
specifiers: [{ name: '*', as: 'z' }] imported: 'y',
namespace: true,
source: 'y'
} }
]); ]);
} }

@ -1,5 +1,28 @@
export default { export default {
test(assert, stats) { test(assert, stats) {
assert.deepEqual(stats.props.sort(), ['cats', 'name']); assert.deepEqual(stats.vars, [
{
name: 'name',
kind: 'let',
exported: 'name',
referenced: true
},
{
name: 'cats',
kind: 'let',
exported: 'name',
referenced: true
},
{
name: 'foo',
kind: 'let',
referenced: true
},
{
name: 'bar',
kind: 'let',
referenced: true
}
]);
} }
}; };

@ -1,8 +1,24 @@
export default { export default {
test(assert, stats) { test(assert, stats) {
assert.equal(stats.templateReferences.size, 3); assert.deepEqual(stats.vars, [
assert.ok(stats.templateReferences.has('foo')); {
assert.ok(stats.templateReferences.has('Bar')); name: 'foo',
assert.ok(stats.templateReferences.has('baz')); kind: 'injected',
exported: 'foo',
referenced: true
},
{
name: 'Bar',
kind: 'injected',
exported: 'Bar',
referenced: true
},
{
name: 'baz',
kind: 'injected',
exported: 'baz',
referenced: true
}
]);
}, },
}; };

Loading…
Cancel
Save