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 {
props: component.props.map(prop => prop.as),
timings,
warnings: this.warnings,
imports,
templateReferences: component && component.template_references
vars: component.vars.filter(variable => !variable.global && !variable.implicit && !variable.internal).map(variable => ({
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 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';
import addToSet from '../utils/addToSet';
import isReference from 'is-reference';
import TemplateScope from './nodes/shared/TemplateScope';
import fuzzymatch from '../utils/fuzzymatch';
@ -56,31 +55,22 @@ export default class Component {
namespace: string;
tag: string;
instance_script: Node;
module_script: Node;
vars: Var[] = [];
var_lookup: Map<string, Var> = new Map();
imports: Node[] = [];
module_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();
node_for_declaration: Map<string, Node> = new Map();
module_exports: Array<{ name: string, as: string }> = [];
partly_hoisted: string[] = [];
fully_hoisted: string[] = [];
reactive_declarations: Array<{ assignees: Set<string>, dependencies: Set<string>, snippet: string }> = [];
reactive_declaration_nodes: Set<Node> = new Set();
has_reactive_assignments = false;
mutable_props: Set<string> = new Set();
indirectDependencies: Map<string, Set<string>> = new Map();
template_references: Set<string> = new Set();
file: string;
locate: (c: number) => { line: number, column: number };
@ -93,7 +83,6 @@ export default class Component {
stylesheet: Stylesheet;
userVars: Set<string> = new Set();
aliases: Map<string, string> = new Map();
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.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.namespace = namespaces[this.meta.namespace] || this.meta.namespace;
@ -169,22 +138,43 @@ export default class Component {
if (!options.customElement) this.stylesheet.reify();
this.stylesheet.warnOnUnusedSelectors(stats);
}
add_var(variable: Var) {
// TODO remove this
if (this.var_lookup.has(variable.name)) {
throw new Error(`dupe: ${variable.name}`);
}
this.vars.push(variable);
this.var_lookup.set(variable.name, variable);
}
if (!this.instance_script) {
const props = [...this.template_references];
this.declarations.push(...props);
addToSet(this.mutable_props, this.template_references);
addToSet(this.writable_declarations, this.template_references);
addToSet(this.userVars, this.template_references);
add_reference(name: string) {
const variable = this.var_lookup.get(name);
this.props = props.map(name => ({
if (variable) {
variable.referenced = true;
} else if (name[0] === '$') {
this.add_var({
name,
as: name
}));
}
injected: true,
referenced: true,
mutated: true,
writable: true
});
// tell the root fragment scope about all of the mutable names we know from the script
this.mutable_props.forEach(name => this.fragment.scope.mutables.add(name));
this.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) {
@ -243,7 +233,10 @@ export default class Component {
options.sveltePath,
importedHelpers,
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
);
@ -313,7 +306,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++}`
);
@ -329,7 +322,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}$`;
@ -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;
content.body.forEach(node => {
@ -412,44 +432,31 @@ 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);
const variable = this.var_lookup.get(name);
variable.export_name = name;
});
});
} else {
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);
} 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.export_name = 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);
});
}
});
}
@ -491,7 +498,7 @@ export default class Component {
}
walk_module_js() {
const script = this.module_script;
const script = this.ast.module;
if (!script) return;
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`
});
}
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);
this.module_javascript = this.extract_javascript(script);
}
walk_instance_js_pre_template() {
const script = this.instance_script;
const script = this.ast.instance;
if (!script) return;
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`
});
}
});
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,
initialised: instance_scope.initialised_declarations.has(name),
writable: kind === 'var' || kind === 'let'
});
}
this.node_for_declaration.set(name, node);
});
this.writable_declarations = instance_scope.writable_declarations;
this.initialised_declarations = instance_scope.initialised_declarations;
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.extract_imports_and_exports(script.content, this.imports, this.props);
}
walk_instance_js_post_template() {
const script = this.instance_script;
const script = this.ast.instance;
if (!script) return;
this.hoist_instance_declarations();
@ -563,28 +618,43 @@ export default class Component {
// TODO merge this with other walks that are independent
track_mutations() {
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) {
let names;
if (map.has(node)) {
scope = map.get(node);
}
let names;
let deep = false;
if (node.type === 'AssignmentExpression') {
names = node.left.type === 'MemberExpression'
? [getObject(node.left).name]
: extractNames(node.left);
deep = node.left.type === 'MemberExpression';
names = deep
? [getObject(node.left).name]
: extractNames(node.left);
} else if (node.type === 'UpdateExpression') {
names = [getObject(node.argument).name];
}
if (names) {
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;
let { instance_scope: scope, instance_scope_map: map } = this;
walk(this.instance_script.content, {
walk(this.ast.instance.content, {
enter(node, parent) {
if (map.has(node)) {
scope = map.get(node);
@ -607,9 +677,6 @@ export default class Component {
if (name[0] === '$' && !scope.has(name)) {
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;
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 = [];
let current_group;
walk(this.instance_script.content, {
walk(this.ast.instance.content, {
enter(node, parent) {
if (/Function/.test(node.type)) {
current_group = null;
@ -650,14 +710,15 @@ export default class Component {
if (node.type === 'VariableDeclaration') {
if (node.kind === 'var' || scope === instance_scope) {
let has_meta_props = false;
let has_exports = false;
let has_only_exports = true;
node.declarations.forEach(declarator => {
extractNames(declarator.id).forEach(name => {
const variable = component.var_lookup.get(name);
if (name === meta.props_object) {
if (exported.has(name)) {
if (variable.export_name) {
component.error(declarator, {
code: 'exported-meta-props',
message: `Cannot export props binding`
@ -676,11 +737,9 @@ export default class Component {
} else {
code.overwrite(declarator.id.end, declarator.end, ' = $$props');
}
has_meta_props = true;
}
if (exported.has(name)) {
if (variable.export_name) {
has_exports = true;
} else {
has_only_exports = false;
@ -724,7 +783,6 @@ export default class Component {
});
coalesced_declarations.forEach(group => {
const kind = group[0].kind;
let c = 0;
let combining = false;
@ -764,16 +822,16 @@ export default class Component {
// reference instance variables other than other
// hoistable functions. TODO others?
const { hoistable_names, hoistable_nodes, imported_declarations, instance_scope: scope } = this;
const template_scope = this.fragment.scope;
const { hoistable_nodes, var_lookup } = this;
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.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 => {
hoistable_names.add(d.id.name);
const variable = this.var_lookup.get(d.id.name);
variable.hoistable = true;
});
hoistable_nodes.add(node);
@ -823,8 +881,9 @@ export default class Component {
else if (owner === instance_scope) {
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)) {
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) {
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);
remove_indentation(this.code, node);
@ -875,7 +935,7 @@ export default class Component {
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 === '$') {
this.reactive_declaration_nodes.add(node);
@ -899,7 +959,7 @@ export default class Component {
const object = getObject(node);
const { name } = object;
if (name[0] === '$' || component.declarations.indexOf(name) !== -1) {
if (name[0] === '$' || component.var_lookup.has(name)) {
dependencies.add(name);
}
@ -982,11 +1042,12 @@ export default class Component {
}
qualify(name) {
if (this.hoistable_names.has(name)) return name;
if (this.imported_declarations.has(name)) return name;
if (this.declarations.indexOf(name) === -1) return name;
const variable = this.var_lookup.get(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}`;
}
@ -998,7 +1059,7 @@ export default class Component {
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.module_scope && this.module_scope.declarations.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`
});
}
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) {

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

@ -34,7 +34,7 @@ export default class Attribute extends Node {
this.isSynthetic = false;
this.expression = new Expression(component, this, scope, info.expression);
this.dependencies = this.expression.dynamic_dependencies;
this.dependencies = this.expression.dependencies;
this.chunks = null;
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);
addToSet(this.dependencies, expression.dynamic_dependencies);
addToSet(this.dependencies, expression.dependencies);
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() {
if (this.isTrue) return true;
if (this.chunks.length === 0) return `""`;

@ -2,6 +2,7 @@ import Node from './shared/Node';
import getObject from '../../utils/getObject';
import Expression from './shared/Expression';
import Component from '../Component';
import TemplateScope from './shared/TemplateScope';
export default class Binding extends Node {
name: string;
@ -10,7 +11,7 @@ export default class Binding extends Node {
obj: string;
prop: string;
constructor(component: Component, parent, scope, info) {
constructor(component: Component, parent, scope: TemplateScope, info) {
super(component, parent, scope, info);
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);
// 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') {
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) {
super(component, parent, scope, info);
this.name = info.name;
this.scope = scope;
const parentElement = parent.findNearest(/^Element/);
this.namespace = this.name === 'svg' ?
@ -196,7 +195,7 @@ export default class Element extends Node {
const dependencies = new Set([l.name]);
l.names.forEach(name => {
this.scope.add(name, dependencies);
this.scope.add(name, dependencies, this);
});
});
} else {

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

@ -62,6 +62,7 @@ const precedence: Record<string, (node?: Node) => number> = {
};
export default class Expression {
type = 'Expression';
component: Component;
owner: Wrapper;
node: any;
@ -69,7 +70,6 @@ export default class Expression {
references: Set<string>;
dependencies: Set<string> = new Set();
contextual_dependencies: Set<string> = new Set();
dynamic_dependencies: Set<string> = new Set();
template_scope: TemplateScope;
scope: Scope;
@ -95,7 +95,7 @@ export default class Expression {
this.owner = owner;
this.is_synthetic = owner.isSynthetic;
const { dependencies, contextual_dependencies, dynamic_dependencies } = this;
const { dependencies, contextual_dependencies } = this;
let { map, scope } = createScopes(info);
this.scope = scope;
@ -104,27 +104,6 @@ export default class Expression {
const expression = this;
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
walk(info, {
enter(node: any, parent: any, key: string) {
@ -143,18 +122,26 @@ export default class Expression {
const { name, nodes } = flattenReference(node);
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;
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 {
add_dependency(name, nodes.length > 1);
component.template_references.add(name);
if (!function_expression) {
dependencies.add(name);
}
component.add_reference(name);
component.warn_if_undefined(nodes[0], template_scope, true);
}
@ -162,17 +149,36 @@ export default class Expression {
}
// track any assignments from template expressions as mutable
let names;
let deep = false;
if (function_expression) {
if (node.type === 'AssignmentExpression') {
const names = node.left.type === 'MemberExpression'
deep = node.left.type === 'MemberExpression';
names = deep
? [getObject(node.left).name]
: extractNames(node.left);
names.forEach(name => template_scope.setMutable(name));
} else if (node.type === 'UpdateExpression') {
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;
}
});
}
},
leave(node) {
@ -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() {
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);
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 (template_scope.names.has(name)) {
@ -241,7 +259,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
@ -276,7 +294,9 @@ export default class Expression {
} else {
names.forEach(name => {
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);
});
@ -285,7 +305,9 @@ export default class Expression {
const { name } = getObject(node.argument);
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);
}
@ -359,23 +381,38 @@ export default class Expression {
// we can hoist this out of the component completely
component.fully_hoisted.push(fn);
code.overwrite(node.start, node.end, name);
component.add_var({
name,
internal: true,
hoistable: true,
referenced: true
});
}
else if (contextual_dependencies.size === 0) {
// function can be hoisted inside the component init
component.partly_hoisted.push(fn);
component.declarations.push(name);
component.template_references.add(name);
code.overwrite(node.start, node.end, `ctx.${name}`);
component.add_var({
name,
internal: true,
referenced: true
});
}
else {
// we need a combo block/init recipe
component.partly_hoisted.push(fn);
component.declarations.push(name);
component.template_references.add(name);
code.overwrite(node.start, node.end, name);
component.add_var({
name,
internal: true,
referenced: true
});
declarations.push(deindent`
function ${name}(${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 (!scope.isTopLevel(name)) return true;
const variable = component.var_lookup.get(name);
// hoistables, module declarations, and imports are non-contextual
if (component.hoistable_names.has(name)) return false;
if (component.module_scope && component.module_scope.declarations.has(name)) return false;
if (component.imported_declarations.has(name)) return false;
if (!variable || variable.hoistable) return false;
// assume contextual
return true;

@ -9,7 +9,6 @@ type NodeWithScope = EachBlock | ThenBlock | CatchBlock | InlineComponent | Elem
export default class TemplateScope {
names: Set<string>;
dependenciesForName: Map<string, Set<string>>;
mutables: Set<string> = new Set();
owners: Map<string, NodeWithScope> = new Map();
parent?: TemplateScope;
@ -31,29 +30,6 @@ export default class TemplateScope {
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) {
return !this.parent || !this.names.has(name) && this.parent.isTopLevel(name);
}
@ -61,4 +37,9 @@ export default class TemplateScope {
getOwner(name: string): NodeWithScope {
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
);
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`
$$props => {
${component.meta.props && deindent`
@ -80,8 +81,8 @@ export default function dom(
@assign(${component.meta.props}, $$props);
$$invalidate('${component.meta.props_object}', ${component.meta.props_object});
`}
${props.map(prop =>
`if ('${prop.as}' in $$props) $$invalidate('${prop.name}', ${prop.name} = $$props.${prop.as});`)}
${writable_props.map(prop =>
`if ('${prop.export_name}' in $$props) $$invalidate('${prop.name}', ${prop.name} = $$props.${prop.export_name});`)}
${renderer.slots.size > 0 &&
`if ('$$scope' in $$props) $$invalidate('$$scope', $$scope = $$props.$$scope);`}
}
@ -93,32 +94,34 @@ export default function dom(
const not_equal = component.meta.immutable ? `@not_equal` : `@safe_not_equal`;
let dev_props_check;
component.props.forEach(x => {
if (component.imported_declarations.has(x.name) || component.hoistable_names.has(x.name)) {
props.forEach(x => {
const variable = component.var_lookup.get(x.name);
if (variable.hoistable) {
body.push(deindent`
get ${x.as}() {
get ${x.export_name}() {
return ${x.name};
}
`);
} else {
body.push(deindent`
get ${x.as}() {
get ${x.export_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`
set ${x.as}(${x.name}) {
set ${x.export_name}(${x.name}) {
this.$set({ ${x.name} });
@flush();
}
`);
} else if (component.options.dev) {
body.push(deindent`
set ${x.as}(value) {
throw new Error("<${component.tag}>: Cannot set read-only property '${x.as}'");
set ${x.export_name}(value) {
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) {
// TODO check no uunexpected props were passed, as well as
// checking that expected ones were passed
const expected = component.props
.map(x => x.name)
.filter(name => !component.initialised_declarations.has(name));
const expected = props.filter(prop => !prop.initialised);
if (expected.length) {
dev_props_check = deindent`
const { ctx } = this.$$;
const props = ${options.customElement ? `this.attributes` : `options.props || {}`};
${expected.map(name => deindent`
if (ctx.${name} === undefined && !('${name}' in props)) {
console.warn("<${component.tag}> was created without expected prop '${name}'");
${expected.map(prop => deindent`
if (ctx.${prop.name} === undefined && !('${prop.export_name}' in props)) {
console.warn("<${component.tag}> was created without expected prop '${prop.export_name}'");
}`)}
`;
}
}
// instrument assignments
if (component.instance_script) {
if (component.ast.instance) {
let scope = component.instance_scope;
let map = component.instance_scope_map;
let pending_assignments = new Set();
walk(component.instance_script.content, {
walk(component.ast.instance.content, {
enter: (node, parent) => {
if (map.has(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('; '));
} else {
names.forEach(name => {
if (component.imported_declarations.has(name)) 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);
component.has_reactive_assignments = true;
});
@ -189,9 +192,11 @@ export default function dom(
else if (node.type === 'UpdateExpression') {
const { name } = getObject(node.argument);
if (component.imported_declarations.has(name)) 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);
component.has_reactive_assignments = true;
}
@ -238,7 +243,7 @@ export default function dom(
}
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');
}
@ -252,22 +257,19 @@ export default function dom(
${component.fully_hoisted.length > 0 && component.fully_hoisted.join('\n\n')}
`);
const filtered_declarations = component.declarations.filter(name => {
if (component.hoistable_names.has(name)) return false;
if (component.imported_declarations.has(name)) return false;
if (component.props.find(p => p.as === name)) return true;
return component.template_references.has(name);
});
const filtered_declarations = component.vars.filter(variable => {
return (variable.referenced || variable.export_name) && !variable.hoistable;
}).map(variable => variable.name);
const filtered_props = props.filter(prop => {
const variable = component.var_lookup.get(prop.name);
const filtered_props = component.props.filter(prop => {
if (component.hoistable_names.has(prop.name)) return false;
if (component.imported_declarations.has(prop.name)) return false;
if (variable.hoistable) return false;
if (prop.name[0] === '$') return false;
return true;
});
const reactive_stores = Array.from(component.template_references).filter(n => n[0] === '$');
filtered_declarations.push(...reactive_stores);
const reactive_stores = component.vars.filter(variable => variable.name[0] === '$');
if (renderer.slots.size > 0) {
const arr = Array.from(renderer.slots);
@ -283,7 +285,6 @@ export default function dom(
filtered_props.length > 0 ||
component.partly_hoisted.length > 0 ||
filtered_declarations.length > 0 ||
reactive_stores.length > 0 ||
component.reactive_declarations.length > 0
);
@ -297,13 +298,13 @@ export default function dom(
});
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;`
: null
);
const reactive_store_subscriptions = reactive_stores.length > 0 && reactive_stores
.map(name => deindent`
.map(({ name }) => deindent`
let ${name};
${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}); }));
@ -354,7 +355,7 @@ export default function dom(
@insert(options.target, this, options.anchor);
}
${(component.props.length > 0 || component.meta.props) && deindent`
${(props.length > 0 || component.meta.props) && deindent`
if (options.props) {
this.$set(options.props);
@flush();
@ -363,7 +364,7 @@ export default function dom(
}
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')}

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

@ -74,8 +74,8 @@ export default class EachBlockWrapper extends Wrapper {
super(renderer, block, parent, node);
this.cannotUseInnerHTML();
const { dynamic_dependencies } = node.expression;
block.addDependencies(dynamic_dependencies);
const { dependencies } = node.expression;
block.addDependencies(dependencies);
this.block = block.child({
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?)
if (this.node.parent.scope.containsMutable(this.node.dependencies) || isSelectValueAttribute) {
const dependencies = Array.from(this.node.dependencies);
const dependencies = this.node.get_dependencies();
if (dependencies.length > 0 || isSelectValueAttribute) {
const changedCheck = (
(block.hasOutros ? `!#current || ` : '') +
dependencies.map(dependency => `changed.${dependency}`).join(' || ')

@ -37,14 +37,14 @@ export default class BindingWrapper {
this.node = node;
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'>`?
if (parent.node.name === 'select') {
parent.selectBindingDependencies = dynamic_dependencies;
dynamic_dependencies.forEach((prop: string) => {
parent.selectBindingDependencies = dependencies;
dependencies.forEach((prop: string) => {
parent.renderer.component.indirectDependencies.set(prop, new Set());
});
}
@ -106,7 +106,7 @@ export default class BindingWrapper {
let updateConditions: string[] = this.needsLock ? [`!${lock}`] : [];
const dependencyArray = [...this.node.expression.dynamic_dependencies]
const dependencyArray = [...this.node.expression.dependencies]
if (dependencyArray.length === 1) {
updateConditions.push(`changed.${dependencyArray[0]}`)

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

@ -132,7 +132,7 @@ export default class ElementWrapper extends Wrapper {
const name = attribute.getStaticValue();
if (!(owner as InlineComponentWrapper).slots.has(name)) {
const child_block = block.child({
const child_block = block.parent.child({
comment: createDebuggingComment(node, this.renderer.component),
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, {
block: child_block,
scope: this.node.scope,
fn
});
this.renderer.blocks.push(child_block);
@ -173,13 +174,13 @@ export default class ElementWrapper extends Wrapper {
// add directive and handler dependencies
[node.animation, node.outro, ...node.actions, ...node.classes].forEach(directive => {
if (directive && directive.expression) {
block.addDependencies(directive.expression.dynamic_dependencies);
block.addDependencies(directive.expression.dependencies);
}
});
node.handlers.forEach(handler => {
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 => {
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
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');
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_var({
name,
internal: true,
referenced: true
});
const { handler, object } = this_binding;

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

@ -15,10 +15,11 @@ import createDebuggingComment from '../../../../utils/createDebuggingComment';
import sanitize from '../../../../utils/sanitize';
import { get_context_merger } from '../shared/get_context_merger';
import EachBlock from '../../../nodes/EachBlock';
import TemplateScope from '../../../nodes/shared/TemplateScope';
export default class InlineComponentWrapper extends Wrapper {
var: string;
slots: Map<string, { block: Block, fn?: string }> = new Map();
slots: Map<string, { block: Block, scope: TemplateScope, fn?: string }> = new Map();
node: InlineComponent;
fragment: FragmentWrapper;
@ -35,7 +36,7 @@ export default class InlineComponentWrapper extends Wrapper {
this.cannotUseInnerHTML();
if (this.node.expression) {
block.addDependencies(this.node.expression.dynamic_dependencies);
block.addDependencies(this.node.expression.dependencies);
}
this.node.attributes.forEach(attr => {
@ -52,12 +53,12 @@ export default class InlineComponentWrapper extends Wrapper {
(eachBlock as EachBlock).has_binding = true;
}
block.addDependencies(binding.expression.dynamic_dependencies);
block.addDependencies(binding.expression.dependencies);
});
this.node.handlers.forEach(handler => {
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', {
block: default_slot,
scope: this.node.scope,
fn
});
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();
this.slots.forEach(slot => {
slot.block.dependencies.forEach(name => {
if (renderer.component.mutable_props.has(name)) {
fragment_dependencies.add(name);
}
const is_let = slot.scope.is_let(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') {
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 object;
@ -264,8 +275,12 @@ 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_var({
name,
internal: true,
referenced: true
});
const updating = block.getUniqueName(`updating_${binding.name}`);
block.addVariable(updating);
@ -279,7 +294,7 @@ export default class InlineComponentWrapper extends Wrapper {
);
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};
}
`);

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

@ -118,16 +118,18 @@ export default class WindowWrapper extends Wrapper {
`);
}
component.declarations.push(handler_name);
component.template_references.add(handler_name);
component.add_var({
name: handler_name,
internal: true,
referenced: true
});
component.partly_hoisted.push(deindent`
function ${handler_name}() {
${props.map(prop => `${prop.name} = window.${prop.value}; $$invalidate('${prop.name}', ${prop.name});`)}
}
`);
block.builders.init.addBlock(deindent`
@add_render_callback(ctx.${handler_name});
`);

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

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

@ -24,17 +24,18 @@ export default function ssr(
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) {
component.rewrite_props();
user_code = component.javascript;
} else if (component.ast.js.length === 0 && component.props.length > 0) {
const props = component.props.map(prop => prop.as).filter(name => name[0] !== '$');
user_code = `let { ${props.join(', ')} } = $$props;`
} else if (!component.ast.instance && !component.ast.module && props.length > 0) {
user_code = `let { ${props.map(prop => prop.export_name).join(', ')} } = $$props;`
}
const reactive_stores = Array.from(component.template_references).filter(n => n[0] === '$');
const reactive_store_values = reactive_stores.map(name => {
const reactive_stores = component.vars.filter(variable => variable.name[0] === '$');
const reactive_store_values = reactive_stores.map(({ name }) => {
const assignment = `const ${name} = @get_store_value(${name.slice(1)});`;
return component.options.dev
@ -44,8 +45,8 @@ export default function ssr(
// TODO only do this for props with a default value
const parent_bindings = component.javascript
? component.props.map(prop => {
return `if ($$props.${prop.as} === void 0 && $$bindings.${prop.as} && ${prop.name} !== void 0) $$bindings.${prop.as}(${prop.name});`;
? props.map(prop => {
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 {
html: Node;
css: Node;
js: Node;
instance: Node;
module: Node;
}
export interface Warning {
@ -76,4 +77,22 @@ export interface CustomElementOptions {
export interface AppendTarget {
slots: Record<string, 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);
export const parse = (source: string, options: any) => Parser.parse(source, {
export const parse = (source: string) => Parser.parse(source, {
sourceType: 'module',
ecmaVersion: 9,
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,
preserveParens: true
});

@ -226,9 +226,27 @@ export default function parse(
}, 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 {
html: parser.html,
css: parser.css,
js: parser.js,
css: parser.css[0],
instance: instance_scripts[0],
module: module_scripts[0]
};
}

@ -5,6 +5,29 @@ import { Node } from '../../interfaces';
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[]) {
const scriptStart = parser.index;
const scriptEnd = parser.template.indexOf(scriptClosingTag, scriptStart);
@ -30,7 +53,7 @@ export default function readScript(parser: Parser, start: number, attributes: No
return {
start,
end: parser.index,
attributes,
context: get_context(parser, attributes, start),
content: ast,
};
}

@ -52,6 +52,10 @@ export function createScopes(expression: Node) {
},
});
scope.declarations.forEach((node, name) => {
globals.delete(name);
});
return { map, scope, globals };
}
@ -60,7 +64,6 @@ export class Scope {
block: boolean;
declarations: Map<string, Node> = new Map();
writable_declarations: Set<string> = new Set();
initialised_declarations: Set<string> = new Set();
constructor(parent: Scope, block: boolean) {
@ -72,13 +75,11 @@ export class Scope {
if (node.kind === 'var' && this.block && this.parent) {
this.parent.addDeclaration(node);
} else if (node.type === 'VariableDeclaration') {
const writable = node.kind !== 'const';
const initialised = !!node.init;
node.declarations.forEach((declarator: Node) => {
extractNames(declarator.id).forEach(name => {
this.declarations.set(name, node);
if (writable) this.writable_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.css, expectedOutput.css);
assert.deepEqual(ast.js, expectedOutput.js);
assert.deepEqual(ast.instance, expectedOutput.instance);
assert.deepEqual(ast.module, expectedOutput.module);
} catch (err) {
if (err.name !== 'ParseError') throw err;
if (!expectedError) throw err;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -27,71 +27,70 @@
}
]
},
"css": [
{
"start": 16,
"end": 56,
"attributes": [],
"children": [
{
"type": "Rule",
"selector": {
"type": "SelectorList",
"children": [
{
"type": "Selector",
"css": {
"start": 16,
"end": 56,
"attributes": [],
"children": [
{
"type": "Rule",
"selector": {
"type": "SelectorList",
"children": [
{
"type": "Selector",
"children": [
{
"type": "TypeSelector",
"name": "div",
"start": 25,
"end": 28
}
],
"start": 25,
"end": 28
}
],
"start": 25,
"end": 28
},
"block": {
"type": "Block",
"children": [
{
"type": "Declaration",
"important": false,
"property": "color",
"value": {
"type": "Value",
"children": [
{
"type": "TypeSelector",
"name": "div",
"start": 25,
"end": 28
"type": "Identifier",
"name": "red",
"start": 40,
"end": 43
}
],
"start": 25,
"end": 28
}
],
"start": 25,
"end": 28
},
"block": {
"type": "Block",
"children": [
{
"type": "Declaration",
"important": false,
"property": "color",
"value": {
"type": "Value",
"children": [
{
"type": "Identifier",
"name": "red",
"start": 40,
"end": 43
}
],
"start": 39,
"end": 43
},
"start": 33,
"start": 39,
"end": 43
}
],
"start": 29,
"end": 47
},
"start": 25,
},
"start": 33,
"end": 43
}
],
"start": 29,
"end": 47
}
],
"content": {
"start": 23,
"end": 48,
"styles": "\n\tdiv {\n\t\tcolor: red;\n\t}\n"
},
"start": 25,
"end": 47
}
],
"content": {
"start": 23,
"end": 48,
"styles": "\n\tdiv {\n\t\tcolor: red;\n\t}\n"
}
],
"js": []
},
"instance": null,
"module": null
}

@ -5,201 +5,199 @@
"type": "Fragment",
"children": []
},
"css": [],
"js": [
{
"start": 0,
"end": 146,
"attributes": [],
"content": {
"type": "Program",
"start": 8,
"end": 137,
"body": [
{
"type": "ImportDeclaration",
"start": 10,
"end": 43,
"specifiers": [
{
"type": "ImportSpecifier",
"css": null,
"instance": {
"start": 0,
"end": 146,
"context": "default",
"content": {
"type": "Program",
"start": 8,
"end": 137,
"body": [
{
"type": "ImportDeclaration",
"start": 10,
"end": 43,
"specifiers": [
{
"type": "ImportSpecifier",
"start": 19,
"end": 26,
"imported": {
"type": "Identifier",
"start": 19,
"end": 26,
"imported": {
"type": "Identifier",
"start": 19,
"end": 26,
"name": "onMount"
},
"local": {
"type": "Identifier",
"start": 19,
"end": 26,
"name": "onMount"
}
"name": "onMount"
},
"local": {
"type": "Identifier",
"start": 19,
"end": 26,
"name": "onMount"
}
],
"source": {
"type": "Literal",
"start": 34,
"end": 42,
"value": "svelte",
"raw": "'svelte'"
}
},
{
"type": "ExpressionStatement",
],
"source": {
"type": "Literal",
"start": 34,
"end": 42,
"value": "svelte",
"raw": "'svelte'"
}
},
{
"type": "ExpressionStatement",
"start": 46,
"end": 136,
"expression": {
"type": "CallExpression",
"start": 46,
"end": 136,
"expression": {
"type": "CallExpression",
"end": 135,
"callee": {
"type": "Identifier",
"start": 46,
"end": 135,
"callee": {
"type": "Identifier",
"start": 46,
"end": 53,
"name": "onMount"
},
"arguments": [
{
"type": "ArrowFunctionExpression",
"start": 54,
"end": 53,
"name": "onMount"
},
"arguments": [
{
"type": "ArrowFunctionExpression",
"start": 54,
"end": 134,
"id": null,
"generator": false,
"expression": false,
"async": false,
"params": [],
"body": {
"type": "BlockStatement",
"start": 60,
"end": 134,
"id": null,
"generator": false,
"expression": false,
"async": false,
"params": [],
"body": {
"type": "BlockStatement",
"start": 60,
"end": 134,
"body": [
{
"type": "ExpressionStatement",
"body": [
{
"type": "ExpressionStatement",
"start": 64,
"end": 131,
"expression": {
"type": "CallExpression",
"start": 64,
"end": 131,
"expression": {
"type": "CallExpression",
"end": 130,
"callee": {
"type": "MemberExpression",
"start": 64,
"end": 130,
"callee": {
"type": "MemberExpression",
"end": 87,
"object": {
"type": "CallExpression",
"start": 64,
"end": 87,
"object": {
"type": "CallExpression",
"end": 82,
"callee": {
"type": "Import",
"start": 64,
"end": 82,
"callee": {
"type": "Import",
"start": 64,
"end": 70
},
"arguments": [
{
"type": "Literal",
"start": 71,
"end": 81,
"value": "./foo.js",
"raw": "'./foo.js'"
}
]
},
"property": {
"type": "Identifier",
"start": 83,
"end": 87,
"name": "then"
"end": 70
},
"computed": false
"arguments": [
{
"type": "Literal",
"start": 71,
"end": 81,
"value": "./foo.js",
"raw": "'./foo.js'"
}
]
},
"property": {
"type": "Identifier",
"start": 83,
"end": 87,
"name": "then"
},
"arguments": [
{
"type": "ArrowFunctionExpression",
"start": 88,
"computed": false
},
"arguments": [
{
"type": "ArrowFunctionExpression",
"start": 88,
"end": 129,
"id": null,
"generator": false,
"expression": false,
"async": false,
"params": [
{
"type": "Identifier",
"start": 88,
"end": 91,
"name": "foo"
}
],
"body": {
"type": "BlockStatement",
"start": 95,
"end": 129,
"id": null,
"generator": false,
"expression": false,
"async": false,
"params": [
"body": [
{
"type": "Identifier",
"start": 88,
"end": 91,
"name": "foo"
}
],
"body": {
"type": "BlockStatement",
"start": 95,
"end": 129,
"body": [
{
"type": "ExpressionStatement",
"type": "ExpressionStatement",
"start": 100,
"end": 125,
"expression": {
"type": "CallExpression",
"start": 100,
"end": 125,
"expression": {
"type": "CallExpression",
"end": 124,
"callee": {
"type": "MemberExpression",
"start": 100,
"end": 124,
"callee": {
"type": "MemberExpression",
"end": 111,
"object": {
"type": "Identifier",
"start": 100,
"end": 107,
"name": "console"
},
"property": {
"type": "Identifier",
"start": 108,
"end": 111,
"name": "log"
},
"computed": false
},
"arguments": [
{
"type": "MemberExpression",
"start": 112,
"end": 123,
"object": {
"type": "Identifier",
"start": 100,
"end": 107,
"name": "console"
"start": 112,
"end": 115,
"name": "foo"
},
"property": {
"type": "Identifier",
"start": 108,
"end": 111,
"name": "log"
"start": 116,
"end": 123,
"name": "default"
},
"computed": false
},
"arguments": [
{
"type": "MemberExpression",
"start": 112,
"end": 123,
"object": {
"type": "Identifier",
"start": 112,
"end": 115,
"name": "foo"
},
"property": {
"type": "Identifier",
"start": 116,
"end": 123,
"name": "default"
},
"computed": false
}
]
}
}
]
}
]
}
}
]
}
]
}
}
]
}
]
}
}
]
}
]
}
}
]
}
],
"sourceType": "module"
}
}
],
"sourceType": "module"
}
]
}
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -20,19 +20,16 @@
}
]
},
"css": [],
"js": [
{
"start": 0,
"end": 43,
"attributes": [],
"content": {
"type": "Program",
"start": 8,
"end": 34,
"body": [],
"sourceType": "module"
}
"instance": {
"start": 0,
"end": 43,
"context": "default",
"content": {
"type": "Program",
"start": 8,
"end": 34,
"body": [],
"sourceType": "module"
}
]
}
}

@ -44,46 +44,43 @@
}
]
},
"css": [],
"js": [
{
"start": 0,
"end": 77,
"attributes": [],
"content": {
"type": "Program",
"start": 8,
"end": 68,
"body": [
{
"type": "VariableDeclaration",
"start": 10,
"end": 29,
"declarations": [
{
"type": "VariableDeclarator",
"instance": {
"start": 0,
"end": 77,
"context": "default",
"content": {
"type": "Program",
"start": 8,
"end": 68,
"body": [
{
"type": "VariableDeclaration",
"start": 10,
"end": 29,
"declarations": [
{
"type": "VariableDeclarator",
"start": 14,
"end": 28,
"id": {
"type": "Identifier",
"start": 14,
"end": 18,
"name": "name"
},
"init": {
"type": "Literal",
"start": 21,
"end": 28,
"id": {
"type": "Identifier",
"start": 14,
"end": 18,
"name": "name"
},
"init": {
"type": "Literal",
"start": 21,
"end": 28,
"value": "world",
"raw": "'world'"
}
"value": "world",
"raw": "'world'"
}
],
"kind": "let"
}
],
"sourceType": "module"
}
}
],
"kind": "let"
}
],
"sourceType": "module"
}
]
}
}

@ -44,46 +44,43 @@
}
]
},
"css": [],
"js": [
{
"start": 0,
"end": 66,
"attributes": [],
"content": {
"type": "Program",
"start": 8,
"end": 57,
"body": [
{
"type": "VariableDeclaration",
"start": 10,
"end": 29,
"declarations": [
{
"type": "VariableDeclarator",
"instance": {
"start": 0,
"end": 66,
"context": "default",
"content": {
"type": "Program",
"start": 8,
"end": 57,
"body": [
{
"type": "VariableDeclaration",
"start": 10,
"end": 29,
"declarations": [
{
"type": "VariableDeclarator",
"start": 14,
"end": 28,
"id": {
"type": "Identifier",
"start": 14,
"end": 18,
"name": "name"
},
"init": {
"type": "Literal",
"start": 21,
"end": 28,
"id": {
"type": "Identifier",
"start": 14,
"end": 18,
"name": "name"
},
"init": {
"type": "Literal",
"start": 21,
"end": 28,
"value": "world",
"raw": "'world'"
}
"value": "world",
"raw": "'world'"
}
],
"kind": "let"
}
],
"sourceType": "module"
}
}
],
"kind": "let"
}
],
"sourceType": "module"
}
]
}
}

@ -44,46 +44,43 @@
}
]
},
"css": [],
"js": [
{
"start": 0,
"end": 39,
"attributes": [],
"content": {
"type": "Program",
"start": 8,
"end": 30,
"body": [
{
"type": "VariableDeclaration",
"start": 10,
"end": 29,
"declarations": [
{
"type": "VariableDeclarator",
"instance": {
"start": 0,
"end": 39,
"context": "default",
"content": {
"type": "Program",
"start": 8,
"end": 30,
"body": [
{
"type": "VariableDeclaration",
"start": 10,
"end": 29,
"declarations": [
{
"type": "VariableDeclarator",
"start": 14,
"end": 28,
"id": {
"type": "Identifier",
"start": 14,
"end": 18,
"name": "name"
},
"init": {
"type": "Literal",
"start": 21,
"end": 28,
"id": {
"type": "Identifier",
"start": 14,
"end": 18,
"name": "name"
},
"init": {
"type": "Literal",
"start": 21,
"end": 28,
"value": "world",
"raw": "'world'"
}
"value": "world",
"raw": "'world'"
}
],
"kind": "let"
}
],
"sourceType": "module"
}
}
],
"kind": "let"
}
],
"sourceType": "module"
}
]
}
}

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

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

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

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

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

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

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

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

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

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

@ -17,6 +17,7 @@
}
]
},
"css": [],
"js": []
"css": null,
"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 {
test(assert, stats) {
assert.deepEqual(stats.imports, [
assert.deepEqual(stats.vars, [
{
source: 'x',
specifiers: [{ name: 'default', as: 'x' }]
name: 'x',
export_name: null,
injected: false,
module: false,
mutated: false,
reassigned: false,
referenced: false,
writable: false
},
{
source: 'y',
specifiers: [{ name: 'y', as: 'y' }]
name: 'y',
export_name: null,
injected: false,
module: false,
mutated: false,
reassigned: false,
referenced: false,
writable: false
},
{
source: 'z',
specifiers: [{ name: '*', as: 'z' }]
name: '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 {
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