diff --git a/src/Stats.ts b/src/Stats.ts index b07b026df8..4c93e6937e 100644 --- a/src/Stats.ts +++ b/src/Stats.ts @@ -99,7 +99,16 @@ export default class Stats { return { timings, warnings: this.warnings, - vars: component.vars + vars: component.vars.map(variable => ({ + name: variable.name, + kind: variable.kind, + import_name: variable.import_name || null, + export_name: variable.export_name || null, + source: variable.source || null, + module: variable.module || false, + mutated: variable.mutated || false, + referenced: variable.referenced || false + })) }; } diff --git a/src/compile/Component.ts b/src/compile/Component.ts index 5a709a1abf..f42315dbba 100644 --- a/src/compile/Component.ts +++ b/src/compile/Component.ts @@ -15,7 +15,6 @@ import { Node, Ast, CompileOptions, Var } from '../interfaces'; import error from '../utils/error'; import getCodeFrame from '../utils/getCodeFrame'; import flattenReference from '../utils/flattenReference'; -import addToSet from '../utils/addToSet'; import isReference from 'is-reference'; import TemplateScope from './nodes/shared/TemplateScope'; import fuzzymatch from '../utils/fuzzymatch'; @@ -65,7 +64,6 @@ export default class Component { declarations: string[] = []; imported_declarations: Set = new Set(); - hoistable_names: Set = new Set(); hoistable_nodes: Set = new Set(); node_for_declaration: Map = new Map(); partly_hoisted: string[] = []; @@ -148,20 +146,25 @@ export default class Component { const props = [...this.template_references]; this.declarations.push(...props); - props.forEach(name => { - this.add_var({ - name, - kind: 'injected', - exported_as: name, - mutated: true, // TODO kind of a misnomer... it's *mutable* but not necessarily *mutated*. is that a problem? - referenced: true, - writable: true - }); - }); + // props.forEach(name => { + // this.add_var({ + // name, + // kind: 'injected', + // export_name: name, + // mutated: true, // TODO kind of a misnomer... it's *mutable* but not necessarily *mutated*. is that a problem? + // referenced: true, + // writable: true + // }); + // }); } } add_var(variable: Var) { + // TODO remove this + if (this.var_lookup.has(variable.name)) { + throw new Error(`dupe: ${variable.name}`); + } + this.vars.push(variable); this.var_lookup.set(variable.name, variable); } @@ -171,13 +174,21 @@ export default class Component { if (variable) { variable.referenced = true; - } else if (!this.ast.instance || name[0] === '$') { + } else if (name[0] === '$') { this.add_var({ name, kind: 'injected', + referenced: true, + mutated: true + }); + } else if (!this.ast.instance) { + this.add_var({ + name, + export_name: name, + kind: 'injected', mutated: true, referenced: true, - writable: name[0] !== '$' + writable: true }); } @@ -241,9 +252,9 @@ export default class Component { options.sveltePath, importedHelpers, this.imports, - this.vars.filter(variable => variable.module && variable.exported_as).map(variable => ({ + this.vars.filter(variable => variable.module && variable.export_name).map(variable => ({ name: variable.name, - as: variable.exported_as + as: variable.export_name })), this.source ); @@ -414,25 +425,19 @@ export default class Component { }); } - const imported_as = specifier.imported + const import_name = 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, + import_name, source: node.source.value, module: is_module, + hoistable: true }); this.imported_declarations.add(specifier.local.name); @@ -459,15 +464,9 @@ export default class Component { if (node.declaration.type === 'VariableDeclaration') { node.declaration.declarations.forEach(declarator => { extractNames(declarator.id).forEach(name => { - this.add_var({ - name, - kind, - exported_as: name, - module: is_module, - mutated: !is_module, - writable: kind === 'let' || kind === 'var', - initialised: !!declarator.init - }); + const variable = this.var_lookup.get(name); + variable.export_name = name; + if (kind !== 'const') variable.mutated = true; }); }); } else { @@ -482,14 +481,8 @@ export default class Component { // sanity check if (!kind) throw new Error(`Unknown declaration type ${node.declaration.type}`); - this.add_var({ - name, - kind, - exported_as: name, - module: is_module, - mutated: !is_module, - initialised: true - }); + const variable = this.var_lookup.get(name); + variable.export_name = name; } code.remove(node.start, node.declaration.start); @@ -499,7 +492,7 @@ export default class Component { const variable = this.var_lookup.get(specifier.local.name); if (variable) { - variable.exported_as = specifier.exported.name; + variable.export_name = specifier.exported.name; } else { // TODO what happens with `export { Math }` or some other global? } @@ -577,7 +570,8 @@ export default class Component { this.add_var({ name, - kind + kind, + module: true }); this.declarations.push(name); @@ -623,7 +617,8 @@ export default class Component { this.add_var({ name, kind, - initialised: instance_scope.initialised_declarations.has(name) + initialised: instance_scope.initialised_declarations.has(name), + writable: kind === 'var' || kind === 'let' }); this.declarations.push(name); @@ -633,6 +628,8 @@ export default class Component { }); globals.forEach(name => { + if (this.module_scope && this.module_scope.declarations.has(name)) return; + this.add_var({ name, kind: 'global' @@ -756,7 +753,7 @@ export default class Component { const variable = component.var_lookup.get(name); if (name === meta.props_object) { - if (variable.exported_as) { + if (variable.export_name) { component.error(declarator, { code: 'exported-meta-props', message: `Cannot export props binding` @@ -777,7 +774,7 @@ export default class Component { } } - if (variable.exported_as) { + if (variable.export_name) { has_exports = true; } else { has_only_exports = false; @@ -861,7 +858,7 @@ export default class Component { // reference instance variables other than other // hoistable functions. TODO others? - const { hoistable_names, hoistable_nodes, imported_declarations, var_lookup } = this; + const { hoistable_nodes, imported_declarations, var_lookup } = this; const top_level_function_declarations = new Map(); @@ -871,7 +868,6 @@ export default class Component { node.declarations.forEach(d => { const variable = this.var_lookup.get(d.id.name); variable.hoistable = true; - hoistable_names.add(d.id.name); }); hoistable_nodes.add(node); @@ -924,7 +920,6 @@ export default class Component { const variable = var_lookup.get(name); if (variable.hoistable) return; - if (imported_declarations.has(name)) return; if (top_level_function_declarations.has(name)) { const other_declaration = top_level_function_declarations.get(name); @@ -962,7 +957,6 @@ export default class Component { if (!checked.has(node) && is_hoistable(node)) { const variable = this.var_lookup.get(name); variable.hoistable = true; - hoistable_names.add(name); hoistable_nodes.add(node); remove_indentation(this.code, node); @@ -1086,7 +1080,6 @@ export default class Component { qualify(name) { const variable = this.var_lookup.get(name); if (variable && variable.hoistable) return name; - if (this.imported_declarations.has(name)) return name; if (this.declarations.indexOf(name) === -1) return name; this.add_reference(name); // TODO we can probably remove most other occurrences of this diff --git a/src/compile/nodes/EventHandler.ts b/src/compile/nodes/EventHandler.ts index fbf740f0f6..aa56b2653b 100644 --- a/src/compile/nodes/EventHandler.ts +++ b/src/compile/nodes/EventHandler.ts @@ -41,7 +41,12 @@ export default class EventHandler extends Node { } } else { const name = component.getUniqueName(`${this.name}_handler`); - component.declarations.push(name); + + component.add_var({ + name, + kind: 'injected', + referenced: true + }); component.partly_hoisted.push(deindent` function ${name}(event) { @@ -57,7 +62,7 @@ export default class EventHandler extends Node { render(block: Block) { if (this.expression) return this.expression.render(block); - this.component.add_reference(this.handler_name); + // this.component.add_reference(this.handler_name); return `ctx.${this.handler_name}`; } } \ No newline at end of file diff --git a/src/compile/nodes/shared/Expression.ts b/src/compile/nodes/shared/Expression.ts index 5a90259be6..b5ed44b207 100644 --- a/src/compile/nodes/shared/Expression.ts +++ b/src/compile/nodes/shared/Expression.ts @@ -371,23 +371,38 @@ export default class Expression { // we can hoist this out of the component completely component.fully_hoisted.push(fn); code.overwrite(node.start, node.end, name); + + component.add_var({ + name, + kind: 'injected', + hoistable: true, + referenced: true + }); } else if (contextual_dependencies.size === 0) { // function can be hoisted inside the component init component.partly_hoisted.push(fn); - component.declarations.push(name); - component.add_reference(name); code.overwrite(node.start, node.end, `ctx.${name}`); + + component.add_var({ + name, + kind: 'injected', + referenced: true + }); } else { // we need a combo block/init recipe component.partly_hoisted.push(fn); - component.declarations.push(name); - component.add_reference(name); code.overwrite(node.start, node.end, name); + component.add_var({ + name, + kind: 'injected', + referenced: true + }); + declarations.push(deindent` function ${name}(${original_params ? '...args' : ''}) { return ctx.${name}(ctx${original_params ? ', ...args' : ''}); @@ -461,8 +476,6 @@ function isContextual(component: Component, scope: TemplateScope, name: string) // hoistables, module declarations, and imports are non-contextual if (variable.hoistable) return false; - if (variable.module) return false; // TODO make all module-level variables hoistable by default - if (variable.import_type) return false; // TODO ditto // assume contextual return true; diff --git a/src/compile/render-dom/index.ts b/src/compile/render-dom/index.ts index 9fc17f713f..a58c8845a2 100644 --- a/src/compile/render-dom/index.ts +++ b/src/compile/render-dom/index.ts @@ -70,7 +70,7 @@ export default function dom( options.css !== false ); - const props = component.vars.filter(variable => !variable.module && variable.exported_as); + const props = component.vars.filter(variable => !variable.module && variable.export_name); const writable_props = props.filter(variable => variable.writable); const set = (component.meta.props || writable_props.length > 0 || renderer.slots.size > 0) @@ -82,7 +82,7 @@ export default function dom( $$invalidate('${component.meta.props_object}', ${component.meta.props_object}); `} ${writable_props.map(prop => - `if ('${prop.exported_as}' in $$props) $$invalidate('${prop.name}', ${prop.name} = $$props.${prop.exported_as});`)} + `if ('${prop.export_name}' in $$props) $$invalidate('${prop.name}', ${prop.name} = $$props.${prop.export_name});`)} ${renderer.slots.size > 0 && `if ('$$scope' in $$props) $$invalidate('$$scope', $$scope = $$props.$$scope);`} } @@ -97,31 +97,31 @@ export default function dom( props.forEach(x => { const variable = component.var_lookup.get(x.name); - if (component.imported_declarations.has(x.name) || variable.hoistable) { + if (variable.hoistable) { body.push(deindent` - get ${x.exported_as}() { + get ${x.export_name}() { return ${x.name}; } `); } else { body.push(deindent` - get ${x.exported_as}() { + get ${x.export_name}() { return this.$$.ctx.${x.name}; } `); } - if (variable.writable && !renderer.readonly.has(x.exported_as)) { + if (variable.writable && !renderer.readonly.has(x.export_name)) { body.push(deindent` - set ${x.exported_as}(${x.name}) { + set ${x.export_name}(${x.name}) { this.$set({ ${x.name} }); @flush(); } `); } else if (component.options.dev) { body.push(deindent` - set ${x.exported_as}(value) { - throw new Error("<${component.tag}>: Cannot set read-only property '${x.exported_as}'"); + set ${x.export_name}(value) { + throw new Error("<${component.tag}>: Cannot set read-only property '${x.export_name}'"); } `); } @@ -137,8 +137,8 @@ export default function dom( const { ctx } = this.$$; const props = ${options.customElement ? `this.attributes` : `options.props || {}`}; ${expected.map(prop => deindent` - if (ctx.${prop.name} === undefined && !('${prop.exported_as}' in props)) { - console.warn("<${component.tag}> was created without expected prop '${prop.exported_as}'"); + if (ctx.${prop.name} === undefined && !('${prop.export_name}' in props)) { + console.warn("<${component.tag}> was created without expected prop '${prop.export_name}'"); }`)} `; } @@ -253,20 +253,14 @@ export default function dom( ${component.fully_hoisted.length > 0 && component.fully_hoisted.join('\n\n')} `); - const filtered_declarations = component.declarations.filter(name => { - const variable = component.var_lookup.get(name); - - if (variable && variable.hoistable) return false; - if (component.imported_declarations.has(name)) return false; - if (props.find(p => p.exported_as === name)) return true; - return component.template_references.has(name); - }); + const filtered_declarations = component.vars.filter(variable => { + return (variable.referenced || variable.export_name) && !variable.hoistable; + }).map(variable => variable.name); const filtered_props = props.filter(prop => { const variable = component.var_lookup.get(prop.name); if (variable.hoistable) return false; - if (component.imported_declarations.has(prop.name)) return false; if (prop.name[0] === '$') return false; return true; }); @@ -368,7 +362,7 @@ export default function dom( } static get observedAttributes() { - return ${JSON.stringify(props.map(x => x.exported_as))}; + return ${JSON.stringify(props.map(x => x.export_name))}; } ${body.length > 0 && body.join('\n\n')} diff --git a/src/compile/render-dom/wrappers/Element/index.ts b/src/compile/render-dom/wrappers/Element/index.ts index 16c3b544cc..53049d3b38 100644 --- a/src/compile/render-dom/wrappers/Element/index.ts +++ b/src/compile/render-dom/wrappers/Element/index.ts @@ -405,8 +405,12 @@ export default class ElementWrapper extends Wrapper { groups.forEach(group => { const handler = renderer.component.getUniqueName(`${this.var}_${group.events.join('_')}_handler`); - renderer.component.declarations.push(handler); - renderer.component.add_reference(handler); + + renderer.component.add_var({ + name: handler, + kind: 'injected', + referenced: true + }); // TODO figure out how to handle locks const needsLock = group.bindings.some(binding => binding.needsLock); @@ -506,8 +510,12 @@ export default class ElementWrapper extends Wrapper { const this_binding = this.bindings.find(b => b.node.name === 'this'); if (this_binding) { const name = renderer.component.getUniqueName(`${this.var}_binding`); - renderer.component.declarations.push(name); - renderer.component.add_reference(name); + + renderer.component.add_var({ + name, + kind: 'injected', + referenced: true + }); const { handler, object } = this_binding; diff --git a/src/compile/render-dom/wrappers/InlineComponent/index.ts b/src/compile/render-dom/wrappers/InlineComponent/index.ts index 4098b76c34..309db4a329 100644 --- a/src/compile/render-dom/wrappers/InlineComponent/index.ts +++ b/src/compile/render-dom/wrappers/InlineComponent/index.ts @@ -238,8 +238,12 @@ export default class InlineComponentWrapper extends Wrapper { if (binding.name === 'this') { const fn = component.getUniqueName(`${this.var}_binding`); - component.declarations.push(fn); - component.add_reference(fn); + + component.add_var({ + name: fn, + kind: 'injected', + referenced: true + }); let lhs; let object; @@ -269,8 +273,12 @@ export default class InlineComponentWrapper extends Wrapper { } const name = component.getUniqueName(`${this.var}_${binding.name}_binding`); - component.declarations.push(name); - component.add_reference(name); + + component.add_var({ + name, + kind: 'injected', + referenced: true + }); const updating = block.getUniqueName(`updating_${binding.name}`); block.addVariable(updating); diff --git a/src/compile/render-dom/wrappers/Window.ts b/src/compile/render-dom/wrappers/Window.ts index f704c35446..9828af4a55 100644 --- a/src/compile/render-dom/wrappers/Window.ts +++ b/src/compile/render-dom/wrappers/Window.ts @@ -118,16 +118,18 @@ export default class WindowWrapper extends Wrapper { `); } - component.declarations.push(handler_name); - component.add_reference(handler_name); + component.add_var({ + name: handler_name, + kind: 'injected', + referenced: true + }); + component.partly_hoisted.push(deindent` function ${handler_name}() { ${props.map(prop => `${prop.name} = window.${prop.value}; $$invalidate('${prop.name}', ${prop.name});`)} } `); - - block.builders.init.addBlock(deindent` @add_render_callback(ctx.${handler_name}); `); diff --git a/src/compile/render-ssr/index.ts b/src/compile/render-ssr/index.ts index 6f49dc517c..93ea3ee37f 100644 --- a/src/compile/render-ssr/index.ts +++ b/src/compile/render-ssr/index.ts @@ -25,13 +25,13 @@ export default function ssr( let user_code; // TODO remove this, just use component.symbols everywhere - const props = component.vars.filter(variable => !variable.module && variable.exported_as); + const props = component.vars.filter(variable => !variable.module && variable.export_name); if (component.javascript) { component.rewrite_props(); user_code = component.javascript; } else if (!component.ast.instance && !component.ast.module && props.length > 0) { - user_code = `let { ${props.map(prop => prop.exported_as).join(', ')} } = $$props;` + user_code = `let { ${props.map(prop => prop.export_name).join(', ')} } = $$props;` } const reactive_stores = Array.from(component.template_references).filter(n => n[0] === '$'); @@ -46,7 +46,7 @@ export default function ssr( // TODO only do this for props with a default value const parent_bindings = component.javascript ? props.map(prop => { - return `if ($$props.${prop.exported_as} === void 0 && $$bindings.${prop.exported_as} && ${prop.name} !== void 0) $$bindings.${prop.exported_as}(${prop.name});`; + return `if ($$props.${prop.export_name} === void 0 && $$bindings.${prop.export_name} && ${prop.name} !== void 0) $$bindings.${prop.export_name}(${prop.name});`; }) : []; diff --git a/src/interfaces.ts b/src/interfaces.ts index 2982bc008e..1ebc55b5ff 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -82,9 +82,8 @@ export interface AppendTarget { 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 }` + import_name?: '*' | 'default' | string; // the `foo` in `import { foo as bar }` + export_name?: string; // the `bar` in `export { foo as bar }` source?: string; module?: boolean; mutated?: boolean; diff --git a/src/utils/annotateWithScopes.ts b/src/utils/annotateWithScopes.ts index bedab0cbce..adf7619bed 100644 --- a/src/utils/annotateWithScopes.ts +++ b/src/utils/annotateWithScopes.ts @@ -52,6 +52,10 @@ export function createScopes(expression: Node) { }, }); + scope.declarations.forEach((node, name) => { + globals.delete(name); + }); + return { map, scope, globals }; }