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 Fragment from './nodes/Fragment';
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 getCodeFrame from '../utils/getCodeFrame';
import flattenReference from '../utils/flattenReference';
@ -56,6 +56,9 @@ export default class Component {
namespace: string;
tag: string;
vars: Var[] = [];
var_lookup: Map<string, Var> = new Map();
imports: Node[] = [];
module_javascript: string;
javascript: string;
@ -90,7 +93,6 @@ export default class Component {
stylesheet: Stylesheet;
userVars: Set<string> = new Set();
aliases: Map<string, string> = new Map();
usedNames: Set<string> = new Set();
@ -152,18 +154,57 @@ export default class Component {
this.declarations.push(...props);
addToSet(this.mutable_props, this.template_references);
addToSet(this.writable_declarations, this.template_references);
addToSet(this.userVars, this.template_references);
this.props = props.map(name => ({
name,
as: name
}));
props.forEach(name => {
this.add_var({
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
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) {
walk(node, {
enter: (node: Node) => {
@ -290,7 +331,7 @@ export default class Component {
for (
let i = 1;
reservedNames.has(alias) ||
this.userVars.has(alias) ||
this.var_lookup.has(alias) ||
this.usedNames.has(alias);
alias = `${name}_${i++}`
);
@ -306,7 +347,7 @@ export default class Component {
}
reservedNames.forEach(add);
this.userVars.forEach(add);
this.var_lookup.forEach((value, key) => add(key));
return (name: string) => {
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;
content.body.forEach(node => {
@ -389,44 +477,60 @@ export default class Component {
if (node.declaration.type === 'VariableDeclaration') {
node.declaration.declarations.forEach(declarator => {
extractNames(declarator.id).forEach(name => {
exports.push({ name, as: name });
this.mutable_props.add(name);
this.add_var({
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 {
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);
} else {
removeNode(code, content.start, content.end, content.body, node);
node.specifiers.forEach(specifier => {
exports.push({
name: specifier.local.name,
as: specifier.exported.name
});
const variable = this.var_lookup.get(specifier.local.name);
if (variable) {
variable.exported_as = specifier.exported.name;
} else {
// TODO what happens with `export { Math }` or some other global?
}
});
}
}
// imports need to be hoisted out of the IIFE
else if (node.type === 'ImportDeclaration') {
removeNode(code, content.start, content.end, content.body, node);
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`
});
}
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);
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() {
@ -507,11 +618,33 @@ export default class Component {
message: `The $ prefix is reserved, and cannot be used for variable and import names`
});
}
});
instance_scope.declarations.forEach((node, name) => {
this.userVars.add(name);
this.declarations.push(name);
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
});
this.declarations.push(name);
}
this.node_for_declaration.set(name, node);
});
@ -520,11 +653,28 @@ export default class Component {
this.initialised_declarations = instance_scope.initialised_declarations;
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.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() {
@ -586,7 +736,7 @@ export default class Component {
component.warn_if_undefined(object, null);
// 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.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}`;
}

@ -57,7 +57,7 @@ export default class EventHandler extends Node {
render(block: 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}`;
}
}

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

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

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

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

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

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

@ -77,4 +77,16 @@ export interface CustomElementOptions {
export interface AppendTarget {
slots: Record<string, 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 {
test(assert, stats) {
assert.deepEqual(stats.imports, [
assert.deepEqual(stats.vars, [
{
source: 'x',
specifiers: [{ name: 'default', as: 'x' }]
kind: 'import',
imported: 'x',
default: true,
source: 'x'
},
{
source: 'y',
specifiers: [{ name: 'y', as: 'y' }]
kind: 'import',
imported: 'y',
named: true,
source: 'y'
},
{
source: 'z',
specifiers: [{ name: '*', as: 'z' }]
kind: 'import',
imported: 'y',
namespace: true,
source: 'y'
}
]);
}

@ -1,5 +1,28 @@
export default {
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 {
test(assert, stats) {
assert.equal(stats.templateReferences.size, 3);
assert.ok(stats.templateReferences.has('foo'));
assert.ok(stats.templateReferences.has('Bar'));
assert.ok(stats.templateReferences.has('baz'));
assert.deepEqual(stats.vars, [
{
name: 'foo',
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