remove prettier

pull/4742/head
pushkine 5 years ago
parent ff027cd290
commit 4f9566892f

@ -1,9 +0,0 @@
{
"printWidth": 120,
"singleQuote": true,
"jsxBracketSameLine": true,
"semi": true,
"useTabs": true,
"jsxSingleQuote": true,
"quoteProps": "consistent"
}

@ -1,5 +1,4 @@
const now = const now = (typeof process !== 'undefined' && process.hrtime)
typeof process !== 'undefined' && process.hrtime
? () => { ? () => {
const t = process.hrtime(); const t = process.hrtime();
return t[0] * 1e3 + t[1] / 1e6; return t[0] * 1e3 + t[1] / 1e6;
@ -15,13 +14,10 @@ interface Timing {
function collapse_timings(timings) { function collapse_timings(timings) {
const result = {}; const result = {};
timings.forEach((timing) => { timings.forEach(timing => {
result[timing.label] = Object.assign( result[timing.label] = Object.assign({
{ total: timing.end - timing.start
total: timing.end - timing.start, }, timing.children && collapse_timings(timing.children));
},
timing.children && collapse_timings(timing.children)
);
}); });
return result; return result;
} }
@ -44,7 +40,7 @@ export default class Stats {
label, label,
start: now(), start: now(),
end: null, end: null,
children: [], children: []
}; };
this.current_children.push(timing); this.current_children.push(timing);
@ -66,13 +62,12 @@ export default class Stats {
} }
render() { render() {
const timings = { const timings = Object.assign({
total: now() - this.start_time, total: now() - this.start_time
...collapse_timings(this.timings), }, collapse_timings(this.timings));
};
return { return {
timings, timings
}; };
} }
} }

@ -1,37 +1,34 @@
import { b, print, x } from 'code-red';
import {
AssignmentExpression,
ExpressionStatement,
Identifier,
ImportDeclaration,
Literal,
Node,
Program,
} from 'estree';
import { walk } from 'estree-walker'; import { walk } from 'estree-walker';
import is_reference from 'is-reference';
import { getLocator } from 'locate-character'; import { getLocator } from 'locate-character';
import { test } from '../config';
import { Ast, CompileOptions, CssResult, Var, Warning } from '../interfaces';
import Stats from '../Stats'; import Stats from '../Stats';
import error from '../utils/error'; import { globals, reserved, is_valid } from '../utils/names';
import fuzzymatch from '../utils/fuzzymatch';
import get_code_frame from '../utils/get_code_frame';
import { globals, is_valid, reserved } from '../utils/names';
import { namespaces } from '../utils/namespaces'; import { namespaces } from '../utils/namespaces';
import create_module from './create_module'; import create_module from './create_module';
import {
create_scopes,
extract_names,
Scope,
extract_identifiers,
} from './utils/scope';
import Stylesheet from './css/Stylesheet'; import Stylesheet from './css/Stylesheet';
import internal_exports from './internal_exports'; import { test } from '../config';
import Fragment from './nodes/Fragment'; import Fragment from './nodes/Fragment';
import internal_exports from './internal_exports';
import { Ast, CompileOptions, Var, Warning, CssResult } from '../interfaces';
import error from '../utils/error';
import get_code_frame from '../utils/get_code_frame';
import flatten_reference from './utils/flatten_reference';
import is_used_as_reference from './utils/is_used_as_reference';
import is_reference from 'is-reference';
import TemplateScope from './nodes/shared/TemplateScope'; import TemplateScope from './nodes/shared/TemplateScope';
import fuzzymatch from '../utils/fuzzymatch';
import get_object from './utils/get_object';
import Slot from './nodes/Slot'; import Slot from './nodes/Slot';
import { Node, ImportDeclaration, Identifier, Program, ExpressionStatement, AssignmentExpression, Literal } from 'estree';
import add_to_set from './utils/add_to_set'; import add_to_set from './utils/add_to_set';
import check_graph_for_cycles from './utils/check_graph_for_cycles'; import check_graph_for_cycles from './utils/check_graph_for_cycles';
import flatten_reference from './utils/flatten_reference'; import { print, x, b } from 'code-red';
import get_object from './utils/get_object';
import is_used_as_reference from './utils/is_used_as_reference';
import { is_reserved_keyword } from './utils/reserved_keywords'; import { is_reserved_keyword } from './utils/reserved_keywords';
import { create_scopes, extract_identifiers, extract_names, Scope } from './utils/scope';
interface ComponentOptions { interface ComponentOptions {
namespace?: string; namespace?: string;
@ -69,8 +66,8 @@ export default class Component {
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();
partly_hoisted: Array<Node | Node[]> = []; partly_hoisted: Array<(Node | Node[])> = [];
fully_hoisted: Array<Node | Node[]> = []; fully_hoisted: Array<(Node | Node[])> = [];
reactive_declarations: Array<{ reactive_declarations: Array<{
assignees: Set<string>; assignees: Set<string>;
dependencies: Set<string>; dependencies: Set<string>;
@ -119,7 +116,7 @@ export default class Component {
html: ast.html, html: ast.html,
css: ast.css, css: ast.css,
instance: ast.instance && JSON.parse(JSON.stringify(ast.instance)), instance: ast.instance && JSON.parse(JSON.stringify(ast.instance)),
module: ast.module, module: ast.module
}; };
this.file = this.file =
@ -130,18 +127,30 @@ export default class Component {
this.locate = getLocator(this.source, { offsetLine: 1 }); this.locate = getLocator(this.source, { offsetLine: 1 });
// styles // styles
this.stylesheet = new Stylesheet(source, ast, compile_options.filename, compile_options.dev); this.stylesheet = new Stylesheet(
source,
ast,
compile_options.filename,
compile_options.dev
);
this.stylesheet.validate(this); this.stylesheet.validate(this);
this.component_options = process_component_options(this, this.ast.html.children); this.component_options = process_component_options(
this.namespace = namespaces[this.component_options.namespace] || this.component_options.namespace; this,
this.ast.html.children
);
this.namespace =
namespaces[this.component_options.namespace] ||
this.component_options.namespace;
if (compile_options.customElement) { if (compile_options.customElement) {
if (this.component_options.tag === undefined && compile_options.tag === undefined) { if (
const svelteOptions = ast.html.children.find((child) => child.name === 'svelte:options') || { this.component_options.tag === undefined &&
start: 0, compile_options.tag === undefined
end: 0, ) {
}; const svelteOptions = ast.html.children.find(
child => child.name === 'svelte:options'
) || { start: 0, end: 0 };
this.warn(svelteOptions, { this.warn(svelteOptions, {
code: 'custom-element-no-tag', code: 'custom-element-no-tag',
message: `No custom element 'tag' option was specified. To automatically register a custom element, specify a name with a hyphen in it, e.g. <svelte:options tag="my-thing"/>. To hide this warning, use <svelte:options tag={null}/>`, message: `No custom element 'tag' option was specified. To automatically register a custom element, specify a name with a hyphen in it, e.g. <svelte:options tag="my-thing"/>. To hide this warning, use <svelte:options tag={null}/>`,
@ -250,19 +259,23 @@ export default class Component {
this.helpers.set(name, alias); this.helpers.set(name, alias);
node.name = alias.name; node.name = alias.name;
} }
} else if (node.name[0] !== '#' && !is_valid(node.name)) { }
else if (node.name[0] !== '#' && !is_valid(node.name)) {
// this hack allows x`foo.${bar}` where bar could be invalid // this hack allows x`foo.${bar}` where bar could be invalid
const literal: Literal = { type: 'Literal', value: node.name }; const literal: Literal = { type: 'Literal', value: node.name };
if (parent.type === 'Property' && key === 'key') { if (parent.type === 'Property' && key === 'key') {
parent.key = literal; parent.key = literal;
} else if (parent.type === 'MemberExpression' && key === 'property') { }
else if (parent.type === 'MemberExpression' && key === 'property') {
parent.property = literal; parent.property = literal;
parent.computed = true; parent.computed = true;
} }
} }
} }
}, }
}); });
const referenced_globals = Array.from( const referenced_globals = Array.from(
@ -287,17 +300,19 @@ export default class Component {
referenced_globals, referenced_globals,
this.imports, this.imports,
this.vars this.vars
.filter((variable) => variable.module && variable.export_name) .filter(variable => variable.module && variable.export_name)
.map((variable) => ({ .map(variable => ({
name: variable.name, name: variable.name,
as: variable.export_name, as: variable.export_name,
})) }))
); );
css = compile_options.customElement ? { code: null, map: null } : result.css; css = compile_options.customElement
? { code: null, map: null }
: result.css;
js = print(program, { js = print(program, {
sourceMapSource: compile_options.filename, sourceMapSource: compile_options.filename
}); });
js.map.sources = [ js.map.sources = [
@ -306,7 +321,9 @@ export default class Component {
: null, : null,
]; ];
js.map.sourcesContent = [this.source]; js.map.sourcesContent = [
this.source
];
} }
return { return {
@ -315,8 +332,8 @@ export default class Component {
ast: this.original_ast, ast: this.original_ast,
warnings: this.warnings, warnings: this.warnings,
vars: this.vars vars: this.vars
.filter((v) => !v.global && !v.internal) .filter(v => !v.global && !v.internal)
.map((v) => ({ .map(v => ({
name: v.name, name: v.name,
export_name: v.export_name || null, export_name: v.export_name || null,
injected: v.injected || false, injected: v.injected || false,
@ -361,13 +378,17 @@ export default class Component {
return (name: string): Identifier => { return (name: string): Identifier => {
if (test) name = `${name}$`; if (test) name = `${name}$`;
let alias = name; let alias = name;
for (let i = 1; this.used_names.has(alias) || local_used_names.has(alias); alias = `${name}_${i++}`); for (
let i = 1;
this.used_names.has(alias) || local_used_names.has(alias);
alias = `${name}_${i++}`
);
local_used_names.add(alias); local_used_names.add(alias);
this.globally_used_names.add(alias); this.globally_used_names.add(alias);
return { return {
type: 'Identifier', type: 'Identifier',
name: alias, name: alias
}; };
}; };
} }
@ -419,7 +440,8 @@ export default class Component {
end, end,
pos: pos.start, pos: pos.start,
filename: this.compile_options.filename, filename: this.compile_options.filename,
toString: () => `${warning.message} (${start.line}:${start.column})\n${frame}`, toString: () =>
`${warning.message} (${start.line}:${start.column})\n${frame}`,
}); });
} }
@ -444,17 +466,14 @@ export default class Component {
} }
if (node.declaration) { if (node.declaration) {
if (node.declaration.type === 'VariableDeclaration') { if (node.declaration.type === 'VariableDeclaration') {
node.declaration.declarations.forEach((declarator) => { node.declaration.declarations.forEach(declarator => {
extract_names(declarator.id).forEach((name) => { extract_names(declarator.id).forEach(name => {
const variable = this.var_lookup.get(name); const variable = this.var_lookup.get(name);
variable.export_name = name; variable.export_name = name;
if ( if (variable.writable && !(variable.referenced || variable.referenced_from_script || variable.subscribable)) {
variable.writable &&
!(variable.referenced || variable.referenced_from_script || variable.subscribable)
) {
this.warn(declarator, { this.warn(declarator, {
code: `unused-export-let`, code: `unused-export-let`,
message: `${this.name.name} has unused export property '${name}'. If it is for external reference only, please consider using \`export const ${name}\``, message: `${this.name.name} has unused export property '${name}'. If it is for external reference only, please consider using \`export const ${name}\``
}); });
} }
}); });
@ -468,19 +487,16 @@ export default class Component {
return node.declaration; return node.declaration;
} else { } else {
node.specifiers.forEach((specifier) => { node.specifiers.forEach(specifier => {
const variable = this.var_lookup.get(specifier.local.name); const variable = this.var_lookup.get(specifier.local.name);
if (variable) { if (variable) {
variable.export_name = specifier.exported.name; variable.export_name = specifier.exported.name;
if ( if (variable.writable && !(variable.referenced || variable.referenced_from_script || variable.subscribable)) {
variable.writable &&
!(variable.referenced || variable.referenced_from_script || variable.subscribable)
) {
this.warn(specifier, { this.warn(specifier, {
code: `unused-export-let`, code: `unused-export-let`,
message: `${this.name.name} has unused export property '${specifier.exported.name}'. If it is for external reference only, please consider using \`export const ${specifier.exported.name}\``, message: `${this.name.name} has unused export property '${specifier.exported.name}'. If it is for external reference only, please consider using \`export const ${specifier.exported.name}\``
}); });
} }
} }
@ -494,12 +510,13 @@ export default class Component {
extract_javascript(script) { extract_javascript(script) {
if (!script) return null; if (!script) return null;
return script.content.body.filter((node) => { return script.content.body.filter(node => {
if (!node) return false; if (!node) return false;
if (this.hoistable_nodes.has(node)) return false; if (this.hoistable_nodes.has(node)) return false;
if (this.reactive_declaration_nodes.has(node)) return false; if (this.reactive_declaration_nodes.has(node)) return false;
if (node.type === 'ImportDeclaration') return false; if (node.type === 'ImportDeclaration') return false;
if (node.type === 'ExportDeclaration' && node.specifiers.length > 0) return false; if (node.type === 'ExportDeclaration' && node.specifiers.length > 0)
return false;
return true; return true;
}); });
} }
@ -537,7 +554,7 @@ export default class Component {
name, name,
module: true, module: true,
hoistable: true, hoistable: true,
writable, writable
}); });
}); });
@ -551,7 +568,7 @@ export default class Component {
this.add_var({ this.add_var({
name, name,
global: true, global: true,
hoistable: true, hoistable: true
}); });
} }
}); });
@ -581,7 +598,7 @@ export default class Component {
if (!script) return; if (!script) return;
// inject vars for reactive declarations // inject vars for reactive declarations
script.content.body.forEach((node) => { script.content.body.forEach(node => {
if (node.type !== 'LabeledStatement') return; if (node.type !== 'LabeledStatement') return;
if (node.body.type !== 'ExpressionStatement') return; if (node.body.type !== 'ExpressionStatement') return;
@ -589,14 +606,16 @@ export default class Component {
if (expression.type !== 'AssignmentExpression') return; if (expression.type !== 'AssignmentExpression') return;
if (expression.left.type === 'MemberExpression') return; if (expression.left.type === 'MemberExpression') return;
extract_names(expression.left).forEach((name) => { extract_names(expression.left).forEach(name => {
if (!this.var_lookup.has(name) && name[0] !== '$') { if (!this.var_lookup.has(name) && name[0] !== '$') {
this.injected_reactive_declaration_vars.add(name); this.injected_reactive_declaration_vars.add(name);
} }
}); });
}); });
const { scope: instance_scope, map, globals } = create_scopes(script.content); const { scope: instance_scope, map, globals } = create_scopes(
script.content
);
this.instance_scope = instance_scope; this.instance_scope = instance_scope;
this.instance_scope_map = map; this.instance_scope_map = map;
@ -613,7 +632,7 @@ export default class Component {
this.add_var({ this.add_var({
name, name,
initialised: instance_scope.initialised_declarations.has(name), initialised: instance_scope.initialised_declarations.has(name),
writable, writable
}); });
this.node_for_declaration.set(name, node); this.node_for_declaration.set(name, node);
@ -639,7 +658,7 @@ export default class Component {
if (name === '$' || name[1] === '$') { if (name === '$' || name[1] === '$') {
this.error(node as any, { this.error(node as any, {
code: 'illegal-global', code: 'illegal-global',
message: `${name} is an illegal variable name`, message: `${name} is an illegal variable name`
}); });
} }
@ -661,7 +680,7 @@ export default class Component {
this.add_var({ this.add_var({
name, name,
global: true, global: true,
hoistable: true, hoistable: true
}); });
} }
}); });
@ -694,12 +713,15 @@ export default class Component {
to_remove.unshift([parent, prop, index]); to_remove.unshift([parent, prop, index]);
}; };
let scope_updated = false; let scope_updated = false;
let generator_count = 0; let generator_count = 0;
walk(content, { walk(content, {
enter(node: Node, parent, prop, index) { enter(node: Node, parent, prop, index) {
if ((node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') && node.generator === true) { if ((node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') && node.generator === true) {
generator_count++; generator_count++;
} }
if (map.has(node)) { if (map.has(node)) {
scope = map.get(node); scope = map.get(node);
} }
@ -729,13 +751,10 @@ export default class Component {
if ((node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') && node.generator === true) { if ((node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') && node.generator === true) {
generator_count--; generator_count--;
} }
// do it on leave, to prevent infinite loop // do it on leave, to prevent infinite loop
if (component.compile_options.dev && component.compile_options.loopGuardTimeout > 0 && generator_count <= 0) { if (component.compile_options.dev && component.compile_options.loopGuardTimeout > 0 && generator_count <= 0) {
const to_replace_for_loop_protect = component.loop_protect( const to_replace_for_loop_protect = component.loop_protect(node, scope, component.compile_options.loopGuardTimeout);
node,
scope,
component.compile_options.loopGuardTimeout
);
if (to_replace_for_loop_protect) { if (to_replace_for_loop_protect) {
this.replace(to_replace_for_loop_protect); this.replace(to_replace_for_loop_protect);
scope_updated = true; scope_updated = true;
@ -787,9 +806,13 @@ export default class Component {
const deep = assignee.type === 'MemberExpression'; const deep = assignee.type === 'MemberExpression';
names.forEach((name) => { names.forEach(name => {
const scope_owner = scope.find_owner(name); const scope_owner = scope.find_owner(name);
if (scope_owner !== null ? scope_owner === instance_scope : module_scope && module_scope.has(name)) { if (
scope_owner !== null
? scope_owner === instance_scope
: module_scope && module_scope.has(name)
) {
const variable = component.var_lookup.get(name); const variable = component.var_lookup.get(name);
variable[deep ? 'mutated' : 'reassigned'] = true; variable[deep ? 'mutated' : 'reassigned'] = true;
} }
@ -814,7 +837,11 @@ export default class Component {
} }
warn_on_undefined_store_value_references(node, parent, scope) { warn_on_undefined_store_value_references(node, parent, scope) {
if (node.type === 'LabeledStatement' && node.label.name === '$' && parent.type !== 'Program') { if (
node.type === 'LabeledStatement' &&
node.label.name === '$' &&
parent.type !== 'Program'
) {
this.warn(node as any, { this.warn(node as any, {
code: 'non-top-level-reactive-declaration', code: 'non-top-level-reactive-declaration',
message: '$: has no effect outside of the top-level', message: '$: has no effect outside of the top-level',
@ -832,7 +859,9 @@ export default class Component {
} }
loop_protect(node, scope: Scope, timeout: number): Node | null { loop_protect(node, scope: Scope, timeout: number): Node | null {
if (node.type === 'WhileStatement' || node.type === 'ForStatement' || node.type === 'DoWhileStatement') { if (node.type === 'WhileStatement' ||
node.type === 'ForStatement' ||
node.type === 'DoWhileStatement') {
const guard = this.get_unique_name('guard', scope); const guard = this.get_unique_name('guard', scope);
this.used_names.add(guard.name); this.used_names.add(guard.name);
@ -850,7 +879,10 @@ export default class Component {
return { return {
type: 'BlockStatement', type: 'BlockStatement',
body: [before[0], node], body: [
before[0],
node,
],
}; };
} }
return null; return null;
@ -875,11 +907,11 @@ 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) {
node.declarations.forEach((declarator) => { node.declarations.forEach(declarator => {
if (declarator.id.type !== 'Identifier') { if (declarator.id.type !== 'Identifier') {
const inserts = []; const inserts = [];
extract_names(declarator.id).forEach((name) => { extract_names(declarator.id).forEach(name => {
const variable = component.var_lookup.get(name); const variable = component.var_lookup.get(name);
if (variable.export_name) { if (variable.export_name) {
@ -906,14 +938,15 @@ export default class Component {
const variable = component.var_lookup.get(name); const variable = component.var_lookup.get(name);
if (variable.export_name && variable.writable) { if (variable.export_name && variable.writable) {
const insert = variable.subscribable ? get_insert(variable) : null; const insert = variable.subscribable
? get_insert(variable)
: null;
parent[key].splice(index + 1, 0, insert); parent[key].splice(index + 1, 0, insert);
declarator.id = { declarator.id = {
type: 'ObjectPattern', type: 'ObjectPattern',
properties: [ properties: [{
{
type: 'Property', type: 'Property',
method: false, method: false,
shorthand: false, shorthand: false,
@ -924,11 +957,10 @@ export default class Component {
? { ? {
type: 'AssignmentPattern', type: 'AssignmentPattern',
left: declarator.id, left: declarator.id,
right: declarator.init, right: declarator.init
} }
: declarator.id, : declarator.id
}, }]
],
}; };
declarator.init = x`$$props`; declarator.init = x`$$props`;
@ -959,7 +991,12 @@ 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_nodes, var_lookup, injected_reactive_declaration_vars, imports } = this; const {
hoistable_nodes,
var_lookup,
injected_reactive_declaration_vars,
imports,
} = this;
const top_level_function_declarations = new Map(); const top_level_function_declarations = new Map();
@ -969,7 +1006,7 @@ export default class Component {
const node = body[i]; const node = body[i];
if (node.type === 'VariableDeclaration') { if (node.type === 'VariableDeclaration') {
const all_hoistable = node.declarations.every((d) => { const all_hoistable = node.declarations.every(d => {
if (!d.init) return false; if (!d.init) return false;
if (d.init.type !== 'Literal') return false; if (d.init.type !== 'Literal') return false;
@ -984,13 +1021,18 @@ export default class Component {
if (v.export_name) return false; if (v.export_name) return false;
if (this.var_lookup.get(name).reassigned) return false; if (this.var_lookup.get(name).reassigned) return false;
if (this.vars.find((variable) => variable.name === name && variable.module)) return false; if (
this.vars.find(
variable => variable.name === name && variable.module
)
)
return false;
return true; return true;
}); });
if (all_hoistable) { if (all_hoistable) {
node.declarations.forEach((d) => { node.declarations.forEach(d => {
const variable = this.var_lookup.get((d.id as Identifier).name); const variable = this.var_lookup.get((d.id as Identifier).name);
variable.hoistable = true; variable.hoistable = true;
}); });
@ -1018,7 +1060,7 @@ export default class Component {
const checked = new Set(); const checked = new Set();
const walking = new Set(); const walking = new Set();
const is_hoistable = (fn_declaration) => { const is_hoistable = fn_declaration => {
if (fn_declaration.type === 'ExportNamedDeclaration') { if (fn_declaration.type === 'ExportNamedDeclaration') {
fn_declaration = fn_declaration.declaration; fn_declaration = fn_declaration.declaration;
} }
@ -1058,7 +1100,9 @@ export default class Component {
if (variable.hoistable) return; 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
);
if (walking.has(other_declaration)) { if (walking.has(other_declaration)) {
hoistable = false; hoistable = false;
@ -1138,7 +1182,7 @@ export default class Component {
if (node.type === 'AssignmentExpression') { if (node.type === 'AssignmentExpression') {
const left = get_object(node.left); const left = get_object(node.left);
extract_identifiers(left).forEach((node) => { extract_identifiers(left).forEach(node => {
assignee_nodes.add(node); assignee_nodes.add(node);
assignees.add(node.name); assignees.add(node.name);
}); });
@ -1156,8 +1200,12 @@ export default class Component {
const owner = scope.find_owner(name); const owner = scope.find_owner(name);
const variable = component.var_lookup.get(name); const variable = component.var_lookup.get(name);
if (variable) variable.is_reactive_dependency = true; if (variable) variable.is_reactive_dependency = true;
const is_writable_or_mutated = variable && (variable.writable || variable.mutated); const is_writable_or_mutated =
if ((!owner || owner === component.instance_scope) && (name[0] === '$' || is_writable_or_mutated)) { variable && (variable.writable || variable.mutated);
if (
(!owner || owner === component.instance_scope) &&
(name[0] === '$' || is_writable_or_mutated)
) {
dependencies.add(name); dependencies.add(name);
} }
} }
@ -1187,8 +1235,8 @@ export default class Component {
const lookup = new Map(); const lookup = new Map();
unsorted_reactive_declarations.forEach((declaration) => { unsorted_reactive_declarations.forEach(declaration => {
declaration.assignees.forEach((name) => { declaration.assignees.forEach(name => {
if (!lookup.has(name)) { if (!lookup.has(name)) {
lookup.set(name, []); lookup.set(name, []);
} }
@ -1199,35 +1247,34 @@ export default class Component {
}); });
}); });
const cycle = check_graph_for_cycles( const cycle = check_graph_for_cycles(unsorted_reactive_declarations.reduce((acc, declaration) => {
unsorted_reactive_declarations.reduce((acc, declaration) => { declaration.assignees.forEach(v => {
declaration.assignees.forEach((v) => { declaration.dependencies.forEach(w => {
declaration.dependencies.forEach((w) => {
if (!declaration.assignees.has(w)) { if (!declaration.assignees.has(w)) {
acc.push([v, w]); acc.push([v, w]);
} }
}); });
}); });
return acc; return acc;
}, []) }, []));
);
if (cycle && cycle.length) { if (cycle && cycle.length) {
const declarationList = lookup.get(cycle[0]); const declarationList = lookup.get(cycle[0]);
const declaration = declarationList[0]; const declaration = declarationList[0];
this.error(declaration.node, { this.error(declaration.node, {
code: 'cyclical-reactive-declaration', code: 'cyclical-reactive-declaration',
message: `Cyclical dependency detected: ${cycle.join(' → ')}`, message: `Cyclical dependency detected: ${cycle.join(' → ')}`
}); });
} }
const add_declaration = (declaration) => { const add_declaration = declaration => {
if (this.reactive_declarations.includes(declaration)) return; if (this.reactive_declarations.includes(declaration)) return;
declaration.dependencies.forEach((name) => { declaration.dependencies.forEach(name => {
if (declaration.assignees.has(name)) return; if (declaration.assignees.has(name)) return;
const earlier_declarations = lookup.get(name); const earlier_declarations = lookup.get(name);
if (earlier_declarations) earlier_declarations.forEach(add_declaration); if (earlier_declarations)
earlier_declarations.forEach(add_declaration);
}); });
this.reactive_declarations.push(declaration); this.reactive_declarations.push(declaration);
@ -1238,10 +1285,10 @@ export default class Component {
warn_if_undefined(name: string, node, template_scope: TemplateScope) { warn_if_undefined(name: string, node, template_scope: TemplateScope) {
if (name[0] === '$') { if (name[0] === '$') {
if (name === '$' || (name[1] === '$' && !is_reserved_keyword(name))) { if (name === '$' || name[1] === '$' && !is_reserved_keyword(name)) {
this.error(node, { this.error(node, {
code: 'illegal-global', code: 'illegal-global',
message: `${name} is an illegal variable name`, message: `${name} is an illegal variable name`
}); });
} }
@ -1257,7 +1304,8 @@ export default class Component {
if (globals.has(name) && node.type !== 'InlineComponent') return; if (globals.has(name) && node.type !== 'InlineComponent') return;
let message = `'${name}' is not defined`; let message = `'${name}' is not defined`;
if (!this.ast.instance) message += `. Consider adding a <script> block with 'export let ${name}' to declare a prop`; if (!this.ast.instance)
message += `. Consider adding a <script> block with 'export let ${name}' to declare a prop`;
this.warn(node, { this.warn(node, {
code: 'missing-declaration', code: 'missing-declaration',
@ -1287,7 +1335,7 @@ function process_component_options(component: Component, nodes) {
preserveWhitespace: !!component.compile_options.preserveWhitespace, preserveWhitespace: !!component.compile_options.preserveWhitespace,
}; };
const node = nodes.find((node) => node.name === 'svelte:options'); const node = nodes.find(node => node.name === 'svelte:options');
function get_value(attribute, code, message) { function get_value(attribute, code, message) {
const { value } = attribute; const { value } = attribute;
@ -1309,7 +1357,7 @@ function process_component_options(component: Component, nodes) {
} }
if (node) { if (node) {
node.attributes.forEach((attribute) => { node.attributes.forEach(attribute => {
if (attribute.type === 'Attribute') { if (attribute.type === 'Attribute') {
const { name } = attribute; const { name } = attribute;
@ -1319,7 +1367,8 @@ function process_component_options(component: Component, nodes) {
const message = `'tag' must be a string literal`; const message = `'tag' must be a string literal`;
const tag = get_value(attribute, code, message); const tag = get_value(attribute, code, message);
if (typeof tag !== 'string' && tag !== null) component.error(attribute, { code, message }); if (typeof tag !== 'string' && tag !== null)
component.error(attribute, { code, message });
if (tag && !/^[a-zA-Z][a-zA-Z0-9]*-[a-zA-Z0-9-]+$/.test(tag)) { if (tag && !/^[a-zA-Z][a-zA-Z0-9]*-[a-zA-Z0-9-]+$/.test(tag)) {
component.error(attribute, { component.error(attribute, {
@ -1331,7 +1380,7 @@ function process_component_options(component: Component, nodes) {
if (tag && !component.compile_options.customElement) { if (tag && !component.compile_options.customElement) {
component.warn(attribute, { component.warn(attribute, {
code: 'missing-custom-element-compile-options', code: 'missing-custom-element-compile-options',
message: `The 'tag' option is used when generating a custom element. Did you forget the 'customElement: true' compile option?`, message: `The 'tag' option is used when generating a custom element. Did you forget the 'customElement: true' compile option?`
}); });
} }
@ -1344,7 +1393,8 @@ function process_component_options(component: Component, nodes) {
const message = `The 'namespace' attribute must be a string literal representing a valid namespace`; const message = `The 'namespace' attribute must be a string literal representing a valid namespace`;
const ns = get_value(attribute, code, message); const ns = get_value(attribute, code, message);
if (typeof ns !== 'string') component.error(attribute, { code, message }); if (typeof ns !== 'string')
component.error(attribute, { code, message });
if (!(ns in namespaces)) { if (!(ns in namespaces)) {
const match = fuzzymatch(ns, namespaces); const match = fuzzymatch(ns, namespaces);
@ -1372,7 +1422,8 @@ function process_component_options(component: Component, nodes) {
const message = `${name} attribute must be true or false`; const message = `${name} attribute must be true or false`;
const value = get_value(attribute, code, message); const value = get_value(attribute, code, message);
if (typeof value !== 'boolean') component.error(attribute, { code, message }); if (typeof value !== 'boolean')
component.error(attribute, { code, message });
component_options[name] = value; component_options[name] = value;
break; break;

@ -23,15 +23,14 @@ export default function create_module(
) { ) {
const internal_path = `${sveltePath}/internal`; const internal_path = `${sveltePath}/internal`;
helpers.sort((a, b) => (a.name < b.name ? -1 : 1)); helpers.sort((a, b) => (a.name < b.name) ? -1 : 1);
globals.sort((a, b) => (a.name < b.name ? -1 : 1)); globals.sort((a, b) => (a.name < b.name) ? -1 : 1);
if (format === 'esm') { if (format === 'esm') {
return esm(program, name, banner, sveltePath, internal_path, helpers, globals, imports, module_exports); return esm(program, name, banner, sveltePath, internal_path, helpers, globals, imports, module_exports);
} }
if (format === 'cjs') if (format === 'cjs') return cjs(program, name, banner, sveltePath, internal_path, helpers, globals, imports, module_exports);
return cjs(program, name, banner, sveltePath, internal_path, helpers, globals, imports, module_exports);
throw new Error(`options.format is invalid (must be ${list(Object.keys(wrappers))})`); throw new Error(`options.format is invalid (must be ${list(Object.keys(wrappers))})`);
} }
@ -44,30 +43,26 @@ function get_internal_globals(
) { ) {
// TODO : internal_globals is too aggressive // TODO : internal_globals is too aggressive
// see output https://svelte.dev/repl/1623b8b2ff604d7ca6e794343d976ae6 // see output https://svelte.dev/repl/1623b8b2ff604d7ca6e794343d976ae6
return ( return globals.length > 0 && {
globals.length > 0 && {
type: 'VariableDeclaration', type: 'VariableDeclaration',
kind: 'const', kind: 'const',
declarations: [ declarations: [{
{
type: 'VariableDeclarator', type: 'VariableDeclarator',
id: { id: {
type: 'ObjectPattern', type: 'ObjectPattern',
properties: globals.map((g) => ({ properties: globals.map(g => ({
type: 'Property', type: 'Property',
method: false, method: false,
shorthand: false, shorthand: false,
computed: false, computed: false,
key: { type: 'Identifier', name: g.name }, key: { type: 'Identifier', name: g.name },
value: g.alias, value: g.alias,
kind: 'init', kind: 'init'
})), }))
},
init: helpers.find(({ name }) => name === 'globals').alias,
}, },
], init: helpers.find(({ name }) => name === 'globals').alias
} }]
); };
} }
function esm( function esm(
@ -83,27 +78,27 @@ function esm(
) { ) {
const import_declaration = { const import_declaration = {
type: 'ImportDeclaration', type: 'ImportDeclaration',
specifiers: helpers.map((h) => ({ specifiers: helpers.map(h => ({
type: 'ImportSpecifier', type: 'ImportSpecifier',
local: h.alias, local: h.alias,
imported: { type: 'Identifier', name: h.name }, imported: { type: 'Identifier', name: h.name }
})), })),
source: { type: 'Literal', value: internal_path }, source: { type: 'Literal', value: internal_path }
}; };
const internal_globals = get_internal_globals(globals, helpers); const internal_globals = get_internal_globals(globals, helpers);
// edit user imports // edit user imports
imports.forEach((node) => { imports.forEach(node => {
node.source.value = edit_source(node.source.value, sveltePath); node.source.value = edit_source(node.source.value, sveltePath);
}); });
const exports = module_exports.length > 0 && { const exports = module_exports.length > 0 && {
type: 'ExportNamedDeclaration', type: 'ExportNamedDeclaration',
specifiers: module_exports.map((x) => ({ specifiers: module_exports.map(x => ({
type: 'Specifier', type: 'Specifier',
local: { type: 'Identifier', name: x.name }, local: { type: 'Identifier', name: x.name },
exported: { type: 'Identifier', name: x.as }, exported: { type: 'Identifier', name: x.as }
})), })),
}; };
@ -135,29 +130,27 @@ function cjs(
const internal_requires = { const internal_requires = {
type: 'VariableDeclaration', type: 'VariableDeclaration',
kind: 'const', kind: 'const',
declarations: [ declarations: [{
{
type: 'VariableDeclarator', type: 'VariableDeclarator',
id: { id: {
type: 'ObjectPattern', type: 'ObjectPattern',
properties: helpers.map((h) => ({ properties: helpers.map(h => ({
type: 'Property', type: 'Property',
method: false, method: false,
shorthand: false, shorthand: false,
computed: false, computed: false,
key: { type: 'Identifier', name: h.name }, key: { type: 'Identifier', name: h.name },
value: h.alias, value: h.alias,
kind: 'init', kind: 'init'
})), }))
},
init: x`require("${internal_path}")`,
}, },
], init: x`require("${internal_path}")`
}]
}; };
const internal_globals = get_internal_globals(globals, helpers); const internal_globals = get_internal_globals(globals, helpers);
const user_requires = imports.map((node) => { const user_requires = imports.map(node => {
const init = x`require("${edit_source(node.source.value, sveltePath)}")`; const init = x`require("${edit_source(node.source.value, sveltePath)}")`;
if (node.specifiers.length === 0) { if (node.specifiers.length === 0) {
return b`${init};`; return b`${init};`;
@ -165,33 +158,28 @@ function cjs(
return { return {
type: 'VariableDeclaration', type: 'VariableDeclaration',
kind: 'const', kind: 'const',
declarations: [ declarations: [{
{
type: 'VariableDeclarator', type: 'VariableDeclarator',
id: id: node.specifiers[0].type === 'ImportNamespaceSpecifier'
node.specifiers[0].type === 'ImportNamespaceSpecifier'
? { type: 'Identifier', name: node.specifiers[0].local.name } ? { type: 'Identifier', name: node.specifiers[0].local.name }
: { : {
type: 'ObjectPattern', type: 'ObjectPattern',
properties: node.specifiers.map((s) => ({ properties: node.specifiers.map(s => ({
type: 'Property', type: 'Property',
method: false, method: false,
shorthand: false, shorthand: false,
computed: false, computed: false,
key: s.type === 'ImportSpecifier' ? s.imported : { type: 'Identifier', name: 'default' }, key: s.type === 'ImportSpecifier' ? s.imported : { type: 'Identifier', name: 'default' },
value: s.local, value: s.local,
kind: 'init', kind: 'init'
})), }))
},
init,
}, },
], init
}]
}; };
}); });
const exports = module_exports.map( const exports = module_exports.map(x => b`exports.${{ type: 'Identifier', name: x.as }} = ${{ type: 'Identifier', name: x.name }};`);
(x) => b`exports.${{ type: 'Identifier', name: x.as }} = ${{ type: 'Identifier', name: x.name }};`
);
program.body = b` program.body = b`
/* ${banner} */ /* ${banner} */

@ -25,13 +25,13 @@ const valid_options = [
'css', 'css',
'loopGuardTimeout', 'loopGuardTimeout',
'preserveComments', 'preserveComments',
'preserveWhitespace', 'preserveWhitespace'
]; ];
function validate_options(options: CompileOptions, warnings: Warning[]) { function validate_options(options: CompileOptions, warnings: Warning[]) {
const { name, filename, loopGuardTimeout, dev } = options; const { name, filename, loopGuardTimeout, dev } = options;
Object.keys(options).forEach((key) => { Object.keys(options).forEach(key => {
if (!valid_options.includes(key)) { if (!valid_options.includes(key)) {
const match = fuzzymatch(key, valid_options); const match = fuzzymatch(key, valid_options);
let message = `Unrecognized option '${key}'`; let message = `Unrecognized option '${key}'`;
@ -89,8 +89,7 @@ export default function compile(source: string, options: CompileOptions = {}) {
); );
stats.stop('create component'); stats.stop('create component');
const result = const result = options.generate === false
options.generate === false
? null ? null
: options.generate === 'ssr' : options.generate === 'ssr'
? render_ssr(component, options) ? render_ssr(component, options)

@ -35,10 +35,13 @@ const a11y_required_attributes = {
// iframe-has-title // iframe-has-title
iframe: ['title'], iframe: ['title'],
img: ['alt'], img: ['alt'],
object: ['title', 'aria-label', 'aria-labelledby'], object: ['title', 'aria-label', 'aria-labelledby']
}; };
const a11y_distracting_elements = new Set(['blink', 'marquee']); const a11y_distracting_elements = new Set([
'blink',
'marquee'
]);
const a11y_required_content = new Set([ const a11y_required_content = new Set([
// anchor-has-content // anchor-has-content
@ -50,7 +53,7 @@ const a11y_required_content = new Set([
'h3', 'h3',
'h4', 'h4',
'h5', 'h5',
'h6', 'h6'
]); ]);
const a11y_no_onchange = new Set([ const a11y_no_onchange = new Set([
@ -60,15 +63,30 @@ const a11y_no_onchange = new Set([
const invisible_elements = new Set(['meta', 'html', 'script', 'style']); const invisible_elements = new Set(['meta', 'html', 'script', 'style']);
const valid_modifiers = new Set(['preventDefault', 'stopPropagation', 'capture', 'once', 'passive', 'self']); const valid_modifiers = new Set([
'preventDefault',
'stopPropagation',
'capture',
'once',
'passive',
'self'
]);
const passive_events = new Set(['wheel', 'touchstart', 'touchmove', 'touchend', 'touchcancel']); const passive_events = new Set([
'wheel',
'touchstart',
'touchmove',
'touchend',
'touchcancel'
]);
function get_namespace(parent: Element, element: Element, explicit_namespace: string) { function get_namespace(parent: Element, element: Element, explicit_namespace: string) {
const parent_element = parent.find_nearest(/^Element/); const parent_element = parent.find_nearest(/^Element/);
if (!parent_element) { if (!parent_element) {
return explicit_namespace || (svg.test(element.name) ? namespaces.svg : null); return explicit_namespace || (svg.test(element.name)
? namespaces.svg
: null);
} }
if (svg.test(element.name.toLowerCase())) return namespaces.svg; if (svg.test(element.name.toLowerCase())) return namespaces.svg;
@ -102,11 +120,11 @@ export default class Element extends Node {
if (this.name === 'textarea') { if (this.name === 'textarea') {
if (info.children.length > 0) { if (info.children.length > 0) {
const value_attribute = info.attributes.find((node) => node.name === 'value'); const value_attribute = info.attributes.find(node => node.name === 'value');
if (value_attribute) { if (value_attribute) {
component.error(value_attribute, { component.error(value_attribute, {
code: `textarea-duplicate-value`, code: `textarea-duplicate-value`,
message: `A <textarea> can have either a value attribute or (equivalently) child content, but not both`, message: `A <textarea> can have either a value attribute or (equivalently) child content, but not both`
}); });
} }
@ -115,7 +133,7 @@ export default class Element extends Node {
info.attributes.push({ info.attributes.push({
type: 'Attribute', type: 'Attribute',
name: 'value', name: 'value',
value: info.children, value: info.children
}); });
info.children = []; info.children = [];
@ -126,19 +144,19 @@ export default class Element extends Node {
// Special case — treat these the same way: // Special case — treat these the same way:
// <option>{foo}</option> // <option>{foo}</option>
// <option value={foo}>{foo}</option> // <option value={foo}>{foo}</option>
const value_attribute = info.attributes.find((attribute) => attribute.name === 'value'); const value_attribute = info.attributes.find(attribute => attribute.name === 'value');
if (!value_attribute) { if (!value_attribute) {
info.attributes.push({ info.attributes.push({
type: 'Attribute', type: 'Attribute',
name: 'value', name: 'value',
value: info.children, value: info.children,
synthetic: true, synthetic: true
}); });
} }
} }
const has_let = info.attributes.some((node) => node.type === 'Let'); const has_let = info.attributes.some(node => node.type === 'Let');
if (has_let) { if (has_let) {
scope = scope.child(); scope = scope.child();
} }
@ -147,7 +165,7 @@ export default class Element extends Node {
const order = ['Binding']; // everything else is -1 const order = ['Binding']; // everything else is -1
info.attributes.sort((a, b) => order.indexOf(a.type) - order.indexOf(b.type)); info.attributes.sort((a, b) => order.indexOf(a.type) - order.indexOf(b.type));
info.attributes.forEach((node) => { info.attributes.forEach(node => {
switch (node.type) { switch (node.type) {
case 'Action': case 'Action':
this.actions.push(new Action(component, this, scope, node)); this.actions.push(new Action(component, this, scope, node));
@ -178,13 +196,14 @@ export default class Element extends Node {
this.lets.push(l); this.lets.push(l);
const dependencies = new Set([l.name.name]); const dependencies = new Set([l.name.name]);
l.names.forEach((name) => { l.names.forEach(name => {
scope.add(name, dependencies, this); scope.add(name, dependencies, this);
}); });
break; break;
} }
case 'Transition': { case 'Transition':
{
const transition = new Transition(component, this, scope, node); const transition = new Transition(component, this, scope, node);
if (node.intro) this.intro = transition; if (node.intro) this.intro = transition;
if (node.outro) this.outro = transition; if (node.outro) this.outro = transition;
@ -213,7 +232,7 @@ export default class Element extends Node {
// no-distracting-elements // no-distracting-elements
this.component.warn(this, { this.component.warn(this, {
code: `a11y-distracting-elements`, code: `a11y-distracting-elements`,
message: `A11y: Avoid <${this.name}> elements`, message: `A11y: Avoid <${this.name}> elements`
}); });
} }
@ -235,7 +254,7 @@ export default class Element extends Node {
if (!is_figure_parent) { if (!is_figure_parent) {
this.component.warn(this, { this.component.warn(this, {
code: `a11y-structure`, code: `a11y-structure`,
message: `A11y: <figcaption> must be an immediate child of <figure>`, message: `A11y: <figcaption> must be an immediate child of <figure>`
}); });
} }
} }
@ -247,12 +266,12 @@ export default class Element extends Node {
return true; return true;
}); });
const index = children.findIndex((child) => (child as Element).name === 'figcaption'); const index = children.findIndex(child => (child as Element).name === 'figcaption');
if (index !== -1 && index !== 0 && index !== children.length - 1) { if (index !== -1 && (index !== 0 && index !== children.length - 1)) {
this.component.warn(children[index], { this.component.warn(children[index], {
code: `a11y-structure`, code: `a11y-structure`,
message: `A11y: <figcaption> must be first or last child of <figure>`, message: `A11y: <figcaption> must be first or last child of <figure>`
}); });
} }
} }
@ -269,7 +288,7 @@ export default class Element extends Node {
const attribute_map = new Map(); const attribute_map = new Map();
this.attributes.forEach((attribute) => { this.attributes.forEach(attribute => {
if (attribute.is_spread) return; if (attribute.is_spread) return;
const name = attribute.name.toLowerCase(); const name = attribute.name.toLowerCase();
@ -280,7 +299,7 @@ export default class Element extends Node {
// aria-unsupported-elements // aria-unsupported-elements
component.warn(attribute, { component.warn(attribute, {
code: `a11y-aria-attributes`, code: `a11y-aria-attributes`,
message: `A11y: <${this.name}> should not have aria-* attributes`, message: `A11y: <${this.name}> should not have aria-* attributes`
}); });
} }
@ -292,14 +311,14 @@ export default class Element extends Node {
component.warn(attribute, { component.warn(attribute, {
code: `a11y-unknown-aria-attribute`, code: `a11y-unknown-aria-attribute`,
message, message
}); });
} }
if (name === 'aria-hidden' && /^h[1-6]$/.test(this.name)) { if (name === 'aria-hidden' && /^h[1-6]$/.test(this.name)) {
component.warn(attribute, { component.warn(attribute, {
code: `a11y-hidden`, code: `a11y-hidden`,
message: `A11y: <${this.name}> element should not be hidden`, message: `A11y: <${this.name}> element should not be hidden`
}); });
} }
} }
@ -310,7 +329,7 @@ export default class Element extends Node {
// aria-unsupported-elements // aria-unsupported-elements
component.warn(attribute, { component.warn(attribute, {
code: `a11y-misplaced-role`, code: `a11y-misplaced-role`,
message: `A11y: <${this.name}> should not have role attribute`, message: `A11y: <${this.name}> should not have role attribute`
}); });
} }
@ -324,7 +343,7 @@ export default class Element extends Node {
component.warn(attribute, { component.warn(attribute, {
code: `a11y-unknown-role`, code: `a11y-unknown-role`,
message, message
}); });
} }
} }
@ -333,7 +352,7 @@ export default class Element extends Node {
if (name === 'accesskey') { if (name === 'accesskey') {
component.warn(attribute, { component.warn(attribute, {
code: `a11y-accesskey`, code: `a11y-accesskey`,
message: `A11y: Avoid using accesskey`, message: `A11y: Avoid using accesskey`
}); });
} }
@ -341,7 +360,7 @@ export default class Element extends Node {
if (name === 'autofocus') { if (name === 'autofocus') {
component.warn(attribute, { component.warn(attribute, {
code: `a11y-autofocus`, code: `a11y-autofocus`,
message: `A11y: Avoid using autofocus`, message: `A11y: Avoid using autofocus`
}); });
} }
@ -349,7 +368,7 @@ export default class Element extends Node {
if (name === 'scope' && this.name !== 'th') { if (name === 'scope' && this.name !== 'th') {
component.warn(attribute, { component.warn(attribute, {
code: `a11y-misplaced-scope`, code: `a11y-misplaced-scope`,
message: `A11y: The scope attribute should only be used with <th> elements`, message: `A11y: The scope attribute should only be used with <th> elements`
}); });
} }
@ -360,7 +379,7 @@ export default class Element extends Node {
if (!isNaN(value) && +value > 0) { if (!isNaN(value) && +value > 0) {
component.warn(attribute, { component.warn(attribute, {
code: `a11y-positive-tabindex`, code: `a11y-positive-tabindex`,
message: `A11y: avoid tabindex values above zero`, message: `A11y: avoid tabindex values above zero`
}); });
} }
} }
@ -368,7 +387,7 @@ export default class Element extends Node {
if (/(^[0-9-.])|[\^$@%&#?!|()[\]{}^*+~;]/.test(name)) { if (/(^[0-9-.])|[\^$@%&#?!|()[\]{}^*+~;]/.test(name)) {
component.error(attribute, { component.error(attribute, {
code: `illegal-attribute`, code: `illegal-attribute`,
message: `'${name}' is not a valid attribute name`, message: `'${name}' is not a valid attribute name`
}); });
} }
@ -383,7 +402,7 @@ export default class Element extends Node {
if (component.slot_outlets.has(name)) { if (component.slot_outlets.has(name)) {
component.error(attribute, { component.error(attribute, {
code: `duplicate-slot-attribute`, code: `duplicate-slot-attribute`,
message: `Duplicate '${name}' slot`, message: `Duplicate '${name}' slot`
}); });
component.slot_outlets.add(name); component.slot_outlets.add(name);
@ -400,7 +419,7 @@ export default class Element extends Node {
if (name === 'is') { if (name === 'is') {
component.warn(attribute, { component.warn(attribute, {
code: 'avoid-is', code: 'avoid-is',
message: `The 'is' attribute is not supported cross-browser and should be avoided`, message: `The 'is' attribute is not supported cross-browser and should be avoided`
}); });
} }
@ -449,7 +468,7 @@ export default class Element extends Node {
} else { } else {
const required_attributes = a11y_required_attributes[this.name]; const required_attributes = a11y_required_attributes[this.name];
if (required_attributes) { if (required_attributes) {
const has_attribute = required_attributes.some((name) => attribute_map.has(name)); const has_attribute = required_attributes.some(name => attribute_map.has(name));
if (!has_attribute) { if (!has_attribute) {
should_have_attribute(this, required_attributes); should_have_attribute(this, required_attributes);
@ -461,7 +480,7 @@ export default class Element extends Node {
const type = attribute_map.get('type'); const type = attribute_map.get('type');
if (type && type.get_static_value() === 'image') { if (type && type.get_static_value() === 'image') {
const required_attributes = ['alt', 'aria-label', 'aria-labelledby']; const required_attributes = ['alt', 'aria-label', 'aria-labelledby'];
const has_attribute = required_attributes.some((name) => attribute_map.has(name)); const has_attribute = required_attributes.some(name => attribute_map.has(name));
if (!has_attribute) { if (!has_attribute) {
should_have_attribute(this, required_attributes, 'input type="image"'); should_have_attribute(this, required_attributes, 'input type="image"');
@ -501,14 +520,16 @@ export default class Element extends Node {
const { component } = this; const { component } = this;
const check_type_attribute = () => { const check_type_attribute = () => {
const attribute = this.attributes.find((attribute: Attribute) => attribute.name === 'type'); const attribute = this.attributes.find(
(attribute: Attribute) => attribute.name === 'type'
);
if (!attribute) return null; if (!attribute) return null;
if (!attribute.is_static) { if (!attribute.is_static) {
component.error(attribute, { component.error(attribute, {
code: `invalid-type`, code: `invalid-type`,
message: `'type' attribute cannot be dynamic if input uses two-way binding`, message: `'type' attribute cannot be dynamic if input uses two-way binding`
}); });
} }
@ -517,31 +538,37 @@ export default class Element extends Node {
if (value === true) { if (value === true) {
component.error(attribute, { component.error(attribute, {
code: `missing-type`, code: `missing-type`,
message: `'type' attribute must be specified`, message: `'type' attribute must be specified`
}); });
} }
return value; return value;
}; };
this.bindings.forEach((binding) => { this.bindings.forEach(binding => {
const { name } = binding; const { name } = binding;
if (name === 'value') { if (name === 'value') {
if (this.name !== 'input' && this.name !== 'textarea' && this.name !== 'select') { if (
this.name !== 'input' &&
this.name !== 'textarea' &&
this.name !== 'select'
) {
component.error(binding, { component.error(binding, {
code: `invalid-binding`, code: `invalid-binding`,
message: `'value' is not a valid binding on <${this.name}> elements`, message: `'value' is not a valid binding on <${this.name}> elements`
}); });
} }
if (this.name === 'select') { if (this.name === 'select') {
const attribute = this.attributes.find((attribute: Attribute) => attribute.name === 'multiple'); const attribute = this.attributes.find(
(attribute: Attribute) => attribute.name === 'multiple'
);
if (attribute && !attribute.is_static) { if (attribute && !attribute.is_static) {
component.error(attribute, { component.error(attribute, {
code: `dynamic-multiple-attribute`, code: `dynamic-multiple-attribute`,
message: `'multiple' attribute cannot be dynamic if select uses two-way binding`, message: `'multiple' attribute cannot be dynamic if select uses two-way binding`
}); });
} }
} else { } else {
@ -551,7 +578,7 @@ export default class Element extends Node {
if (this.name !== 'input') { if (this.name !== 'input') {
component.error(binding, { component.error(binding, {
code: `invalid-binding`, code: `invalid-binding`,
message: `'${name}' is not a valid binding on <${this.name}> elements`, message: `'${name}' is not a valid binding on <${this.name}> elements`
}); });
} }
@ -566,7 +593,7 @@ export default class Element extends Node {
if (this.name !== 'input') { if (this.name !== 'input') {
component.error(binding, { component.error(binding, {
code: `invalid-binding`, code: `invalid-binding`,
message: `'group' is not a valid binding on <${this.name}> elements`, message: `'group' is not a valid binding on <${this.name}> elements`
}); });
} }
@ -575,14 +602,14 @@ export default class Element extends Node {
if (type !== 'checkbox' && type !== 'radio') { if (type !== 'checkbox' && type !== 'radio') {
component.error(binding, { component.error(binding, {
code: `invalid-binding`, code: `invalid-binding`,
message: `'group' binding can only be used with <input type="checkbox"> or <input type="radio">`, message: `'group' binding can only be used with <input type="checkbox"> or <input type="radio">`
}); });
} }
} else if (name === 'files') { } else if (name === 'files') {
if (this.name !== 'input') { if (this.name !== 'input') {
component.error(binding, { component.error(binding, {
code: `invalid-binding`, code: `invalid-binding`,
message: `'files' is not a valid binding on <${this.name}> elements`, message: `'files' is not a valid binding on <${this.name}> elements`
}); });
} }
@ -591,14 +618,14 @@ export default class Element extends Node {
if (type !== 'file') { if (type !== 'file') {
component.error(binding, { component.error(binding, {
code: `invalid-binding`, code: `invalid-binding`,
message: `'files' binding can only be used with <input type="file">`, message: `'files' binding can only be used with <input type="file">`
}); });
} }
} else if (name === 'open') { } else if (name === 'open') {
if (this.name !== 'details') { if (this.name !== 'details') {
component.error(binding, { component.error(binding, {
code: `invalid-binding`, code: `invalid-binding`,
message: `'${name}' binding can only be used with <details>`, message: `'${name}' binding can only be used with <details>`
}); });
} }
} else if ( } else if (
@ -617,54 +644,59 @@ export default class Element extends Node {
if (this.name !== 'audio' && this.name !== 'video') { if (this.name !== 'audio' && this.name !== 'video') {
component.error(binding, { component.error(binding, {
code: `invalid-binding`, code: `invalid-binding`,
message: `'${name}' binding can only be used with <audio> or <video>`, message: `'${name}' binding can only be used with <audio> or <video>`
}); });
} }
} else if (name === 'videoHeight' || name === 'videoWidth') { } else if (
name === 'videoHeight' ||
name === 'videoWidth'
) {
if (this.name !== 'video') { if (this.name !== 'video') {
component.error(binding, { component.error(binding, {
code: `invalid-binding`, code: `invalid-binding`,
message: `'${name}' binding can only be used with <video>`, message: `'${name}' binding can only be used with <video>`
}); });
} }
} else if (dimensions.test(name)) { } else if (dimensions.test(name)) {
if (this.name === 'svg' && (name === 'offsetWidth' || name === 'offsetHeight')) { if (this.name === 'svg' && (name === 'offsetWidth' || name === 'offsetHeight')) {
component.error(binding, { component.error(binding, {
code: 'invalid-binding', code: 'invalid-binding',
message: `'${binding.name}' is not a valid binding on <svg>. Use '${name.replace( message: `'${binding.name}' is not a valid binding on <svg>. Use '${name.replace('offset', 'client')}' instead`,
'offset',
'client'
)}' instead`,
}); });
} else if (svg.test(this.name)) { } else if (svg.test(this.name)) {
component.error(binding, { component.error(binding, {
code: 'invalid-binding', code: 'invalid-binding',
message: `'${binding.name}' is not a valid binding on SVG elements`, message: `'${binding.name}' is not a valid binding on SVG elements`
}); });
} else if (is_void(this.name)) { } else if (is_void(this.name)) {
component.error(binding, { component.error(binding, {
code: 'invalid-binding', code: 'invalid-binding',
message: `'${binding.name}' is not a valid binding on void elements like <${this.name}>. Use a wrapper element instead`, message: `'${binding.name}' is not a valid binding on void elements like <${this.name}>. Use a wrapper element instead`
}); });
} }
} else if (name === 'textContent' || name === 'innerHTML') { } else if (
const contenteditable = this.attributes.find((attribute: Attribute) => attribute.name === 'contenteditable'); name === 'textContent' ||
name === 'innerHTML'
) {
const contenteditable = this.attributes.find(
(attribute: Attribute) => attribute.name === 'contenteditable'
);
if (!contenteditable) { if (!contenteditable) {
component.error(binding, { component.error(binding, {
code: `missing-contenteditable-attribute`, code: `missing-contenteditable-attribute`,
message: `'contenteditable' attribute is required for textContent and innerHTML two-way bindings`, message: `'contenteditable' attribute is required for textContent and innerHTML two-way bindings`
}); });
} else if (contenteditable && !contenteditable.is_static) { } else if (contenteditable && !contenteditable.is_static) {
component.error(contenteditable, { component.error(contenteditable, {
code: `dynamic-contenteditable-attribute`, code: `dynamic-contenteditable-attribute`,
message: `'contenteditable' attribute cannot be dynamic if element uses two-way binding`, message: `'contenteditable' attribute cannot be dynamic if element uses two-way binding`
}); });
} }
} else if (name !== 'this') { } else if (name !== 'this') {
component.error(binding, { component.error(binding, {
code: `invalid-binding`, code: `invalid-binding`,
message: `'${binding.name}' is not a valid binding`, message: `'${binding.name}' is not a valid binding`
}); });
} }
}); });
@ -676,7 +708,7 @@ export default class Element extends Node {
if (this.children.length === 0) { if (this.children.length === 0) {
this.component.warn(this, { this.component.warn(this, {
code: `a11y-missing-content`, code: `a11y-missing-content`,
message: `A11y: <${this.name}> element should have child content`, message: `A11y: <${this.name}> element should have child content`
}); });
} }
} }
@ -684,19 +716,19 @@ export default class Element extends Node {
validate_event_handlers() { validate_event_handlers() {
const { component } = this; const { component } = this;
this.handlers.forEach((handler) => { this.handlers.forEach(handler => {
if (handler.modifiers.has('passive') && handler.modifiers.has('preventDefault')) { if (handler.modifiers.has('passive') && handler.modifiers.has('preventDefault')) {
component.error(handler, { component.error(handler, {
code: 'invalid-event-modifier', code: 'invalid-event-modifier',
message: `The 'passive' and 'preventDefault' modifiers cannot be used together`, message: `The 'passive' and 'preventDefault' modifiers cannot be used together`
}); });
} }
handler.modifiers.forEach((modifier) => { handler.modifiers.forEach(modifier => {
if (!valid_modifiers.has(modifier)) { if (!valid_modifiers.has(modifier)) {
component.error(handler, { component.error(handler, {
code: 'invalid-event-modifier', code: 'invalid-event-modifier',
message: `Valid event modifiers are ${list(Array.from(valid_modifiers))}`, message: `Valid event modifiers are ${list(Array.from(valid_modifiers))}`
}); });
} }
@ -705,13 +737,13 @@ export default class Element extends Node {
if (handler.can_make_passive) { if (handler.can_make_passive) {
component.warn(handler, { component.warn(handler, {
code: 'redundant-event-modifier', code: 'redundant-event-modifier',
message: `Touch event handlers that don't use the 'event' object are passive by default`, message: `Touch event handlers that don't use the 'event' object are passive by default`
}); });
} }
} else { } else {
component.warn(handler, { component.warn(handler, {
code: 'redundant-event-modifier', code: 'redundant-event-modifier',
message: `The passive modifier only works with wheel and touch events`, message: `The passive modifier only works with wheel and touch events`
}); });
} }
} }
@ -729,14 +761,14 @@ export default class Element extends Node {
} }
add_css_class() { add_css_class() {
if (this.attributes.some((attr) => attr.is_spread)) { if (this.attributes.some(attr => attr.is_spread)) {
this.needs_manual_style_scoping = true; this.needs_manual_style_scoping = true;
return; return;
} }
const { id } = this.component.stylesheet; const { id } = this.component.stylesheet;
const class_attribute = this.attributes.find((a) => a.name === 'class'); const class_attribute = this.attributes.find(a => a.name === 'class');
if (class_attribute && !class_attribute.is_true) { if (class_attribute && !class_attribute.is_true) {
if (class_attribute.chunks.length === 1 && class_attribute.chunks[0].type === 'Text') { if (class_attribute.chunks.length === 1 && class_attribute.chunks[0].type === 'Text') {
@ -755,23 +787,26 @@ export default class Element extends Node {
new Attribute(this.component, this, this.scope, { new Attribute(this.component, this, this.scope, {
type: 'Attribute', type: 'Attribute',
name: 'class', name: 'class',
value: [{ type: 'Text', data: id, synthetic: true }], value: [{ type: 'Text', data: id, synthetic: true }]
}) })
); );
} }
} }
} }
function should_have_attribute(node, attributes: string[], name = node.name) { function should_have_attribute(
node,
attributes: string[],
name = node.name
) {
const article = /^[aeiou]/.test(attributes[0]) ? 'an' : 'a'; const article = /^[aeiou]/.test(attributes[0]) ? 'an' : 'a';
const sequence = const sequence = attributes.length > 1 ?
attributes.length > 1 attributes.slice(0, -1).join(', ') + ` or ${attributes[attributes.length - 1]}` :
? attributes.slice(0, -1).join(', ') + ` or ${attributes[attributes.length - 1]}` attributes[0];
: attributes[0];
node.component.warn(node, { node.component.warn(node, {
code: `a11y-missing-attribute`, code: `a11y-missing-attribute`,
message: `A11y: <${name}> element should have ${article} ${sequence} attribute`, message: `A11y: <${name}> element should have ${article} ${sequence} attribute`
}); });
} }

@ -30,7 +30,7 @@ export default class Expression {
scope: Scope; scope: Scope;
scope_map: WeakMap<Node, Scope>; scope_map: WeakMap<Node, Scope>;
declarations: Array<Node | Node[]> = []; declarations: Array<(Node | Node[])> = [];
uses_context = false; uses_context = false;
manipulated: Node; manipulated: Node;
@ -40,8 +40,8 @@ export default class Expression {
// TODO revert to direct property access in prod? // TODO revert to direct property access in prod?
Object.defineProperties(this, { Object.defineProperties(this, {
component: { component: {
value: component, value: component
}, }
}); });
this.node = info; this.node = info;
@ -79,13 +79,12 @@ export default class Expression {
if (name[0] === '$' && template_scope.names.has(name.slice(1))) { if (name[0] === '$' && template_scope.names.has(name.slice(1))) {
component.error(node, { component.error(node, {
code: `contextual-store`, code: `contextual-store`,
message: `Stores must be declared at the top level of the component (this may change in a future version of Svelte)`, message: `Stores must be declared at the top level of the component (this may change in a future version of Svelte)`
}); });
} }
if (template_scope.is_let(name)) { if (template_scope.is_let(name)) {
if (!function_expression) { if (!function_expression) { // TODO should this be `!lazy` ?
// TODO should this be `!lazy` ?
contextual_dependencies.add(name); contextual_dependencies.add(name);
dependencies.add(name); dependencies.add(name);
} }
@ -98,7 +97,7 @@ export default class Expression {
const is_index = owner.type === 'EachBlock' && owner.key && name === owner.index; const is_index = owner.type === 'EachBlock' && owner.key && name === owner.index;
if (!lazy || is_index) { if (!lazy || is_index) {
template_scope.dependencies_for_name.get(name).forEach((name) => dependencies.add(name)); template_scope.dependencies_for_name.get(name).forEach(name => dependencies.add(name));
} }
} else { } else {
if (!lazy) { if (!lazy) {
@ -119,7 +118,9 @@ export default class Expression {
if (function_expression) { if (function_expression) {
if (node.type === 'AssignmentExpression') { if (node.type === 'AssignmentExpression') {
deep = node.left.type === 'MemberExpression'; deep = node.left.type === 'MemberExpression';
names = deep ? [get_object(node.left).name] : extract_names(node.left); names = deep
? [get_object(node.left).name]
: extract_names(node.left);
} else if (node.type === 'UpdateExpression') { } else if (node.type === 'UpdateExpression') {
const { name } = get_object(node.argument); const { name } = get_object(node.argument);
names = [name]; names = [name];
@ -127,9 +128,9 @@ export default class Expression {
} }
if (names) { if (names) {
names.forEach((name) => { names.forEach(name => {
if (template_scope.names.has(name)) { if (template_scope.names.has(name)) {
template_scope.dependencies_for_name.get(name).forEach((name) => { template_scope.dependencies_for_name.get(name).forEach(name => {
const variable = component.var_lookup.get(name); const variable = component.var_lookup.get(name);
if (variable) variable[deep ? 'mutated' : 'reassigned'] = true; if (variable) variable[deep ? 'mutated' : 'reassigned'] = true;
}); });
@ -151,12 +152,12 @@ export default class Expression {
if (node === function_expression) { if (node === function_expression) {
function_expression = null; function_expression = null;
} }
}, }
}); });
} }
dynamic_dependencies() { dynamic_dependencies() {
return Array.from(this.dependencies).filter((name) => { return Array.from(this.dependencies).filter(name => {
if (this.template_scope.is_let(name)) return true; if (this.template_scope.is_let(name)) return true;
if (is_reserved_keyword(name)) return true; if (is_reserved_keyword(name)) return true;
@ -171,7 +172,13 @@ export default class Expression {
// multiple times // multiple times
if (this.manipulated) return this.manipulated; if (this.manipulated) return this.manipulated;
const { component, declarations, scope_map: map, template_scope, owner } = this; const {
component,
declarations,
scope_map: map,
template_scope,
owner
} = this;
let scope = this.scope; let scope = this.scope;
let function_expression; let function_expression;
@ -199,7 +206,7 @@ export default class Expression {
if (template_scope.names.has(name)) { if (template_scope.names.has(name)) {
contextual_dependencies.add(name); contextual_dependencies.add(name);
template_scope.dependencies_for_name.get(name).forEach((dependency) => { template_scope.dependencies_for_name.get(name).forEach(dependency => {
dependencies.add(dependency); dependencies.add(dependency);
}); });
} else { } else {
@ -231,7 +238,9 @@ export default class Expression {
if (map.has(node)) scope = scope.parent; if (map.has(node)) scope = scope.parent;
if (node === function_expression) { if (node === function_expression) {
const id = component.get_unique_name(sanitize(get_function_name(node, owner))); const id = component.get_unique_name(
sanitize(get_function_name(node, owner))
);
const declaration = b`const ${id} = ${node}`; const declaration = b`const ${id} = ${node}`;
@ -245,21 +254,25 @@ export default class Expression {
name: id.name, name: id.name,
internal: true, internal: true,
hoistable: true, hoistable: true,
referenced: 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(declaration); component.partly_hoisted.push(declaration);
block.renderer.add_to_context(id.name); block.renderer.add_to_context(id.name);
this.replace(block.renderer.reference(id)); this.replace(block.renderer.reference(id));
} else { }
else {
// we need a combo block/init recipe // we need a combo block/init recipe
const deps = Array.from(contextual_dependencies); const deps = Array.from(contextual_dependencies);
(node as FunctionExpression).params = [ (node as FunctionExpression).params = [
...deps.map((name) => ({ type: 'Identifier', name } as Identifier)), ...deps.map(name => ({ type: 'Identifier', name } as Identifier)),
...(node as FunctionExpression).params, ...(node as FunctionExpression).params
]; ];
const context_args = deps.map((name) => block.renderer.reference(name)); const context_args = deps.map((name) => block.renderer.reference(name));
@ -305,10 +318,10 @@ export default class Expression {
const names = new Set(extract_names(assignee)); const names = new Set(extract_names(assignee));
const traced: Set<string> = new Set(); const traced: Set<string> = new Set();
names.forEach((name) => { names.forEach(name => {
const dependencies = template_scope.dependencies_for_name.get(name); const dependencies = template_scope.dependencies_for_name.get(name);
if (dependencies) { if (dependencies) {
dependencies.forEach((name) => traced.add(name)); dependencies.forEach(name => traced.add(name));
} else { } else {
traced.add(name); traced.add(name);
} }
@ -316,12 +329,12 @@ export default class Expression {
this.replace(invalidate(block.renderer, scope, node, traced)); this.replace(invalidate(block.renderer, scope, node, traced));
} }
}, }
}); });
if (declarations.length > 0) { if (declarations.length > 0) {
block.maintain_context = true; block.maintain_context = true;
declarations.forEach((declaration) => { declarations.forEach(declaration => {
block.chunks.init.push(declaration); block.chunks.init.push(declaration);
}); });
} }

@ -11,17 +11,14 @@ export interface BlockOptions {
renderer?: Renderer; renderer?: Renderer;
comment?: string; comment?: string;
key?: Identifier; key?: Identifier;
bindings?: Map< bindings?: Map<string, {
string,
{
object: Identifier; object: Identifier;
property: Identifier; property: Identifier;
snippet: Node; snippet: Node;
store: string; store: string;
tail: Node; tail: Node;
modifier: (node: Node) => Node; modifier: (node: Node) => Node;
} }>;
>;
dependencies?: Set<string>; dependencies?: Set<string>;
} }
@ -39,17 +36,14 @@ export default class Block {
dependencies: Set<string> = new Set(); dependencies: Set<string> = new Set();
bindings: Map< bindings: Map<string, {
string,
{
object: Identifier; object: Identifier;
property: Identifier; property: Identifier;
snippet: Node; snippet: Node;
store: string; store: string;
tail: Node; tail: Node;
modifier: (node: Node) => Node; modifier: (node: Node) => Node;
} }>;
>;
chunks: { chunks: {
declarations: Array<Node | Node[]>; declarations: Array<Node | Node[]>;
@ -163,7 +157,7 @@ export default class Block {
} }
add_dependencies(dependencies: Set<string>) { add_dependencies(dependencies: Set<string>) {
dependencies.forEach((dependency) => { dependencies.forEach(dependency => {
this.dependencies.add(dependency); this.dependencies.add(dependency);
}); });
@ -173,7 +167,13 @@ export default class Block {
} }
} }
add_element(id: Identifier, render_statement: Node, claim_statement: Node, parent_node: Node, no_detach?: boolean) { add_element(
id: Identifier,
render_statement: Node,
claim_statement: Node,
parent_node: Node,
no_detach?: boolean
) {
this.add_variable(id); this.add_variable(id);
this.chunks.create.push(b`${id} = ${render_statement};`); this.chunks.create.push(b`${id} = ${render_statement};`);
@ -207,7 +207,9 @@ export default class Block {
add_variable(id: Identifier, init?: Node) { add_variable(id: Identifier, init?: Node) {
if (this.variables.has(id.name)) { if (this.variables.has(id.name)) {
throw new Error(`Variable '${id.name}' already initialised with a different value`); throw new Error(
`Variable '${id.name}' already initialised with a different value`
);
} }
this.variables.set(id.name, { id, init }); this.variables.set(id.name, { id, init });
@ -266,8 +268,11 @@ export default class Block {
if (this.chunks.create.length === 0 && this.chunks.hydrate.length === 0) { if (this.chunks.create.length === 0 && this.chunks.hydrate.length === 0) {
properties.create = noop; properties.create = noop;
} else { } else {
const hydrate = const hydrate = this.chunks.hydrate.length > 0 && (
this.chunks.hydrate.length > 0 && (this.renderer.options.hydratable ? b`this.h();` : this.chunks.hydrate); this.renderer.options.hydratable
? b`this.h();`
: this.chunks.hydrate
);
properties.create = x`function #create() { properties.create = x`function #create() {
${this.chunks.create} ${this.chunks.create}
@ -393,25 +398,23 @@ export default class Block {
${this.chunks.declarations} ${this.chunks.declarations}
${Array.from(this.variables.values()).map(({ id, init }) => { ${Array.from(this.variables.values()).map(({ id, init }) => {
return init ? b`let ${id} = ${init}` : b`let ${id}`; return init
? b`let ${id} = ${init}`
: b`let ${id}`;
})} })}
${this.chunks.init} ${this.chunks.init}
${ ${dev
dev
? b` ? b`
const ${block} = ${return_value}; const ${block} = ${return_value};
${ @dispatch_dev("SvelteRegisterBlock", {
dev &&
b`@dispatch_dev("SvelteRegisterBlock", {
block: ${block}, block: ${block},
id: ${this.name || 'create_fragment'}.name, id: ${this.name || 'create_fragment'}.name,
type: "${this.type}", type: "${this.type}",
source: "${this.comment ? this.comment.replace(/"/g, '\\"') : ''}", source: "${this.comment ? this.comment.replace(/"/g, '\\"') : ''}",
ctx: #ctx ctx: #ctx
});` });
}
return ${block};` return ${block};`
: b` : b`
return ${return_value};` return ${return_value};`
@ -422,8 +425,7 @@ export default class Block {
} }
has_content(): boolean { has_content(): boolean {
return ( return !!this.first ||
!!this.first ||
this.event_listeners.length > 0 || this.event_listeners.length > 0 ||
this.chunks.intro.length > 0 || this.chunks.intro.length > 0 ||
this.chunks.outro.length > 0 || this.chunks.outro.length > 0 ||
@ -433,8 +435,8 @@ export default class Block {
this.chunks.mount.length > 0 || this.chunks.mount.length > 0 ||
this.chunks.update.length > 0 || this.chunks.update.length > 0 ||
this.chunks.destroy.length > 0 || this.chunks.destroy.length > 0 ||
this.has_animation this.has_animation;
);
} }
render() { render() {
@ -458,7 +460,7 @@ export default class Block {
if (this.event_listeners.length > 0) { if (this.event_listeners.length > 0) {
const dispose: Identifier = { const dispose: Identifier = {
type: 'Identifier', type: 'Identifier',
name: `dispose${chunk}`, name: `dispose${chunk}`
}; };
this.add_variable(dispose); this.add_variable(dispose);
@ -471,7 +473,9 @@ export default class Block {
` `
); );
this.chunks.destroy.push(b`${dispose}();`); this.chunks.destroy.push(
b`${dispose}();`
);
} else { } else {
this.chunks.mount.push(b` this.chunks.mount.push(b`
if (#remount) for(let i=0;i<${dispose}.length;i++){ ${dispose}[i](); } if (#remount) for(let i=0;i<${dispose}.length;i++){ ${dispose}[i](); }

@ -1,14 +1,24 @@
import { b, x, p } from 'code-red'; import { b, x, p } from "code-red";
import Component from '../Component'; import Component from "../Component";
import Renderer from './Renderer'; import Renderer from "./Renderer";
import { CompileOptions, CssResult } from '../../interfaces'; import { CompileOptions, CssResult } from "../../interfaces";
import { walk } from 'estree-walker'; import { walk } from "estree-walker";
import { extract_names, Scope } from '../utils/scope'; import { extract_names, Scope } from "../utils/scope";
import { invalidate } from './invalidate'; import { invalidate } from "./invalidate";
import Block from './Block'; import Block from "./Block";
import { ClassDeclaration, FunctionExpression, Node, Statement, ObjectExpression, Expression } from 'estree'; import {
ClassDeclaration,
export default function dom(component: Component, options: CompileOptions): { js: Node[]; css: CssResult } { FunctionExpression,
Node,
Statement,
ObjectExpression,
Expression,
} from "estree";
export default function dom(
component: Component,
options: CompileOptions
): { js: Node[]; css: CssResult } {
const { name } = component; const { name } = component;
const renderer = new Renderer(component, options); const renderer = new Renderer(component, options);
@ -26,15 +36,19 @@ export default function dom(component: Component, options: CompileOptions): { js
body.push(b`const ${renderer.file_var} = ${file};`); body.push(b`const ${renderer.file_var} = ${file};`);
} }
const css = component.stylesheet.render(options.filename, !options.customElement); const css = component.stylesheet.render(
options.filename,
!options.customElement
);
const styles = const styles =
component.stylesheet.has_styles && options.dev component.stylesheet.has_styles && options.dev
? `${css.code}\n/*# sourceMappingURL=${css.map.toUrl()} */` ? `${css.code}\n/*# sourceMappingURL=${css.map.toUrl()} */`
: css.code; : css.code;
const add_css = component.get_unique_name('add_css'); const add_css = component.get_unique_name("add_css");
const should_add_css = !options.customElement && !!styles && options.css !== false; const should_add_css =
!options.customElement && !!styles && options.css !== false;
if (should_add_css) { if (should_add_css) {
body.push(b` body.push(b`
@ -66,17 +80,21 @@ export default function dom(component: Component, options: CompileOptions): { js
); );
} }
const uses_props = component.var_lookup.has('$$props'); const uses_props = component.var_lookup.has("$$props");
const uses_rest = component.var_lookup.has('$$restProps'); const uses_rest = component.var_lookup.has("$$restProps");
const uses_any = uses_props || uses_rest; const uses_any = uses_props || uses_rest;
const $$props = uses_any ? `$$new_props` : `$$props`; const $$props = uses_any ? `$$new_props` : `$$props`;
const props = component.vars.filter((variable) => !variable.module && variable.export_name); const props = component.vars.filter(
(variable) => !variable.module && variable.export_name
);
const writable_props = props.filter((variable) => variable.writable); const writable_props = props.filter((variable) => variable.writable);
const rest = uses_rest const rest = uses_rest
? b` ? b`
let #k; let #k;
const #keys = new Set([${props.map((prop) => `"${prop.export_name}"`).join(',')}]); const #keys = new Set([${props
.map((prop) => `"${prop.export_name}"`)
.join(",")}]);
const $$restProps = {}; const $$restProps = {};
for (#k in $$props) { for (#k in $$props) {
if (!#keys.has(#k) && #k[0] !== '$') { if (!#keys.has(#k) && #k[0] !== '$') {
@ -106,8 +124,8 @@ export default function dom(component: Component, options: CompileOptions): { js
} }
} }
} }
${uses_props && renderer.invalidate('$$props')} ${uses_props && renderer.invalidate("$$props")}
${uses_rest && renderer.invalidate('$$restProps')} ${uses_rest && renderer.invalidate("$$restProps")}
` `
} }
${writable_props.map( ${writable_props.map(
@ -119,14 +137,19 @@ export default function dom(component: Component, options: CompileOptions): { js
)} )}
${ ${
component.slots.size && component.slots.size &&
b`if ('$$scope' in ${$$props}) ${renderer.invalidate('$$scope', x`$$scope = ${$$props}.$$scope`)};` b`if ('$$scope' in ${$$props}) ${renderer.invalidate(
"$$scope",
x`$$scope = ${$$props}.$$scope`
)};`
} }
}` }`
: null; : null;
const accessors = []; const accessors = [];
const not_equal = component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`; const not_equal = component.component_options.immutable
? x`@not_equal`
: x`@safe_not_equal`;
let dev_props_check: Node[] | Node; let dev_props_check: Node[] | Node;
let inject_state: Expression; let inject_state: Expression;
let capture_state: Expression; let capture_state: Expression;
@ -137,18 +160,22 @@ export default function dom(component: Component, options: CompileOptions): { js
if (!variable.writable || component.component_options.accessors) { if (!variable.writable || component.component_options.accessors) {
accessors.push({ accessors.push({
type: 'MethodDefinition', type: "MethodDefinition",
kind: 'get', kind: "get",
key: { type: 'Identifier', name: prop.export_name }, key: { type: "Identifier", name: prop.export_name },
value: x`function() { value: x`function() {
return ${prop.hoistable ? prop.name : x`this.$$.ctx[${renderer.context_lookup.get(prop.name).index}]`} return ${
prop.hoistable
? prop.name
: x`this.$$.ctx[${renderer.context_lookup.get(prop.name).index}]`
}
}`, }`,
}); });
} else if (component.compile_options.dev) { } else if (component.compile_options.dev) {
accessors.push({ accessors.push({
type: 'MethodDefinition', type: "MethodDefinition",
kind: 'get', kind: "get",
key: { type: 'Identifier', name: prop.export_name }, key: { type: "Identifier", name: prop.export_name },
value: x`function() { value: x`function() {
throw new @_Error("<${component.tag}>: Props cannot be read directly from the component instance unless compiling with 'accessors: true' or '<svelte:options accessors/>'"); throw new @_Error("<${component.tag}>: Props cannot be read directly from the component instance unless compiling with 'accessors: true' or '<svelte:options accessors/>'");
}`, }`,
@ -158,9 +185,9 @@ export default function dom(component: Component, options: CompileOptions): { js
if (component.component_options.accessors) { if (component.component_options.accessors) {
if (variable.writable && !renderer.readonly.has(prop.name)) { if (variable.writable && !renderer.readonly.has(prop.name)) {
accessors.push({ accessors.push({
type: 'MethodDefinition', type: "MethodDefinition",
kind: 'set', kind: "set",
key: { type: 'Identifier', name: prop.export_name }, key: { type: "Identifier", name: prop.export_name },
value: x`function(${prop.name}) { value: x`function(${prop.name}) {
this.$set({ ${prop.export_name}: ${prop.name} }); this.$set({ ${prop.export_name}: ${prop.name} });
@flush(); @flush();
@ -168,9 +195,9 @@ export default function dom(component: Component, options: CompileOptions): { js
}); });
} else if (component.compile_options.dev) { } else if (component.compile_options.dev) {
accessors.push({ accessors.push({
type: 'MethodDefinition', type: "MethodDefinition",
kind: 'set', kind: "set",
key: { type: 'Identifier', name: prop.export_name }, key: { type: "Identifier", name: prop.export_name },
value: x`function(value) { value: x`function(value) {
throw new @_Error("<${component.tag}>: Cannot set read-only property '${prop.export_name}'"); throw new @_Error("<${component.tag}>: Cannot set read-only property '${prop.export_name}'");
}`, }`,
@ -178,9 +205,9 @@ export default function dom(component: Component, options: CompileOptions): { js
} }
} else if (component.compile_options.dev) { } else if (component.compile_options.dev) {
accessors.push({ accessors.push({
type: 'MethodDefinition', type: "MethodDefinition",
kind: 'set', kind: "set",
key: { type: 'Identifier', name: prop.export_name }, key: { type: "Identifier", name: prop.export_name },
value: x`function(value) { value: x`function(value) {
throw new @_Error("<${component.tag}>: Props cannot be set directly on the component instance unless compiling with 'accessors: true' or '<svelte:options accessors/>'"); throw new @_Error("<${component.tag}>: Props cannot be set directly on the component instance unless compiling with 'accessors: true' or '<svelte:options accessors/>'");
}`, }`,
@ -195,30 +222,52 @@ export default function dom(component: Component, options: CompileOptions): { js
if (expected.length) { if (expected.length) {
dev_props_check = b` dev_props_check = b`
const { ctx: #ctx } = this.$$; const { ctx: #ctx } = this.$$;
const props = ${options.customElement ? x`this.attributes` : x`options.props || {}`}; const props = ${
options.customElement ? x`this.attributes` : x`options.props || {}`
};
${expected.map( ${expected.map(
(prop) => b` (prop) => b`
if (${renderer.reference(prop.name)} === undefined && !('${prop.export_name}' in props)) { if (${renderer.reference(prop.name)} === undefined && !('${
@_console.warn("<${component.tag}> was created without expected prop '${prop.export_name}'"); prop.export_name
}' in props)) {
@_console.warn("<${component.tag}> was created without expected prop '${
prop.export_name
}'");
}` }`
)} )}
`; `;
} }
const capturable_vars = component.vars.filter((v) => !v.internal && !v.global && !v.name.startsWith('$$')); const capturable_vars = component.vars.filter(
(v) => !v.internal && !v.global && !v.name.startsWith("$$")
);
if (capturable_vars.length > 0) { if (capturable_vars.length > 0) {
capture_state = x`() => ({ ${capturable_vars.map((prop) => p`${prop.name}`)} })`; capture_state = x`() => ({ ${capturable_vars.map(
(prop) => p`${prop.name}`
)} })`;
} }
const injectable_vars = capturable_vars.filter((v) => !v.module && v.writable && v.name[0] !== '$'); const injectable_vars = capturable_vars.filter(
(v) => !v.module && v.writable && v.name[0] !== "$"
);
if (uses_props || injectable_vars.length > 0) { if (uses_props || injectable_vars.length > 0) {
inject_state = x` inject_state = x`
${$$props} => { ${$$props} => {
${uses_props && renderer.invalidate('$$props', x`$$props = { ...$$props, ...$$new_props }`)} ${
uses_props &&
renderer.invalidate(
"$$props",
x`$$props = { ...$$props, ...$$new_props }`
)
}
${injectable_vars.map( ${injectable_vars.map(
(v) => b`if ('${v.name}' in $$props) ${renderer.invalidate(v.name, x`${v.name} = ${$$props}.${v.name}`)};` (v) =>
b`if ('${v.name}' in $$props) ${renderer.invalidate(
v.name,
x`${v.name} = ${$$props}.${v.name}`
)};`
)} )}
} }
`; `;
@ -245,7 +294,11 @@ export default function dom(component: Component, options: CompileOptions): { js
if (!execution_context && !scope.block) { if (!execution_context && !scope.block) {
execution_context = node; execution_context = node;
} }
} else if (!execution_context && node.type === 'LabeledStatement' && node.label.name === '$') { } else if (
!execution_context &&
node.type === "LabeledStatement" &&
node.label.name === "$"
) {
execution_context = node; execution_context = node;
} }
}, },
@ -259,8 +312,12 @@ export default function dom(component: Component, options: CompileOptions): { js
execution_context = null; execution_context = null;
} }
if (node.type === 'AssignmentExpression' || node.type === 'UpdateExpression') { if (
const assignee = node.type === 'AssignmentExpression' ? node.left : node.argument; node.type === "AssignmentExpression" ||
node.type === "UpdateExpression"
) {
const assignee =
node.type === "AssignmentExpression" ? node.left : node.argument;
// normally (`a = 1`, `b.c = 2`), there'll be a single name // normally (`a = 1`, `b.c = 2`), there'll be a single name
// (a or b). In destructuring cases (`[d, e] = [e, d]`) there // (a or b). In destructuring cases (`[d, e] = [e, d]`) there
@ -268,7 +325,9 @@ export default function dom(component: Component, options: CompileOptions): { js
// onto the initial function call // onto the initial function call
const names = new Set(extract_names(assignee)); const names = new Set(extract_names(assignee));
this.replace(invalidate(renderer, scope, node, names, execution_context === null)); this.replace(
invalidate(renderer, scope, node, names, execution_context === null)
);
} }
}, },
}); });
@ -279,8 +338,8 @@ export default function dom(component: Component, options: CompileOptions): { js
const insert = const insert =
reassigned || export_name reassigned || export_name
? b`${`$$subscribe_${name}`}();` ? b`${`$$subscribe_${name}`}()`
: b`$$self.$$.on_destroy.push(@subscribe(${name}, (value) => {$$invalidate(${i}, (${value} = value));}));`; : b`@component_subscribe($$self, ${name}, #value => $$invalidate(${i}, ${value} = #value))`;
if (component.compile_options.dev) { if (component.compile_options.dev) {
return b`@validate_store_dev(${name}, '${name}'); ${insert}`; return b`@validate_store_dev(${name}, '${name}'); ${insert}`;
@ -292,7 +351,11 @@ export default function dom(component: Component, options: CompileOptions): { js
const args = [x`$$self`]; const args = [x`$$self`];
const has_invalidate = const has_invalidate =
props.length > 0 || component.has_reactive_assignments || component.slots.size > 0 || capture_state || inject_state; props.length > 0 ||
component.has_reactive_assignments ||
component.slots.size > 0 ||
capture_state ||
inject_state;
if (has_invalidate) { if (has_invalidate) {
args.push(x`$$props`, x`$$invalidate`); args.push(x`$$props`, x`$$invalidate`);
} else if (component.compile_options.dev) { } else if (component.compile_options.dev) {
@ -300,7 +363,8 @@ export default function dom(component: Component, options: CompileOptions): { js
args.push(x`$$props`); args.push(x`$$props`);
} }
const has_create_fragment = component.compile_options.dev || block.has_content(); const has_create_fragment =
component.compile_options.dev || block.has_content();
if (has_create_fragment) { if (has_create_fragment) {
body.push(b` body.push(b`
function create_fragment(#ctx) { function create_fragment(#ctx) {
@ -319,13 +383,17 @@ export default function dom(component: Component, options: CompileOptions): { js
const variable = component.var_lookup.get(prop.name); const variable = component.var_lookup.get(prop.name);
if (variable.hoistable) return false; if (variable.hoistable) return false;
if (prop.name[0] === '$') return false; if (prop.name[0] === "$") return false;
return true; return true;
}); });
const reactive_stores = component.vars.filter((variable) => variable.name[0] === '$' && variable.name[1] !== '$'); const reactive_stores = component.vars.filter(
(variable) => variable.name[0] === "$" && variable.name[1] !== "$"
);
const instance_javascript = component.extract_javascript(component.ast.instance); const instance_javascript = component.extract_javascript(
component.ast.instance
);
let i = renderer.context.length; let i = renderer.context.length;
while (i--) { while (i--) {
@ -349,7 +417,9 @@ export default function dom(component: Component, options: CompileOptions): { js
capture_state || capture_state ||
inject_state; inject_state;
const definition = has_definition ? component.alias('instance') : { type: 'Literal', value: null }; const definition = has_definition
? component.alias("instance")
: { type: "Literal", value: null };
const reactive_store_subscriptions = reactive_stores const reactive_store_subscriptions = reactive_stores
.filter((store) => { .filter((store) => {
@ -358,7 +428,10 @@ export default function dom(component: Component, options: CompileOptions): { js
}) })
.map( .map(
({ name }) => b` ({ name }) => b`
${component.compile_options.dev && b`@validate_store_dev(${name.slice(1)}, '${name.slice(1)}');`} ${
component.compile_options.dev &&
b`@validate_store_dev(${name.slice(1)}, '${name.slice(1)}');`
}
$$self.$$.on_destroy.push(@subscribe(${name.slice(1)}, #value => { $$self.$$.on_destroy.push(@subscribe(${name.slice(1)}, #value => {
$$invalidate(${renderer.context_lookup.get(name).index}, ${name} = #value); $$invalidate(${renderer.context_lookup.get(name).index}, ${name} = #value);
})); }));
@ -370,7 +443,12 @@ export default function dom(component: Component, options: CompileOptions): { js
const variable = component.var_lookup.get(store.name.slice(1)); const variable = component.var_lookup.get(store.name.slice(1));
return variable && (variable.reassigned || variable.export_name); return variable && (variable.reassigned || variable.export_name);
}) })
.map(({ name }) => b`$$self.$$.on_destroy.push(() => ${`$$unsubscribe_${name.slice(1)}`}());`); .map(
({ name }) =>
b`$$self.$$.on_destroy.push(() => ${`$$unsubscribe_${name.slice(
1
)}`}());`
);
if (has_definition) { if (has_definition) {
const reactive_declarations: Node | Node[] = []; const reactive_declarations: Node | Node[] = [];
@ -378,29 +456,40 @@ export default function dom(component: Component, options: CompileOptions): { js
component.reactive_declarations.forEach((d) => { component.reactive_declarations.forEach((d) => {
const dependencies = Array.from(d.dependencies); const dependencies = Array.from(d.dependencies);
const uses_rest_or_props = !!dependencies.find((n) => n === '$$props' || n === '$$restProps'); const uses_rest_or_props = !!dependencies.find(
(n) => n === "$$props" || n === "$$restProps"
);
const writable = dependencies.filter((n) => { const writable = dependencies.filter((n) => {
const variable = component.var_lookup.get(n); const variable = component.var_lookup.get(n);
return variable && (variable.export_name || variable.mutated || variable.reassigned); return (
variable &&
(variable.export_name || variable.mutated || variable.reassigned)
);
}); });
const condition = !uses_rest_or_props && writable.length > 0 && renderer.dirty(writable, true); const condition =
!uses_rest_or_props &&
writable.length > 0 &&
renderer.dirty(writable, true);
let statement = d.node; // TODO remove label (use d.node.body) if it's not referenced let statement = d.node; // TODO remove label (use d.node.body) if it's not referenced
if (condition) statement = b`if (${condition}) { ${statement} }`[0] as Statement; if (condition)
statement = b`if (${condition}) { ${statement} }`[0] as Statement;
if (condition || uses_rest_or_props) { if (condition || uses_rest_or_props) {
statement && reactive_declarations.push(statement); reactive_declarations.push(statement);
} else { } else {
statement && fixed_reactive_declarations.push(statement); fixed_reactive_declarations.push(statement);
} }
}); });
const injected = Array.from(component.injected_reactive_declaration_vars).filter((name) => { const injected = Array.from(
component.injected_reactive_declaration_vars
).filter((name) => {
const variable = component.var_lookup.get(name); const variable = component.var_lookup.get(name);
return variable.injected && variable.name[0] !== '$'; return variable.injected && variable.name[0] !== "$";
}); });
const reactive_store_declarations = reactive_stores.map((variable) => { const reactive_store_declarations = reactive_stores.map((variable) => {
@ -413,7 +502,13 @@ export default function dom(component: Component, options: CompileOptions): { js
const subscribe = `$$subscribe_${name}`; const subscribe = `$$subscribe_${name}`;
const i = renderer.context_lookup.get($name).index; const i = renderer.context_lookup.get($name).index;
return b`let ${$name},${unsubscribe}=@noop,${subscribe}=()=>(${unsubscribe}(),${unsubscribe}=@subscribe(${name},(#value)=>{$$invalidate(${i},${$name}=#value);}),${name});`; return b`
let ${$name},
${unsubscribe} = @noop,
${subscribe}= () => ( ${unsubscribe}(),
${unsubscribe} = @subscribe(${name}, (#value) => { $$invalidate(${i}, ${$name} = #value); }),
${name}
);`;
} }
return b`let ${$name};`; return b`let ${$name};`;
@ -422,7 +517,9 @@ export default function dom(component: Component, options: CompileOptions): { js
let unknown_props_check; let unknown_props_check;
if (component.compile_options.dev && !(uses_props || uses_rest)) { if (component.compile_options.dev && !(uses_props || uses_rest)) {
unknown_props_check = b` unknown_props_check = b`
const writable_props = [${writable_props.map((prop) => x`'${prop.export_name}'`)}]; const writable_props = [${writable_props.map(
(prop) => x`'${prop.export_name}'`
)}];
@_Object.keys($$props).forEach(key => { @_Object.keys($$props).forEach(key => {
if (!~writable_props.indexOf(key) && key.slice(0, 2) !== '$$') @_console.warn(\`<${ if (!~writable_props.indexOf(key) && key.slice(0, 2) !== '$$') @_console.warn(\`<${
component.tag component.tag
@ -432,11 +529,11 @@ export default function dom(component: Component, options: CompileOptions): { js
} }
const return_value = { const return_value = {
type: 'ArrayExpression', type: "ArrayExpression",
elements: initial_context.map( elements: initial_context.map(
(member) => (member) =>
({ ({
type: 'Identifier', type: "Identifier",
name: member.name, name: member.name,
} as Expression) } as Expression)
), ),
@ -456,15 +553,27 @@ export default function dom(component: Component, options: CompileOptions): { js
${unknown_props_check} ${unknown_props_check}
${component.slots.size || component.compile_options.dev ? b`let { $$slots = {}, $$scope } = $$props;` : null} ${
component.slots.size || component.compile_options.dev
? b`let { $$slots = {}, $$scope } = $$props;`
: null
}
${ ${
component.compile_options.dev && component.compile_options.dev &&
b`@validate_slots_dev('${component.tag}', $$slots, [${[...component.slots.keys()] b`@validate_slots_dev('${component.tag}', $$slots, [${[
...component.slots.keys(),
]
.map((key) => `'${key}'`) .map((key) => `'${key}'`)
.join(',')}]);` .join(",")}]);`
} }
${renderer.binding_groups.length ? b`const $$binding_groups = [${renderer.binding_groups.map((_) => x`[]`)}];` : null} ${
renderer.binding_groups.length
? b`const $$binding_groups = [${renderer.binding_groups.map(
(_) => x`[]`
)}];`
: null
}
${component.partly_hoisted} ${component.partly_hoisted}
@ -478,7 +587,14 @@ export default function dom(component: Component, options: CompileOptions): { js
${/* before reactive declarations */ props_inject} ${/* before reactive declarations */ props_inject}
${reactive_declarations.length ? b`$$self.$$.update = () => {${reactive_declarations}};` : null} ${
reactive_declarations.length > 0 &&
b`
$$self.$$.update = () => {
${reactive_declarations}
};
`
}
${fixed_reactive_declarations} ${fixed_reactive_declarations}
@ -497,7 +613,9 @@ export default function dom(component: Component, options: CompileOptions): { js
const prop_indexes = x`{ const prop_indexes = x`{
${props ${props
.filter((v) => v.export_name && !v.module) .filter((v) => v.export_name && !v.module)
.map((v) => p`${v.export_name}: ${renderer.context_lookup.get(v.name).index}`)} .map(
(v) => p`${v.export_name}: ${renderer.context_lookup.get(v.name).index}`
)}
}` as ObjectExpression; }` as ObjectExpression;
let dirty; let dirty;
@ -516,13 +634,16 @@ export default function dom(component: Component, options: CompileOptions): { js
${ ${
css.code && css.code &&
b`this.shadowRoot.innerHTML = \`<style>${css.code.replace(/\\/g, '\\\\')}${ b`this.shadowRoot.innerHTML = \`<style>${css.code.replace(
options.dev ? `\n/*# sourceMappingURL=${css.map.toUrl()} */` : '' /\\/g,
"\\\\"
)}${
options.dev ? `\n/*# sourceMappingURL=${css.map.toUrl()} */` : ""
}</style>\`;` }</style>\`;`
} }
@init(this, { target: this.shadowRoot }, ${definition}, ${ @init(this, { target: this.shadowRoot }, ${definition}, ${
has_create_fragment ? 'create_fragment' : 'null' has_create_fragment ? "create_fragment" : "null"
}, ${not_equal}, ${prop_indexes}, ${dirty}); }, ${not_equal}, ${prop_indexes}, ${dirty});
${dev_props_check} ${dev_props_check}
@ -532,15 +653,11 @@ export default function dom(component: Component, options: CompileOptions): { js
@insert(options.target, this, options.anchor); @insert(options.target, this, options.anchor);
} }
${ ${(props.length > 0 || uses_props || uses_rest) && b`
props.length > 0 || uses_props || uses_rest
? b`
if (options.props) { if (options.props) {
this.$set(options.props); this.$set(options.props);
@flush(); @flush();
}` }`}
: null
}
} }
} }
} }
@ -548,14 +665,14 @@ export default function dom(component: Component, options: CompileOptions): { js
if (props.length > 0) { if (props.length > 0) {
declaration.body.body.push({ declaration.body.body.push({
type: 'MethodDefinition', type: "MethodDefinition",
kind: 'get', kind: "get",
static: true, static: true,
computed: false, computed: false,
key: { type: 'Identifier', name: 'observedAttributes' }, key: { type: "Identifier", name: "observedAttributes" },
value: x`function() { value: x`function() {
return [${props.map((prop) => x`"${prop.export_name}"`)}]; return [${props.map(prop => x`"${prop.export_name}"`)}];
}` as FunctionExpression, }` as FunctionExpression
}); });
} }
@ -570,8 +687,8 @@ export default function dom(component: Component, options: CompileOptions): { js
} }
} else { } else {
const superclass = { const superclass = {
type: 'Identifier', type: "Identifier",
name: options.dev ? '@SvelteComponentDev' : '@SvelteComponent', name: options.dev ? "@SvelteComponentDev" : "@SvelteComponent"
}; };
const declaration = b` const declaration = b`
@ -579,14 +696,8 @@ export default function dom(component: Component, options: CompileOptions): { js
constructor(options) { constructor(options) {
super(${options.dev && `options`}); super(${options.dev && `options`});
${should_add_css && b`if (!@_document.getElementById("${component.stylesheet.id}-style")) ${add_css}();`} ${should_add_css && b`if (!@_document.getElementById("${component.stylesheet.id}-style")) ${add_css}();`}
@init(this, options, ${definition}, ${ @init(this, options, ${definition}, ${has_create_fragment ? 'create_fragment': 'null'}, ${not_equal}, ${prop_indexes}, ${dirty});
has_create_fragment ? 'create_fragment' : 'null' ${options.dev && b`@dispatch_dev("SvelteRegisterComponent", { component: this, tagName: "${name.name}", options, id: create_fragment.name });`}
}, ${not_equal}, ${prop_indexes}, ${dirty});
${
options.dev &&
b`@dispatch_dev("SvelteRegisterComponent", { component: this, tagName: "${name.name}", options, id: create_fragment.name });`
}
${dev_props_check} ${dev_props_check}
} }
} }

@ -5,24 +5,17 @@ import { Node, Expression } from 'estree';
import Renderer from './Renderer'; import Renderer from './Renderer';
import { Var } from '../../interfaces'; import { Var } from '../../interfaces';
export function invalidate( export function invalidate(renderer: Renderer, scope: Scope, node: Node, names: Set<string>, main_execution_context: boolean = false) {
renderer: Renderer,
scope: Scope,
node: Node,
names: Set<string>,
main_execution_context = false
) {
const { component } = renderer; const { component } = renderer;
const [head, ...tail] = Array.from(names) const [head, ...tail] = Array.from(names)
.filter((name) => { .filter(name => {
const owner = scope.find_owner(name); const owner = scope.find_owner(name);
return !owner || owner === component.instance_scope; return !owner || owner === component.instance_scope;
}) })
.map((name) => component.var_lookup.get(name)) .map(name => component.var_lookup.get(name))
.filter((variable) => { .filter(variable => {
return ( return variable && (
variable &&
!variable.hoistable && !variable.hoistable &&
!variable.global && !variable.global &&
!variable.module && !variable.module &&
@ -30,7 +23,8 @@ export function invalidate(
variable.subscribable || variable.subscribable ||
variable.is_reactive_dependency || variable.is_reactive_dependency ||
variable.export_name || variable.export_name ||
variable.name[0] === '$') variable.name[0] === '$'
)
); );
}) as Var[]; }) as Var[];
@ -45,26 +39,22 @@ export function invalidate(
if (head) { if (head) {
component.has_reactive_assignments = true; component.has_reactive_assignments = true;
if ( if (node.type === 'AssignmentExpression' && node.operator === '=' && nodes_match(node.left, node.right) && tail.length === 0) {
!tail.length &&
node.type === 'AssignmentExpression' &&
node.operator === '=' &&
nodes_match(node.left, node.right)
) {
return get_invalidated(head, node); return get_invalidated(head, node);
} else { } else {
const is_store_value = head.name[0] === '$' && head.name[1] !== '$'; const is_store_value = head.name[0] === '$' && head.name[1] !== '$';
const extra_args = tail.map((variable) => get_invalidated(variable)); const extra_args = tail.map(variable => get_invalidated(variable));
const pass_value = const pass_value = (
extra_args.length > 0 || extra_args.length > 0 ||
(node.type === 'AssignmentExpression' && node.left.type !== 'Identifier') || (node.type === 'AssignmentExpression' && node.left.type !== 'Identifier') ||
(node.type === 'UpdateExpression' && (!node.prefix || node.argument.type !== 'Identifier')); (node.type === 'UpdateExpression' && (!node.prefix || node.argument.type !== 'Identifier'))
);
if (pass_value) { if (pass_value) {
extra_args.unshift({ extra_args.unshift({
type: 'Identifier', type: 'Identifier',
name: head.name, name: head.name
}); });
} }

@ -43,7 +43,7 @@ class AwaitBlockBranch extends Wrapper {
this.block = block.child({ this.block = block.child({
comment: create_debugging_comment(node, this.renderer.component), comment: create_debugging_comment(node, this.renderer.component),
name: this.renderer.component.get_unique_name(`create_${status}_block`), name: this.renderer.component.get_unique_name(`create_${status}_block`),
type: status, type: status
}); });
this.add_context(parent.node[status + '_node'], parent.node[status + '_contexts']); this.add_context(parent.node[status + '_node'], parent.node[status + '_contexts']);
@ -132,7 +132,15 @@ export default class AwaitBlockWrapper extends Wrapper {
['pending', 'then', 'catch'].forEach((status: Status) => { ['pending', 'then', 'catch'].forEach((status: Status) => {
const child = this.node[status]; const child = this.node[status];
const branch = new AwaitBlockBranch(status, renderer, block, this, child, strip_whitespace, next_sibling); const branch = new AwaitBlockBranch(
status,
renderer,
block,
this,
child,
strip_whitespace,
next_sibling
);
renderer.blocks.push(branch.block); renderer.blocks.push(branch.block);
@ -148,7 +156,7 @@ export default class AwaitBlockWrapper extends Wrapper {
this[status] = branch; this[status] = branch;
}); });
['pending', 'then', 'catch'].forEach((status) => { ['pending', 'then', 'catch'].forEach(status => {
this[status].block.has_update_method = is_dynamic; this[status].block.has_update_method = is_dynamic;
this[status].block.has_intro_method = has_intros; this[status].block.has_intro_method = has_intros;
this[status].block.has_outro_method = has_outros; this[status].block.has_outro_method = has_outros;
@ -159,7 +167,11 @@ export default class AwaitBlockWrapper extends Wrapper {
} }
} }
render(block: Block, parent_node: Identifier, parent_nodes: Identifier) { render(
block: Block,
parent_node: Identifier,
parent_nodes: Identifier
) {
const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes); const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes);
const update_mount_node = this.get_update_mount_node(anchor); const update_mount_node = this.get_update_mount_node(anchor);
@ -268,7 +280,7 @@ export default class AwaitBlockWrapper extends Wrapper {
${info} = null; ${info} = null;
`); `);
[this.pending, this.then, this.catch].forEach((branch) => { [this.pending, this.then, this.catch].forEach(branch => {
branch.render(branch.block, null, x`#nodes` as Identifier); branch.render(branch.block, null, x`#nodes` as Identifier);
}); });
} }

@ -29,7 +29,7 @@ export class ElseBlockWrapper extends Wrapper {
this.block = block.child({ this.block = block.child({
comment: create_debugging_comment(node, this.renderer.component), comment: create_debugging_comment(node, this.renderer.component),
name: this.renderer.component.get_unique_name(`create_else_block`), name: this.renderer.component.get_unique_name(`create_else_block`),
type: 'else', type: 'else'
}); });
this.fragment = new FragmentWrapper( this.fragment = new FragmentWrapper(
@ -79,7 +79,7 @@ export default class EachBlockWrapper extends Wrapper {
const { dependencies } = node.expression; const { dependencies } = node.expression;
block.add_dependencies(dependencies); block.add_dependencies(dependencies);
this.node.contexts.forEach((context) => { this.node.contexts.forEach(context => {
renderer.add_to_context(context.key.name, true); renderer.add_to_context(context.key.name, true);
}); });
@ -89,6 +89,7 @@ export default class EachBlockWrapper extends Wrapper {
type: 'each', type: 'each',
// @ts-ignore todo: probably error // @ts-ignore todo: probably error
key: node.key as string, key: node.key as string,
bindings: new Map(block.bindings), bindings: new Map(block.bindings),
}); });
@ -110,18 +111,19 @@ export default class EachBlockWrapper extends Wrapper {
renderer.add_to_context(this.index_name.name, true); renderer.add_to_context(this.index_name.name, true);
const store = const store =
node.expression.node.type === 'Identifier' && node.expression.node.name[0] === '$' node.expression.node.type === 'Identifier' &&
node.expression.node.name[0] === '$'
? node.expression.node.name.slice(1) ? node.expression.node.name.slice(1)
: null; : null;
node.contexts.forEach((prop) => { node.contexts.forEach(prop => {
this.block.bindings.set(prop.key.name, { this.block.bindings.set(prop.key.name, {
object: this.vars.each_block_value, object: this.vars.each_block_value,
property: this.index_name, property: this.index_name,
modifier: prop.modifier, modifier: prop.modifier,
snippet: prop.modifier(x`${this.vars.each_block_value}[${this.index_name}]` as Node), snippet: prop.modifier(x`${this.vars.each_block_value}[${this.index_name}]` as Node),
store, store,
tail: prop.modifier(x`[${this.index_name}]` as Node), tail: prop.modifier(x`[${this.index_name}]` as Node)
}); });
}); });
@ -134,7 +136,14 @@ export default class EachBlockWrapper extends Wrapper {
this.fragment = new FragmentWrapper(renderer, this.block, node.children, this, strip_whitespace, next_sibling); this.fragment = new FragmentWrapper(renderer, this.block, node.children, this, strip_whitespace, next_sibling);
if (this.node.else) { if (this.node.else) {
this.else = new ElseBlockWrapper(renderer, block, this, this.node.else, strip_whitespace, next_sibling); this.else = new ElseBlockWrapper(
renderer,
block,
this,
this.node.else,
strip_whitespace,
next_sibling
);
renderer.blocks.push(this.else.block); renderer.blocks.push(this.else.block);
@ -152,21 +161,20 @@ export default class EachBlockWrapper extends Wrapper {
render(block: Block, parent_node: Identifier, parent_nodes: Identifier) { render(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
if (this.fragment.nodes.length === 0) return; if (this.fragment.nodes.length === 0) return;
const __DEV__ = this.renderer.options.dev; const __DEV__ = this.renderer.options.dev;
const { each_block_value, iterations: each_block } = this.vars; const { each_block_value, iterations: each_block } = this.vars;
const { renderer } = this; const { renderer } = this;
const { component } = renderer; const { component } = renderer;
const needs_anchor = this.next ? !this.next.is_dom_node() : !parent_node || !this.parent.is_dom_node(); const needs_anchor = this.next
? !this.next.is_dom_node() :
!parent_node || !this.parent.is_dom_node();
this.context_props = this.node.contexts.map( this.context_props = this.node.contexts.map(prop => b`child_ctx[${renderer.context_lookup.get(prop.key.name).index}] = ${prop.modifier(x`list[i]`)};`);
(prop) => b`child_ctx[${renderer.context_lookup.get(prop.key.name).index}] = ${prop.modifier(x`list[i]`)};`
);
if (this.node.has_binding) if (this.node.has_binding) this.context_props.push(b`child_ctx[${renderer.context_lookup.get(this.vars.each_block_value.name).index}] = list;`);
this.context_props.push(b`child_ctx[${renderer.context_lookup.get(each_block_value.name).index}] = list;`); if (this.node.has_binding || this.node.index) this.context_props.push(b`child_ctx[${renderer.context_lookup.get(this.index_name.name).index}] = i;`);
if (this.node.has_binding || this.node.index)
this.context_props.push(b`child_ctx[${renderer.context_lookup.get(this.index_name.name).index}] = i;`);
const snippet = this.node.expression.manipulate(block); const snippet = this.node.expression.manipulate(block);
@ -198,7 +206,7 @@ export default class EachBlockWrapper extends Wrapper {
initial_anchor_node, initial_anchor_node,
initial_mount_node, initial_mount_node,
update_anchor_node, update_anchor_node,
update_mount_node, update_mount_node
}; };
const all_dependencies = new Set(this.block.dependencies); // TODO should be dynamic deps only const all_dependencies = new Set(this.block.dependencies); // TODO should be dynamic deps only
@ -218,7 +226,12 @@ export default class EachBlockWrapper extends Wrapper {
} }
if (needs_anchor) { if (needs_anchor) {
block.add_element(update_anchor_node as Identifier, x`@empty()`, parent_nodes && x`@empty()`, parent_node); block.add_element(
update_anchor_node as Identifier,
x`@empty()`,
parent_nodes && x`@empty()`,
parent_node
);
} }
if (this.else) { if (this.else) {
@ -235,7 +248,8 @@ export default class EachBlockWrapper extends Wrapper {
block.chunks.create.push(b` block.chunks.create.push(b`
if (${each_block_else}) { if (${each_block_else}) {
${each_block_else}.c(); ${each_block_else}.c();
}`); }
`);
if (this.renderer.options.hydratable) { if (this.renderer.options.hydratable) {
block.chunks.claim.push(b` block.chunks.claim.push(b`
@ -274,9 +288,7 @@ export default class EachBlockWrapper extends Wrapper {
); );
block.chunks.destroy.push(b` block.chunks.destroy.push(b`
if (${each_block_else}){ if (${each_block_else}) ${each_block_else}.d(${parent_node ? '' : 'detaching'});
${each_block_else}.d(${parent_node ? '' : 'detaching'});
}
`); `);
} }
@ -304,7 +316,7 @@ export default class EachBlockWrapper extends Wrapper {
initial_anchor_node, initial_anchor_node,
initial_mount_node, initial_mount_node,
update_anchor_node, update_anchor_node,
update_mount_node, update_mount_node
}: { }: {
block: Block; block: Block;
parent_node: Identifier; parent_node: Identifier;
@ -333,14 +345,17 @@ export default class EachBlockWrapper extends Wrapper {
block.add_variable(each_block, x`[]`); block.add_variable(each_block, x`[]`);
block.add_variable(lookup, x`new @_Map()`); block.add_variable(lookup, x`new @_Map()`);
if (this.fragment.nodes[0].is_dom_node()) this.block.first = this.fragment.nodes[0].var; if (this.fragment.nodes[0].is_dom_node()) {
else this.block.first = this.fragment.nodes[0].var;
} else {
this.block.first = this.block.get_unique_name('first');
this.block.add_element( this.block.add_element(
(this.block.first = this.block.get_unique_name('first')), this.block.first,
x`@empty()`, x`@empty()`,
parent_nodes && x`@empty()`, parent_nodes && x`@empty()`,
null null
); );
}
const validate_each_keys = const validate_each_keys =
__DEV__ && b`@check_duplicate_keys_dev(#ctx, ${each_block_value}, ${each_context_getter}, ${key_getter});`; __DEV__ && b`@check_duplicate_keys_dev(#ctx, ${each_block_value}, ${each_context_getter}, ${key_getter});`;
@ -396,7 +411,7 @@ export default class EachBlockWrapper extends Wrapper {
initial_anchor_node, initial_anchor_node,
initial_mount_node, initial_mount_node,
update_anchor_node, update_anchor_node,
update_mount_node, update_mount_node
}: { }: {
block: Block; block: Block;
parent_nodes: Identifier; parent_nodes: Identifier;

@ -29,7 +29,7 @@ export default class AttributeWrapper {
select = select.parent; select = select.parent;
if (select && select.select_binding_dependencies) { if (select && select.select_binding_dependencies) {
select.select_binding_dependencies.forEach((prop) => { select.select_binding_dependencies.forEach(prop => {
this.node.dependencies.forEach((dependency: string) => { this.node.dependencies.forEach((dependency: string) => {
this.parent.renderer.component.indirect_dependencies.get(prop).add(dependency); this.parent.renderer.component.indirect_dependencies.get(prop).add(dependency);
}); });
@ -42,11 +42,13 @@ export default class AttributeWrapper {
is_indirectly_bound_value() { is_indirectly_bound_value() {
const element = this.parent; const element = this.parent;
const name = fix_attribute_casing(this.node.name); const name = fix_attribute_casing(this.node.name);
return ( return name === 'value' &&
name === 'value' &&
(element.node.name === 'option' || // TODO check it's actually bound (element.node.name === 'option' || // TODO check it's actually bound
(element.node.name === 'input' && element.node.bindings.some((binding) => /checked|group/.test(binding.name)))) (element.node.name === 'input' &&
); element.node.bindings.some(
(binding) =>
/checked|group/.test(binding.name)
)));
} }
render(block: Block) { render(block: Block) {
@ -57,7 +59,9 @@ export default class AttributeWrapper {
const is_indirectly_bound_value = this.is_indirectly_bound_value(); const is_indirectly_bound_value = this.is_indirectly_bound_value();
const property_name = is_indirectly_bound_value ? '__value' : metadata && metadata.property_name; const property_name = is_indirectly_bound_value
? '__value'
: metadata && metadata.property_name;
// xlink is a special case... we could maybe extend this to generic // xlink is a special case... we could maybe extend this to generic
// namespaced attributes but I'm not sure that's applicable in // namespaced attributes but I'm not sure that's applicable in
@ -72,14 +76,16 @@ export default class AttributeWrapper {
const value = this.get_value(block); const value = this.get_value(block);
const is_src = this.node.name === 'src'; // TODO retire this exception in favour of https://github.com/sveltejs/svelte/issues/3750 const is_src = this.node.name === 'src'; // TODO retire this exception in favour of https://github.com/sveltejs/svelte/issues/3750
const is_select_value_attribute = name === 'value' && element.node.name === 'select'; const is_select_value_attribute =
name === 'value' && element.node.name === 'select';
const is_input_value = name === 'value' && element.node.name === 'input'; const is_input_value = name === 'value' && element.node.name === 'input';
const should_cache = is_src || this.node.should_cache() || is_select_value_attribute; // TODO is this necessary? const should_cache = is_src || this.node.should_cache() || is_select_value_attribute; // TODO is this necessary?
const last = const last = should_cache && block.get_unique_name(
should_cache && block.get_unique_name(`${element.var.name}_${name.replace(/[^a-zA-Z_$]/g, '_')}_value`); `${element.var.name}_${name.replace(/[^a-zA-Z_$]/g, '_')}_value`
);
if (should_cache) block.add_variable(last); if (should_cache) block.add_variable(last);
@ -114,15 +120,21 @@ export default class AttributeWrapper {
${updater} ${updater}
`); `);
} else if (is_src) { } else if (is_src) {
block.chunks.hydrate.push(b`if (${element.var}.src !== ${init}) ${method}(${element.var}, "${name}", ${last});`); block.chunks.hydrate.push(
b`if (${element.var}.src !== ${init}) ${method}(${element.var}, "${name}", ${last});`
);
updater = b`${method}(${element.var}, "${name}", ${should_cache ? last : value});`; updater = b`${method}(${element.var}, "${name}", ${should_cache ? last : value});`;
} else if (property_name) { } else if (property_name) {
block.chunks.hydrate.push(b`${element.var}.${property_name} = ${init};`); block.chunks.hydrate.push(
b`${element.var}.${property_name} = ${init};`
);
updater = block.renderer.options.dev updater = block.renderer.options.dev
? b`@prop_dev(${element.var}, "${property_name}", ${should_cache ? last : value});` ? b`@prop_dev(${element.var}, "${property_name}", ${should_cache ? last : value});`
: b`${element.var}.${property_name} = ${should_cache ? last : value};`; : b`${element.var}.${property_name} = ${should_cache ? last : value};`;
} else { } else {
block.chunks.hydrate.push(b`${method}(${element.var}, "${name}", ${init});`); block.chunks.hydrate.push(
b`${method}(${element.var}, "${name}", ${init});`
);
updater = b`${method}(${element.var}, "${name}", ${should_cache ? last : value});`; updater = b`${method}(${element.var}, "${name}", ${should_cache ? last : value});`;
} }
@ -138,7 +150,7 @@ export default class AttributeWrapper {
if (is_input_value) { if (is_input_value) {
const type = element.node.get_static_attribute_value('type'); const type = element.node.get_static_attribute_value('type');
if (type === null || type === '' || type === 'text' || type === 'email' || type === 'password') { if (type === null || type === "" || type === "text" || type === "email" || type === "password") {
condition = x`${condition} && ${element.var}.${property_name} !== ${should_cache ? last : value}`; condition = x`${condition} && ${element.var}.${property_name} !== ${should_cache ? last : value}`;
} }
} }
@ -191,8 +203,7 @@ export default class AttributeWrapper {
: (this.node.chunks[0] as Expression).manipulate(block); : (this.node.chunks[0] as Expression).manipulate(block);
} }
let value = let value = this.node.name === 'class'
this.node.name === 'class'
? this.get_class_name_text(block) ? this.get_class_name_text(block)
: this.render_chunks(block).reduce((lhs, rhs) => x`${lhs} + ${rhs}`); : this.render_chunks(block).reduce((lhs, rhs) => x`${lhs} + ${rhs}`);
@ -232,11 +243,11 @@ export default class AttributeWrapper {
const value = this.node.chunks; const value = this.node.chunks;
if (value.length === 0) return `=""`; if (value.length === 0) return `=""`;
return `="${value return `="${value.map(chunk => {
.map((chunk) => { return chunk.type === 'Text'
return chunk.type === 'Text' ? chunk.data.replace(/"/g, '\\"') : `\${${chunk.manipulate()}}`; ? chunk.data.replace(/"/g, '\\"')
}) : `\${${chunk.manipulate()}}`;
.join('')}"`; }).join('')}"`;
} }
} }
@ -252,7 +263,16 @@ const attribute_lookup = {
default: { applies_to: ['track'] }, default: { applies_to: ['track'] },
defer: { applies_to: ['script'] }, defer: { applies_to: ['script'] },
disabled: { disabled: {
applies_to: ['button', 'fieldset', 'input', 'keygen', 'optgroup', 'option', 'select', 'textarea'], applies_to: [
'button',
'fieldset',
'input',
'keygen',
'optgroup',
'option',
'select',
'textarea',
],
}, },
formnovalidate: { property_name: 'formNoValidate', applies_to: ['button', 'input'] }, formnovalidate: { property_name: 'formNoValidate', applies_to: ['button', 'input'] },
hidden: {}, hidden: {},
@ -270,11 +290,21 @@ const attribute_lookup = {
reversed: { applies_to: ['ol'] }, reversed: { applies_to: ['ol'] },
selected: { applies_to: ['option'] }, selected: { applies_to: ['option'] },
value: { value: {
applies_to: ['button', 'option', 'input', 'li', 'meter', 'progress', 'param', 'select', 'textarea'], applies_to: [
'button',
'option',
'input',
'li',
'meter',
'progress',
'param',
'select',
'textarea',
],
}, },
}; };
Object.keys(attribute_lookup).forEach((name) => { Object.keys(attribute_lookup).forEach(name => {
const metadata = attribute_lookup[name]; const metadata = attribute_lookup[name];
if (!metadata.property_name) metadata.property_name = name; if (!metadata.property_name) metadata.property_name = name;
}); });
@ -305,5 +335,5 @@ const boolean_attribute = new Set([
'readonly', 'readonly',
'required', 'required',
'reversed', 'reversed',
'selected', 'selected'
]); ]);

@ -27,9 +27,7 @@ export default class EventHandlerWrapper {
} }
get_snippet(block) { get_snippet(block) {
const snippet = this.node.expression const snippet = this.node.expression ? this.node.expression.manipulate(block) : block.renderer.reference(this.node.handler_name);
? this.node.expression.manipulate(block)
: block.renderer.reference(this.node.handler_name);
if (this.node.reassigned) { if (this.node.reassigned) {
block.maintain_context = true; block.maintain_context = true;
@ -47,9 +45,11 @@ export default class EventHandlerWrapper {
const args = []; const args = [];
const opts = ['passive', 'once', 'capture'].filter((mod) => this.node.modifiers.has(mod)); const opts = ['passive', 'once', 'capture'].filter(mod => this.node.modifiers.has(mod));
if (opts.length) { if (opts.length) {
args.push(opts.length === 1 && opts[0] === 'capture' ? TRUE : x`{ ${opts.map((opt) => p`${opt}: true`)} }`); args.push((opts.length === 1 && opts[0] === 'capture')
? TRUE
: x`{ ${opts.map(opt => p`${opt}: true`)} }`);
} else if (block.renderer.options.dev) { } else if (block.renderer.options.dev) {
args.push(FALSE); args.push(FALSE);
} }
@ -59,6 +59,8 @@ export default class EventHandlerWrapper {
args.push(this.node.modifiers.has('stopPropagation') ? TRUE : FALSE); args.push(this.node.modifiers.has('stopPropagation') ? TRUE : FALSE);
} }
block.event_listeners.push(x`@listen(${target}, "${this.node.name}", ${snippet}, ${args})`); block.event_listeners.push(
x`@listen(${target}, "${this.node.name}", ${snippet}, ${args})`
);
} }
} }

@ -33,52 +33,62 @@ const events = [
event_names: ['input'], event_names: ['input'],
filter: (node: Element, _name: string) => filter: (node: Element, _name: string) =>
node.name === 'textarea' || node.name === 'textarea' ||
(node.name === 'input' && !/radio|checkbox|range|file/.test(node.get_static_attribute_value('type') as string)), node.name === 'input' && !/radio|checkbox|range|file/.test(node.get_static_attribute_value('type') as string)
}, },
{ {
event_names: ['input'], event_names: ['input'],
filter: (node: Element, name: string) => filter: (node: Element, name: string) =>
(name === 'textContent' || name === 'innerHTML') && (name === 'textContent' || name === 'innerHTML') &&
node.attributes.some((attribute) => attribute.name === 'contenteditable'), node.attributes.some(attribute => attribute.name === 'contenteditable')
}, },
{ {
event_names: ['change'], event_names: ['change'],
filter: (node: Element, _name: string) => filter: (node: Element, _name: string) =>
node.name === 'select' || node.name === 'select' ||
(node.name === 'input' && /radio|checkbox|file/.test(node.get_static_attribute_value('type') as string)), node.name === 'input' && /radio|checkbox|file/.test(node.get_static_attribute_value('type') as string)
}, },
{ {
event_names: ['change', 'input'], event_names: ['change', 'input'],
filter: (node: Element, _name: string) => filter: (node: Element, _name: string) =>
node.name === 'input' && node.get_static_attribute_value('type') === 'range', node.name === 'input' && node.get_static_attribute_value('type') === 'range'
}, },
{ {
event_names: ['elementresize'], event_names: ['elementresize'],
filter: (_node: Element, name: string) => dimensions.test(name), filter: (_node: Element, name: string) =>
dimensions.test(name)
}, },
// media events // media events
{ {
event_names: ['timeupdate'], event_names: ['timeupdate'],
filter: (node: Element, name: string) => filter: (node: Element, name: string) =>
node.is_media_node() && (name === 'currentTime' || name === 'played' || name === 'ended'), node.is_media_node() &&
(name === 'currentTime' || name === 'played' || name === 'ended')
}, },
{ {
event_names: ['durationchange'], event_names: ['durationchange'],
filter: (node: Element, name: string) => node.is_media_node() && name === 'duration', filter: (node: Element, name: string) =>
node.is_media_node() &&
name === 'duration'
}, },
{ {
event_names: ['play', 'pause'], event_names: ['play', 'pause'],
filter: (node: Element, name: string) => node.is_media_node() && name === 'paused', filter: (node: Element, name: string) =>
node.is_media_node() &&
name === 'paused'
}, },
{ {
event_names: ['progress'], event_names: ['progress'],
filter: (node: Element, name: string) => node.is_media_node() && name === 'buffered', filter: (node: Element, name: string) =>
node.is_media_node() &&
name === 'buffered'
}, },
{ {
event_names: ['loadedmetadata'], event_names: ['loadedmetadata'],
filter: (node: Element, name: string) => node.is_media_node() && (name === 'buffered' || name === 'seekable'), filter: (node: Element, name: string) =>
node.is_media_node() &&
(name === 'buffered' || name === 'seekable')
}, },
{ {
event_names: ['volumechange'], event_names: ['volumechange'],
@ -88,25 +98,34 @@ const events = [
}, },
{ {
event_names: ['ratechange'], event_names: ['ratechange'],
filter: (node: Element, name: string) => node.is_media_node() && name === 'playbackRate', filter: (node: Element, name: string) =>
node.is_media_node() &&
name === 'playbackRate'
}, },
{ {
event_names: ['seeking', 'seeked'], event_names: ['seeking', 'seeked'],
filter: (node: Element, name: string) => node.is_media_node() && name === 'seeking', filter: (node: Element, name: string) =>
node.is_media_node() &&
(name === 'seeking')
}, },
{ {
event_names: ['ended'], event_names: ['ended'],
filter: (node: Element, name: string) => node.is_media_node() && name === 'ended', filter: (node: Element, name: string) =>
node.is_media_node() &&
name === 'ended'
}, },
{ {
event_names: ['resize'], event_names: ['resize'],
filter: (node: Element, name: string) => node.is_media_node() && (name === 'videoHeight' || name === 'videoWidth'), filter: (node: Element, name: string) =>
node.is_media_node() &&
(name === 'videoHeight' || name === 'videoWidth')
}, },
// details event // details event
{ {
event_names: ['toggle'], event_names: ['toggle'],
filter: (node: Element, _name: string) => node.name === 'details', filter: (node: Element, _name: string) =>
node.name === 'details'
}, },
]; ];
@ -135,7 +154,7 @@ export default class ElementWrapper extends Wrapper {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.var = { this.var = {
type: 'Identifier', type: 'Identifier',
name: node.name.replace(/[^a-zA-Z0-9_$]/g, '_'), name: node.name.replace(/[^a-zA-Z0-9_$]/g, '_')
}; };
this.void = is_void(node.name); this.void = is_void(node.name);
@ -143,14 +162,14 @@ export default class ElementWrapper extends Wrapper {
this.class_dependencies = []; this.class_dependencies = [];
if (this.node.children.length) { if (this.node.children.length) {
this.node.lets.forEach((l) => { this.node.lets.forEach(l => {
extract_names(l.value || l.name).forEach((name) => { extract_names(l.value || l.name).forEach(name => {
renderer.add_to_context(name, true); renderer.add_to_context(name, true);
}); });
}); });
} }
this.attributes = this.node.attributes.map((attribute) => { this.attributes = this.node.attributes.map(attribute => {
if (attribute.name === 'slot') { if (attribute.name === 'slot') {
// TODO make separate subclass for this? // TODO make separate subclass for this?
let owner = this.parent; let owner = this.parent;
@ -169,28 +188,28 @@ export default class ElementWrapper extends Wrapper {
if (owner && owner.node.type === 'InlineComponent') { if (owner && owner.node.type === 'InlineComponent') {
const name = attribute.get_static_value() as string; const name = attribute.get_static_value() as string;
if (!((owner as unknown) as InlineComponentWrapper).slots.has(name)) { if (!(owner as unknown as InlineComponentWrapper).slots.has(name)) {
const child_block = block.child({ const child_block = block.child({
comment: create_debugging_comment(node, this.renderer.component), comment: create_debugging_comment(node, this.renderer.component),
name: this.renderer.component.get_unique_name(`create_${sanitize(name)}_slot`), name: this.renderer.component.get_unique_name(`create_${sanitize(name)}_slot`),
type: 'slot', type: 'slot'
}); });
const { scope, lets } = this.node; const { scope, lets } = this.node;
const seen = new Set(lets.map((l) => l.name.name)); const seen = new Set(lets.map(l => l.name.name));
((owner as unknown) as InlineComponentWrapper).node.lets.forEach((l) => { (owner as unknown as InlineComponentWrapper).node.lets.forEach(l => {
if (!seen.has(l.name.name)) lets.push(l); if (!seen.has(l.name.name)) lets.push(l);
}); });
((owner as unknown) as InlineComponentWrapper).slots.set( (owner as unknown as InlineComponentWrapper).slots.set(
name, name,
get_slot_definition(child_block, scope, lets) get_slot_definition(child_block, scope, lets)
); );
this.renderer.blocks.push(child_block); this.renderer.blocks.push(child_block);
} }
this.slot_block = ((owner as unknown) as InlineComponentWrapper).slots.get(name).block; this.slot_block = (owner as unknown as InlineComponentWrapper).slots.get(name).block;
block = this.slot_block; block = this.slot_block;
} }
} }
@ -203,9 +222,9 @@ export default class ElementWrapper extends Wrapper {
// ordinarily, there'll only be one... but we need to handle // ordinarily, there'll only be one... but we need to handle
// the rare case where an element can have multiple bindings, // the rare case where an element can have multiple bindings,
// e.g. <audio bind:paused bind:currentTime> // e.g. <audio bind:paused bind:currentTime>
this.bindings = this.node.bindings.map((binding) => new Binding(block, binding, this)); this.bindings = this.node.bindings.map(binding => new Binding(block, binding, this));
this.event_handlers = this.node.handlers.map((event_handler) => new EventHandler(event_handler, this)); this.event_handlers = this.node.handlers.map(event_handler => new EventHandler(event_handler, this));
if (node.intro || node.outro) { if (node.intro || node.outro) {
if (node.intro) block.add_intro(node.intro.is_local); if (node.intro) block.add_intro(node.intro.is_local);
@ -217,29 +236,27 @@ 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.add_dependencies(directive.expression.dependencies); block.add_dependencies(directive.expression.dependencies);
} }
}); });
node.handlers.forEach((handler) => { node.handlers.forEach(handler => {
if (handler.expression) { if (handler.expression) {
block.add_dependencies(handler.expression.dependencies); block.add_dependencies(handler.expression.dependencies);
} }
}); });
if (this.parent) { if (this.parent) {
if ( if (node.actions.length > 0 ||
renderer.options.dev ||
node.actions.length ||
node.bindings.length ||
node.handlers.length ||
node.classes.length ||
node.intro ||
node.outro ||
node.animation || node.animation ||
this.node.name === 'option' node.bindings.length > 0 ||
node.classes.length > 0 ||
node.intro || node.outro ||
node.handlers.length > 0 ||
this.node.name === 'option' ||
renderer.options.dev
) { ) {
this.parent.cannot_use_innerhtml(); // need to use add_location this.parent.cannot_use_innerhtml(); // need to use add_location
this.parent.not_static_content(); this.parent.not_static_content();
@ -273,22 +290,32 @@ export default class ElementWrapper extends Wrapper {
block.add_variable(node); block.add_variable(node);
const render_statement = this.get_render_statement(block); const render_statement = this.get_render_statement(block);
block.chunks.create.push(b`${node} = ${render_statement};`); block.chunks.create.push(
b`${node} = ${render_statement};`
);
if (renderer.options.hydratable) { if (renderer.options.hydratable) {
if (parent_nodes) { if (parent_nodes) {
block.chunks.claim.push(b`${node} = ${this.get_claim_statement(parent_nodes)};`); block.chunks.claim.push(b`
${node} = ${this.get_claim_statement(parent_nodes)};
`);
if (!this.void && this.node.children.length > 0) { if (!this.void && this.node.children.length > 0) {
block.chunks.claim.push(b`var ${nodes} = ${children};`); block.chunks.claim.push(b`
var ${nodes} = ${children};
`);
} }
} else { } else {
block.chunks.claim.push(b`${node} = ${render_statement};`); block.chunks.claim.push(
b`${node} = ${render_statement};`
);
} }
} }
if (parent_node) { if (parent_node) {
block.chunks.mount.push(b`@append(${parent_node}, ${node});`); block.chunks.mount.push(
b`@append(${parent_node}, ${node});`
);
if (is_head(parent_node)) { if (is_head(parent_node)) {
block.chunks.destroy.push(b`@detach(${node});`); block.chunks.destroy.push(b`@detach(${node});`);
@ -313,38 +340,39 @@ export default class ElementWrapper extends Wrapper {
const state = { const state = {
quasi: { quasi: {
type: 'TemplateElement', type: 'TemplateElement',
value: { raw: '' }, value: { raw: '' }
}, }
}; };
const literal = { const literal = {
type: 'TemplateLiteral', type: 'TemplateLiteral',
expressions: [], expressions: [],
quasis: [], quasis: []
}; };
const can_use_raw_text = !this.can_use_innerhtml && can_use_textcontent; const can_use_raw_text = !this.can_use_innerhtml && can_use_textcontent;
to_html( to_html((this.fragment.nodes as unknown as Array<ElementWrapper | TextWrapper>), block, literal, state, can_use_raw_text);
(this.fragment.nodes as unknown) as Array<ElementWrapper | TextWrapper>,
block,
literal,
state,
can_use_raw_text
);
literal.quasis.push(state.quasi); literal.quasis.push(state.quasi);
block.chunks.create.push(b`${node}.${this.can_use_innerhtml ? 'innerHTML' : 'textContent'} = ${literal};`); block.chunks.create.push(
b`${node}.${this.can_use_innerhtml ? 'innerHTML': 'textContent'} = ${literal};`
);
} }
} else { } else {
this.fragment.nodes.forEach((child: Wrapper) => { this.fragment.nodes.forEach((child: Wrapper) => {
child.render(block, this.node.name === 'template' ? x`${node}.content` : node, nodes); child.render(
block,
this.node.name === 'template' ? x`${node}.content` : node,
nodes
);
}); });
} }
const event_handler_or_binding_uses_context = const event_handler_or_binding_uses_context = (
this.bindings.some((binding) => binding.handler.uses_context) || this.bindings.some(binding => binding.handler.uses_context) ||
this.node.handlers.some((handler) => handler.uses_context) || this.node.handlers.some(handler => handler.uses_context) ||
this.node.actions.some((action) => action.uses_context); this.node.actions.some(action => action.uses_context)
);
if (event_handler_or_binding_uses_context) { if (event_handler_or_binding_uses_context) {
block.maintain_context = true; block.maintain_context = true;
@ -368,7 +396,9 @@ export default class ElementWrapper extends Wrapper {
this.add_manual_style_scoping(block); this.add_manual_style_scoping(block);
if (nodes && this.renderer.options.hydratable && !this.void) { if (nodes && this.renderer.options.hydratable && !this.void) {
block.chunks.claim.push(b`${this.node.children.length > 0 ? nodes : children}.forEach(@detach);`); block.chunks.claim.push(
b`${this.node.children.length > 0 ? nodes : children}.forEach(@detach);`
);
} }
if (renderer.options.dev) { if (renderer.options.dev) {
@ -380,10 +410,7 @@ export default class ElementWrapper extends Wrapper {
} }
can_use_textcontent() { can_use_textcontent() {
return ( return this.is_static_content && this.fragment.nodes.every(node => node.node.type === 'Text' || node.node.type === 'MustacheTag');
this.is_static_content &&
this.fragment.nodes.every((node) => node.node.type === 'Text' || node.node.type === 'MustacheTag')
);
} }
get_render_statement(block: Block) { get_render_statement(block: Block) {
@ -397,7 +424,7 @@ export default class ElementWrapper extends Wrapper {
return x`@_document.createElementNS("${namespace}", "${name}")`; return x`@_document.createElementNS("${namespace}", "${name}")`;
} }
const is = this.attributes.find((attr) => attr.node.name === 'is'); const is = this.attributes.find(attr => attr.node.name === 'is');
if (is) { if (is) {
return x`@element_is("${name}", ${is.render_chunks(block).reduce((lhs, rhs) => x`${lhs} + ${rhs}`)})`; return x`@element_is("${name}", ${is.render_chunks(block).reduce((lhs, rhs) => x`${lhs} + ${rhs}`)})`;
} }
@ -410,7 +437,9 @@ export default class ElementWrapper extends Wrapper {
.filter((attr) => attr.type === 'Attribute') .filter((attr) => attr.type === 'Attribute')
.map((attr) => p`${attr.name}: true`); .map((attr) => p`${attr.name}: true`);
const name = this.node.namespace ? this.node.name : this.node.name.toUpperCase(); const name = this.node.namespace
? this.node.name
: this.node.name.toUpperCase();
const svg = this.node.namespace === namespaces.svg || this.node.namespace === 'svg' ? 1 : null; const svg = this.node.namespace === namespaces.svg || this.node.namespace === 'svg' ? 1 : null;
@ -426,15 +455,15 @@ export default class ElementWrapper extends Wrapper {
type OrderedAttribute = EventHandler | BindingGroup | Binding | Action; type OrderedAttribute = EventHandler | BindingGroup | Binding | Action;
const bindingGroups = events const bindingGroups = events
.map((event) => ({ .map(event => ({
events: event.event_names, events: event.event_names,
bindings: this.bindings bindings: this.bindings
.filter((binding) => binding.node.name !== 'this') .filter(binding => binding.node.name !== 'this')
.filter((binding) => event.filter(this.node, binding.node.name)), .filter(binding => event.filter(this.node, binding.node.name))
})) }))
.filter((group) => group.bindings.length); .filter(group => group.bindings.length);
const this_binding = this.bindings.find((b) => b.node.name === 'this'); const this_binding = this.bindings.find(b => b.node.name === 'this');
function getOrder (item: OrderedAttribute) { function getOrder (item: OrderedAttribute) {
if (item instanceof EventHandler) { if (item instanceof EventHandler) {
@ -448,10 +477,15 @@ export default class ElementWrapper extends Wrapper {
} }
} }
([...bindingGroups, ...this.event_handlers, this_binding, ...this.node.actions] as OrderedAttribute[]) ([
...bindingGroups,
...this.event_handlers,
this_binding,
...this.node.actions
] as OrderedAttribute[])
.filter(Boolean) .filter(Boolean)
.sort((a, b) => getOrder(a) - getOrder(b)) .sort((a, b) => getOrder(a) - getOrder(b))
.forEach((item) => { .forEach(item => {
if (item instanceof EventHandler) { if (item instanceof EventHandler) {
add_event_handler(block, this.var, item); add_event_handler(block, this.var, item);
} else if (item instanceof Binding) { } else if (item instanceof Binding) {
@ -471,23 +505,23 @@ export default class ElementWrapper extends Wrapper {
renderer.component.has_reactive_assignments = true; renderer.component.has_reactive_assignments = true;
const lock = bindingGroup.bindings.some((binding) => binding.needs_lock) const lock = bindingGroup.bindings.some(binding => binding.needs_lock) ?
? block.get_unique_name(`${this.var.name}_updating`) block.get_unique_name(`${this.var.name}_updating`) :
: null; null;
if (lock) block.add_variable(lock, x`false`); if (lock) block.add_variable(lock, x`false`);
[bindingGroup].forEach((group) => { [bindingGroup].forEach(group => {
const handler = renderer.component.get_unique_name(`${this.var.name}_${group.events.join('_')}_handler`); const handler = renderer.component.get_unique_name(`${this.var.name}_${group.events.join('_')}_handler`);
renderer.add_to_context(handler.name); renderer.add_to_context(handler.name);
// TODO figure out how to handle locks // TODO figure out how to handle locks
const needs_lock = group.bindings.some((binding) => binding.needs_lock); const needs_lock = group.bindings.some(binding => binding.needs_lock);
const dependencies: Set<string> = new Set(); const dependencies: Set<string> = new Set();
const contextual_dependencies: Set<string> = new Set(); const contextual_dependencies: Set<string> = new Set();
group.bindings.forEach((binding) => { group.bindings.forEach(binding => {
// TODO this is a mess // TODO this is a mess
add_to_set(dependencies, binding.get_dependencies()); add_to_set(dependencies, binding.get_dependencies());
add_to_set(contextual_dependencies, binding.node.expression.contextual_dependencies); add_to_set(contextual_dependencies, binding.node.expression.contextual_dependencies);
@ -511,7 +545,7 @@ export default class ElementWrapper extends Wrapper {
// TODO dry this out — similar code for event handlers and component bindings // TODO dry this out — similar code for event handlers and component bindings
if (has_local_function) { if (has_local_function) {
const args = Array.from(contextual_dependencies).map((name) => renderer.reference(name)); const args = Array.from(contextual_dependencies).map(name => renderer.reference(name));
// need to create a block-local function that calls an instance-level function // need to create a block-local function that calls an instance-level function
if (animation_frame) { if (animation_frame) {
@ -539,20 +573,20 @@ export default class ElementWrapper extends Wrapper {
const params = Array.from(contextual_dependencies).map((name) => ({ const params = Array.from(contextual_dependencies).map((name) => ({
type: 'Identifier', type: 'Identifier',
name, name
})); }));
this.renderer.component.partly_hoisted.push(b` this.renderer.component.partly_hoisted.push(b`
function ${handler}(${params}) { function ${handler}(${params}) {
${group.bindings.map((b) => b.handler.mutation)} ${group.bindings.map(b => b.handler.mutation)}
${Array.from(dependencies) ${Array.from(dependencies)
.filter((dep) => dep[0] !== '$') .filter(dep => dep[0] !== '$')
.filter((dep) => !contextual_dependencies.has(dep)) .filter(dep => !contextual_dependencies.has(dep))
.map((dep) => b`${this.renderer.invalidate(dep)};`)} .map(dep => b`${this.renderer.invalidate(dep)};`)}
} }
`); `);
group.events.forEach((name) => { group.events.forEach(name => {
if (name === 'elementresize') { if (name === 'elementresize') {
// special case // special case
const resize_listener = block.get_unique_name(`${this.var.name}_resize_listener`); const resize_listener = block.get_unique_name(`${this.var.name}_resize_listener`);
@ -562,19 +596,23 @@ export default class ElementWrapper extends Wrapper {
b`${resize_listener} = @add_resize_listener(${this.var}, ${callee}.bind(${this.var}));` b`${resize_listener} = @add_resize_listener(${this.var}, ${callee}.bind(${this.var}));`
); );
block.chunks.destroy.push(b`${resize_listener}();`); block.chunks.destroy.push(
b`${resize_listener}();`
);
} else { } else {
block.event_listeners.push(x`@listen(${this.var}, "${name}", ${callee})`); block.event_listeners.push(
x`@listen(${this.var}, "${name}", ${callee})`
);
} }
}); });
const some_initial_state_is_undefined = group.bindings const some_initial_state_is_undefined = group.bindings
.map((binding) => x`${binding.snippet} === void 0`) .map(binding => x`${binding.snippet} === void 0`)
.reduce((lhs, rhs) => x`${lhs} || ${rhs}`); .reduce((lhs, rhs) => x`${lhs} || ${rhs}`);
const should_initialise = const should_initialise =
this.node.name === 'select' || this.node.name === 'select' ||
group.bindings.find((binding) => { group.bindings.find(binding => {
return ( return (
binding.node.name === 'indeterminate' || binding.node.name === 'indeterminate' ||
binding.node.name === 'textContent' || binding.node.name === 'textContent' ||
@ -585,11 +623,15 @@ export default class ElementWrapper extends Wrapper {
if (should_initialise) { if (should_initialise) {
const callback = has_local_function ? handler : x`() => ${callee}.call(${this.var})`; const callback = has_local_function ? handler : x`() => ${callee}.call(${this.var})`;
block.chunks.hydrate.push(b`if (${some_initial_state_is_undefined}) @add_render_callback(${callback});`); block.chunks.hydrate.push(
b`if (${some_initial_state_is_undefined}) @add_render_callback(${callback});`
);
} }
if (group.events[0] === 'elementresize') { if (group.events[0] === 'elementresize') {
block.chunks.hydrate.push(b`@add_render_callback(() => ${callee}.call(${this.var}));`); block.chunks.hydrate.push(
b`@add_render_callback(() => ${callee}.call(${this.var}));`
);
} }
}); });
@ -609,19 +651,19 @@ export default class ElementWrapper extends Wrapper {
add_attributes(block: Block) { add_attributes(block: Block) {
// Get all the class dependencies first // Get all the class dependencies first
this.attributes.forEach((attribute) => { this.attributes.forEach(attribute => {
if (attribute.node.name === 'class') { if (attribute.node.name === 'class') {
const dependencies = attribute.node.get_dependencies(); const dependencies = attribute.node.get_dependencies();
this.class_dependencies.push(...dependencies); this.class_dependencies.push(...dependencies);
} }
}); });
if (this.node.attributes.some((attr) => attr.is_spread)) { if (this.node.attributes.some(attr => attr.is_spread)) {
this.add_spread_attributes(block); this.add_spread_attributes(block);
return; return;
} }
this.attributes.forEach((attribute) => { this.attributes.forEach(attribute => {
attribute.render(block); attribute.render(block);
}); });
} }
@ -633,9 +675,11 @@ export default class ElementWrapper extends Wrapper {
const initial_props = []; const initial_props = [];
const updates = []; const updates = [];
this.attributes.forEach((attr) => { this.attributes
const condition = .forEach(attr => {
attr.node.dependencies.size > 0 ? block.renderer.dirty(Array.from(attr.node.dependencies)) : null; const condition = attr.node.dependencies.size > 0
? block.renderer.dirty(Array.from(attr.node.dependencies))
: null;
if (attr.node.is_spread) { if (attr.node.is_spread) {
const snippet = attr.node.expression.manipulate(block); const snippet = attr.node.expression.manipulate(block);
@ -665,9 +709,15 @@ export default class ElementWrapper extends Wrapper {
const fn = this.node.namespace === namespaces.svg ? x`@set_svg_attributes` : x`@set_attributes`; const fn = this.node.namespace === namespaces.svg ? x`@set_svg_attributes` : x`@set_attributes`;
block.chunks.hydrate.push(b`${fn}(${this.var}, ${data});`); block.chunks.hydrate.push(
b`${fn}(${this.var}, ${data});`
);
block.chunks.update.push(b`${fn}(${this.var}, @get_spread_update(${levels}, [${updates}]));`); block.chunks.update.push(b`
${fn}(${this.var}, @get_spread_update(${levels}, [
${updates}
]));
`);
} }
add_bidi_transition(block: Block, intro: Transition) { add_bidi_transition(block: Block, intro: Transition) {
const name = block.get_unique_name(`${this.var.name}_transition`); const name = block.get_unique_name(`${this.var.name}_transition`);
@ -767,8 +817,8 @@ export default class ElementWrapper extends Wrapper {
} }
add_classes(block: Block) { add_classes(block: Block) {
const has_spread = this.node.attributes.some((attr) => attr.is_spread); const has_spread = this.node.attributes.some(attr => attr.is_spread);
this.node.classes.forEach((class_directive) => { this.node.classes.forEach(class_directive => {
const { expression, name } = class_directive; const { expression, name } = class_directive;
let snippet; let snippet;
let dependencies; let dependencies;
@ -806,20 +856,18 @@ export default class ElementWrapper extends Wrapper {
} }
} }
function to_html( function to_html(wrappers: Array<ElementWrapper | TextWrapper | TagWrapper>, block: Block, literal: any, state: any, can_use_raw_text?: boolean) {
wrappers: Array<ElementWrapper | TextWrapper | TagWrapper>, wrappers.forEach(wrapper => {
block: Block,
literal: any,
state: any,
can_use_raw_text?: boolean
) {
wrappers.forEach((wrapper) => {
if (wrapper.node.type === 'Text') { if (wrapper.node.type === 'Text') {
if ((wrapper as TextWrapper).use_space()) state.quasi.value.raw += ' '; if ((wrapper as TextWrapper).use_space()) state.quasi.value.raw += ' ';
const parent = wrapper.node.parent as Element; const parent = wrapper.node.parent as Element;
const raw = parent && (parent.name === 'script' || parent.name === 'style' || can_use_raw_text); const raw = parent && (
parent.name === 'script' ||
parent.name === 'style' ||
can_use_raw_text
);
state.quasi.value.raw += (raw ? wrapper.node.data : escape_html(wrapper.node.data)) state.quasi.value.raw += (raw ? wrapper.node.data : escape_html(wrapper.node.data))
.replace(/\\/g, '\\\\') .replace(/\\/g, '\\\\')
@ -830,7 +878,7 @@ function to_html(
literal.expressions.push(wrapper.node.expression.manipulate(block)); literal.expressions.push(wrapper.node.expression.manipulate(block));
state.quasi = { state.quasi = {
type: 'TemplateElement', type: 'TemplateElement',
value: { raw: '' }, value: { raw: '' }
}; };
} else if (wrapper.node.name === 'noscript') { } else if (wrapper.node.name === 'noscript') {
// do nothing // do nothing
@ -841,7 +889,7 @@ function to_html(
(wrapper as ElementWrapper).attributes.forEach((attr: AttributeWrapper) => { (wrapper as ElementWrapper).attributes.forEach((attr: AttributeWrapper) => {
state.quasi.value.raw += ` ${fix_attribute_casing(attr.node.name)}="`; state.quasi.value.raw += ` ${fix_attribute_casing(attr.node.name)}="`;
attr.node.chunks.forEach((chunk) => { attr.node.chunks.forEach(chunk => {
if (chunk.type === 'Text') { if (chunk.type === 'Text') {
state.quasi.value.raw += escape_html(chunk.data); state.quasi.value.raw += escape_html(chunk.data);
} else { } else {
@ -850,7 +898,7 @@ function to_html(
state.quasi = { state.quasi = {
type: 'TemplateElement', type: 'TemplateElement',
value: { raw: '' }, value: { raw: '' }
}; };
} }
}); });
@ -861,12 +909,7 @@ function to_html(
state.quasi.value.raw += '>'; state.quasi.value.raw += '>';
if (!(wrapper as ElementWrapper).void) { if (!(wrapper as ElementWrapper).void) {
to_html( to_html((wrapper as ElementWrapper).fragment.nodes as Array<ElementWrapper | TextWrapper>, block, literal, state);
(wrapper as ElementWrapper).fragment.nodes as Array<ElementWrapper | TextWrapper>,
block,
literal,
state
);
state.quasi.value.raw += `</${wrapper.node.name}>`; state.quasi.value.raw += `</${wrapper.node.name}>`;
} }

@ -36,17 +36,14 @@ const wrappers = {
Slot, Slot,
Text, Text,
Title, Title,
Window, Window
}; };
function trimmable_at(child: INode, next_sibling: Wrapper): boolean { function trimmable_at(child: INode, next_sibling: Wrapper): boolean {
// Whitespace is trimmable if one of the following is true: // Whitespace is trimmable if one of the following is true:
// The child and its sibling share a common nearest each block (not at an each block boundary) // The child and its sibling share a common nearest each block (not at an each block boundary)
// The next sibling's previous node is an each block // The next sibling's previous node is an each block
return ( return (next_sibling.node.find_nearest(/EachBlock/) === child.find_nearest(/EachBlock/)) || next_sibling.node.prev.type === 'EachBlock';
next_sibling.node.find_nearest(/EachBlock/) === child.find_nearest(/EachBlock/) ||
next_sibling.node.prev.type === 'EachBlock'
);
} }
export default class FragmentWrapper { export default class FragmentWrapper {
@ -90,11 +87,9 @@ export default class FragmentWrapper {
// We want to remove trailing whitespace inside an element/component/block, // We want to remove trailing whitespace inside an element/component/block,
// *unless* there is no whitespace between this node and its next sibling // *unless* there is no whitespace between this node and its next sibling
if (this.nodes.length === 0) { if (this.nodes.length === 0) {
const should_trim = next_sibling const should_trim = (
? next_sibling.node.type === 'Text' && next_sibling ? (next_sibling.node.type === 'Text' && /^\s/.test(next_sibling.node.data) && trimmable_at(child, next_sibling)) : !child.has_ancestor('EachBlock')
/^\s/.test(next_sibling.node.data) && );
trimmable_at(child, next_sibling)
: !child.has_ancestor('EachBlock');
if (should_trim) { if (should_trim) {
data = trim_end(data); data = trim_end(data);
@ -113,7 +108,7 @@ export default class FragmentWrapper {
this.nodes.unshift(wrapper); this.nodes.unshift(wrapper);
link(last_child, (last_child = wrapper)); link(last_child, last_child = wrapper);
} else { } else {
const Wrapper = wrappers[child.type]; const Wrapper = wrappers[child.type];
if (!Wrapper) continue; if (!Wrapper) continue;
@ -121,7 +116,7 @@ export default class FragmentWrapper {
const wrapper = new Wrapper(renderer, block, parent, child, strip_whitespace, last_child || next_sibling); const wrapper = new Wrapper(renderer, block, parent, child, strip_whitespace, last_child || next_sibling);
this.nodes.unshift(wrapper); this.nodes.unshift(wrapper);
link(last_child, (last_child = wrapper)); link(last_child, last_child = wrapper);
} }
} }

@ -22,7 +22,14 @@ export default class HeadWrapper extends Wrapper {
this.can_use_innerhtml = false; this.can_use_innerhtml = false;
this.fragment = new FragmentWrapper(renderer, block, node.children, this, strip_whitespace, next_sibling); this.fragment = new FragmentWrapper(
renderer,
block,
node.children,
this,
strip_whitespace,
next_sibling
);
} }
render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) { render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) {
@ -34,10 +41,12 @@ export default class HeadWrapper extends Wrapper {
); );
} }
this.fragment.render(block, (x`@_document.head` as unknown) as Identifier, nodes); this.fragment.render(block, x`@_document.head` as unknown as Identifier, nodes);
if (nodes && this.renderer.options.hydratable) { if (nodes && this.renderer.options.hydratable) {
block.chunks.claim.push(b`${nodes}.forEach(@detach);`); block.chunks.claim.push(
b`${nodes}.forEach(@detach);`
);
} }
} }
} }

@ -12,7 +12,9 @@ import { is_head } from './shared/is_head';
import { Identifier, Node, UnaryExpression } from 'estree'; import { Identifier, Node, UnaryExpression } from 'estree';
function is_else_if(node: ElseBlock) { function is_else_if(node: ElseBlock) {
return node && node.children.length === 1 && node.children[0].type === 'IfBlock'; return (
node && node.children.length === 1 && node.children[0].type === 'IfBlock'
);
} }
class IfBlockBranch extends Wrapper { class IfBlockBranch extends Wrapper {
@ -35,7 +37,7 @@ class IfBlockBranch extends Wrapper {
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
const { expression } = node as IfBlock; const { expression } = (node as IfBlock);
const is_else = !expression; const is_else = !expression;
if (expression) { if (expression) {
@ -49,12 +51,12 @@ class IfBlockBranch extends Wrapper {
if (node.type === 'CallExpression' || node.type === 'NewExpression') { if (node.type === 'CallExpression' || node.type === 'NewExpression') {
should_cache = true; should_cache = true;
} }
}, }
}); });
if (should_cache) { if (should_cache) {
this.condition = block.get_unique_name(`show_if`); this.condition = block.get_unique_name(`show_if`);
this.snippet = expression.manipulate(block) as Node; this.snippet = (expression.manipulate(block) as Node);
} else { } else {
this.condition = expression.manipulate(block); this.condition = expression.manipulate(block);
} }
@ -62,8 +64,10 @@ class IfBlockBranch extends Wrapper {
this.block = block.child({ this.block = block.child({
comment: create_debugging_comment(node, parent.renderer.component), comment: create_debugging_comment(node, parent.renderer.component),
name: parent.renderer.component.get_unique_name(is_else ? `create_else_block` : `create_if_block`), name: parent.renderer.component.get_unique_name(
type: (node as IfBlock).expression ? 'if' : 'else', is_else ? `create_else_block` : `create_if_block`
),
type: (node as IfBlock).expression ? 'if' : 'else'
}); });
this.fragment = new FragmentWrapper(renderer, this.block, node.children, parent, strip_whitespace, next_sibling); this.fragment = new FragmentWrapper(renderer, this.block, node.children, parent, strip_whitespace, next_sibling);
@ -100,7 +104,14 @@ export default class IfBlockWrapper extends Wrapper {
let has_outros = false; let has_outros = false;
const create_branches = (node: IfBlock) => { const create_branches = (node: IfBlock) => {
const branch = new IfBlockBranch(renderer, block, this, node, strip_whitespace, next_sibling); const branch = new IfBlockBranch(
renderer,
block,
this,
node,
strip_whitespace,
next_sibling
);
this.branches.push(branch); this.branches.push(branch);
@ -124,7 +135,14 @@ export default class IfBlockWrapper extends Wrapper {
if (is_else_if(node.else)) { if (is_else_if(node.else)) {
create_branches(node.else.children[0] as IfBlock); create_branches(node.else.children[0] as IfBlock);
} else if (node.else) { } else if (node.else) {
const branch = new IfBlockBranch(renderer, block, this, node.else, strip_whitespace, next_sibling); const branch = new IfBlockBranch(
renderer,
block,
this,
node.else,
strip_whitespace,
next_sibling
);
this.branches.push(branch); this.branches.push(branch);
@ -142,7 +160,7 @@ export default class IfBlockWrapper extends Wrapper {
create_branches(this.node); create_branches(this.node);
blocks.forEach((block) => { blocks.forEach(block => {
block.has_update_method = is_dynamic; block.has_update_method = is_dynamic;
block.has_intro_method = has_intros; block.has_intro_method = has_intros;
block.has_outro_method = has_outros; block.has_outro_method = has_outros;
@ -151,7 +169,11 @@ export default class IfBlockWrapper extends Wrapper {
renderer.blocks.push(...blocks); renderer.blocks.push(...blocks);
} }
render(block: Block, parent_node: Identifier, parent_nodes: Identifier) { render(
block: Block,
parent_node: Identifier,
parent_nodes: Identifier
) {
const name = this.var; const name = this.var;
const needs_anchor = this.next ? !this.next.is_dom_node() : !parent_node || !this.parent.is_dom_node(); const needs_anchor = this.next ? !this.next.is_dom_node() : !parent_node || !this.parent.is_dom_node();
@ -159,7 +181,7 @@ export default class IfBlockWrapper extends Wrapper {
? block.get_unique_name(`${this.var.name}_anchor`) ? block.get_unique_name(`${this.var.name}_anchor`)
: (this.next && this.next.var) || 'null'; : (this.next && this.next.var) || 'null';
const has_else = !this.branches[this.branches.length - 1].condition; const has_else = !(this.branches[this.branches.length - 1].condition);
const if_exists_condition = has_else ? null : name; const if_exists_condition = has_else ? null : name;
const dynamic = this.branches[0].block.has_update_method; // can use [0] as proxy for all, since they necessarily have the same value const dynamic = this.branches[0].block.has_update_method; // can use [0] as proxy for all, since they necessarily have the same value
@ -172,7 +194,7 @@ export default class IfBlockWrapper extends Wrapper {
const detaching = parent_node && !is_head(parent_node) ? null : 'detaching'; const detaching = parent_node && !is_head(parent_node) ? null : 'detaching';
if (this.node.else) { if (this.node.else) {
this.branches.forEach((branch) => { this.branches.forEach(branch => {
if (branch.snippet) block.add_variable(branch.condition); if (branch.snippet) block.add_variable(branch.condition);
}); });
@ -199,9 +221,13 @@ export default class IfBlockWrapper extends Wrapper {
if (parent_nodes && this.renderer.options.hydratable) { if (parent_nodes && this.renderer.options.hydratable) {
if (if_exists_condition) { if (if_exists_condition) {
block.chunks.claim.push(b`if (${if_exists_condition}) ${name}.l(${parent_nodes});`); block.chunks.claim.push(
b`if (${if_exists_condition}) ${name}.l(${parent_nodes});`
);
} else { } else {
block.chunks.claim.push(b`${name}.l(${parent_nodes});`); block.chunks.claim.push(
b`${name}.l(${parent_nodes});`
);
} }
} }
@ -210,10 +236,15 @@ export default class IfBlockWrapper extends Wrapper {
} }
if (needs_anchor) { if (needs_anchor) {
block.add_element(anchor as Identifier, x`@empty()`, parent_nodes && x`@empty()`, parent_node); block.add_element(
anchor as Identifier,
x`@empty()`,
parent_nodes && x`@empty()`,
parent_node
);
} }
this.branches.forEach((branch) => { this.branches.forEach(branch => {
branch.fragment.render(branch.block, null, (x`#nodes` as unknown) as Identifier); branch.fragment.render(branch.block, null, (x`#nodes` as unknown) as Identifier);
}); });
} }
@ -235,26 +266,23 @@ export default class IfBlockWrapper extends Wrapper {
if (this.needs_update) { if (this.needs_update) {
block.chunks.init.push(b` block.chunks.init.push(b`
function ${select_block_type}(#ctx, #dirty) { function ${select_block_type}(#ctx, #dirty) {
${this.branches.map(({ dependencies, condition, snippet, block }) => ${this.branches.map(({ dependencies, condition, snippet, block }) => condition
condition
? b` ? b`
${ ${snippet && (
snippet && dependencies.length > 0
(dependencies.length > 0 ? b`if (${condition} == null || ${block.renderer.dirty(dependencies)}) ${condition} = !!${snippet}`
? b`if (${condition} == null || ${block.renderer.dirty(dependencies)}) ${condition} = !!${snippet};` : b`if (${condition} == null) ${condition} = !!${snippet}`
: b`if (${condition} == null) ${condition} = !!${snippet};`)
}
if (${condition}) return ${block.name};`
: b`return ${block.name};`
)} )}
if (${condition}) return ${block.name};`
: b`return ${block.name};`)}
} }
`); `);
} else { } else {
block.chunks.init.push(b` block.chunks.init.push(b`
function ${select_block_type}(#ctx, #dirty) { function ${select_block_type}(#ctx, #dirty) {
${this.branches.map(({ condition, snippet, block }) => ${this.branches.map(({ condition, snippet, block }) => condition
condition ? b`if (${snippet || condition}) return ${block.name};` : b`return ${block.name};` ? b`if (${snippet || condition}) return ${block.name};`
)} : b`return ${block.name};`)}
} }
`); `);
} }
@ -268,9 +296,13 @@ export default class IfBlockWrapper extends Wrapper {
const anchor_node = parent_node ? 'null' : '#anchor'; const anchor_node = parent_node ? 'null' : '#anchor';
if (if_exists_condition) { if (if_exists_condition) {
block.chunks.mount.push(b`if (${if_exists_condition}) ${name}.m(${initial_mount_node}, ${anchor_node});`); block.chunks.mount.push(
b`if (${if_exists_condition}) ${name}.m(${initial_mount_node}, ${anchor_node});`
);
} else { } else {
block.chunks.mount.push(b`${name}.m(${initial_mount_node}, ${anchor_node});`); block.chunks.mount.push(
b`${name}.m(${initial_mount_node}, ${anchor_node});`
);
} }
if (this.needs_update) { if (this.needs_update) {
@ -339,47 +371,42 @@ export default class IfBlockWrapper extends Wrapper {
const if_blocks = block.get_unique_name(`if_blocks`); const if_blocks = block.get_unique_name(`if_blocks`);
const if_current_block_type_index = has_else const if_current_block_type_index = has_else
? (nodes) => nodes ? nodes => nodes
: (nodes) => b`if (~${current_block_type_index}) { ${nodes} }`; : nodes => b`if (~${current_block_type_index}) { ${nodes} }`;
block.add_variable(current_block_type_index); block.add_variable(current_block_type_index);
block.add_variable(name); block.add_variable(name);
block.chunks.init.push(b` block.chunks.init.push(b`
const ${if_block_creators} = [ const ${if_block_creators} = [
${this.branches.map((branch) => branch.block.name)} ${this.branches.map(branch => branch.block.name)}
]; ];
const ${if_blocks} = []; const ${if_blocks} = [];
${ ${this.needs_update
this.needs_update
? b` ? b`
function ${select_block_type}(#ctx, #dirty) { function ${select_block_type}(#ctx, #dirty) {
${this.branches.map(({ dependencies, condition, snippet }, i) => ${this.branches.map(({ dependencies, condition, snippet }, i) => condition
condition
? b` ? b`
${ ${snippet && (
snippet && dependencies.length > 0
(dependencies.length > 0
? b`if (${block.renderer.dirty(dependencies)}) ${condition} = !!${snippet}` ? b`if (${block.renderer.dirty(dependencies)}) ${condition} = !!${snippet}`
: b`if (${condition} == null) ${condition} = !!${snippet}` : b`if (${condition} == null) ${condition} = !!${snippet}`
)} )}
if (${condition}) return ${i};` if (${condition}) return ${i};`
: b`return ${i};` : b`return ${i};`)}
)}
${!has_else && b`return -1;`} ${!has_else && b`return -1;`}
} }
` `
: b` : b`
function ${select_block_type}(#ctx, #dirty) { function ${select_block_type}(#ctx, #dirty) {
${this.branches.map(({ condition, snippet }, i) => ${this.branches.map(({ condition, snippet }, i) => condition
condition ? b`if (${snippet || condition}) return ${i};` : b`return ${i};` ? b`if (${snippet || condition}) return ${i};`
)} : b`return ${i};`)}
${!has_else && b`return -1;`} ${!has_else && b`return -1;`}
} }
` `}
}
`); `);
if (has_else) { if (has_else) {
@ -490,7 +517,9 @@ export default class IfBlockWrapper extends Wrapper {
const initial_mount_node = parent_node || '#target'; const initial_mount_node = parent_node || '#target';
const anchor_node = parent_node ? 'null' : '#anchor'; const anchor_node = parent_node ? 'null' : '#anchor';
block.chunks.mount.push(b`if (${name}) ${name}.m(${initial_mount_node}, ${anchor_node});`); block.chunks.mount.push(
b`if (${name}) ${name}.m(${initial_mount_node}, ${anchor_node});`
);
if (branch.dependencies.length > 0) { if (branch.dependencies.length > 0) {
const update_mount_node = this.get_update_mount_node(anchor); const update_mount_node = this.get_update_mount_node(anchor);
@ -513,9 +542,7 @@ export default class IfBlockWrapper extends Wrapper {
`; `;
if (branch.snippet) { if (branch.snippet) {
block.chunks.update.push( block.chunks.update.push(b`if (${block.renderer.dirty(branch.dependencies)}) ${branch.condition} = ${branch.snippet}`);
b`if (${block.renderer.dirty(branch.dependencies)}) ${branch.condition} = ${branch.snippet}`
);
} }
// no `p()` here — we don't want to update outroing nodes, // no `p()` here — we don't want to update outroing nodes,

@ -41,11 +41,11 @@ export default class InlineComponentWrapper extends Wrapper {
block.add_dependencies(this.node.expression.dependencies); block.add_dependencies(this.node.expression.dependencies);
} }
this.node.attributes.forEach((attr) => { this.node.attributes.forEach(attr => {
block.add_dependencies(attr.dependencies); block.add_dependencies(attr.dependencies);
}); });
this.node.bindings.forEach((binding) => { this.node.bindings.forEach(binding => {
if (binding.is_contextual) { if (binding.is_contextual) {
// we need to ensure that the each block creates a context including // we need to ensure that the each block creates a context including
// the list and the index, if they're not otherwise referenced // the list and the index, if they're not otherwise referenced
@ -58,7 +58,7 @@ export default class InlineComponentWrapper extends Wrapper {
block.add_dependencies(binding.expression.dependencies); block.add_dependencies(binding.expression.dependencies);
}); });
this.node.handlers.forEach((handler) => { this.node.handlers.forEach(handler => {
if (handler.expression) { if (handler.expression) {
block.add_dependencies(handler.expression.dependencies); block.add_dependencies(handler.expression.dependencies);
} }
@ -66,17 +66,16 @@ export default class InlineComponentWrapper extends Wrapper {
this.var = { this.var = {
type: 'Identifier', type: 'Identifier',
name: (this.node.name === 'svelte:self' name: (
? renderer.component.name.name this.node.name === 'svelte:self' ? renderer.component.name.name :
: this.node.name === 'svelte:component' this.node.name === 'svelte:component' ? 'switch_instance' :
? 'switch_instance' sanitize(this.node.name)
: sanitize(this.node.name) ).toLowerCase()
).toLowerCase(),
}; };
if (this.node.children.length) { if (this.node.children.length) {
this.node.lets.forEach((l) => { this.node.lets.forEach(l => {
extract_names(l.value || l.name).forEach((name) => { extract_names(l.value || l.name).forEach(name => {
renderer.add_to_context(name, true); renderer.add_to_context(name, true);
}); });
}); });
@ -84,7 +83,7 @@ export default class InlineComponentWrapper extends Wrapper {
const default_slot = block.child({ const default_slot = block.child({
comment: create_debugging_comment(node, renderer.component), comment: create_debugging_comment(node, renderer.component),
name: renderer.component.get_unique_name(`create_default_slot`), name: renderer.component.get_unique_name(`create_default_slot`),
type: 'slot', type: 'slot'
}); });
this.renderer.blocks.push(default_slot); this.renderer.blocks.push(default_slot);
@ -122,7 +121,11 @@ export default class InlineComponentWrapper extends Wrapper {
} }
} }
render(block: Block, parent_node: Identifier, parent_nodes: Identifier) { render(
block: Block,
parent_node: Identifier,
parent_nodes: Identifier
) {
this.warn_if_reactive(); this.warn_if_reactive();
const { renderer } = this; const { renderer } = this;
@ -140,14 +143,14 @@ export default class InlineComponentWrapper extends Wrapper {
const default_slot = this.slots.get('default'); const default_slot = this.slots.get('default');
this.fragment.nodes.forEach((child) => { this.fragment.nodes.forEach((child) => {
child.render(default_slot.block, null, (x`#nodes` as unknown) as Identifier); child.render(default_slot.block, null, x`#nodes` as unknown as Identifier);
}); });
} }
let props; let props;
const name_changes = block.get_unique_name(`${name.name}_changes`); const name_changes = block.get_unique_name(`${name.name}_changes`);
const uses_spread = !!this.node.attributes.find((a) => a.is_spread); const uses_spread = !!this.node.attributes.find(a => a.is_spread);
// removing empty slot // removing empty slot
for (const slot of this.slots.keys()) { for (const slot of this.slots.keys()) {
@ -157,8 +160,7 @@ export default class InlineComponentWrapper extends Wrapper {
} }
} }
const initial_props = const initial_props = this.slots.size > 0
this.slots.size > 0
? [ ? [
p`$$slots: { p`$$slots: {
${Array.from(this.slots).map(([name, slot]) => { ${Array.from(this.slots).map(([name, slot]) => {
@ -174,7 +176,7 @@ export default class InlineComponentWrapper extends Wrapper {
const attribute_object = uses_spread const attribute_object = uses_spread
? x`{ ${initial_props} }` ? x`{ ${initial_props} }`
: x`{ : x`{
${this.node.attributes.map((attr) => p`${attr.name}: ${attr.get_value(block)}`)}, ${this.node.attributes.map(attr => p`${attr.name}: ${attr.get_value(block)}`)},
${initial_props} ${initial_props}
}`; }`;
@ -196,8 +198,8 @@ export default class InlineComponentWrapper extends Wrapper {
} }
const fragment_dependencies = new Set(this.fragment ? ['$$scope'] : []); const fragment_dependencies = new Set(this.fragment ? ['$$scope'] : []);
this.slots.forEach((slot) => { this.slots.forEach(slot => {
slot.block.dependencies.forEach((name) => { slot.block.dependencies.forEach(name => {
const is_let = slot.scope.is_let(name); const is_let = slot.scope.is_let(name);
const variable = renderer.component.var_lookup.get(name); const variable = renderer.component.var_lookup.get(name);
@ -205,12 +207,9 @@ export default class InlineComponentWrapper extends Wrapper {
}); });
}); });
const dynamic_attributes = this.node.attributes.filter((a) => a.get_dependencies().length > 0); const dynamic_attributes = this.node.attributes.filter(a => a.get_dependencies().length > 0);
if ( if (!uses_spread && (dynamic_attributes.length > 0 || this.node.bindings.length > 0 || fragment_dependencies.size > 0)) {
!uses_spread &&
(dynamic_attributes.length > 0 || this.node.bindings.length > 0 || fragment_dependencies.size > 0)
) {
updates.push(b`const ${name_changes} = {};`); updates.push(b`const ${name_changes} = {};`);
} }
@ -223,15 +222,14 @@ export default class InlineComponentWrapper extends Wrapper {
const all_dependencies: Set<string> = new Set(); const all_dependencies: Set<string> = new Set();
this.node.attributes.forEach((attr) => { this.node.attributes.forEach(attr => {
add_to_set(all_dependencies, attr.dependencies); add_to_set(all_dependencies, attr.dependencies);
}); });
this.node.attributes.forEach((attr, i) => { this.node.attributes.forEach((attr, i) => {
const { name, dependencies } = attr; const { name, dependencies } = attr;
const condition = const condition = dependencies.size > 0 && (dependencies.size !== all_dependencies.size)
dependencies.size > 0 && dependencies.size !== all_dependencies.size
? renderer.dirty(Array.from(dependencies)) ? renderer.dirty(Array.from(dependencies))
: null; : null;
const unchanged = dependencies.size === 0; const unchanged = dependencies.size === 0;
@ -251,11 +249,19 @@ export default class InlineComponentWrapper extends Wrapper {
} }
changes.push( changes.push(
unchanged ? x`${levels}[${i}]` : condition ? x`${condition} && ${change_object}` : change_object unchanged
? x`${levels}[${i}]`
: condition
? x`${condition} && ${change_object}`
: change_object
); );
}); });
block.chunks.init.push(b`const ${levels} = [${initial_props}];`); block.chunks.init.push(b`
const ${levels} = [
${initial_props}
];
`);
statements.push(b` statements.push(b`
for (let i = 0; i < ${levels}.length; i += 1) { for (let i = 0; i < ${levels}.length; i += 1) {
@ -296,7 +302,7 @@ export default class InlineComponentWrapper extends Wrapper {
}`); }`);
} }
const munged_bindings = this.node.bindings.map((binding) => { const munged_bindings = this.node.bindings.map(binding => {
component.has_reactive_assignments = true; component.has_reactive_assignments = true;
if (binding.name === 'this') { if (binding.name === 'this') {
@ -312,7 +318,11 @@ export default class InlineComponentWrapper extends Wrapper {
const snippet = binding.expression.manipulate(block); const snippet = binding.expression.manipulate(block);
statements.push(b`if (${snippet} !== undefined) ${props}.${binding.name} = ${snippet};`); statements.push(b`
if (${snippet} !== void 0) {
${props}.${binding.name} = ${snippet};
}`
);
updates.push(b` updates.push(b`
if (!${updating} && ${renderer.dirty(Array.from(binding.expression.dependencies))}) { if (!${updating} && ${renderer.dirty(Array.from(binding.expression.dependencies))}) {
@ -361,7 +371,7 @@ export default class InlineComponentWrapper extends Wrapper {
return b`@binding_callbacks.push(() => @bind(${this.var}, '${binding.name}', ${id}));`; return b`@binding_callbacks.push(() => @bind(${this.var}, '${binding.name}', ${id}));`;
}); });
const munged_handlers = this.node.handlers.map((handler) => { const munged_handlers = this.node.handlers.map(handler => {
const event_handler = new EventHandler(handler, this); const event_handler = new EventHandler(handler, this);
let snippet = event_handler.get_snippet(block); let snippet = event_handler.get_snippet(block);
if (handler.modifiers.has('once')) snippet = x`@once(${snippet})`; if (handler.modifiers.has('once')) snippet = x`@once(${snippet})`;
@ -392,10 +402,14 @@ export default class InlineComponentWrapper extends Wrapper {
} }
`); `);
block.chunks.create.push(b`if (${name} && ${name}.$$.fragment) ${name}.$$.fragment.c();`); block.chunks.create.push(
b`if (${name}) @create_component(${name}.$$.fragment);`
);
if (parent_nodes && this.renderer.options.hydratable) { if (parent_nodes && this.renderer.options.hydratable) {
block.chunks.claim.push(b`if (${name} && ${name}.$$.fragment) ${name}.$$.fragment.l(${parent_nodes});`); block.chunks.claim.push(
b`if (${name}) @claim_component(${name}.$$.fragment, ${parent_nodes});`
);
} }
block.chunks.mount.push(b` block.chunks.mount.push(b`
@ -444,18 +458,19 @@ export default class InlineComponentWrapper extends Wrapper {
if (${name}) @transition_in(${name}.$$.fragment, #local); if (${name}) @transition_in(${name}.$$.fragment, #local);
`); `);
block.chunks.outro.push(b`if (${name}) @transition_out(${name}.$$.fragment, #local);`); block.chunks.outro.push(
b`if (${name}) @transition_out(${name}.$$.fragment, #local);`
);
block.chunks.destroy.push(b`if (${name}) @destroy_component(${name}, ${parent_node ? null : 'detaching'});`); block.chunks.destroy.push(b`if (${name}) @destroy_component(${name}, ${parent_node ? null : 'detaching'});`);
} else { } else {
const expression = this.node.name === 'svelte:self' ? component.name : this.renderer.reference(this.node.name); const expression = this.node.name === 'svelte:self'
? component.name
: this.renderer.reference(this.node.name);
block.chunks.init.push(b` block.chunks.init.push(b`
${ ${(this.node.attributes.length > 0 || this.node.bindings.length > 0) && b`
(this.node.attributes.length > 0 || this.node.bindings.length > 0) && ${props && b`let ${props} = ${attribute_object};`}`}
b`
${props && b`let ${props} = ${attribute_object};`}`
}
${statements} ${statements}
const ${name} = new ${expression}(${component_opts}); const ${name} = new ${expression}(${component_opts});
@ -488,7 +503,9 @@ export default class InlineComponentWrapper extends Wrapper {
@destroy_component(${name}, ${parent_node ? null : 'detaching'}); @destroy_component(${name}, ${parent_node ? null : 'detaching'});
`); `);
block.chunks.outro.push(b`@transition_out(${name}.$$.fragment, #local);`); block.chunks.outro.push(
b`@transition_out(${name}.$$.fragment, #local);`
);
} }
} }
} }

@ -36,14 +36,21 @@ export default class SlotWrapper extends Wrapper {
this.fallback = block.child({ this.fallback = block.child({
comment: create_debugging_comment(this.node.children[0], this.renderer.component), comment: create_debugging_comment(this.node.children[0], this.renderer.component),
name: this.renderer.component.get_unique_name(`fallback_block`), name: this.renderer.component.get_unique_name(`fallback_block`),
type: 'fallback', type: 'fallback'
}); });
renderer.blocks.push(this.fallback); renderer.blocks.push(this.fallback);
} }
this.fragment = new FragmentWrapper(renderer, this.fallback, node.children, this, strip_whitespace, next_sibling); this.fragment = new FragmentWrapper(
renderer,
this.fallback,
node.children,
this,
strip_whitespace,
next_sibling
);
this.node.values.forEach((attribute) => { this.node.values.forEach(attribute => {
add_to_set(this.dependencies, attribute.dependencies); add_to_set(this.dependencies, attribute.dependencies);
}); });
@ -54,7 +61,11 @@ export default class SlotWrapper extends Wrapper {
block.add_outro(); block.add_outro();
} }
render(block: Block, parent_node: Identifier, parent_nodes: Identifier) { render(
block: Block,
parent_node: Identifier,
parent_nodes: Identifier
) {
const { renderer } = this; const { renderer } = this;
const { slot_name } = this.node; const { slot_name } = this.node;
@ -70,20 +81,20 @@ export default class SlotWrapper extends Wrapper {
const dependencies = new Set(); const dependencies = new Set();
this.node.values.forEach((attribute) => { this.node.values.forEach(attribute => {
attribute.chunks.forEach((chunk) => { attribute.chunks.forEach(chunk => {
if ((chunk as Expression).dependencies) { if ((chunk as Expression).dependencies) {
add_to_set(dependencies, (chunk as Expression).contextual_dependencies); add_to_set(dependencies, (chunk as Expression).contextual_dependencies);
// add_to_set(dependencies, (chunk as Expression).dependencies); // add_to_set(dependencies, (chunk as Expression).dependencies);
(chunk as Expression).dependencies.forEach((name) => { (chunk as Expression).dependencies.forEach(name => {
const variable = renderer.component.var_lookup.get(name); const variable = renderer.component.var_lookup.get(name);
if (variable && !variable.hoistable) dependencies.add(name); if (variable && !variable.hoistable) dependencies.add(name);
}); });
} }
}); });
const dynamic_dependencies = Array.from(attribute.dependencies).filter((name) => { const dynamic_dependencies = Array.from(attribute.dependencies).filter(name => {
if (this.node.scope.is_let(name)) return true; if (this.node.scope.is_let(name)) return true;
const variable = renderer.component.var_lookup.get(name); const variable = renderer.component.var_lookup.get(name);
return is_dynamic(variable); return is_dynamic(variable);
@ -127,10 +138,14 @@ export default class SlotWrapper extends Wrapper {
${has_fallback ? b`const ${slot_or_fallback} = ${slot} || ${this.fallback.name}(#ctx);` : null} ${has_fallback ? b`const ${slot_or_fallback} = ${slot} || ${this.fallback.name}(#ctx);` : null}
`); `);
block.chunks.create.push(b`if (${slot_or_fallback}) ${slot_or_fallback}.c();`); block.chunks.create.push(
b`if (${slot_or_fallback}) ${slot_or_fallback}.c();`
);
if (renderer.options.hydratable) { if (renderer.options.hydratable) {
block.chunks.claim.push(b`if (${slot_or_fallback}) ${slot_or_fallback}.l(${parent_nodes});`); block.chunks.claim.push(
b`if (${slot_or_fallback}) ${slot_or_fallback}.l(${parent_nodes});`
);
} }
block.chunks.mount.push(b` block.chunks.mount.push(b`
@ -139,11 +154,15 @@ export default class SlotWrapper extends Wrapper {
} }
`); `);
block.chunks.intro.push(b`@transition_in(${slot_or_fallback}, #local);`); block.chunks.intro.push(
b`@transition_in(${slot_or_fallback}, #local);`
);
block.chunks.outro.push(b`@transition_out(${slot_or_fallback}, #local);`); block.chunks.outro.push(
b`@transition_out(${slot_or_fallback}, #local);`
);
const is_dependency_dynamic = (name) => { const is_dependency_dynamic = name => {
if (name === '$$scope') return true; if (name === '$$scope') return true;
if (this.node.scope.is_let(name)) return true; if (this.node.scope.is_let(name)) return true;
const variable = renderer.component.var_lookup.get(name); const variable = renderer.component.var_lookup.get(name);
@ -187,6 +206,8 @@ export default class SlotWrapper extends Wrapper {
`); `);
} }
block.chunks.destroy.push(b`if (${slot_or_fallback}) ${slot_or_fallback}.d(detaching);`); block.chunks.destroy.push(
b`if (${slot_or_fallback}) ${slot_or_fallback}.d(detaching);`
);
} }
} }

@ -2,8 +2,12 @@ import { b, x } from 'code-red';
import Block from '../../Block'; import Block from '../../Block';
import Action from '../../../nodes/Action'; import Action from '../../../nodes/Action';
export default function add_actions(block: Block, target: string, actions: Action[]) { export default function add_actions(
actions.forEach((action) => add_action(block, target, action)); block: Block,
target: string,
actions: Action[]
) {
actions.forEach(action => add_action(block, target, action));
} }
export function add_action(block: Block, target: string, action: Action) { export function add_action(block: Block, target: string, action: Action) {
@ -16,7 +20,9 @@ export function add_action(block: Block, target: string, action: Action) {
dependencies = expression.dynamic_dependencies(); dependencies = expression.dynamic_dependencies();
} }
const id = block.get_unique_name(`${action.name.replace(/[^a-zA-Z0-9_$]/g, '_')}_action`); const id = block.get_unique_name(
`${action.name.replace(/[^a-zA-Z0-9_$]/g, '_')}_action`
);
block.add_variable(id); block.add_variable(id);
@ -32,6 +38,8 @@ export function add_action(block: Block, target: string, action: Action) {
condition = x`${condition} && ${block.renderer.dirty(dependencies)}`; condition = x`${condition} && ${block.renderer.dirty(dependencies)}`;
} }
block.chunks.update.push(b`if (${condition}) ${id}.update.call(null, ${snippet});`); block.chunks.update.push(
b`if (${condition}) ${id}.update.call(null, ${snippet});`
);
} }
} }

@ -8,22 +8,20 @@ import { x } from 'code-red';
import Expression from '../../nodes/shared/Expression'; import Expression from '../../nodes/shared/Expression';
import remove_whitespace_children from './utils/remove_whitespace_children'; import remove_whitespace_children from './utils/remove_whitespace_children';
export default function ( export default function(node: Element, renderer: Renderer, options: RenderOptions & {
node: Element,
renderer: Renderer,
options: RenderOptions & {
slot_scopes: Map<any, any>; slot_scopes: Map<any, any>;
} }) {
) {
const children = remove_whitespace_children(node.children, node.next); const children = remove_whitespace_children(node.children, node.next);
// awkward special case // awkward special case
let node_contents; let node_contents;
const contenteditable = const contenteditable = (
node.name !== 'textarea' && node.name !== 'textarea' &&
node.name !== 'input' && node.name !== 'input' &&
node.attributes.some((attribute) => attribute.name === 'contenteditable'); node.attributes.some((attribute) => attribute.name === 'contenteditable')
);
const slot = node.get_static_attribute_value('slot'); const slot = node.get_static_attribute_value('slot');
const nearest_inline_component = node.find_nearest(/InlineComponent/); const nearest_inline_component = node.find_nearest(/InlineComponent/);
@ -34,7 +32,7 @@ export default function (
renderer.add_string(`<${node.name}`); renderer.add_string(`<${node.name}`);
const class_expression_list = node.classes.map((class_directive) => { const class_expression_list = node.classes.map(class_directive => {
const { expression, name } = class_directive; const { expression, name } = class_directive;
const snippet = expression ? expression.node : x`#ctx.${name}`; // TODO is this right? const snippet = expression ? expression.node : x`#ctx.${name}`; // TODO is this right?
return x`${snippet} ? "${name}" : ""`; return x`${snippet} ? "${name}" : ""`;
@ -43,12 +41,13 @@ export default function (
class_expression_list.push(x`"${node.component.stylesheet.id}"`); class_expression_list.push(x`"${node.component.stylesheet.id}"`);
} }
const class_expression = const class_expression =
class_expression_list.length > 0 && class_expression_list.reduce((lhs, rhs) => x`${lhs} + ' ' + ${rhs}`); class_expression_list.length > 0 &&
class_expression_list.reduce((lhs, rhs) => x`${lhs} + ' ' + ${rhs}`);
if (node.attributes.some((attr) => attr.is_spread)) { if (node.attributes.some(attr => attr.is_spread)) {
// TODO dry this out // TODO dry this out
const args = []; const args = [];
node.attributes.forEach((attribute) => { node.attributes.forEach(attribute => {
if (attribute.is_spread) { if (attribute.is_spread) {
args.push(attribute.expression.node); args.push(attribute.expression.node);
} else { } else {
@ -79,7 +78,11 @@ export default function (
node_contents = get_attribute_value(attribute); node_contents = get_attribute_value(attribute);
} else if (attribute.is_true) { } else if (attribute.is_true) {
renderer.add_string(` ${attribute.name}`); renderer.add_string(` ${attribute.name}`);
} else if (boolean_attributes.has(name) && attribute.chunks.length === 1 && attribute.chunks[0].type !== 'Text') { } else if (
boolean_attributes.has(name) &&
attribute.chunks.length === 1 &&
attribute.chunks[0].type !== 'Text'
) {
// a boolean attribute with one non-Text chunk // a boolean attribute with one non-Text chunk
renderer.add_string(` `); renderer.add_string(` `);
renderer.add_expression(x`${(attribute.chunks[0] as Expression).node} ? "${attribute.name}" : ""`); renderer.add_expression(x`${(attribute.chunks[0] as Expression).node} ? "${attribute.name}" : ""`);
@ -90,9 +93,7 @@ export default function (
renderer.add_string(`"`); renderer.add_string(`"`);
} else if (attribute.chunks.length === 1 && attribute.chunks[0].type !== 'Text') { } else if (attribute.chunks.length === 1 && attribute.chunks[0].type !== 'Text') {
const snippet = (attribute.chunks[0] as Expression).node; const snippet = (attribute.chunks[0] as Expression).node;
renderer.add_expression( renderer.add_expression(x`@add_attribute("${attribute.name}", ${snippet}, ${boolean_attributes.has(name) ? 1 : 0})`);
x`@add_attribute("${attribute.name}", ${snippet}, ${boolean_attributes.has(name) ? 1 : 0})`
);
} else { } else {
renderer.add_string(` ${attribute.name}="`); renderer.add_string(` ${attribute.name}="`);
renderer.add_expression((name === 'class' ? get_class_attribute_value : get_attribute_value)(attribute)); renderer.add_expression((name === 'class' ? get_class_attribute_value : get_attribute_value)(attribute));
@ -104,7 +105,7 @@ export default function (
} }
} }
node.bindings.forEach((binding) => { node.bindings.forEach(binding => {
const { name, expression } = binding; const { name, expression } = binding;
if (binding.is_readonly) { if (binding.is_readonly) {
@ -155,15 +156,15 @@ export default function (
} }
const lets = node.lets; const lets = node.lets;
const seen = new Set(lets.map((l) => l.name.name)); const seen = new Set(lets.map(l => l.name.name));
nearest_inline_component.lets.forEach((l) => { nearest_inline_component.lets.forEach(l => {
if (!seen.has(l.name.name)) lets.push(l); if (!seen.has(l.name.name)) lets.push(l);
}); });
options.slot_scopes.set(slot, { options.slot_scopes.set(slot, {
input: get_slot_scope(node.lets), input: get_slot_scope(node.lets),
output: renderer.pop(), output: renderer.pop()
}); });
} else { } else {
renderer.render(children, options); renderer.render(children, options);

@ -10,7 +10,7 @@ function get_prop_value(attribute) {
if (attribute.chunks.length === 0) return x`''`; if (attribute.chunks.length === 0) return x`''`;
return attribute.chunks return attribute.chunks
.map((chunk) => { .map(chunk => {
if (chunk.type === 'Text') return string_literal(chunk.data); if (chunk.type === 'Text') return string_literal(chunk.data);
return chunk.node; return chunk.node;
}) })
@ -21,7 +21,7 @@ export default function (node: InlineComponent, renderer: Renderer, options: Ren
const binding_props = []; const binding_props = [];
const binding_fns = []; const binding_fns = [];
node.bindings.forEach((binding) => { node.bindings.forEach(binding => {
renderer.has_bindings = true; renderer.has_bindings = true;
// TODO this probably won't work for contextual bindings // TODO this probably won't work for contextual bindings
@ -31,23 +31,25 @@ export default function (node: InlineComponent, renderer: Renderer, options: Ren
binding_fns.push(p`${binding.name}: $$value => { ${snippet} = $$value; $$settled = false }`); binding_fns.push(p`${binding.name}: $$value => { ${snippet} = $$value; $$settled = false }`);
}); });
const uses_spread = node.attributes.find((attr) => attr.is_spread); const uses_spread = node.attributes.find(attr => attr.is_spread);
let props; let props;
if (uses_spread) { if (uses_spread) {
props = x`@_Object.assign(${node.attributes props = x`@_Object.assign(${
.map((attribute) => { node.attributes
.map(attribute => {
if (attribute.is_spread) { if (attribute.is_spread) {
return attribute.expression.node; return attribute.expression.node;
} else { } else {
return x`{ ${attribute.name}: ${get_prop_value(attribute)} }`; return x`{ ${attribute.name}: ${get_prop_value(attribute)} }`;
} }
}) })
.concat(binding_props.map((p) => x`{ ${p} }`))})`; .concat(binding_props.map(p => x`{ ${p} }`))
})`;
} else { } else {
props = x`{ props = x`{
${node.attributes.map((attribute) => p`${attribute.name}: ${get_prop_value(attribute)}`)}, ${node.attributes.map(attribute => p`${attribute.name}: ${get_prop_value(attribute)}`)},
${binding_props} ${binding_props}
}`; }`;
} }
@ -56,13 +58,13 @@ export default function (node: InlineComponent, renderer: Renderer, options: Ren
${binding_fns} ${binding_fns}
}`; }`;
const expression = const expression = (
node.name === 'svelte:self' node.name === 'svelte:self'
? renderer.name ? renderer.name
: node.name === 'svelte:component' : node.name === 'svelte:component'
? x`(${node.expression.node}) || @missing_component` ? x`(${node.expression.node}) || @missing_component`
: node.name.split('.').reduce(((lhs, rhs) => x`${lhs}.${rhs}`) as any); : node.name.split('.').reduce(((lhs, rhs) => x`${lhs}.${rhs}`) as any)
);
const slot_fns = []; const slot_fns = [];
const children = remove_whitespace_children(node.children, node.next); const children = remove_whitespace_children(node.children, node.next);
@ -72,21 +74,20 @@ export default function (node: InlineComponent, renderer: Renderer, options: Ren
renderer.push(); renderer.push();
renderer.render( renderer.render(children, Object.assign({}, options, {
children, slot_scopes
Object.assign({}, options, { }));
slot_scopes,
})
);
slot_scopes.set('default', { slot_scopes.set('default', {
input: get_slot_scope(node.lets), input: get_slot_scope(node.lets),
output: renderer.pop(), output: renderer.pop()
}); });
slot_scopes.forEach(({ input, output }, name) => { slot_scopes.forEach(({ input, output }, name) => {
if (!is_empty_template_literal(output)) { if (!is_empty_template_literal(output)) {
slot_fns.push(p`${name}: (${input}) => ${output}`); slot_fns.push(
p`${name}: (${input}) => ${output}`
);
} }
}); });
} }
@ -95,15 +96,13 @@ export default function (node: InlineComponent, renderer: Renderer, options: Ren
${slot_fns} ${slot_fns}
}`; }`;
renderer.add_expression( renderer.add_expression(x`@validate_component(${expression}, "${node.name}").$$render($$result, ${props}, ${bindings}, ${slots})`);
x`@validate_component(${expression}, "${node.name}").$$render($$result, ${props}, ${bindings}, ${slots})`
);
} }
function is_empty_template_literal(template_literal) { function is_empty_template_literal(template_literal) {
return ( return (
template_literal.expressions.length === 0 && template_literal.expressions.length === 0 &&
template_literal.quasis.length === 1 && template_literal.quasis.length === 1 &&
template_literal.quasis[0].value.raw === '' template_literal.quasis[0].value.raw === ""
); );
} }

@ -21,7 +21,7 @@ export function get_attribute_value(attribute: Attribute): ESTreeExpression {
return attribute.chunks return attribute.chunks
.map((chunk) => { .map((chunk) => {
return chunk.type === 'Text' return chunk.type === 'Text'
? (string_literal(chunk.data.replace(/"/g, '&quot;')) as ESTreeExpression) ? string_literal(chunk.data.replace(/"/g, '&quot;')) as ESTreeExpression
: x`@escape(${chunk.node})`; : x`@escape(${chunk.node})`;
}) })
.reduce((lhs, rhs) => x`${lhs} + ${rhs}`); .reduce((lhs, rhs) => x`${lhs} + ${rhs}`);

@ -1,4 +1,5 @@
import { INode } from '../../../nodes/interfaces'; import { INode } from '../../../nodes/interfaces';
import { trim_end, trim_start } from '../../../../utils/trim';
import { link } from '../../../../utils/link'; import { link } from '../../../../utils/link';
// similar logic from `compile/render_dom/wrappers/Fragment` // similar logic from `compile/render_dom/wrappers/Fragment`
@ -20,11 +21,13 @@ export default function remove_whitespace_children(children: INode[], next?: INo
if (nodes.length === 0) { if (nodes.length === 0) {
const should_trim = next const should_trim = next
? next.type === 'Text' && /^\s/.test(next.data) && trimmable_at(child, next) ? next.type === 'Text' &&
/^\s/.test(next.data) &&
trimmable_at(child, next)
: !child.has_ancestor('EachBlock'); : !child.has_ancestor('EachBlock');
if (should_trim) { if (should_trim) {
data = data.trimRight(); data = trim_end(data);
if (!data) continue; if (!data) continue;
} }
} }
@ -36,16 +39,16 @@ export default function remove_whitespace_children(children: INode[], next?: INo
} }
nodes.unshift(child); nodes.unshift(child);
link(last_child, (last_child = child)); link(last_child, last_child = child);
} else { } else {
nodes.unshift(child); nodes.unshift(child);
link(last_child, (last_child = child)); link(last_child, last_child = child);
} }
} }
const first = nodes[0]; const first = nodes[0];
if (first && first.type === 'Text') { if (first && first.type === 'Text') {
first.data = first.data.trimLeft(); first.data = trim_start(first.data);
if (!first.data) { if (!first.data) {
first.var = null; first.var = null;
nodes.shift(); nodes.shift();
@ -64,6 +67,7 @@ function trimmable_at(child: INode, next_sibling: INode): boolean {
// The child and its sibling share a common nearest each block (not at an each block boundary) // The child and its sibling share a common nearest each block (not at an each block boundary)
// The next sibling's previous node is an each block // The next sibling's previous node is an each block
return ( return (
next_sibling.find_nearest(/EachBlock/) === child.find_nearest(/EachBlock/) || next_sibling.prev.type === 'EachBlock' next_sibling.find_nearest(/EachBlock/) ===
child.find_nearest(/EachBlock/) || next_sibling.prev.type === 'EachBlock'
); );
} }

@ -8,33 +8,40 @@ import Text from '../nodes/Text';
import { extract_names } from '../utils/scope'; import { extract_names } from '../utils/scope';
import { LabeledStatement, Statement, ExpressionStatement, AssignmentExpression, Node } from 'estree'; import { LabeledStatement, Statement, ExpressionStatement, AssignmentExpression, Node } from 'estree';
export default function ssr(component: Component, options: CompileOptions): { js: Node[]; css: CssResult } { export default function ssr(
component: Component,
options: CompileOptions
): {js: Node[]; css: CssResult} {
const renderer = new Renderer({ const renderer = new Renderer({
name: component.name, name: component.name
}); });
const { name } = component; const { name } = component;
// create $$render function // create $$render function
renderer.render(trim(component.fragment.children), { locate: component.locate, ...options }); renderer.render(trim(component.fragment.children), Object.assign({
locate: component.locate
}, options));
// TODO put this inside the Renderer class // TODO put this inside the Renderer class
const literal = renderer.pop(); const literal = renderer.pop();
// TODO concatenate CSS maps // TODO concatenate CSS maps
const css = options.customElement ? { code: null, map: null } : component.stylesheet.render(options.filename, true); const css = options.customElement ?
{ code: null, map: null } :
component.stylesheet.render(options.filename, true);
const uses_rest = component.var_lookup.has('$$restProps'); const uses_rest = component.var_lookup.has('$$restProps');
const props = component.vars.filter((variable) => !variable.module && variable.export_name); const props = component.vars.filter((variable => !variable.module && variable.export_name));
const rest = uses_rest const rest = uses_rest
? b` ? b`
let #k; let #k;
const #keys = new Set([${props.map((prop) => `"${prop.export_name}"`).join(',')}]); const #keys = new Set([${props.map(prop => `"${prop.export_name}"`).join(',')}]);
let $$restProps = {}; let $$restProps = {};
for (#k in $$props){ if (!#keys.has(#k) && #k[0] !== '$'){ $$restProps[#k] = $$props[#k];}}` for (#k in $$props){ if (!#keys.has(#k) && #k[0] !== '$'){ $$restProps[#k] = $$props[#k];}}`
: null; : null;
const reactive_stores = component.vars.filter((variable) => variable.name[0] === '$' && variable.name[1] !== '$'); const reactive_stores = component.vars.filter(variable => variable.name[0] === '$' && variable.name[1] !== '$');
const reactive_store_values = reactive_stores const reactive_store_values = reactive_stores
.map(({ name }) => { .map(({ name }) => {
const store_name = name.slice(1); const store_name = name.slice(1);
@ -59,40 +66,42 @@ export default function ssr(component: Component, options: CompileOptions): { js
// TODO only do this for props with a default value // TODO only do this for props with a default value
const parent_bindings = instance_javascript const parent_bindings = instance_javascript
? component.vars ? component.vars
.filter((variable) => !variable.module && variable.export_name) .filter(variable => !variable.module && variable.export_name)
.map((prop) => { .map(prop => {
return b`if ($$props.${prop.export_name} === void 0 && $$bindings.${prop.export_name} && ${prop.name} !== void 0) $$bindings.${prop.export_name}(${prop.name});`; return b`if ($$props.${prop.export_name} === void 0 && $$bindings.${prop.export_name} && ${prop.name} !== void 0) $$bindings.${prop.export_name}(${prop.name});`;
}) })
: []; : [];
const reactive_declarations = component.reactive_declarations.map((d) => { const reactive_declarations = component.reactive_declarations.map(d => {
const body: Statement = (d.node as LabeledStatement).body; const body: Statement = (d.node as LabeledStatement).body;
let statement = b`${body}`; let statement = b`${body}`;
if (d.declaration) { if (d.declaration) {
const declared = extract_names(d.declaration); const declared = extract_names(d.declaration);
const injected = declared.filter((name) => { const injected = declared.filter(name => {
return name[0] !== '$' && component.var_lookup.get(name).injected; return name[0] !== '$' && component.var_lookup.get(name).injected;
}); });
const self_dependencies = injected.filter((name) => d.dependencies.has(name)); const self_dependencies = injected.filter(name => d.dependencies.has(name));
if (injected.length) { if (injected.length) {
// in some cases we need to do `let foo; [expression]`, in // in some cases we need to do `let foo; [expression]`, in
// others we can do `let [expression]` // others we can do `let [expression]`
const separate = self_dependencies.length > 0 || declared.length > injected.length; const separate = (
self_dependencies.length > 0 ||
declared.length > injected.length
);
const { left, right } = (body as ExpressionStatement).expression as AssignmentExpression; const { left, right } = (body as ExpressionStatement).expression as AssignmentExpression;
statement = separate statement = separate
? b` ? b`
${injected.map((name) => b`let ${name};`)} ${injected.map(name => b`let ${name};`)}
${statement}` ${statement}`
: b`let ${left} = ${right}`; : b`let ${left} = ${right}`;
} }
} else { } else { // TODO do not add label if it's not referenced
// TODO do not add label if it's not referenced
statement = b`$: { ${statement} }`; statement = b`$: { ${statement} }`;
} }
@ -128,23 +137,22 @@ export default function ssr(component: Component, options: CompileOptions): { js
...reactive_stores.map(({ name }) => { ...reactive_stores.map(({ name }) => {
const store_name = name.slice(1); const store_name = name.slice(1);
const store = component.var_lookup.get(store_name); const store = component.var_lookup.get(store_name);
return b`let ${name};${store && store.hoistable && b`@subscribe(${store_name},#v=>{${name}=#v;})();`}`; return b`
let ${name};
${store && store.hoistable && b`@subscribe(${store_name},#v=>{${name}=#v;})();`}`;
}), }),
instance_javascript, instance_javascript,
...parent_bindings, ...parent_bindings,
css.code && b`$$result.css.add(#css);`, css.code && b`$$result.css.add(#css);`,
main, main
].filter(Boolean); ].filter(Boolean);
const js = b` const js = b`
${ ${css.code ? b`
css.code const #css = {
? b`const #css = {
code: "${css.code}", code: "${css.code}",
map: ${css.map ? string_literal(css.map.toString()) : 'null'} map: ${css.map ? string_literal(css.map.toString()) : 'null'}
};` };` : null}
: null
}
${component.extract_javascript(component.ast.module)} ${component.extract_javascript(component.ast.module)}

@ -7,8 +7,8 @@ export default function get_slot_data(values: Map<string, Attribute>, block: Blo
return { return {
type: 'ObjectExpression', type: 'ObjectExpression',
properties: Array.from(values.values()) properties: Array.from(values.values())
.filter((attribute) => attribute.name !== 'name') .filter(attribute => attribute.name !== 'name')
.map((attribute) => { .map(attribute => {
const value = get_value(block, attribute); const value = get_value(block, attribute);
return p`${attribute.name}: ${value}`; return p`${attribute.name}: ${value}`;
}), }),

@ -1,4 +1,4 @@
import { Node, Program } from 'estree'; import { Node, Program } from "estree";
import { SourceMap } from 'magic-string'; import { SourceMap } from 'magic-string';
interface BaseNode { interface BaseNode {
@ -24,8 +24,7 @@ export interface MustacheTag extends BaseNode {
expression: Node; expression: Node;
} }
export type DirectiveType = export type DirectiveType = 'Action'
| 'Action'
| 'Animation' | 'Animation'
| 'Binding' | 'Binding'
| 'Class' | 'Class'
@ -49,7 +48,11 @@ export interface Transition extends BaseDirective {
export type Directive = BaseDirective | Transition; export type Directive = BaseDirective | Transition;
export type TemplateNode = Text | MustacheTag | BaseNode | Directive | Transition; export type TemplateNode = Text
| MustacheTag
| BaseNode
| Directive
| Transition;
export interface Parser { export interface Parser {
readonly template: string; readonly template: string;
@ -102,9 +105,9 @@ export interface Warning {
export type ModuleFormat = 'esm' | 'cjs'; export type ModuleFormat = 'esm' | 'cjs';
export interface CompileOptions { export interface CompileOptions {
filename?: string;
format?: ModuleFormat; format?: ModuleFormat;
name?: string; name?: string;
filename?: string;
generate?: string | false; generate?: string | false;
outputFilename?: string; outputFilename?: string;

@ -49,12 +49,7 @@ export default function mustache(parser: Parser) {
block = parser.current(); block = parser.current();
} }
if ( if (block.type === 'ElseBlock' || block.type === 'PendingBlock' || block.type === 'ThenBlock' || block.type === 'CatchBlock') {
block.type === 'ElseBlock' ||
block.type === 'PendingBlock' ||
block.type === 'ThenBlock' ||
block.type === 'CatchBlock'
) {
block.end = start; block.end = start;
parser.stack.pop(); parser.stack.pop();
block = parser.current(); block = parser.current();
@ -71,7 +66,7 @@ export default function mustache(parser: Parser) {
} else { } else {
parser.error({ parser.error({
code: `unexpected-block-close`, code: `unexpected-block-close`,
message: `Unexpected block closing tag`, message: `Unexpected block closing tag`
}); });
} }
@ -103,7 +98,7 @@ export default function mustache(parser: Parser) {
if (parser.eat('if')) { if (parser.eat('if')) {
parser.error({ parser.error({
code: 'invalid-elseif', code: 'invalid-elseif',
message: `'elseif' should be 'else if'`, message: `'elseif' should be 'else if'`
}); });
} }
@ -115,9 +110,9 @@ export default function mustache(parser: Parser) {
if (block.type !== 'IfBlock') { if (block.type !== 'IfBlock') {
parser.error({ parser.error({
code: `invalid-elseif-placement`, code: `invalid-elseif-placement`,
message: parser.stack.some((block) => block.type === 'IfBlock') message: parser.stack.some(block => block.type === 'IfBlock')
? `Expected to close ${to_string(block)} before seeing {:else if ...} block` ? `Expected to close ${to_string(block)} before seeing {:else if ...} block`
: `Cannot have an {:else if ...} block outside an {#if ...} block`, : `Cannot have an {:else if ...} block outside an {#if ...} block`
}); });
} }
@ -153,9 +148,9 @@ export default function mustache(parser: Parser) {
if (block.type !== 'IfBlock' && block.type !== 'EachBlock') { if (block.type !== 'IfBlock' && block.type !== 'EachBlock') {
parser.error({ parser.error({
code: `invalid-else-placement`, code: `invalid-else-placement`,
message: parser.stack.some((block) => block.type === 'IfBlock' || block.type === 'EachBlock') message: parser.stack.some(block => block.type === 'IfBlock' || block.type === 'EachBlock')
? `Expected to close ${to_string(block)} before seeing {:else} block` ? `Expected to close ${to_string(block)} before seeing {:else} block`
: `Cannot have an {:else} block outside an {#if ...} or {#each ...} block`, : `Cannot have an {:else} block outside an {#if ...} or {#each ...} block`
}); });
} }
@ -179,18 +174,18 @@ export default function mustache(parser: Parser) {
if (block.type !== 'PendingBlock') { if (block.type !== 'PendingBlock') {
parser.error({ parser.error({
code: `invalid-then-placement`, code: `invalid-then-placement`,
message: parser.stack.some((block) => block.type === 'PendingBlock') message: parser.stack.some(block => block.type === 'PendingBlock')
? `Expected to close ${to_string(block)} before seeing {:then} block` ? `Expected to close ${to_string(block)} before seeing {:then} block`
: `Cannot have an {:then} block outside an {#await ...} block`, : `Cannot have an {:then} block outside an {#await ...} block`
}); });
} }
} else { } else {
if (block.type !== 'ThenBlock' && block.type !== 'PendingBlock') { if (block.type !== 'ThenBlock' && block.type !== 'PendingBlock') {
parser.error({ parser.error({
code: `invalid-catch-placement`, code: `invalid-catch-placement`,
message: parser.stack.some((block) => block.type === 'ThenBlock' || block.type === 'PendingBlock') message: parser.stack.some(block => block.type === 'ThenBlock' || block.type === 'PendingBlock')
? `Expected to close ${to_string(block)} before seeing {:catch} block` ? `Expected to close ${to_string(block)} before seeing {:catch} block`
: `Cannot have an {:catch} block outside an {#await ...} block`, : `Cannot have an {:catch} block outside an {#await ...} block`
}); });
} }
} }
@ -211,7 +206,7 @@ export default function mustache(parser: Parser) {
end: null, end: null,
type: is_then ? 'ThenBlock': 'CatchBlock', type: is_then ? 'ThenBlock': 'CatchBlock',
children: [], children: [],
skip: false, skip: false
}; };
await_block[is_then ? 'then' : 'catch'] = new_block; await_block[is_then ? 'then' : 'catch'] = new_block;
@ -229,7 +224,7 @@ export default function mustache(parser: Parser) {
} else { } else {
parser.error({ parser.error({
code: `expected-block-type`, code: `expected-block-type`,
message: `Expected if, each or await`, message: `Expected if, each or await`
}); });
} }
@ -237,9 +232,8 @@ export default function mustache(parser: Parser) {
const expression = read_expression(parser); const expression = read_expression(parser);
const block: TemplateNode = const block: TemplateNode = type === 'AwaitBlock' ?
type === 'AwaitBlock' {
? {
start, start,
end: null, end: null,
type, type,
@ -251,24 +245,24 @@ export default function mustache(parser: Parser) {
end: null, end: null,
type: 'PendingBlock', type: 'PendingBlock',
children: [], children: [],
skip: true, skip: true
}, },
then: { then: {
start: null, start: null,
end: null, end: null,
type: 'ThenBlock', type: 'ThenBlock',
children: [], children: [],
skip: true, skip: true
}, },
catch: { catch: {
start: null, start: null,
end: null, end: null,
type: 'CatchBlock', type: 'CatchBlock',
children: [], children: [],
skip: true, skip: true
}, },
} } :
: { {
start, start,
end: null, end: null,
type, type,
@ -290,10 +284,9 @@ export default function mustache(parser: Parser) {
if (parser.eat(',')) { if (parser.eat(',')) {
parser.allow_whitespace(); parser.allow_whitespace();
block.index = parser.read_identifier(); block.index = parser.read_identifier();
if (!block.index) if (!block.index) parser.error({
parser.error({
code: `expected-name`, code: `expected-name`,
message: `Expected name`, message: `Expected name`
}); });
parser.allow_whitespace(); parser.allow_whitespace();
@ -368,17 +361,16 @@ export default function mustache(parser: Parser) {
} else { } else {
const expression = read_expression(parser); const expression = read_expression(parser);
identifiers = expression.type === 'SequenceExpression' ? expression.expressions : [expression]; identifiers = expression.type === 'SequenceExpression'
? expression.expressions
: [expression];
identifiers.forEach((node) => { identifiers.forEach(node => {
if (node.type !== 'Identifier') { if (node.type !== 'Identifier') {
parser.error( parser.error({
{
code: 'invalid-debug-args', code: 'invalid-debug-args',
message: '{@debug ...} arguments must be identifiers, not arbitrary expressions', message: '{@debug ...} arguments must be identifiers, not arbitrary expressions'
}, }, node.start);
node.start
);
} }
}); });
@ -390,7 +382,7 @@ export default function mustache(parser: Parser) {
start, start,
end: parser.index, end: parser.index,
type: 'DebugTag', type: 'DebugTag',
identifiers, identifiers
}); });
} else { } else {
const expression = read_expression(parser); const expression = read_expression(parser);

@ -35,7 +35,10 @@ const windows_1252 = [
376, 376,
]; ];
const entity_pattern = new RegExp(`&(#?(?:x[\\w\\d]+|\\d+|${Object.keys(entities).join('|')}))(?:;|\\b)`, 'g'); const entity_pattern = new RegExp(
`&(#?(?:x[\\w\\d]+|\\d+|${Object.keys(entities).join('|')}))(?:;|\\b)`,
'g'
);
export function decode_character_references(html: string) { export function decode_character_references(html: string) {
return html.replace(entity_pattern, (match, entity) => { return html.replace(entity_pattern, (match, entity) => {
@ -57,7 +60,7 @@ export function decode_character_references(html: string) {
return String.fromCodePoint(validate_code(code)); return String.fromCodePoint(validate_code(code));
}); });
} }
// this is necessary
const NUL = 0; const NUL = 0;
// some code points are verboten. If we were inserting HTML, the browser would replace the illegal // some code points are verboten. If we were inserting HTML, the browser would replace the illegal

@ -156,7 +156,11 @@ class FuzzySet {
let results = []; let results = [];
// start with high gram size and if there are no results, go to lower gram sizes // start with high gram size and if there are no results, go to lower gram sizes
for (let gram_size = GRAM_SIZE_UPPER; gram_size >= GRAM_SIZE_LOWER; --gram_size) { for (
let gram_size = GRAM_SIZE_UPPER;
gram_size >= GRAM_SIZE_LOWER;
--gram_size
) {
results = this.__get(value, gram_size); results = this.__get(value, gram_size);
if (results) { if (results) {
return results; return results;
@ -200,7 +204,10 @@ class FuzzySet {
// build a results list of [score, str] // build a results list of [score, str]
for (const match_index in matches) { for (const match_index in matches) {
match_score = matches[match_index]; match_score = matches[match_index];
results.push([match_score / (vector_normal * items[match_index][0]), items[match_index][1]]); results.push([
match_score / (vector_normal * items[match_index][0]),
items[match_index][1],
]);
} }
results.sort(sort_descending); results.sort(sort_descending);
@ -209,7 +216,10 @@ class FuzzySet {
const end_index = Math.min(50, results.length); const end_index = Math.min(50, results.length);
// truncate somewhat arbitrarily to 50 // truncate somewhat arbitrarily to 50
for (let i = 0; i < end_index; ++i) { for (let i = 0; i < end_index; ++i) {
new_results.push([_distance(results[i][1], normalized_value), results[i][1]]); new_results.push([
_distance(results[i][1], normalized_value),
results[i][1],
]);
} }
results = new_results; results = new_results;
results.sort(sort_descending); results.sort(sort_descending);

@ -1,2 +1,4 @@
export const link = <T extends { next?: T; prev?: T }>(next: T, prev: T) => export function link<T extends { next?: T; prev?: T }>(next: T, prev: T) {
void ((prev.next = next) && (next.prev = prev)); prev.next = next;
if (next) next.prev = prev;
}

@ -1,2 +1,6 @@
export default (items: string[], conjunction = 'or') => export default function list(items: string[], conjunction = 'or') {
items.length === 1 ? items[0] : items.slice(0, -1).join(', ') + ` ${conjunction} ${items[items.length - 1]}`; if (items.length === 1) return items[0];
return `${items.slice(0, -1).join(', ')} ${conjunction} ${items[
items.length - 1
]}`;
}

@ -40,7 +40,8 @@ const run_timed = (now: number) => {
timed_tasks[j + 1] = this_task; timed_tasks[j + 1] = this_task;
last_index++; last_index++;
} }
pending_inserts = !!(pending_insert_timed.length = 0); pending_insert_timed.length = 0;
pending_inserts = false;
} }
return (running_timed = !!(timed_tasks.length = last_index + 1)); return (running_timed = !!(timed_tasks.length = last_index + 1));
}; };
@ -66,7 +67,7 @@ export const setFrameTimeout = (callback: (t: number) => void, timestamp: number
return () => void (task.callback = noop); return () => void (task.callback = noop);
}; };
/** /**
* Calls function every frame with a value going from 0 to 1 * Calls function every frame with linear tween from 0 to 1
*/ */
export const setTweenTimeout = ( export const setTweenTimeout = (
stop: (now: number) => void, stop: (now: number) => void,
@ -91,7 +92,7 @@ export const setTweenTimeout = (
}; };
}; };
/** /**
* Calls function every frame with the amount of elapsed frames * Calls function every frame with time elapsed in seconds
*/ */
export const onEachFrame = ( export const onEachFrame = (
callback: (seconds_elapsed: number) => boolean, callback: (seconds_elapsed: number) => boolean,

@ -47,7 +47,7 @@ export const group_transition_out = (fn) => {
}); });
block.o(1); block.o(1);
}); });
if (!current_group.r) for (let i = 0; i < current_group.c.length; i++) current_group.c[i](); if (!current_group.r) for (let i = 0; i < c.length; i++) c[i]();
transition_group = transition_group.p; transition_group = transition_group.p;
}; };

Loading…
Cancel
Save