Overhaul stats — closes

pull/2020/head
Rich Harris 6 years ago committed by GitHub
parent 80b0bdfdb1
commit b1d919f3f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -97,11 +97,18 @@ export default class Stats {
}); });
return { return {
props: component.props.map(prop => prop.as),
timings, timings,
warnings: this.warnings, warnings: this.warnings,
imports, vars: component.vars.filter(variable => !variable.global && !variable.implicit && !variable.internal).map(variable => ({
templateReferences: component && component.template_references name: variable.name,
export_name: variable.export_name || null,
injected: variable.injected || false,
module: variable.module || false,
mutated: variable.mutated || false,
reassigned: variable.reassigned || false,
referenced: variable.referenced || false,
writable: variable.writable || false
}))
}; };
} }

@ -11,11 +11,10 @@ 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';
import addToSet from '../utils/addToSet';
import isReference from 'is-reference'; import isReference from 'is-reference';
import TemplateScope from './nodes/shared/TemplateScope'; import TemplateScope from './nodes/shared/TemplateScope';
import fuzzymatch from '../utils/fuzzymatch'; import fuzzymatch from '../utils/fuzzymatch';
@ -56,31 +55,22 @@ export default class Component {
namespace: string; namespace: string;
tag: string; tag: string;
instance_script: Node; vars: Var[] = [];
module_script: Node; var_lookup: Map<string, Var> = new Map();
imports: Node[] = []; imports: Node[] = [];
module_javascript: string; module_javascript: string;
javascript: string; javascript: string;
declarations: string[] = [];
props: Array<{ name: string, as: string }> = [];
writable_declarations: Set<string> = new Set();
initialised_declarations: Set<string> = new Set();
imported_declarations: Set<string> = new Set();
hoistable_names: Set<string> = new Set();
hoistable_nodes: Set<Node> = new Set(); hoistable_nodes: Set<Node> = new Set();
node_for_declaration: Map<string, Node> = new Map(); node_for_declaration: Map<string, Node> = new Map();
module_exports: Array<{ name: string, as: string }> = [];
partly_hoisted: string[] = []; partly_hoisted: string[] = [];
fully_hoisted: string[] = []; fully_hoisted: string[] = [];
reactive_declarations: Array<{ assignees: Set<string>, dependencies: Set<string>, snippet: string }> = []; reactive_declarations: Array<{ assignees: Set<string>, dependencies: Set<string>, snippet: string }> = [];
reactive_declaration_nodes: Set<Node> = new Set(); reactive_declaration_nodes: Set<Node> = new Set();
has_reactive_assignments = false; has_reactive_assignments = false;
mutable_props: Set<string> = new Set();
indirectDependencies: Map<string, Set<string>> = new Map(); indirectDependencies: Map<string, Set<string>> = new Map();
template_references: Set<string> = new Set();
file: string; file: string;
locate: (c: number) => { line: number, column: number }; locate: (c: number) => { line: number, column: number };
@ -93,7 +83,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();
@ -122,26 +111,6 @@ export default class Component {
this.stylesheet = new Stylesheet(source, ast, options.filename, options.dev); this.stylesheet = new Stylesheet(source, ast, options.filename, options.dev);
this.stylesheet.validate(this); this.stylesheet.validate(this);
const module_scripts = ast.js.filter(script => this.get_context(script) === 'module');
const instance_scripts = ast.js.filter(script => this.get_context(script) === 'default');
if (module_scripts.length > 1) {
this.error(module_scripts[1], {
code: `invalid-script`,
message: `A component can only have one <script context="module"> element`
});
}
if (instance_scripts.length > 1) {
this.error(instance_scripts[1], {
code: `invalid-script`,
message: `A component can only have one instance-level <script> element`
});
}
this.module_script = module_scripts[0];
this.instance_script = instance_scripts[0];
this.meta = process_meta(this, this.ast.html.children); this.meta = process_meta(this, this.ast.html.children);
this.namespace = namespaces[this.meta.namespace] || this.meta.namespace; this.namespace = namespaces[this.meta.namespace] || this.meta.namespace;
@ -169,22 +138,43 @@ export default class Component {
if (!options.customElement) this.stylesheet.reify(); if (!options.customElement) this.stylesheet.reify();
this.stylesheet.warnOnUnusedSelectors(stats); this.stylesheet.warnOnUnusedSelectors(stats);
}
if (!this.instance_script) { add_var(variable: Var) {
const props = [...this.template_references]; // TODO remove this
this.declarations.push(...props); if (this.var_lookup.has(variable.name)) {
addToSet(this.mutable_props, this.template_references); throw new Error(`dupe: ${variable.name}`);
addToSet(this.writable_declarations, this.template_references); }
addToSet(this.userVars, this.template_references);
this.props = props.map(name => ({ this.vars.push(variable);
name, this.var_lookup.set(variable.name, variable);
as: name
}));
} }
// tell the root fragment scope about all of the mutable names we know from the script add_reference(name: string) {
this.mutable_props.forEach(name => this.fragment.scope.mutables.add(name)); const variable = this.var_lookup.get(name);
if (variable) {
variable.referenced = true;
} else if (name[0] === '$') {
this.add_var({
name,
injected: true,
referenced: true,
mutated: true,
writable: true
});
this.add_reference(name.slice(1));
} else if (!this.ast.instance) {
this.add_var({
name,
export_name: name,
implicit: true,
mutated: false,
referenced: true,
writable: true
});
}
} }
addSourcemapLocations(node: Node) { addSourcemapLocations(node: Node) {
@ -243,7 +233,10 @@ export default class Component {
options.sveltePath, options.sveltePath,
importedHelpers, importedHelpers,
this.imports, this.imports,
this.module_exports, this.vars.filter(variable => variable.module && variable.export_name).map(variable => ({
name: variable.name,
as: variable.export_name
})),
this.source this.source
); );
@ -313,7 +306,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++}`
); );
@ -329,7 +322,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}$`;
@ -396,7 +389,34 @@ 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`
});
}
this.add_var({
name: specifier.local.name,
module: is_module,
hoistable: true
});
});
}
});
}
extract_exports(content, is_module: boolean) {
const { code } = this; const { code } = this;
content.body.forEach(node => { content.body.forEach(node => {
@ -412,44 +432,31 @@ 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 }); const variable = this.var_lookup.get(name);
this.mutable_props.add(name); variable.export_name = name;
}); });
}); });
} else { } else {
const { name } = node.declaration.id; const { name } = node.declaration.id;
exports.push({ name, as: name });
const variable = this.var_lookup.get(name);
variable.export_name = name;
} }
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.export_name = 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);
}); });
} }
}
}); });
} }
@ -491,7 +498,7 @@ export default class Component {
} }
walk_module_js() { walk_module_js() {
const script = this.module_script; const script = this.ast.module;
if (!script) return; if (!script) return;
this.addSourcemapLocations(script.content); this.addSourcemapLocations(script.content);
@ -506,15 +513,35 @@ 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,
module: true,
hoistable: true
});
}
}); });
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);
} }
walk_instance_js_pre_template() { walk_instance_js_pre_template() {
const script = this.instance_script; const script = this.ast.instance;
if (!script) return; if (!script) return;
this.addSourcemapLocations(script.content); this.addSourcemapLocations(script.content);
@ -530,28 +557,56 @@ 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`
}); });
} }
});
instance_scope.declarations.forEach((node, name) => { if (!/Import/.test(node.type)) {
this.userVars.add(name); const kind = node.type === 'VariableDeclaration'
this.declarations.push(name); ? node.kind
: node.type === 'ClassDeclaration'
? 'class'
: node.type === 'FunctionDeclaration'
? 'function'
: null;
this.node_for_declaration.set(name, node); // sanity check
if (!kind) throw new Error(`Unknown declaration type ${node.type}`);
this.add_var({
name,
initialised: instance_scope.initialised_declarations.has(name),
writable: kind === 'var' || kind === 'let'
}); });
}
this.writable_declarations = instance_scope.writable_declarations; this.node_for_declaration.set(name, node);
this.initialised_declarations = instance_scope.initialised_declarations; });
globals.forEach(name => { globals.forEach(name => {
this.userVars.add(name); if (this.module_scope && this.module_scope.declarations.has(name)) return;
if (name[0] === '$') {
this.add_var({
name,
injected: true,
mutated: true,
writable: true
});
this.add_reference(name.slice(1));
} else {
this.add_var({
name,
global: true
});
}
}); });
this.extract_imports(script.content, false);
this.extract_exports(script.content, false);
this.track_mutations(); this.track_mutations();
this.extract_imports_and_exports(script.content, this.imports, this.props);
} }
walk_instance_js_post_template() { walk_instance_js_post_template() {
const script = this.instance_script; const script = this.ast.instance;
if (!script) return; if (!script) return;
this.hoist_instance_declarations(); this.hoist_instance_declarations();
@ -563,17 +618,23 @@ export default class Component {
// TODO merge this with other walks that are independent // TODO merge this with other walks that are independent
track_mutations() { track_mutations() {
const component = this; const component = this;
let { instance_scope: scope, instance_scope_map: map } = this; const { instance_scope, instance_scope_map: map } = this;
let scope = instance_scope;
walk(this.instance_script.content, { walk(this.ast.instance.content, {
enter(node, parent) { enter(node, parent) {
let names;
if (map.has(node)) { if (map.has(node)) {
scope = map.get(node); scope = map.get(node);
} }
let names;
let deep = false;
if (node.type === 'AssignmentExpression') { if (node.type === 'AssignmentExpression') {
names = node.left.type === 'MemberExpression' deep = node.left.type === 'MemberExpression';
names = deep
? [getObject(node.left).name] ? [getObject(node.left).name]
: extractNames(node.left); : extractNames(node.left);
} else if (node.type === 'UpdateExpression') { } else if (node.type === 'UpdateExpression') {
@ -582,9 +643,18 @@ export default class Component {
if (names) { if (names) {
names.forEach(name => { names.forEach(name => {
if (scope.has(name)) component.mutable_props.add(name); if (scope.findOwner(name) === instance_scope) {
const variable = component.var_lookup.get(name);
variable[deep ? 'mutated' : 'reassigned'] = true;
}
}); });
} }
},
leave(node) {
if (map.has(node)) {
scope = scope.parent;
}
} }
}) })
} }
@ -595,7 +665,7 @@ export default class Component {
const component = this; const component = this;
let { instance_scope: scope, instance_scope_map: map } = this; let { instance_scope: scope, instance_scope_map: map } = this;
walk(this.instance_script.content, { walk(this.ast.instance.content, {
enter(node, parent) { enter(node, parent) {
if (map.has(node)) { if (map.has(node)) {
scope = map.get(node); scope = map.get(node);
@ -607,9 +677,6 @@ export default class Component {
if (name[0] === '$' && !scope.has(name)) { if (name[0] === '$' && !scope.has(name)) {
component.warn_if_undefined(object, null); component.warn_if_undefined(object, null);
// cheeky hack
component.template_references.add(name);
} }
} }
}, },
@ -627,17 +694,10 @@ export default class Component {
const { code, instance_scope, instance_scope_map: map, meta } = this; const { code, instance_scope, instance_scope_map: map, meta } = this;
let scope = instance_scope; let scope = instance_scope;
// TODO we will probably end up wanting to use this elsewhere
const exported = new Set();
this.props.forEach(prop => {
exported.add(prop.name);
this.mutable_props.add(prop.name);
});
const coalesced_declarations = []; const coalesced_declarations = [];
let current_group; let current_group;
walk(this.instance_script.content, { walk(this.ast.instance.content, {
enter(node, parent) { enter(node, parent) {
if (/Function/.test(node.type)) { if (/Function/.test(node.type)) {
current_group = null; current_group = null;
@ -650,14 +710,15 @@ export default class Component {
if (node.type === 'VariableDeclaration') { if (node.type === 'VariableDeclaration') {
if (node.kind === 'var' || scope === instance_scope) { if (node.kind === 'var' || scope === instance_scope) {
let has_meta_props = false;
let has_exports = false; let has_exports = false;
let has_only_exports = true; let has_only_exports = true;
node.declarations.forEach(declarator => { node.declarations.forEach(declarator => {
extractNames(declarator.id).forEach(name => { extractNames(declarator.id).forEach(name => {
const variable = component.var_lookup.get(name);
if (name === meta.props_object) { if (name === meta.props_object) {
if (exported.has(name)) { if (variable.export_name) {
component.error(declarator, { component.error(declarator, {
code: 'exported-meta-props', code: 'exported-meta-props',
message: `Cannot export props binding` message: `Cannot export props binding`
@ -676,11 +737,9 @@ export default class Component {
} else { } else {
code.overwrite(declarator.id.end, declarator.end, ' = $$props'); code.overwrite(declarator.id.end, declarator.end, ' = $$props');
} }
has_meta_props = true;
} }
if (exported.has(name)) { if (variable.export_name) {
has_exports = true; has_exports = true;
} else { } else {
has_only_exports = false; has_only_exports = false;
@ -724,7 +783,6 @@ export default class Component {
}); });
coalesced_declarations.forEach(group => { coalesced_declarations.forEach(group => {
const kind = group[0].kind;
let c = 0; let c = 0;
let combining = false; let combining = false;
@ -764,16 +822,16 @@ export default class Component {
// reference instance variables other than other // reference instance variables other than other
// hoistable functions. TODO others? // hoistable functions. TODO others?
const { hoistable_names, hoistable_nodes, imported_declarations, instance_scope: scope } = this; const { hoistable_nodes, var_lookup } = this;
const template_scope = this.fragment.scope;
const top_level_function_declarations = new Map(); const top_level_function_declarations = new Map();
this.instance_script.content.body.forEach(node => { this.ast.instance.content.body.forEach(node => {
if (node.type === 'VariableDeclaration') { if (node.type === 'VariableDeclaration') {
if (node.declarations.every(d => d.init && d.init.type === 'Literal' && !this.mutable_props.has(d.id.name) && !template_scope.containsMutable([d.id.name]))) { if (node.declarations.every(d => d.init && d.init.type === 'Literal' && !this.var_lookup.get(d.id.name).reassigned)) {
node.declarations.forEach(d => { node.declarations.forEach(d => {
hoistable_names.add(d.id.name); const variable = this.var_lookup.get(d.id.name);
variable.hoistable = true;
}); });
hoistable_nodes.add(node); hoistable_nodes.add(node);
@ -823,8 +881,9 @@ export default class Component {
else if (owner === instance_scope) { else if (owner === instance_scope) {
if (name === fn_declaration.id.name) return; if (name === fn_declaration.id.name) return;
if (hoistable_names.has(name)) return;
if (imported_declarations.has(name)) return; const variable = var_lookup.get(name);
if (variable.hoistable) return;
if (top_level_function_declarations.has(name)) { if (top_level_function_declarations.has(name)) {
const other_declaration = top_level_function_declarations.get(name); const other_declaration = top_level_function_declarations.get(name);
@ -860,7 +919,8 @@ export default class Component {
for (const [name, node] of top_level_function_declarations) { for (const [name, node] of top_level_function_declarations) {
if (!checked.has(node) && is_hoistable(node)) { if (!checked.has(node) && is_hoistable(node)) {
hoistable_names.add(name); const variable = this.var_lookup.get(name);
variable.hoistable = true;
hoistable_nodes.add(node); hoistable_nodes.add(node);
remove_indentation(this.code, node); remove_indentation(this.code, node);
@ -875,7 +935,7 @@ export default class Component {
const unsorted_reactive_declarations = []; const unsorted_reactive_declarations = [];
this.instance_script.content.body.forEach(node => { this.ast.instance.content.body.forEach(node => {
if (node.type === 'LabeledStatement' && node.label.name === '$') { if (node.type === 'LabeledStatement' && node.label.name === '$') {
this.reactive_declaration_nodes.add(node); this.reactive_declaration_nodes.add(node);
@ -899,7 +959,7 @@ export default class Component {
const object = getObject(node); const object = getObject(node);
const { name } = object; const { name } = object;
if (name[0] === '$' || component.declarations.indexOf(name) !== -1) { if (name[0] === '$' || component.var_lookup.has(name)) {
dependencies.add(name); dependencies.add(name);
} }
@ -982,11 +1042,12 @@ export default class Component {
} }
qualify(name) { qualify(name) {
if (this.hoistable_names.has(name)) return name; const variable = this.var_lookup.get(name);
if (this.imported_declarations.has(name)) return name;
if (this.declarations.indexOf(name) === -1) return name; if (!variable) return name;
if (variable && variable.hoistable) 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}`;
} }
@ -998,7 +1059,7 @@ export default class Component {
this.has_reactive_assignments = true; this.has_reactive_assignments = true;
} }
if (allow_implicit && !this.instance_script) return; if (allow_implicit && !this.ast.instance && !this.ast.module) return;
if (this.instance_scope && this.instance_scope.declarations.has(name)) return; if (this.instance_scope && this.instance_scope.declarations.has(name)) return;
if (this.module_scope && this.module_scope.declarations.has(name)) return; if (this.module_scope && this.module_scope.declarations.has(name)) return;
if (template_scope && template_scope.names.has(name)) return; if (template_scope && template_scope.names.has(name)) return;
@ -1009,29 +1070,6 @@ export default class Component {
message: `'${name}' is not defined` message: `'${name}' is not defined`
}); });
} }
get_context(script) {
const context = script.attributes.find(attribute => attribute.name === 'context');
if (!context) return 'default';
if (context.value.length !== 1 || context.value[0].type !== 'Text') {
this.error(script, {
code: 'invalid-script',
message: `context attribute must be static`
});
}
const value = context.value[0].data;
if (value !== 'module') {
this.error(context, {
code: `invalid-script`,
message: `If the context attribute is supplied, its value must be "module"`
});
}
return value;
}
} }
function process_meta(component, nodes) { function process_meta(component, nodes) {

@ -265,15 +265,15 @@ export default class Stylesheet {
this.nodesWithCssClass = new Set(); this.nodesWithCssClass = new Set();
this.nodesWithRefCssClass = new Map(); this.nodesWithRefCssClass = new Map();
if (ast.css[0] && ast.css[0].children.length) { if (ast.css && ast.css.children.length) {
this.id = `svelte-${hash(ast.css[0].content.styles)}`; this.id = `svelte-${hash(ast.css.content.styles)}`;
this.hasStyles = true; this.hasStyles = true;
const stack: (Rule | Atrule)[] = []; const stack: (Rule | Atrule)[] = [];
let currentAtrule: Atrule = null; let currentAtrule: Atrule = null;
walk(ast.css[0], { walk(ast.css, {
enter: (node: Node) => { enter: (node: Node) => {
if (node.type === 'Atrule') { if (node.type === 'Atrule') {
const last = stack[stack.length - 1]; const last = stack[stack.length - 1];

@ -34,7 +34,7 @@ export default class Attribute extends Node {
this.isSynthetic = false; this.isSynthetic = false;
this.expression = new Expression(component, this, scope, info.expression); this.expression = new Expression(component, this, scope, info.expression);
this.dependencies = this.expression.dynamic_dependencies; this.dependencies = this.expression.dependencies;
this.chunks = null; this.chunks = null;
this.isDynamic = true; // TODO not necessarily this.isDynamic = true; // TODO not necessarily
@ -59,7 +59,7 @@ export default class Attribute extends Node {
const expression = new Expression(component, this, scope, node.expression); const expression = new Expression(component, this, scope, node.expression);
addToSet(this.dependencies, expression.dynamic_dependencies); addToSet(this.dependencies, expression.dependencies);
return expression; return expression;
}); });
@ -73,6 +73,19 @@ export default class Attribute extends Node {
} }
} }
get_dependencies() {
if (this.isSpread) return this.expression.dynamic_dependencies();
const dependencies = new Set();
this.chunks.forEach(chunk => {
if (chunk.type === 'Expression') {
addToSet(dependencies, chunk.dynamic_dependencies());
}
});
return [...dependencies];
}
getValue() { getValue() {
if (this.isTrue) return true; if (this.isTrue) return true;
if (this.chunks.length === 0) return `""`; if (this.chunks.length === 0) return `""`;

@ -2,6 +2,7 @@ import Node from './shared/Node';
import getObject from '../../utils/getObject'; import getObject from '../../utils/getObject';
import Expression from './shared/Expression'; import Expression from './shared/Expression';
import Component from '../Component'; import Component from '../Component';
import TemplateScope from './shared/TemplateScope';
export default class Binding extends Node { export default class Binding extends Node {
name: string; name: string;
@ -10,7 +11,7 @@ export default class Binding extends Node {
obj: string; obj: string;
prop: string; prop: string;
constructor(component: Component, parent, scope, info) { constructor(component: Component, parent, scope: TemplateScope, info) {
super(component, parent, scope, info); super(component, parent, scope, info);
if (info.expression.type !== 'Identifier' && info.expression.type !== 'MemberExpression') { if (info.expression.type !== 'Identifier' && info.expression.type !== 'MemberExpression') {
@ -30,7 +31,21 @@ export default class Binding extends Node {
this.isContextual = scope.names.has(name); this.isContextual = scope.names.has(name);
// make sure we track this as a mutable ref // make sure we track this as a mutable ref
scope.setMutable(name); if (this.isContextual) {
scope.dependenciesForName.get(name).forEach(name => {
const variable = component.var_lookup.get(name);
variable[this.expression.node.type === 'MemberExpression' ? 'mutated' : 'reassigned'] = true;
});
} else {
const variable = component.var_lookup.get(name);
if (!variable) component.error(this.expression.node, {
code: 'binding-undeclared',
message: `${name} is not declared`
});
variable[this.expression.node.type === 'MemberExpression' ? 'mutated' : 'reassigned'] = true;
}
if (this.expression.node.type === 'MemberExpression') { if (this.expression.node.type === 'MemberExpression') {
prop = `[✂${this.expression.node.property.start}-${this.expression.node.property.end}✂]`; prop = `[✂${this.expression.node.property.start}-${this.expression.node.property.end}✂]`;

@ -92,7 +92,6 @@ export default class Element extends Node {
constructor(component, parent, scope, info: any) { constructor(component, parent, scope, info: any) {
super(component, parent, scope, info); super(component, parent, scope, info);
this.name = info.name; this.name = info.name;
this.scope = scope;
const parentElement = parent.findNearest(/^Element/); const parentElement = parent.findNearest(/^Element/);
this.namespace = this.name === 'svg' ? this.namespace = this.name === 'svg' ?
@ -196,7 +195,7 @@ export default class Element extends Node {
const dependencies = new Set([l.name]); const dependencies = new Set([l.name]);
l.names.forEach(name => { l.names.forEach(name => {
this.scope.add(name, dependencies); this.scope.add(name, dependencies, this);
}); });
}); });
} else { } else {

@ -41,7 +41,12 @@ export default class EventHandler extends Node {
} }
} else { } else {
const name = component.getUniqueName(`${this.name}_handler`); const name = component.getUniqueName(`${this.name}_handler`);
component.declarations.push(name);
component.add_var({
name,
internal: true,
referenced: true
});
component.partly_hoisted.push(deindent` component.partly_hoisted.push(deindent`
function ${name}(event) { function ${name}(event) {
@ -57,7 +62,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;

@ -62,6 +62,7 @@ const precedence: Record<string, (node?: Node) => number> = {
}; };
export default class Expression { export default class Expression {
type = 'Expression';
component: Component; component: Component;
owner: Wrapper; owner: Wrapper;
node: any; node: any;
@ -69,7 +70,6 @@ export default class Expression {
references: Set<string>; references: Set<string>;
dependencies: Set<string> = new Set(); dependencies: Set<string> = new Set();
contextual_dependencies: Set<string> = new Set(); contextual_dependencies: Set<string> = new Set();
dynamic_dependencies: Set<string> = new Set();
template_scope: TemplateScope; template_scope: TemplateScope;
scope: Scope; scope: Scope;
@ -95,7 +95,7 @@ export default class Expression {
this.owner = owner; this.owner = owner;
this.is_synthetic = owner.isSynthetic; this.is_synthetic = owner.isSynthetic;
const { dependencies, contextual_dependencies, dynamic_dependencies } = this; const { dependencies, contextual_dependencies } = this;
let { map, scope } = createScopes(info); let { map, scope } = createScopes(info);
this.scope = scope; this.scope = scope;
@ -104,27 +104,6 @@ export default class Expression {
const expression = this; const expression = this;
let function_expression; let function_expression;
function add_dependency(name, deep = false) {
dependencies.add(name);
if (!function_expression) {
// dynamic_dependencies is used to create `if (changed.foo || ...)`
// conditions — it doesn't apply if the dependency is inside a
// function, and it only applies if the dependency is writable
// or a sub-path of a non-writable
if (component.instance_script) {
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)) {
dynamic_dependencies.add(name);
}
} else {
dynamic_dependencies.add(name);
}
}
}
// discover dependencies, but don't change the code yet // discover dependencies, but don't change the code yet
walk(info, { walk(info, {
enter(node: any, parent: any, key: string) { enter(node: any, parent: any, key: string) {
@ -143,18 +122,26 @@ export default class Expression {
const { name, nodes } = flattenReference(node); const { name, nodes } = flattenReference(node);
if (scope.has(name)) return; if (scope.has(name)) return;
if (globalWhitelist.has(name) && component.declarations.indexOf(name) === -1) return; if (globalWhitelist.has(name) && !component.var_lookup.has(name)) return;
if (template_scope.names.has(name)) { if (template_scope.is_let(name)) {
if (!function_expression) {
dependencies.add(name);
}
} else if (template_scope.names.has(name)) {
expression.usesContext = true; expression.usesContext = true;
contextual_dependencies.add(name); contextual_dependencies.add(name);
template_scope.dependenciesForName.get(name).forEach(name => add_dependency(name, true)); if (!function_expression) {
template_scope.dependenciesForName.get(name).forEach(name => dependencies.add(name));
}
} else { } else {
add_dependency(name, nodes.length > 1); if (!function_expression) {
component.template_references.add(name); dependencies.add(name);
}
component.add_reference(name);
component.warn_if_undefined(nodes[0], template_scope, true); component.warn_if_undefined(nodes[0], template_scope, true);
} }
@ -162,16 +149,35 @@ export default class Expression {
} }
// track any assignments from template expressions as mutable // track any assignments from template expressions as mutable
let names;
let deep = false;
if (function_expression) { if (function_expression) {
if (node.type === 'AssignmentExpression') { if (node.type === 'AssignmentExpression') {
const names = node.left.type === 'MemberExpression' deep = node.left.type === 'MemberExpression';
names = deep
? [getObject(node.left).name] ? [getObject(node.left).name]
: extractNames(node.left); : extractNames(node.left);
names.forEach(name => template_scope.setMutable(name));
} else if (node.type === 'UpdateExpression') { } else if (node.type === 'UpdateExpression') {
const { name } = getObject(node.argument); const { name } = getObject(node.argument);
template_scope.setMutable(name); names = [name];
}
} }
if (names) {
names.forEach(name => {
if (template_scope.names.has(name)) {
template_scope.dependenciesForName.get(name).forEach(name => {
const variable = component.var_lookup.get(name);
if (variable) variable[deep ? 'mutated' : 'reassigned'] = true;
});
} else {
component.add_reference(name);
const variable = component.var_lookup.get(name);
if (variable) variable[deep ? 'mutated' : 'reassigned'] = true;
}
});
} }
}, },
@ -187,6 +193,18 @@ export default class Expression {
}); });
} }
dynamic_dependencies() {
return Array.from(this.dependencies).filter(name => {
if (this.template_scope.is_let(name)) return true;
const variable = this.component.var_lookup.get(name);
if (!variable) return false;
if (variable.mutated || variable.reassigned) return true; // dynamic internal state
if (!variable.module && variable.writable && variable.export_name) return true; // writable props
});
}
getPrecedence() { getPrecedence() {
return this.node.type in precedence ? precedence[this.node.type](this.node) : 0; return this.node.type in precedence ? precedence[this.node.type](this.node) : 0;
} }
@ -230,7 +248,7 @@ export default class Expression {
const { name, nodes } = flattenReference(node); const { name, nodes } = flattenReference(node);
if (scope.has(name)) return; if (scope.has(name)) return;
if (globalWhitelist.has(name) && component.declarations.indexOf(name) === -1) return; if (globalWhitelist.has(name) && !component.var_lookup.has(name)) return;
if (function_expression) { if (function_expression) {
if (template_scope.names.has(name)) { if (template_scope.names.has(name)) {
@ -241,7 +259,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
@ -276,7 +294,9 @@ export default class Expression {
} else { } else {
names.forEach(name => { names.forEach(name => {
if (scope.declarations.has(name)) return; if (scope.declarations.has(name)) return;
if (component.imported_declarations.has(name)) return;
const variable = component.var_lookup.get(name);
if (variable && variable.hoistable) return;
pending_assignments.add(name); pending_assignments.add(name);
}); });
@ -285,7 +305,9 @@ export default class Expression {
const { name } = getObject(node.argument); const { name } = getObject(node.argument);
if (scope.declarations.has(name)) return; if (scope.declarations.has(name)) return;
if (component.imported_declarations.has(name)) return;
const variable = component.var_lookup.get(name);
if (variable && variable.hoistable) return;
pending_assignments.add(name); pending_assignments.add(name);
} }
@ -359,23 +381,38 @@ export default class Expression {
// we can hoist this out of the component completely // we can hoist this out of the component completely
component.fully_hoisted.push(fn); component.fully_hoisted.push(fn);
code.overwrite(node.start, node.end, name); code.overwrite(node.start, node.end, name);
component.add_var({
name,
internal: true,
hoistable: true,
referenced: true
});
} }
else if (contextual_dependencies.size === 0) { else if (contextual_dependencies.size === 0) {
// 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.template_references.add(name);
code.overwrite(node.start, node.end, `ctx.${name}`); code.overwrite(node.start, node.end, `ctx.${name}`);
component.add_var({
name,
internal: true,
referenced: true
});
} }
else { else {
// 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.template_references.add(name);
code.overwrite(node.start, node.end, name); code.overwrite(node.start, node.end, name);
component.add_var({
name,
internal: true,
referenced: true
});
declarations.push(deindent` declarations.push(deindent`
function ${name}(${original_params ? '...args' : ''}) { function ${name}(${original_params ? '...args' : ''}) {
return ctx.${name}(ctx${original_params ? ', ...args' : ''}); return ctx.${name}(ctx${original_params ? ', ...args' : ''});
@ -445,10 +482,10 @@ function isContextual(component: Component, scope: TemplateScope, name: string)
// if it's a name below root scope, it's contextual // if it's a name below root scope, it's contextual
if (!scope.isTopLevel(name)) return true; if (!scope.isTopLevel(name)) return true;
const variable = component.var_lookup.get(name);
// hoistables, module declarations, and imports are non-contextual // hoistables, module declarations, and imports are non-contextual
if (component.hoistable_names.has(name)) return false; if (!variable || variable.hoistable) return false;
if (component.module_scope && component.module_scope.declarations.has(name)) return false;
if (component.imported_declarations.has(name)) return false;
// assume contextual // assume contextual
return true; return true;

@ -9,7 +9,6 @@ type NodeWithScope = EachBlock | ThenBlock | CatchBlock | InlineComponent | Elem
export default class TemplateScope { export default class TemplateScope {
names: Set<string>; names: Set<string>;
dependenciesForName: Map<string, Set<string>>; dependenciesForName: Map<string, Set<string>>;
mutables: Set<string> = new Set();
owners: Map<string, NodeWithScope> = new Map(); owners: Map<string, NodeWithScope> = new Map();
parent?: TemplateScope; parent?: TemplateScope;
@ -31,29 +30,6 @@ export default class TemplateScope {
return child; return child;
} }
setMutable(name: string) {
if (this.names.has(name)) {
this.mutables.add(name);
if (this.parent && this.dependenciesForName.has(name)) this.dependenciesForName.get(name).forEach(dep => this.parent.setMutable(dep));
} else if (this.parent) this.parent.setMutable(name);
else this.mutables.add(name);
}
containsMutable(names: Iterable<string>) {
for (const name of names) {
const owner = this.getOwner(name);
const is_let = owner && (owner.type === 'InlineComponent' || owner.type === 'Element');
if (is_let) return true;
if (name[0] === '$') return true;
if (this.mutables.has(name)) return true;
else if (this.dependenciesForName.has(name) && this.containsMutable(this.dependenciesForName.get(name))) return true;
}
if (this.parent) return this.parent.containsMutable(names);
else return false;
}
isTopLevel(name: string) { isTopLevel(name: string) {
return !this.parent || !this.names.has(name) && this.parent.isTopLevel(name); return !this.parent || !this.names.has(name) && this.parent.isTopLevel(name);
} }
@ -61,4 +37,9 @@ export default class TemplateScope {
getOwner(name: string): NodeWithScope { getOwner(name: string): NodeWithScope {
return this.owners.get(name) || (this.parent && this.parent.getOwner(name)); return this.owners.get(name) || (this.parent && this.parent.getOwner(name));
} }
is_let(name: string) {
const owner = this.getOwner(name);
return owner && (owner.type === 'Element' || owner.type === 'InlineComponent');
}
} }

@ -70,9 +70,10 @@ export default function dom(
options.css !== false options.css !== false
); );
const props = component.props.filter(x => component.writable_declarations.has(x.name)); const props = component.vars.filter(variable => !variable.module && variable.export_name);
const writable_props = props.filter(variable => variable.writable);
const set = (component.meta.props || props.length > 0 || renderer.slots.size > 0) const set = (component.meta.props || writable_props.length > 0 || renderer.slots.size > 0)
? deindent` ? deindent`
$$props => { $$props => {
${component.meta.props && deindent` ${component.meta.props && deindent`
@ -80,8 +81,8 @@ export default function dom(
@assign(${component.meta.props}, $$props); @assign(${component.meta.props}, $$props);
$$invalidate('${component.meta.props_object}', ${component.meta.props_object}); $$invalidate('${component.meta.props_object}', ${component.meta.props_object});
`} `}
${props.map(prop => ${writable_props.map(prop =>
`if ('${prop.as}' in $$props) $$invalidate('${prop.name}', ${prop.name} = $$props.${prop.as});`)} `if ('${prop.export_name}' in $$props) $$invalidate('${prop.name}', ${prop.name} = $$props.${prop.export_name});`)}
${renderer.slots.size > 0 && ${renderer.slots.size > 0 &&
`if ('$$scope' in $$props) $$invalidate('$$scope', $$scope = $$props.$$scope);`} `if ('$$scope' in $$props) $$invalidate('$$scope', $$scope = $$props.$$scope);`}
} }
@ -93,32 +94,34 @@ export default function dom(
const not_equal = component.meta.immutable ? `@not_equal` : `@safe_not_equal`; const not_equal = component.meta.immutable ? `@not_equal` : `@safe_not_equal`;
let dev_props_check; let dev_props_check;
component.props.forEach(x => { props.forEach(x => {
if (component.imported_declarations.has(x.name) || component.hoistable_names.has(x.name)) { const variable = component.var_lookup.get(x.name);
if (variable.hoistable) {
body.push(deindent` body.push(deindent`
get ${x.as}() { get ${x.export_name}() {
return ${x.name}; return ${x.name};
} }
`); `);
} else { } else {
body.push(deindent` body.push(deindent`
get ${x.as}() { get ${x.export_name}() {
return this.$$.ctx.${x.name}; return this.$$.ctx.${x.name};
} }
`); `);
} }
if (component.writable_declarations.has(x.as) && !renderer.readonly.has(x.as)) { if (variable.writable && !renderer.readonly.has(x.export_name)) {
body.push(deindent` body.push(deindent`
set ${x.as}(${x.name}) { set ${x.export_name}(${x.name}) {
this.$set({ ${x.name} }); this.$set({ ${x.name} });
@flush(); @flush();
} }
`); `);
} else if (component.options.dev) { } else if (component.options.dev) {
body.push(deindent` body.push(deindent`
set ${x.as}(value) { set ${x.export_name}(value) {
throw new Error("<${component.tag}>: Cannot set read-only property '${x.as}'"); throw new Error("<${component.tag}>: Cannot set read-only property '${x.export_name}'");
} }
`); `);
} }
@ -127,30 +130,28 @@ export default function dom(
if (component.options.dev) { if (component.options.dev) {
// TODO check no uunexpected props were passed, as well as // TODO check no uunexpected props were passed, as well as
// checking that expected ones were passed // checking that expected ones were passed
const expected = component.props const expected = props.filter(prop => !prop.initialised);
.map(x => x.name)
.filter(name => !component.initialised_declarations.has(name));
if (expected.length) { if (expected.length) {
dev_props_check = deindent` dev_props_check = deindent`
const { ctx } = this.$$; const { ctx } = this.$$;
const props = ${options.customElement ? `this.attributes` : `options.props || {}`}; const props = ${options.customElement ? `this.attributes` : `options.props || {}`};
${expected.map(name => deindent` ${expected.map(prop => deindent`
if (ctx.${name} === undefined && !('${name}' in props)) { if (ctx.${prop.name} === undefined && !('${prop.export_name}' in props)) {
console.warn("<${component.tag}> was created without expected prop '${name}'"); console.warn("<${component.tag}> was created without expected prop '${prop.export_name}'");
}`)} }`)}
`; `;
} }
} }
// instrument assignments // instrument assignments
if (component.instance_script) { if (component.ast.instance) {
let scope = component.instance_scope; let scope = component.instance_scope;
let map = component.instance_scope_map; let map = component.instance_scope_map;
let pending_assignments = new Set(); let pending_assignments = new Set();
walk(component.instance_script.content, { walk(component.ast.instance.content, {
enter: (node, parent) => { enter: (node, parent) => {
if (map.has(node)) { if (map.has(node)) {
scope = map.get(node); scope = map.get(node);
@ -177,9 +178,11 @@ export default function dom(
code.overwrite(node.start, node.end, dirty.map(n => `$$invalidate('${n}', ${n})`).join('; ')); code.overwrite(node.start, node.end, dirty.map(n => `$$invalidate('${n}', ${n})`).join('; '));
} else { } else {
names.forEach(name => { names.forEach(name => {
if (component.imported_declarations.has(name)) return;
if (scope.findOwner(name) !== component.instance_scope) return; if (scope.findOwner(name) !== component.instance_scope) return;
const variable = component.var_lookup.get(name);
if (variable && variable.hoistable) return;
pending_assignments.add(name); pending_assignments.add(name);
component.has_reactive_assignments = true; component.has_reactive_assignments = true;
}); });
@ -189,9 +192,11 @@ export default function dom(
else if (node.type === 'UpdateExpression') { else if (node.type === 'UpdateExpression') {
const { name } = getObject(node.argument); const { name } = getObject(node.argument);
if (component.imported_declarations.has(name)) return;
if (scope.findOwner(name) !== component.instance_scope) return; if (scope.findOwner(name) !== component.instance_scope) return;
const variable = component.var_lookup.get(name);
if (variable && variable.hoistable) return;
pending_assignments.add(name); pending_assignments.add(name);
component.has_reactive_assignments = true; component.has_reactive_assignments = true;
} }
@ -238,7 +243,7 @@ export default function dom(
} }
const args = ['$$self']; const args = ['$$self'];
if (component.props.length > 0 || component.has_reactive_assignments || renderer.slots.size > 0) { if (props.length > 0 || component.has_reactive_assignments || renderer.slots.size > 0) {
args.push('$$props', '$$invalidate'); args.push('$$props', '$$invalidate');
} }
@ -252,22 +257,19 @@ export default function dom(
${component.fully_hoisted.length > 0 && component.fully_hoisted.join('\n\n')} ${component.fully_hoisted.length > 0 && component.fully_hoisted.join('\n\n')}
`); `);
const filtered_declarations = component.declarations.filter(name => { const filtered_declarations = component.vars.filter(variable => {
if (component.hoistable_names.has(name)) return false; return (variable.referenced || variable.export_name) && !variable.hoistable;
if (component.imported_declarations.has(name)) return false; }).map(variable => variable.name);
if (component.props.find(p => p.as === name)) return true;
return component.template_references.has(name); const filtered_props = props.filter(prop => {
}); const variable = component.var_lookup.get(prop.name);
const filtered_props = component.props.filter(prop => { if (variable.hoistable) return false;
if (component.hoistable_names.has(prop.name)) return false;
if (component.imported_declarations.has(prop.name)) return false;
if (prop.name[0] === '$') return false; if (prop.name[0] === '$') return false;
return true; return true;
}); });
const reactive_stores = Array.from(component.template_references).filter(n => n[0] === '$'); const reactive_stores = component.vars.filter(variable => variable.name[0] === '$');
filtered_declarations.push(...reactive_stores);
if (renderer.slots.size > 0) { if (renderer.slots.size > 0) {
const arr = Array.from(renderer.slots); const arr = Array.from(renderer.slots);
@ -283,7 +285,6 @@ export default function dom(
filtered_props.length > 0 || filtered_props.length > 0 ||
component.partly_hoisted.length > 0 || component.partly_hoisted.length > 0 ||
filtered_declarations.length > 0 || filtered_declarations.length > 0 ||
reactive_stores.length > 0 ||
component.reactive_declarations.length > 0 component.reactive_declarations.length > 0
); );
@ -297,13 +298,13 @@ export default function dom(
}); });
const user_code = component.javascript || ( const user_code = component.javascript || (
component.ast.js.length === 0 && filtered_props.length > 0 !component.ast.instance && !component.ast.module && filtered_props.length > 0
? `let { ${filtered_props.map(x => x.name).join(', ')} } = $$props;` ? `let { ${filtered_props.map(x => x.name).join(', ')} } = $$props;`
: null : null
); );
const reactive_store_subscriptions = reactive_stores.length > 0 && reactive_stores const reactive_store_subscriptions = reactive_stores.length > 0 && reactive_stores
.map(name => deindent` .map(({ name }) => deindent`
let ${name}; let ${name};
${component.options.dev && `@validate_store(${name.slice(1)}, '${name.slice(1)}');`} ${component.options.dev && `@validate_store(${name.slice(1)}, '${name.slice(1)}');`}
$$self.$$.on_destroy.push(${name.slice(1)}.subscribe($$value => { ${name} = $$value; $$invalidate('${name}', ${name}); })); $$self.$$.on_destroy.push(${name.slice(1)}.subscribe($$value => { ${name} = $$value; $$invalidate('${name}', ${name}); }));
@ -354,7 +355,7 @@ export default function dom(
@insert(options.target, this, options.anchor); @insert(options.target, this, options.anchor);
} }
${(component.props.length > 0 || component.meta.props) && deindent` ${(props.length > 0 || component.meta.props) && deindent`
if (options.props) { if (options.props) {
this.$set(options.props); this.$set(options.props);
@flush(); @flush();
@ -363,7 +364,7 @@ export default function dom(
} }
static get observedAttributes() { static get observedAttributes() {
return ${JSON.stringify(component.props.map(x => x.as))}; return ${JSON.stringify(props.map(x => x.export_name))};
} }
${body.length > 0 && body.join('\n\n')} ${body.length > 0 && body.join('\n\n')}

@ -67,7 +67,7 @@ export default class AwaitBlockWrapper extends Wrapper {
this.cannotUseInnerHTML(); this.cannotUseInnerHTML();
block.addDependencies(this.node.expression.dynamic_dependencies); block.addDependencies(this.node.expression.dependencies);
let isDynamic = false; let isDynamic = false;
let hasIntros = false; let hasIntros = false;
@ -181,9 +181,11 @@ export default class AwaitBlockWrapper extends Wrapper {
} }
const conditions = []; const conditions = [];
if (this.node.expression.dependencies.size > 0) { const dependencies = this.node.expression.dynamic_dependencies();
if (dependencies.length > 0) {
conditions.push( conditions.push(
`(${[...this.node.expression.dependencies].map(dep => `'${dep}' in changed`).join(' || ')})` `(${dependencies.map(dep => `'${dep}' in changed`).join(' || ')})`
); );
} }

@ -45,7 +45,7 @@ export default class DebugTagWrapper extends Wrapper {
const dependencies = new Set(); const dependencies = new Set();
this.node.expressions.forEach(expression => { this.node.expressions.forEach(expression => {
addToSet(dependencies, expression.dynamic_dependencies); addToSet(dependencies, expression.dependencies);
}); });
const condition = [...dependencies].map(d => `changed.${d}`).join(' || '); const condition = [...dependencies].map(d => `changed.${d}`).join(' || ');

@ -74,8 +74,8 @@ export default class EachBlockWrapper extends Wrapper {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.cannotUseInnerHTML(); this.cannotUseInnerHTML();
const { dynamic_dependencies } = node.expression; const { dependencies } = node.expression;
block.addDependencies(dynamic_dependencies); block.addDependencies(dependencies);
this.block = block.child({ this.block = block.child({
comment: createDebuggingComment(this.node, this.renderer.component), comment: createDebuggingComment(this.node, this.renderer.component),

@ -160,8 +160,8 @@ export default class AttributeWrapper {
} }
// only add an update if mutations are involved (or it's a select?) // only add an update if mutations are involved (or it's a select?)
if (this.node.parent.scope.containsMutable(this.node.dependencies) || isSelectValueAttribute) { const dependencies = this.node.get_dependencies();
const dependencies = Array.from(this.node.dependencies); if (dependencies.length > 0 || isSelectValueAttribute) {
const changedCheck = ( const changedCheck = (
(block.hasOutros ? `!#current || ` : '') + (block.hasOutros ? `!#current || ` : '') +
dependencies.map(dependency => `changed.${dependency}`).join(' || ') dependencies.map(dependency => `changed.${dependency}`).join(' || ')

@ -37,14 +37,14 @@ export default class BindingWrapper {
this.node = node; this.node = node;
this.parent = parent; this.parent = parent;
const { dynamic_dependencies } = this.node.expression; const { dependencies } = this.node.expression;
block.addDependencies(dynamic_dependencies); block.addDependencies(dependencies);
// TODO does this also apply to e.g. `<input type='checkbox' bind:group='foo'>`? // TODO does this also apply to e.g. `<input type='checkbox' bind:group='foo'>`?
if (parent.node.name === 'select') { if (parent.node.name === 'select') {
parent.selectBindingDependencies = dynamic_dependencies; parent.selectBindingDependencies = dependencies;
dynamic_dependencies.forEach((prop: string) => { dependencies.forEach((prop: string) => {
parent.renderer.component.indirectDependencies.set(prop, new Set()); parent.renderer.component.indirectDependencies.set(prop, new Set());
}); });
} }
@ -106,7 +106,7 @@ export default class BindingWrapper {
let updateConditions: string[] = this.needsLock ? [`!${lock}`] : []; let updateConditions: string[] = this.needsLock ? [`!${lock}`] : [];
const dependencyArray = [...this.node.expression.dynamic_dependencies] const dependencyArray = [...this.node.expression.dependencies]
if (dependencyArray.length === 1) { if (dependencyArray.length === 1) {
updateConditions.push(`changed.${dependencyArray[0]}`) updateConditions.push(`changed.${dependencyArray[0]}`)

@ -35,7 +35,7 @@ export default class StyleAttributeWrapper extends AttributeWrapper {
} else { } else {
const snippet = chunk.render(); const snippet = chunk.render();
addToSet(propDependencies, chunk.dynamic_dependencies); addToSet(propDependencies, chunk.dependencies);
return chunk.getPrecedence() <= 13 ? `(${snippet})` : snippet; return chunk.getPrecedence() <= 13 ? `(${snippet})` : snippet;
} }

@ -132,7 +132,7 @@ export default class ElementWrapper extends Wrapper {
const name = attribute.getStaticValue(); const name = attribute.getStaticValue();
if (!(owner as InlineComponentWrapper).slots.has(name)) { if (!(owner as InlineComponentWrapper).slots.has(name)) {
const child_block = block.child({ const child_block = block.parent.child({
comment: createDebuggingComment(node, this.renderer.component), comment: createDebuggingComment(node, this.renderer.component),
name: this.renderer.component.getUniqueName(`create_${sanitize(name)}_slot`) name: this.renderer.component.getUniqueName(`create_${sanitize(name)}_slot`)
}); });
@ -141,6 +141,7 @@ export default class ElementWrapper extends Wrapper {
(owner as InlineComponentWrapper).slots.set(name, { (owner as InlineComponentWrapper).slots.set(name, {
block: child_block, block: child_block,
scope: this.node.scope,
fn fn
}); });
this.renderer.blocks.push(child_block); this.renderer.blocks.push(child_block);
@ -173,13 +174,13 @@ export default class ElementWrapper extends Wrapper {
// add directive and handler dependencies // add directive and handler dependencies
[node.animation, node.outro, ...node.actions, ...node.classes].forEach(directive => { [node.animation, node.outro, ...node.actions, ...node.classes].forEach(directive => {
if (directive && directive.expression) { if (directive && directive.expression) {
block.addDependencies(directive.expression.dynamic_dependencies); block.addDependencies(directive.expression.dependencies);
} }
}); });
node.handlers.forEach(handler => { node.handlers.forEach(handler => {
if (handler.expression) { if (handler.expression) {
block.addDependencies(handler.expression.dynamic_dependencies); block.addDependencies(handler.expression.dependencies);
} }
}); });
@ -404,8 +405,12 @@ 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.template_references.add(handler); renderer.component.add_var({
name: handler,
internal: true,
referenced: true
});
// 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);
@ -505,8 +510,12 @@ export default class ElementWrapper extends Wrapper {
const this_binding = this.bindings.find(b => b.node.name === 'this'); const this_binding = this.bindings.find(b => b.node.name === 'this');
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.template_references.add(name); renderer.component.add_var({
name,
internal: true,
referenced: true
});
const { handler, object } = this_binding; const { handler, object } = this_binding;

@ -87,7 +87,7 @@ export default class IfBlockWrapper extends Wrapper {
this.branches.push(branch); this.branches.push(branch);
blocks.push(branch.block); blocks.push(branch.block);
block.addDependencies(node.expression.dynamic_dependencies); block.addDependencies(node.expression.dependencies);
if (branch.block.dependencies.size > 0) { if (branch.block.dependencies.size > 0) {
isDynamic = true; isDynamic = true;

@ -15,10 +15,11 @@ import createDebuggingComment from '../../../../utils/createDebuggingComment';
import sanitize from '../../../../utils/sanitize'; import sanitize from '../../../../utils/sanitize';
import { get_context_merger } from '../shared/get_context_merger'; import { get_context_merger } from '../shared/get_context_merger';
import EachBlock from '../../../nodes/EachBlock'; import EachBlock from '../../../nodes/EachBlock';
import TemplateScope from '../../../nodes/shared/TemplateScope';
export default class InlineComponentWrapper extends Wrapper { export default class InlineComponentWrapper extends Wrapper {
var: string; var: string;
slots: Map<string, { block: Block, fn?: string }> = new Map(); slots: Map<string, { block: Block, scope: TemplateScope, fn?: string }> = new Map();
node: InlineComponent; node: InlineComponent;
fragment: FragmentWrapper; fragment: FragmentWrapper;
@ -35,7 +36,7 @@ export default class InlineComponentWrapper extends Wrapper {
this.cannotUseInnerHTML(); this.cannotUseInnerHTML();
if (this.node.expression) { if (this.node.expression) {
block.addDependencies(this.node.expression.dynamic_dependencies); block.addDependencies(this.node.expression.dependencies);
} }
this.node.attributes.forEach(attr => { this.node.attributes.forEach(attr => {
@ -52,12 +53,12 @@ export default class InlineComponentWrapper extends Wrapper {
(eachBlock as EachBlock).has_binding = true; (eachBlock as EachBlock).has_binding = true;
} }
block.addDependencies(binding.expression.dynamic_dependencies); block.addDependencies(binding.expression.dependencies);
}); });
this.node.handlers.forEach(handler => { this.node.handlers.forEach(handler => {
if (handler.expression) { if (handler.expression) {
block.addDependencies(handler.expression.dynamic_dependencies); block.addDependencies(handler.expression.dependencies);
} }
}); });
@ -79,6 +80,7 @@ export default class InlineComponentWrapper extends Wrapper {
this.slots.set('default', { this.slots.set('default', {
block: default_slot, block: default_slot,
scope: this.node.scope,
fn fn
}); });
this.fragment = new FragmentWrapper(renderer, default_slot, node.children, this, stripWhitespace, nextSibling); this.fragment = new FragmentWrapper(renderer, default_slot, node.children, this, stripWhitespace, nextSibling);
@ -147,9 +149,14 @@ export default class InlineComponentWrapper extends Wrapper {
const fragment_dependencies = new Set(); const fragment_dependencies = new Set();
this.slots.forEach(slot => { this.slots.forEach(slot => {
slot.block.dependencies.forEach(name => { slot.block.dependencies.forEach(name => {
if (renderer.component.mutable_props.has(name)) { const is_let = slot.scope.is_let(name);
fragment_dependencies.add(name); const variable = renderer.component.var_lookup.get(name);
}
if (is_let) fragment_dependencies.add(name);
if (!variable) return;
if (variable.mutated || variable.reassigned) fragment_dependencies.add(name);
if (!variable.module && variable.writable && variable.export_name) fragment_dependencies.add(name);
}); });
}); });
@ -233,8 +240,12 @@ 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.template_references.add(fn); component.add_var({
name: fn,
internal: true,
referenced: true
});
let lhs; let lhs;
let object; let object;
@ -264,8 +275,12 @@ 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.template_references.add(name); component.add_var({
name,
internal: true,
referenced: true
});
const updating = block.getUniqueName(`updating_${binding.name}`); const updating = block.getUniqueName(`updating_${binding.name}`);
block.addVariable(updating); block.addVariable(updating);
@ -279,7 +294,7 @@ export default class InlineComponentWrapper extends Wrapper {
); );
updates.push(deindent` updates.push(deindent`
if (!${updating} && ${[...binding.expression.dynamic_dependencies].map((dependency: string) => `changed.${dependency}`).join(' || ')}) { if (!${updating} && ${[...binding.expression.dependencies].map((dependency: string) => `changed.${dependency}`).join(' || ')}) {
${name_changes}${quotePropIfNecessary(binding.name)} = ${snippet}; ${name_changes}${quotePropIfNecessary(binding.name)} = ${snippet};
} }
`); `);

@ -34,7 +34,7 @@ export default class TitleWrapper extends Wrapper {
// single {tag} — may be a non-string // single {tag} — may be a non-string
const { expression } = this.node.children[0]; const { expression } = this.node.children[0];
value = expression.render(block); value = expression.render(block);
addToSet(allDependencies, expression.dynamic_dependencies); addToSet(allDependencies, expression.dependencies);
} else { } else {
// '{foo} {bar}' — treat as string concatenation // '{foo} {bar}' — treat as string concatenation
value = value =
@ -46,7 +46,7 @@ export default class TitleWrapper extends Wrapper {
} else { } else {
const snippet = chunk.expression.render(block); const snippet = chunk.expression.render(block);
chunk.expression.dynamic_dependencies.forEach(d => { chunk.expression.dependencies.forEach(d => {
allDependencies.add(d); allDependencies.add(d);
}); });

@ -118,16 +118,18 @@ export default class WindowWrapper extends Wrapper {
`); `);
} }
component.declarations.push(handler_name); component.add_var({
component.template_references.add(handler_name); name: handler_name,
internal: true,
referenced: true
});
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});`)}
} }
`); `);
block.builders.init.addBlock(deindent` block.builders.init.addBlock(deindent`
@add_render_callback(ctx.${handler_name}); @add_render_callback(ctx.${handler_name});
`); `);

@ -11,14 +11,14 @@ export default class Tag extends Wrapper {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.cannotUseInnerHTML(); this.cannotUseInnerHTML();
block.addDependencies(node.expression.dynamic_dependencies); block.addDependencies(node.expression.dependencies);
} }
renameThisMethod( renameThisMethod(
block: Block, block: Block,
update: ((value: string) => string) update: ((value: string) => string)
) { ) {
const dependencies = this.node.expression.dynamic_dependencies; const dependencies = this.node.expression.dynamic_dependencies();
const snippet = this.node.expression.render(block); const snippet = this.node.expression.render(block);
const value = this.node.shouldCache && block.getUniqueName(`${this.var}_value`); const value = this.node.shouldCache && block.getUniqueName(`${this.var}_value`);
@ -26,28 +26,23 @@ export default class Tag extends Wrapper {
if (this.node.shouldCache) block.addVariable(value, snippet); if (this.node.shouldCache) block.addVariable(value, snippet);
if (dependencies.size) { if (dependencies.length > 0) {
const changedCheck = ( const changedCheck = (
(block.hasOutros ? `!#current || ` : '') + (block.hasOutros ? `!#current || ` : '') +
[...dependencies].map((dependency: string) => `changed.${dependency}`).join(' || ') dependencies.map((dependency: string) => `changed.${dependency}`).join(' || ')
); );
const updateCachedValue = `${value} !== (${value} = ${snippet})`; const updateCachedValue = `${value} !== (${value} = ${snippet})`;
const condition =this.node.shouldCache const condition =this.node.shouldCache
? dependencies.size > 0
? `(${changedCheck}) && ${updateCachedValue}` ? `(${changedCheck}) && ${updateCachedValue}`
: updateCachedValue
: changedCheck; : changedCheck;
// only update if there's a mutation involved
if (this.node.expression.template_scope.containsMutable(dependencies)) {
block.builders.update.addConditional( block.builders.update.addConditional(
condition, condition,
update(content) update(content)
); );
} }
}
return { init: content }; return { init: content };
} }

@ -1,4 +1,3 @@
import Renderer from '../../Renderer';
import Block from '../../Block'; import Block from '../../Block';
import Action from '../../../nodes/Action'; import Action from '../../../nodes/Action';
import Component from '../../../Component'; import Component from '../../../Component';
@ -15,7 +14,7 @@ export default function addActions(
if (expression) { if (expression) {
snippet = expression.render(block); snippet = expression.render(block);
dependencies = expression.dynamic_dependencies; dependencies = expression.dynamic_dependencies();
} }
const name = block.getUniqueName( const name = block.getUniqueName(
@ -23,20 +22,17 @@ export default function addActions(
); );
block.addVariable(name); block.addVariable(name);
const fn = component.imported_declarations.has(action.name) || component.hoistable_names.has(action.name)
? action.name
: `ctx.${action.name}`;
component.template_references.add(action.name); const fn = component.qualify(action.name);
block.builders.mount.addLine( block.builders.mount.addLine(
`${name} = ${fn}.call(null, ${target}${snippet ? `, ${snippet}` : ''}) || {};` `${name} = ${fn}.call(null, ${target}${snippet ? `, ${snippet}` : ''}) || {};`
); );
if (dependencies && dependencies.size > 0) { if (dependencies && dependencies.length > 0) {
let conditional = `typeof ${name}.update === 'function' && `; let conditional = `typeof ${name}.update === 'function' && `;
const deps = [...dependencies].map(dependency => `changed.${dependency}`).join(' || '); const deps = dependencies.map(dependency => `changed.${dependency}`).join(' || ');
conditional += dependencies.size > 1 ? `(${deps})` : deps; conditional += dependencies.length > 1 ? `(${deps})` : deps;
block.builders.update.addConditional( block.builders.update.addConditional(
conditional, conditional,

@ -24,17 +24,18 @@ export default function ssr(
let user_code; let user_code;
// TODO remove this, just use component.symbols everywhere
const props = component.vars.filter(variable => !variable.module && variable.export_name);
if (component.javascript) { if (component.javascript) {
component.rewrite_props(); component.rewrite_props();
user_code = component.javascript; user_code = component.javascript;
} else if (component.ast.js.length === 0 && component.props.length > 0) { } else if (!component.ast.instance && !component.ast.module && props.length > 0) {
const props = component.props.map(prop => prop.as).filter(name => name[0] !== '$'); user_code = `let { ${props.map(prop => prop.export_name).join(', ')} } = $$props;`
user_code = `let { ${props.join(', ')} } = $$props;`
} }
const reactive_stores = Array.from(component.template_references).filter(n => n[0] === '$'); const reactive_stores = component.vars.filter(variable => variable.name[0] === '$');
const reactive_store_values = reactive_stores.map(name => { const reactive_store_values = reactive_stores.map(({ name }) => {
const assignment = `const ${name} = @get_store_value(${name.slice(1)});`; const assignment = `const ${name} = @get_store_value(${name.slice(1)});`;
return component.options.dev return component.options.dev
@ -44,8 +45,8 @@ export default function ssr(
// TODO only do this for props with a default value // TODO only do this for props with a default value
const parent_bindings = component.javascript const parent_bindings = component.javascript
? component.props.map(prop => { ? props.map(prop => {
return `if ($$props.${prop.as} === void 0 && $$bindings.${prop.as} && ${prop.name} !== void 0) $$bindings.${prop.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});`;
}) })
: []; : [];

@ -21,7 +21,8 @@ export interface Parser {
export interface Ast { export interface Ast {
html: Node; html: Node;
css: Node; css: Node;
js: Node; instance: Node;
module: Node;
} }
export interface Warning { export interface Warning {
@ -77,3 +78,21 @@ export interface AppendTarget {
slots: Record<string, string>; slots: Record<string, string>;
slotStack: string[] slotStack: string[]
} }
export interface Var {
name: string;
export_name?: string; // the `bar` in `export { foo as bar }`
injected?: boolean;
module?: boolean;
mutated?: boolean;
reassigned?: boolean;
referenced?: boolean;
writable?: boolean;
// used internally, but not exposed
global?: boolean;
implicit?: boolean; // logic-less template references
internal?: boolean; // event handlers, bindings
initialised?: boolean;
hoistable?: boolean;
}

@ -3,13 +3,13 @@ import dynamicImport from 'acorn-dynamic-import';
const Parser = acorn.Parser.extend(dynamicImport); const Parser = acorn.Parser.extend(dynamicImport);
export const parse = (source: string, options: any) => Parser.parse(source, { export const parse = (source: string) => Parser.parse(source, {
sourceType: 'module', sourceType: 'module',
ecmaVersion: 9, ecmaVersion: 9,
preserveParens: true preserveParens: true
}); });
export const parseExpressionAt = (source: string, index: number, options: any) => Parser.parseExpressionAt(source, index, { export const parseExpressionAt = (source: string, index: number) => Parser.parseExpressionAt(source, index, {
ecmaVersion: 9, ecmaVersion: 9,
preserveParens: true preserveParens: true
}); });

@ -226,9 +226,27 @@ export default function parse(
}, parser.css[1].start); }, parser.css[1].start);
} }
const instance_scripts = parser.js.filter(script => script.context === 'default');
const module_scripts = parser.js.filter(script => script.context === 'module');
if (instance_scripts.length > 1) {
parser.error({
code: `invalid-script`,
message: `A component can only have one instance-level <script> element`
}, instance_scripts[1].start);
}
if (module_scripts.length > 1) {
parser.error({
code: `invalid-script`,
message: `A component can only have one <script context="module"> element`
}, module_scripts[1].start);
}
return { return {
html: parser.html, html: parser.html,
css: parser.css, css: parser.css[0],
js: parser.js, instance: instance_scripts[0],
module: module_scripts[0]
}; };
} }

@ -5,6 +5,29 @@ import { Node } from '../../interfaces';
const scriptClosingTag = '</script>'; const scriptClosingTag = '</script>';
function get_context(parser: Parser, attributes: Node[], start: number) {
const context = attributes.find(attribute => attribute.name === 'context');
if (!context) return 'default';
if (context.value.length !== 1 || context.value[0].type !== 'Text') {
parser.error({
code: 'invalid-script',
message: `context attribute must be static`
}, start);
}
const value = context.value[0].data;
if (value !== 'module') {
parser.error({
code: `invalid-script`,
message: `If the context attribute is supplied, its value must be "module"`
}, context.start);
}
return value;
}
export default function readScript(parser: Parser, start: number, attributes: Node[]) { export default function readScript(parser: Parser, start: number, attributes: Node[]) {
const scriptStart = parser.index; const scriptStart = parser.index;
const scriptEnd = parser.template.indexOf(scriptClosingTag, scriptStart); const scriptEnd = parser.template.indexOf(scriptClosingTag, scriptStart);
@ -30,7 +53,7 @@ export default function readScript(parser: Parser, start: number, attributes: No
return { return {
start, start,
end: parser.index, end: parser.index,
attributes, context: get_context(parser, attributes, start),
content: ast, content: ast,
}; };
} }

@ -52,6 +52,10 @@ export function createScopes(expression: Node) {
}, },
}); });
scope.declarations.forEach((node, name) => {
globals.delete(name);
});
return { map, scope, globals }; return { map, scope, globals };
} }
@ -60,7 +64,6 @@ export class Scope {
block: boolean; block: boolean;
declarations: Map<string, Node> = new Map(); declarations: Map<string, Node> = new Map();
writable_declarations: Set<string> = new Set();
initialised_declarations: Set<string> = new Set(); initialised_declarations: Set<string> = new Set();
constructor(parent: Scope, block: boolean) { constructor(parent: Scope, block: boolean) {
@ -72,13 +75,11 @@ export class Scope {
if (node.kind === 'var' && this.block && this.parent) { if (node.kind === 'var' && this.block && this.parent) {
this.parent.addDeclaration(node); this.parent.addDeclaration(node);
} else if (node.type === 'VariableDeclaration') { } else if (node.type === 'VariableDeclaration') {
const writable = node.kind !== 'const';
const initialised = !!node.init; const initialised = !!node.init;
node.declarations.forEach((declarator: Node) => { node.declarations.forEach((declarator: Node) => {
extractNames(declarator.id).forEach(name => { extractNames(declarator.id).forEach(name => {
this.declarations.set(name, node); this.declarations.set(name, node);
if (writable) this.writable_declarations.add(name);
if (initialised) this.initialised_declarations.add(name); if (initialised) this.initialised_declarations.add(name);
}); });
}); });

@ -31,7 +31,8 @@ describe('parse', () => {
assert.deepEqual(ast.html, expectedOutput.html); assert.deepEqual(ast.html, expectedOutput.html);
assert.deepEqual(ast.css, expectedOutput.css); assert.deepEqual(ast.css, expectedOutput.css);
assert.deepEqual(ast.js, expectedOutput.js); assert.deepEqual(ast.instance, expectedOutput.instance);
assert.deepEqual(ast.module, expectedOutput.module);
} catch (err) { } catch (err) {
if (err.name !== 'ParseError') throw err; if (err.name !== 'ParseError') throw err;
if (!expectedError) throw err; if (!expectedError) throw err;

@ -42,6 +42,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -28,6 +28,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -29,6 +29,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -23,6 +23,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -55,6 +55,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -36,6 +36,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -34,6 +34,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -34,6 +34,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -58,6 +58,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -29,6 +29,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -43,6 +43,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -34,6 +34,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -22,6 +22,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -29,6 +29,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -29,6 +29,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -155,6 +155,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -28,6 +28,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -28,6 +28,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -12,6 +12,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -37,6 +37,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -21,6 +21,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -12,6 +12,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -27,8 +27,7 @@
} }
] ]
}, },
"css": [ "css": {
{
"start": 16, "start": 16,
"end": 56, "end": 56,
"attributes": [], "attributes": [],
@ -91,7 +90,7 @@
"end": 48, "end": 48,
"styles": "\n\tdiv {\n\t\tcolor: red;\n\t}\n" "styles": "\n\tdiv {\n\t\tcolor: red;\n\t}\n"
} }
} },
], "instance": null,
"js": [] "module": null
} }

@ -5,12 +5,11 @@
"type": "Fragment", "type": "Fragment",
"children": [] "children": []
}, },
"css": [], "css": null,
"js": [ "instance": {
{
"start": 0, "start": 0,
"end": 146, "end": 146,
"attributes": [], "context": "default",
"content": { "content": {
"type": "Program", "type": "Program",
"start": 8, "start": 8,
@ -201,5 +200,4 @@
"sourceType": "module" "sourceType": "module"
} }
} }
]
} }

@ -75,6 +75,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -67,6 +67,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -63,6 +63,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -63,6 +63,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -45,6 +45,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -38,6 +38,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -21,6 +21,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -22,6 +22,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -98,6 +98,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -56,6 +56,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -96,6 +96,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -25,6 +25,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -66,6 +66,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -21,6 +21,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -55,6 +55,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -28,6 +28,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -20,12 +20,10 @@
} }
] ]
}, },
"css": [], "instance": {
"js": [
{
"start": 0, "start": 0,
"end": 43, "end": 43,
"attributes": [], "context": "default",
"content": { "content": {
"type": "Program", "type": "Program",
"start": 8, "start": 8,
@ -34,5 +32,4 @@
"sourceType": "module" "sourceType": "module"
} }
} }
]
} }

@ -44,12 +44,10 @@
} }
] ]
}, },
"css": [], "instance": {
"js": [
{
"start": 0, "start": 0,
"end": 77, "end": 77,
"attributes": [], "context": "default",
"content": { "content": {
"type": "Program", "type": "Program",
"start": 8, "start": 8,
@ -85,5 +83,4 @@
"sourceType": "module" "sourceType": "module"
} }
} }
]
} }

@ -44,12 +44,10 @@
} }
] ]
}, },
"css": [], "instance": {
"js": [
{
"start": 0, "start": 0,
"end": 66, "end": 66,
"attributes": [], "context": "default",
"content": { "content": {
"type": "Program", "type": "Program",
"start": 8, "start": 8,
@ -85,5 +83,4 @@
"sourceType": "module" "sourceType": "module"
} }
} }
]
} }

@ -44,12 +44,10 @@
} }
] ]
}, },
"css": [], "instance": {
"js": [
{
"start": 0, "start": 0,
"end": 39, "end": 39,
"attributes": [], "context": "default",
"content": { "content": {
"type": "Program", "type": "Program",
"start": 8, "start": 8,
@ -85,5 +83,4 @@
"sourceType": "module" "sourceType": "module"
} }
} }
]
} }

@ -14,6 +14,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -73,6 +73,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -72,6 +72,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -26,6 +26,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -44,6 +44,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -32,6 +32,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -60,6 +60,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -45,6 +45,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -27,6 +27,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -62,6 +62,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -17,6 +17,7 @@
} }
] ]
}, },
"css": [], "css": null,
"js": [] "instance": null,
"module": null
} }

@ -0,0 +1,5 @@
export default {
test(assert, stats) {
assert.deepEqual(stats.vars, []);
},
};

@ -0,0 +1,5 @@
export default {
test(assert, stats) {
assert.deepEqual(stats.vars, []);
},
};

@ -1,17 +1,35 @@
export default { export default {
test(assert, stats) { test(assert, stats) {
assert.deepEqual(stats.imports, [ assert.deepEqual(stats.vars, [
{ {
source: 'x', name: 'x',
specifiers: [{ name: 'default', as: 'x' }] export_name: null,
injected: false,
module: false,
mutated: false,
reassigned: false,
referenced: false,
writable: false
}, },
{ {
source: 'y', name: 'y',
specifiers: [{ name: 'y', as: 'y' }] export_name: null,
injected: false,
module: false,
mutated: false,
reassigned: false,
referenced: false,
writable: false
}, },
{ {
source: 'z', name: 'z',
specifiers: [{ name: '*', as: 'z' }] export_name: null,
injected: false,
module: false,
mutated: false,
reassigned: false,
referenced: false,
writable: false
} }
]); ]);
} }

@ -0,0 +1,26 @@
export default {
test(assert, stats) {
assert.deepEqual(stats.vars, [
{
name: 'count',
export_name: null,
injected: false,
module: false,
mutated: false,
reassigned: true,
referenced: true,
writable: true
},
{
name: 'user',
export_name: null,
injected: false,
module: false,
mutated: true,
reassigned: false,
referenced: true,
writable: false
}
]);
}
};

@ -0,0 +1,7 @@
<script>
let count;
const user = { name: 'world' };
</script>
<input bind:value={user.name}>
<input type=number bind:value={count}>

@ -0,0 +1,26 @@
export default {
test(assert, stats) {
assert.deepEqual(stats.vars, [
{
name: 'count',
export_name: null,
injected: false,
module: false,
mutated: false,
reassigned: true,
referenced: true,
writable: true
},
{
name: 'user',
export_name: null,
injected: false,
module: false,
mutated: true,
reassigned: false,
referenced: true,
writable: false
}
]);
}
};

@ -0,0 +1,7 @@
<script>
let count;
const user = { name: 'world' };
</script>
<input on:input="{e => user.name = e.value}">
<input type=number on:input="{e => count = +e.value}">

@ -1,5 +1,46 @@
export default { export default {
test(assert, stats) { test(assert, stats) {
assert.deepEqual(stats.props.sort(), ['cats', 'name']); assert.deepEqual(stats.vars, [
{
name: 'name',
export_name: 'name',
injected: false,
module: false,
mutated: false,
reassigned: false,
referenced: true,
writable: true
},
{
name: 'cats',
export_name: 'cats',
injected: false,
module: false,
mutated: false,
reassigned: false,
referenced: true,
writable: true
},
{
name: 'foo',
export_name: null,
injected: false,
module: false,
mutated: false,
reassigned: false,
referenced: false,
writable: true
},
{
name: 'bar',
export_name: null,
injected: false,
module: false,
mutated: false,
reassigned: true,
referenced: true,
writable: true
}
]);
} }
}; };

@ -0,0 +1,26 @@
export default {
test(assert, stats) {
assert.deepEqual(stats.vars, [
{
name: 'foo',
export_name: null,
injected: false,
module: false,
mutated: false,
reassigned: false,
referenced: true,
writable: true
},
{
name: '$foo',
export_name: null,
injected: true,
module: false,
mutated: true,
reassigned: false,
referenced: true,
writable: true
}
]);
}
};

@ -0,0 +1,5 @@
<script>
let foo;
</script>
{$foo}

@ -0,0 +1,26 @@
export default {
test(assert, stats) {
assert.deepEqual(stats.vars, [
{
name: 'foo',
export_name: null,
injected: false,
module: false,
mutated: false,
reassigned: false,
referenced: true,
writable: true
},
{
name: '$foo',
export_name: null,
injected: true,
module: false,
mutated: true,
reassigned: false,
referenced: false,
writable: true
}
]);
}
};

@ -0,0 +1,4 @@
<script>
let foo;
console.log($foo);
</script>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save