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 =
typeof process !== 'undefined' && process.hrtime
const now = (typeof process !== 'undefined' && process.hrtime)
? () => {
const t = process.hrtime();
return t[0] * 1e3 + t[1] / 1e6;
@ -15,13 +14,10 @@ interface Timing {
function collapse_timings(timings) {
const result = {};
timings.forEach((timing) => {
result[timing.label] = Object.assign(
{
total: timing.end - timing.start,
},
timing.children && collapse_timings(timing.children)
);
timings.forEach(timing => {
result[timing.label] = Object.assign({
total: timing.end - timing.start
}, timing.children && collapse_timings(timing.children));
});
return result;
}
@ -44,7 +40,7 @@ export default class Stats {
label,
start: now(),
end: null,
children: [],
children: []
};
this.current_children.push(timing);
@ -66,13 +62,12 @@ export default class Stats {
}
render() {
const timings = {
total: now() - this.start_time,
...collapse_timings(this.timings),
};
const timings = Object.assign({
total: now() - this.start_time
}, collapse_timings(this.timings));
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 is_reference from 'is-reference';
import { getLocator } from 'locate-character';
import { test } from '../config';
import { Ast, CompileOptions, CssResult, Var, Warning } from '../interfaces';
import Stats from '../Stats';
import error from '../utils/error';
import fuzzymatch from '../utils/fuzzymatch';
import get_code_frame from '../utils/get_code_frame';
import { globals, is_valid, reserved } from '../utils/names';
import { globals, reserved, is_valid } from '../utils/names';
import { namespaces } from '../utils/namespaces';
import create_module from './create_module';
import {
create_scopes,
extract_names,
Scope,
extract_identifiers,
} from './utils/scope';
import Stylesheet from './css/Stylesheet';
import internal_exports from './internal_exports';
import { test } from '../config';
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 fuzzymatch from '../utils/fuzzymatch';
import get_object from './utils/get_object';
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 check_graph_for_cycles from './utils/check_graph_for_cycles';
import flatten_reference from './utils/flatten_reference';
import get_object from './utils/get_object';
import is_used_as_reference from './utils/is_used_as_reference';
import { print, x, b } from 'code-red';
import { is_reserved_keyword } from './utils/reserved_keywords';
import { create_scopes, extract_identifiers, extract_names, Scope } from './utils/scope';
interface ComponentOptions {
namespace?: string;
@ -69,8 +66,8 @@ export default class Component {
hoistable_nodes: Set<Node> = new Set();
node_for_declaration: Map<string, Node> = new Map();
partly_hoisted: Array<Node | Node[]> = [];
fully_hoisted: Array<Node | Node[]> = [];
partly_hoisted: Array<(Node | Node[])> = [];
fully_hoisted: Array<(Node | Node[])> = [];
reactive_declarations: Array<{
assignees: Set<string>;
dependencies: Set<string>;
@ -119,7 +116,7 @@ export default class Component {
html: ast.html,
css: ast.css,
instance: ast.instance && JSON.parse(JSON.stringify(ast.instance)),
module: ast.module,
module: ast.module
};
this.file =
@ -130,18 +127,30 @@ export default class Component {
this.locate = getLocator(this.source, { offsetLine: 1 });
// 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.component_options = process_component_options(this, this.ast.html.children);
this.namespace = namespaces[this.component_options.namespace] || this.component_options.namespace;
this.component_options = process_component_options(
this,
this.ast.html.children
);
this.namespace =
namespaces[this.component_options.namespace] ||
this.component_options.namespace;
if (compile_options.customElement) {
if (this.component_options.tag === undefined && compile_options.tag === undefined) {
const svelteOptions = ast.html.children.find((child) => child.name === 'svelte:options') || {
start: 0,
end: 0,
};
if (
this.component_options.tag === undefined &&
compile_options.tag === undefined
) {
const svelteOptions = ast.html.children.find(
child => child.name === 'svelte:options'
) || { start: 0, end: 0 };
this.warn(svelteOptions, {
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}/>`,
@ -250,19 +259,23 @@ export default class Component {
this.helpers.set(name, alias);
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
const literal: Literal = { type: 'Literal', value: node.name };
if (parent.type === 'Property' && key === 'key') {
parent.key = literal;
} else if (parent.type === 'MemberExpression' && key === 'property') {
}
else if (parent.type === 'MemberExpression' && key === 'property') {
parent.property = literal;
parent.computed = true;
}
}
}
},
}
});
const referenced_globals = Array.from(
@ -287,17 +300,19 @@ export default class Component {
referenced_globals,
this.imports,
this.vars
.filter((variable) => variable.module && variable.export_name)
.map((variable) => ({
.filter(variable => variable.module && variable.export_name)
.map(variable => ({
name: variable.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, {
sourceMapSource: compile_options.filename,
sourceMapSource: compile_options.filename
});
js.map.sources = [
@ -306,7 +321,9 @@ export default class Component {
: null,
];
js.map.sourcesContent = [this.source];
js.map.sourcesContent = [
this.source
];
}
return {
@ -315,8 +332,8 @@ export default class Component {
ast: this.original_ast,
warnings: this.warnings,
vars: this.vars
.filter((v) => !v.global && !v.internal)
.map((v) => ({
.filter(v => !v.global && !v.internal)
.map(v => ({
name: v.name,
export_name: v.export_name || null,
injected: v.injected || false,
@ -361,13 +378,17 @@ export default class Component {
return (name: string): Identifier => {
if (test) name = `${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);
this.globally_used_names.add(alias);
return {
type: 'Identifier',
name: alias,
name: alias
};
};
}
@ -419,7 +440,8 @@ export default class Component {
end,
pos: pos.start,
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.type === 'VariableDeclaration') {
node.declaration.declarations.forEach((declarator) => {
extract_names(declarator.id).forEach((name) => {
node.declaration.declarations.forEach(declarator => {
extract_names(declarator.id).forEach(name => {
const variable = this.var_lookup.get(name);
variable.export_name = name;
if (
variable.writable &&
!(variable.referenced || variable.referenced_from_script || variable.subscribable)
) {
if (variable.writable && !(variable.referenced || variable.referenced_from_script || variable.subscribable)) {
this.warn(declarator, {
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;
} else {
node.specifiers.forEach((specifier) => {
node.specifiers.forEach(specifier => {
const variable = this.var_lookup.get(specifier.local.name);
if (variable) {
variable.export_name = specifier.exported.name;
if (
variable.writable &&
!(variable.referenced || variable.referenced_from_script || variable.subscribable)
) {
if (variable.writable && !(variable.referenced || variable.referenced_from_script || variable.subscribable)) {
this.warn(specifier, {
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) {
if (!script) return null;
return script.content.body.filter((node) => {
return script.content.body.filter(node => {
if (!node) return false;
if (this.hoistable_nodes.has(node)) return false;
if (this.reactive_declaration_nodes.has(node)) 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;
});
}
@ -537,7 +554,7 @@ export default class Component {
name,
module: true,
hoistable: true,
writable,
writable
});
});
@ -551,7 +568,7 @@ export default class Component {
this.add_var({
name,
global: true,
hoistable: true,
hoistable: true
});
}
});
@ -581,7 +598,7 @@ export default class Component {
if (!script) return;
// inject vars for reactive declarations
script.content.body.forEach((node) => {
script.content.body.forEach(node => {
if (node.type !== 'LabeledStatement') return;
if (node.body.type !== 'ExpressionStatement') return;
@ -589,14 +606,16 @@ export default class Component {
if (expression.type !== 'AssignmentExpression') 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] !== '$') {
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_map = map;
@ -613,7 +632,7 @@ export default class Component {
this.add_var({
name,
initialised: instance_scope.initialised_declarations.has(name),
writable,
writable
});
this.node_for_declaration.set(name, node);
@ -639,7 +658,7 @@ export default class Component {
if (name === '$' || name[1] === '$') {
this.error(node as any, {
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({
name,
global: true,
hoistable: true,
hoistable: true
});
}
});
@ -694,12 +713,15 @@ export default class Component {
to_remove.unshift([parent, prop, index]);
};
let scope_updated = false;
let generator_count = 0;
walk(content, {
enter(node: Node, parent, prop, index) {
if ((node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') && node.generator === true) {
generator_count++;
}
if (map.has(node)) {
scope = map.get(node);
}
@ -729,13 +751,10 @@ export default class Component {
if ((node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') && node.generator === true) {
generator_count--;
}
// do it on leave, to prevent infinite loop
if (component.compile_options.dev && component.compile_options.loopGuardTimeout > 0 && generator_count <= 0) {
const to_replace_for_loop_protect = component.loop_protect(
node,
scope,
component.compile_options.loopGuardTimeout
);
const to_replace_for_loop_protect = component.loop_protect(node, scope, component.compile_options.loopGuardTimeout);
if (to_replace_for_loop_protect) {
this.replace(to_replace_for_loop_protect);
scope_updated = true;
@ -787,9 +806,13 @@ export default class Component {
const deep = assignee.type === 'MemberExpression';
names.forEach((name) => {
names.forEach(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);
variable[deep ? 'mutated' : 'reassigned'] = true;
}
@ -814,7 +837,11 @@ export default class Component {
}
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, {
code: 'non-top-level-reactive-declaration',
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 {
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);
this.used_names.add(guard.name);
@ -850,7 +879,10 @@ export default class Component {
return {
type: 'BlockStatement',
body: [before[0], node],
body: [
before[0],
node,
],
};
}
return null;
@ -875,11 +907,11 @@ export default class Component {
if (node.type === 'VariableDeclaration') {
if (node.kind === 'var' || scope === instance_scope) {
node.declarations.forEach((declarator) => {
node.declarations.forEach(declarator => {
if (declarator.id.type !== 'Identifier') {
const inserts = [];
extract_names(declarator.id).forEach((name) => {
extract_names(declarator.id).forEach(name => {
const variable = component.var_lookup.get(name);
if (variable.export_name) {
@ -906,14 +938,15 @@ export default class Component {
const variable = component.var_lookup.get(name);
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);
declarator.id = {
type: 'ObjectPattern',
properties: [
{
properties: [{
type: 'Property',
method: false,
shorthand: false,
@ -924,11 +957,10 @@ export default class Component {
? {
type: 'AssignmentPattern',
left: declarator.id,
right: declarator.init,
right: declarator.init
}
: declarator.id,
},
],
: declarator.id
}]
};
declarator.init = x`$$props`;
@ -959,7 +991,12 @@ export default class Component {
// reference instance variables other than other
// 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();
@ -969,7 +1006,7 @@ export default class Component {
const node = body[i];
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.type !== 'Literal') return false;
@ -984,13 +1021,18 @@ export default class Component {
if (v.export_name) 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;
});
if (all_hoistable) {
node.declarations.forEach((d) => {
node.declarations.forEach(d => {
const variable = this.var_lookup.get((d.id as Identifier).name);
variable.hoistable = true;
});
@ -1018,7 +1060,7 @@ export default class Component {
const checked = new Set();
const walking = new Set();
const is_hoistable = (fn_declaration) => {
const is_hoistable = fn_declaration => {
if (fn_declaration.type === 'ExportNamedDeclaration') {
fn_declaration = fn_declaration.declaration;
}
@ -1058,7 +1100,9 @@ export default class Component {
if (variable.hoistable) return;
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)) {
hoistable = false;
@ -1138,7 +1182,7 @@ export default class Component {
if (node.type === 'AssignmentExpression') {
const left = get_object(node.left);
extract_identifiers(left).forEach((node) => {
extract_identifiers(left).forEach(node => {
assignee_nodes.add(node);
assignees.add(node.name);
});
@ -1156,8 +1200,12 @@ export default class Component {
const owner = scope.find_owner(name);
const variable = component.var_lookup.get(name);
if (variable) variable.is_reactive_dependency = true;
const is_writable_or_mutated = variable && (variable.writable || variable.mutated);
if ((!owner || owner === component.instance_scope) && (name[0] === '$' || is_writable_or_mutated)) {
const is_writable_or_mutated =
variable && (variable.writable || variable.mutated);
if (
(!owner || owner === component.instance_scope) &&
(name[0] === '$' || is_writable_or_mutated)
) {
dependencies.add(name);
}
}
@ -1187,8 +1235,8 @@ export default class Component {
const lookup = new Map();
unsorted_reactive_declarations.forEach((declaration) => {
declaration.assignees.forEach((name) => {
unsorted_reactive_declarations.forEach(declaration => {
declaration.assignees.forEach(name => {
if (!lookup.has(name)) {
lookup.set(name, []);
}
@ -1199,35 +1247,34 @@ export default class Component {
});
});
const cycle = check_graph_for_cycles(
unsorted_reactive_declarations.reduce((acc, declaration) => {
declaration.assignees.forEach((v) => {
declaration.dependencies.forEach((w) => {
const cycle = check_graph_for_cycles(unsorted_reactive_declarations.reduce((acc, declaration) => {
declaration.assignees.forEach(v => {
declaration.dependencies.forEach(w => {
if (!declaration.assignees.has(w)) {
acc.push([v, w]);
}
});
});
return acc;
}, [])
);
}, []));
if (cycle && cycle.length) {
const declarationList = lookup.get(cycle[0]);
const declaration = declarationList[0];
this.error(declaration.node, {
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;
declaration.dependencies.forEach((name) => {
declaration.dependencies.forEach(name => {
if (declaration.assignees.has(name)) return;
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);
@ -1238,10 +1285,10 @@ export default class Component {
warn_if_undefined(name: string, node, template_scope: TemplateScope) {
if (name[0] === '$') {
if (name === '$' || (name[1] === '$' && !is_reserved_keyword(name))) {
if (name === '$' || name[1] === '$' && !is_reserved_keyword(name)) {
this.error(node, {
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;
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, {
code: 'missing-declaration',
@ -1287,7 +1335,7 @@ function process_component_options(component: Component, nodes) {
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) {
const { value } = attribute;
@ -1309,7 +1357,7 @@ function process_component_options(component: Component, nodes) {
}
if (node) {
node.attributes.forEach((attribute) => {
node.attributes.forEach(attribute => {
if (attribute.type === 'Attribute') {
const { name } = attribute;
@ -1319,7 +1367,8 @@ function process_component_options(component: Component, nodes) {
const message = `'tag' must be a string literal`;
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)) {
component.error(attribute, {
@ -1331,7 +1380,7 @@ function process_component_options(component: Component, nodes) {
if (tag && !component.compile_options.customElement) {
component.warn(attribute, {
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 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)) {
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 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;
break;

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

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

@ -35,10 +35,13 @@ const a11y_required_attributes = {
// iframe-has-title
iframe: ['title'],
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([
// anchor-has-content
@ -50,7 +53,7 @@ const a11y_required_content = new Set([
'h3',
'h4',
'h5',
'h6',
'h6'
]);
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 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) {
const parent_element = parent.find_nearest(/^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;
@ -102,11 +120,11 @@ export default class Element extends Node {
if (this.name === 'textarea') {
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) {
component.error(value_attribute, {
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({
type: 'Attribute',
name: 'value',
value: info.children,
value: info.children
});
info.children = [];
@ -126,19 +144,19 @@ export default class Element extends Node {
// Special case — treat these the same way:
// <option>{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) {
info.attributes.push({
type: 'Attribute',
name: 'value',
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) {
scope = scope.child();
}
@ -147,7 +165,7 @@ export default class Element extends Node {
const order = ['Binding']; // everything else is -1
info.attributes.sort((a, b) => order.indexOf(a.type) - order.indexOf(b.type));
info.attributes.forEach((node) => {
info.attributes.forEach(node => {
switch (node.type) {
case 'Action':
this.actions.push(new Action(component, this, scope, node));
@ -178,13 +196,14 @@ export default class Element extends Node {
this.lets.push(l);
const dependencies = new Set([l.name.name]);
l.names.forEach((name) => {
l.names.forEach(name => {
scope.add(name, dependencies, this);
});
break;
}
case 'Transition': {
case 'Transition':
{
const transition = new Transition(component, this, scope, node);
if (node.intro) this.intro = transition;
if (node.outro) this.outro = transition;
@ -213,7 +232,7 @@ export default class Element extends Node {
// no-distracting-elements
this.component.warn(this, {
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) {
this.component.warn(this, {
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;
});
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], {
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();
this.attributes.forEach((attribute) => {
this.attributes.forEach(attribute => {
if (attribute.is_spread) return;
const name = attribute.name.toLowerCase();
@ -280,7 +299,7 @@ export default class Element extends Node {
// aria-unsupported-elements
component.warn(attribute, {
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, {
code: `a11y-unknown-aria-attribute`,
message,
message
});
}
if (name === 'aria-hidden' && /^h[1-6]$/.test(this.name)) {
component.warn(attribute, {
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
component.warn(attribute, {
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, {
code: `a11y-unknown-role`,
message,
message
});
}
}
@ -333,7 +352,7 @@ export default class Element extends Node {
if (name === 'accesskey') {
component.warn(attribute, {
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') {
component.warn(attribute, {
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') {
component.warn(attribute, {
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) {
component.warn(attribute, {
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)) {
component.error(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)) {
component.error(attribute, {
code: `duplicate-slot-attribute`,
message: `Duplicate '${name}' slot`,
message: `Duplicate '${name}' slot`
});
component.slot_outlets.add(name);
@ -400,7 +419,7 @@ export default class Element extends Node {
if (name === 'is') {
component.warn(attribute, {
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 {
const required_attributes = a11y_required_attributes[this.name];
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) {
should_have_attribute(this, required_attributes);
@ -461,7 +480,7 @@ export default class Element extends Node {
const type = attribute_map.get('type');
if (type && type.get_static_value() === 'image') {
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) {
should_have_attribute(this, required_attributes, 'input type="image"');
@ -501,14 +520,16 @@ export default class Element extends Node {
const { component } = this;
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.is_static) {
component.error(attribute, {
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) {
component.error(attribute, {
code: `missing-type`,
message: `'type' attribute must be specified`,
message: `'type' attribute must be specified`
});
}
return value;
};
this.bindings.forEach((binding) => {
this.bindings.forEach(binding => {
const { name } = binding;
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, {
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') {
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) {
component.error(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 {
@ -551,7 +578,7 @@ export default class Element extends Node {
if (this.name !== 'input') {
component.error(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') {
component.error(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') {
component.error(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') {
if (this.name !== 'input') {
component.error(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') {
component.error(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') {
if (this.name !== 'details') {
component.error(binding, {
code: `invalid-binding`,
message: `'${name}' binding can only be used with <details>`,
message: `'${name}' binding can only be used with <details>`
});
}
} else if (
@ -617,54 +644,59 @@ export default class Element extends Node {
if (this.name !== 'audio' && this.name !== 'video') {
component.error(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') {
component.error(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)) {
if (this.name === 'svg' && (name === 'offsetWidth' || name === 'offsetHeight')) {
component.error(binding, {
code: 'invalid-binding',
message: `'${binding.name}' is not a valid binding on <svg>. Use '${name.replace(
'offset',
'client'
)}' instead`,
message: `'${binding.name}' is not a valid binding on <svg>. Use '${name.replace('offset', 'client')}' instead`,
});
} else if (svg.test(this.name)) {
component.error(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)) {
component.error(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') {
const contenteditable = this.attributes.find((attribute: Attribute) => attribute.name === 'contenteditable');
} else if (
name === 'textContent' ||
name === 'innerHTML'
) {
const contenteditable = this.attributes.find(
(attribute: Attribute) => attribute.name === 'contenteditable'
);
if (!contenteditable) {
component.error(binding, {
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) {
component.error(contenteditable, {
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') {
component.error(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) {
this.component.warn(this, {
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() {
const { component } = this;
this.handlers.forEach((handler) => {
this.handlers.forEach(handler => {
if (handler.modifiers.has('passive') && handler.modifiers.has('preventDefault')) {
component.error(handler, {
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)) {
component.error(handler, {
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) {
component.warn(handler, {
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 {
component.warn(handler, {
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() {
if (this.attributes.some((attr) => attr.is_spread)) {
if (this.attributes.some(attr => attr.is_spread)) {
this.needs_manual_style_scoping = true;
return;
}
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.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, {
type: 'Attribute',
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 sequence =
attributes.length > 1
? attributes.slice(0, -1).join(', ') + ` or ${attributes[attributes.length - 1]}`
: attributes[0];
const sequence = attributes.length > 1 ?
attributes.slice(0, -1).join(', ') + ` or ${attributes[attributes.length - 1]}` :
attributes[0];
node.component.warn(node, {
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_map: WeakMap<Node, Scope>;
declarations: Array<Node | Node[]> = [];
declarations: Array<(Node | Node[])> = [];
uses_context = false;
manipulated: Node;
@ -40,8 +40,8 @@ export default class Expression {
// TODO revert to direct property access in prod?
Object.defineProperties(this, {
component: {
value: component,
},
value: component
}
});
this.node = info;
@ -79,13 +79,12 @@ export default class Expression {
if (name[0] === '$' && template_scope.names.has(name.slice(1))) {
component.error(node, {
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 (!function_expression) {
// TODO should this be `!lazy` ?
if (!function_expression) { // TODO should this be `!lazy` ?
contextual_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;
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 {
if (!lazy) {
@ -119,7 +118,9 @@ export default class Expression {
if (function_expression) {
if (node.type === 'AssignmentExpression') {
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') {
const { name } = get_object(node.argument);
names = [name];
@ -127,9 +128,9 @@ export default class Expression {
}
if (names) {
names.forEach((name) => {
names.forEach(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);
if (variable) variable[deep ? 'mutated' : 'reassigned'] = true;
});
@ -151,12 +152,12 @@ export default class Expression {
if (node === function_expression) {
function_expression = null;
}
},
}
});
}
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 (is_reserved_keyword(name)) return true;
@ -171,7 +172,13 @@ export default class Expression {
// multiple times
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 function_expression;
@ -199,7 +206,7 @@ export default class Expression {
if (template_scope.names.has(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);
});
} else {
@ -231,7 +238,9 @@ export default class Expression {
if (map.has(node)) scope = scope.parent;
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}`;
@ -245,21 +254,25 @@ export default class Expression {
name: id.name,
internal: 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
component.partly_hoisted.push(declaration);
block.renderer.add_to_context(id.name);
this.replace(block.renderer.reference(id));
} else {
}
else {
// we need a combo block/init recipe
const deps = Array.from(contextual_dependencies);
(node as FunctionExpression).params = [
...deps.map((name) => ({ type: 'Identifier', name } as Identifier)),
...(node as FunctionExpression).params,
...deps.map(name => ({ type: 'Identifier', name } as Identifier)),
...(node as FunctionExpression).params
];
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 traced: Set<string> = new Set();
names.forEach((name) => {
names.forEach(name => {
const dependencies = template_scope.dependencies_for_name.get(name);
if (dependencies) {
dependencies.forEach((name) => traced.add(name));
dependencies.forEach(name => traced.add(name));
} else {
traced.add(name);
}
@ -316,12 +329,12 @@ export default class Expression {
this.replace(invalidate(block.renderer, scope, node, traced));
}
},
}
});
if (declarations.length > 0) {
block.maintain_context = true;
declarations.forEach((declaration) => {
declarations.forEach(declaration => {
block.chunks.init.push(declaration);
});
}

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

@ -1,14 +1,24 @@
import { b, x, p } from 'code-red';
import Component from '../Component';
import Renderer from './Renderer';
import { CompileOptions, CssResult } from '../../interfaces';
import { walk } from 'estree-walker';
import { extract_names, Scope } from '../utils/scope';
import { invalidate } from './invalidate';
import Block from './Block';
import { ClassDeclaration, FunctionExpression, Node, Statement, ObjectExpression, Expression } from 'estree';
export default function dom(component: Component, options: CompileOptions): { js: Node[]; css: CssResult } {
import { b, x, p } from "code-red";
import Component from "../Component";
import Renderer from "./Renderer";
import { CompileOptions, CssResult } from "../../interfaces";
import { walk } from "estree-walker";
import { extract_names, Scope } from "../utils/scope";
import { invalidate } from "./invalidate";
import Block from "./Block";
import {
ClassDeclaration,
FunctionExpression,
Node,
Statement,
ObjectExpression,
Expression,
} from "estree";
export default function dom(
component: Component,
options: CompileOptions
): { js: Node[]; css: CssResult } {
const { name } = component;
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};`);
}
const css = component.stylesheet.render(options.filename, !options.customElement);
const css = component.stylesheet.render(
options.filename,
!options.customElement
);
const styles =
component.stylesheet.has_styles && options.dev
? `${css.code}\n/*# sourceMappingURL=${css.map.toUrl()} */`
: 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) {
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_rest = component.var_lookup.has('$$restProps');
const uses_props = component.var_lookup.has("$$props");
const uses_rest = component.var_lookup.has("$$restProps");
const uses_any = uses_props || uses_rest;
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 rest = uses_rest
? b`
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 = {};
for (#k in $$props) {
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_rest && renderer.invalidate('$$restProps')}
${uses_props && renderer.invalidate("$$props")}
${uses_rest && renderer.invalidate("$$restProps")}
`
}
${writable_props.map(
@ -119,14 +137,19 @@ export default function dom(component: Component, options: CompileOptions): { js
)}
${
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;
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 inject_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) {
accessors.push({
type: 'MethodDefinition',
kind: 'get',
key: { type: 'Identifier', name: prop.export_name },
type: "MethodDefinition",
kind: "get",
key: { type: "Identifier", name: prop.export_name },
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) {
accessors.push({
type: 'MethodDefinition',
kind: 'get',
key: { type: 'Identifier', name: prop.export_name },
type: "MethodDefinition",
kind: "get",
key: { type: "Identifier", name: prop.export_name },
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/>'");
}`,
@ -158,9 +185,9 @@ export default function dom(component: Component, options: CompileOptions): { js
if (component.component_options.accessors) {
if (variable.writable && !renderer.readonly.has(prop.name)) {
accessors.push({
type: 'MethodDefinition',
kind: 'set',
key: { type: 'Identifier', name: prop.export_name },
type: "MethodDefinition",
kind: "set",
key: { type: "Identifier", name: prop.export_name },
value: x`function(${prop.name}) {
this.$set({ ${prop.export_name}: ${prop.name} });
@flush();
@ -168,9 +195,9 @@ export default function dom(component: Component, options: CompileOptions): { js
});
} else if (component.compile_options.dev) {
accessors.push({
type: 'MethodDefinition',
kind: 'set',
key: { type: 'Identifier', name: prop.export_name },
type: "MethodDefinition",
kind: "set",
key: { type: "Identifier", name: prop.export_name },
value: x`function(value) {
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) {
accessors.push({
type: 'MethodDefinition',
kind: 'set',
key: { type: 'Identifier', name: prop.export_name },
type: "MethodDefinition",
kind: "set",
key: { type: "Identifier", name: prop.export_name },
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/>'");
}`,
@ -195,30 +222,52 @@ export default function dom(component: Component, options: CompileOptions): { js
if (expected.length) {
dev_props_check = b`
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(
(prop) => b`
if (${renderer.reference(prop.name)} === undefined && !('${prop.export_name}' in props)) {
@_console.warn("<${component.tag}> was created without expected prop '${prop.export_name}'");
if (${renderer.reference(prop.name)} === undefined && !('${
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) {
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) {
inject_state = x`
${$$props} => {
${uses_props && renderer.invalidate('$$props', x`$$props = { ...$$props, ...$$new_props }`)}
${
uses_props &&
renderer.invalidate(
"$$props",
x`$$props = { ...$$props, ...$$new_props }`
)
}
${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) {
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;
}
},
@ -259,8 +312,12 @@ export default function dom(component: Component, options: CompileOptions): { js
execution_context = null;
}
if (node.type === 'AssignmentExpression' || node.type === 'UpdateExpression') {
const assignee = node.type === 'AssignmentExpression' ? node.left : node.argument;
if (
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
// (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
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 =
reassigned || export_name
? b`${`$$subscribe_${name}`}();`
: b`$$self.$$.on_destroy.push(@subscribe(${name}, (value) => {$$invalidate(${i}, (${value} = value));}));`;
? b`${`$$subscribe_${name}`}()`
: b`@component_subscribe($$self, ${name}, #value => $$invalidate(${i}, ${value} = #value))`;
if (component.compile_options.dev) {
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 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) {
args.push(x`$$props`, x`$$invalidate`);
} else if (component.compile_options.dev) {
@ -300,7 +363,8 @@ export default function dom(component: Component, options: CompileOptions): { js
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) {
body.push(b`
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);
if (variable.hoistable) return false;
if (prop.name[0] === '$') return false;
if (prop.name[0] === "$") return false;
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;
while (i--) {
@ -349,7 +417,9 @@ export default function dom(component: Component, options: CompileOptions): { js
capture_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
.filter((store) => {
@ -358,7 +428,10 @@ export default function dom(component: Component, options: CompileOptions): { js
})
.map(
({ 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 => {
$$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));
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) {
const reactive_declarations: Node | Node[] = [];
@ -378,29 +456,40 @@ export default function dom(component: Component, options: CompileOptions): { js
component.reactive_declarations.forEach((d) => {
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 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
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) {
statement && reactive_declarations.push(statement);
reactive_declarations.push(statement);
} 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);
return variable.injected && variable.name[0] !== '$';
return variable.injected && variable.name[0] !== "$";
});
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 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};`;
@ -422,7 +517,9 @@ export default function dom(component: Component, options: CompileOptions): { js
let unknown_props_check;
if (component.compile_options.dev && !(uses_props || uses_rest)) {
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 => {
if (!~writable_props.indexOf(key) && key.slice(0, 2) !== '$$') @_console.warn(\`<${
component.tag
@ -432,11 +529,11 @@ export default function dom(component: Component, options: CompileOptions): { js
}
const return_value = {
type: 'ArrayExpression',
type: "ArrayExpression",
elements: initial_context.map(
(member) =>
({
type: 'Identifier',
type: "Identifier",
name: member.name,
} as Expression)
),
@ -456,15 +553,27 @@ export default function dom(component: Component, options: CompileOptions): { js
${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 &&
b`@validate_slots_dev('${component.tag}', $$slots, [${[...component.slots.keys()]
b`@validate_slots_dev('${component.tag}', $$slots, [${[
...component.slots.keys(),
]
.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}
@ -478,7 +587,14 @@ export default function dom(component: Component, options: CompileOptions): { js
${/* 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}
@ -497,7 +613,9 @@ export default function dom(component: Component, options: CompileOptions): { js
const prop_indexes = x`{
${props
.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;
let dirty;
@ -516,13 +634,16 @@ export default function dom(component: Component, options: CompileOptions): { js
${
css.code &&
b`this.shadowRoot.innerHTML = \`<style>${css.code.replace(/\\/g, '\\\\')}${
options.dev ? `\n/*# sourceMappingURL=${css.map.toUrl()} */` : ''
b`this.shadowRoot.innerHTML = \`<style>${css.code.replace(
/\\/g,
"\\\\"
)}${
options.dev ? `\n/*# sourceMappingURL=${css.map.toUrl()} */` : ""
}</style>\`;`
}
@init(this, { target: this.shadowRoot }, ${definition}, ${
has_create_fragment ? 'create_fragment' : 'null'
has_create_fragment ? "create_fragment" : "null"
}, ${not_equal}, ${prop_indexes}, ${dirty});
${dev_props_check}
@ -532,15 +653,11 @@ export default function dom(component: Component, options: CompileOptions): { js
@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) {
this.$set(options.props);
@flush();
}`
: null
}
}`}
}
}
}
@ -548,14 +665,14 @@ export default function dom(component: Component, options: CompileOptions): { js
if (props.length > 0) {
declaration.body.body.push({
type: 'MethodDefinition',
kind: 'get',
type: "MethodDefinition",
kind: "get",
static: true,
computed: false,
key: { type: 'Identifier', name: 'observedAttributes' },
key: { type: "Identifier", name: "observedAttributes" },
value: x`function() {
return [${props.map((prop) => x`"${prop.export_name}"`)}];
}` as FunctionExpression,
return [${props.map(prop => x`"${prop.export_name}"`)}];
}` as FunctionExpression
});
}
@ -570,8 +687,8 @@ export default function dom(component: Component, options: CompileOptions): { js
}
} else {
const superclass = {
type: 'Identifier',
name: options.dev ? '@SvelteComponentDev' : '@SvelteComponent',
type: "Identifier",
name: options.dev ? "@SvelteComponentDev" : "@SvelteComponent"
};
const declaration = b`
@ -579,14 +696,8 @@ export default function dom(component: Component, options: CompileOptions): { js
constructor(options) {
super(${options.dev && `options`});
${should_add_css && b`if (!@_document.getElementById("${component.stylesheet.id}-style")) ${add_css}();`}
@init(this, options, ${definition}, ${
has_create_fragment ? 'create_fragment' : 'null'
}, ${not_equal}, ${prop_indexes}, ${dirty});
${
options.dev &&
b`@dispatch_dev("SvelteRegisterComponent", { component: this, tagName: "${name.name}", options, id: create_fragment.name });`
}
@init(this, options, ${definition}, ${has_create_fragment ? 'create_fragment': 'null'}, ${not_equal}, ${prop_indexes}, ${dirty});
${options.dev && b`@dispatch_dev("SvelteRegisterComponent", { component: this, tagName: "${name.name}", options, id: create_fragment.name });`}
${dev_props_check}
}
}

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

@ -43,7 +43,7 @@ class AwaitBlockBranch extends Wrapper {
this.block = block.child({
comment: create_debugging_comment(node, this.renderer.component),
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']);
@ -132,7 +132,15 @@ export default class AwaitBlockWrapper extends Wrapper {
['pending', 'then', 'catch'].forEach((status: 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);
@ -148,7 +156,7 @@ export default class AwaitBlockWrapper extends Wrapper {
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_intro_method = has_intros;
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 update_mount_node = this.get_update_mount_node(anchor);
@ -268,7 +280,7 @@ export default class AwaitBlockWrapper extends Wrapper {
${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);
});
}

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

@ -29,7 +29,7 @@ export default class AttributeWrapper {
select = select.parent;
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.parent.renderer.component.indirect_dependencies.get(prop).add(dependency);
});
@ -42,11 +42,13 @@ export default class AttributeWrapper {
is_indirectly_bound_value() {
const element = this.parent;
const name = fix_attribute_casing(this.node.name);
return (
name === 'value' &&
return name === 'value' &&
(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) {
@ -57,7 +59,9 @@ export default class AttributeWrapper {
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
// 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 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 should_cache = is_src || this.node.should_cache() || is_select_value_attribute; // TODO is this necessary?
const last =
should_cache && block.get_unique_name(`${element.var.name}_${name.replace(/[^a-zA-Z_$]/g, '_')}_value`);
const last = should_cache && block.get_unique_name(
`${element.var.name}_${name.replace(/[^a-zA-Z_$]/g, '_')}_value`
);
if (should_cache) block.add_variable(last);
@ -114,15 +120,21 @@ export default class AttributeWrapper {
${updater}
`);
} 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});`;
} 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
? b`@prop_dev(${element.var}, "${property_name}", ${should_cache ? last : value});`
: b`${element.var}.${property_name} = ${should_cache ? last : value};`;
} 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});`;
}
@ -138,7 +150,7 @@ export default class AttributeWrapper {
if (is_input_value) {
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}`;
}
}
@ -191,8 +203,7 @@ export default class AttributeWrapper {
: (this.node.chunks[0] as Expression).manipulate(block);
}
let value =
this.node.name === 'class'
let value = this.node.name === 'class'
? this.get_class_name_text(block)
: this.render_chunks(block).reduce((lhs, rhs) => x`${lhs} + ${rhs}`);
@ -232,11 +243,11 @@ export default class AttributeWrapper {
const value = this.node.chunks;
if (value.length === 0) return `=""`;
return `="${value
.map((chunk) => {
return chunk.type === 'Text' ? chunk.data.replace(/"/g, '\\"') : `\${${chunk.manipulate()}}`;
})
.join('')}"`;
return `="${value.map(chunk => {
return chunk.type === 'Text'
? chunk.data.replace(/"/g, '\\"')
: `\${${chunk.manipulate()}}`;
}).join('')}"`;
}
}
@ -252,7 +263,16 @@ const attribute_lookup = {
default: { applies_to: ['track'] },
defer: { applies_to: ['script'] },
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'] },
hidden: {},
@ -270,11 +290,21 @@ const attribute_lookup = {
reversed: { applies_to: ['ol'] },
selected: { applies_to: ['option'] },
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];
if (!metadata.property_name) metadata.property_name = name;
});
@ -305,5 +335,5 @@ const boolean_attribute = new Set([
'readonly',
'required',
'reversed',
'selected',
'selected'
]);

@ -27,9 +27,7 @@ export default class EventHandlerWrapper {
}
get_snippet(block) {
const snippet = this.node.expression
? this.node.expression.manipulate(block)
: block.renderer.reference(this.node.handler_name);
const snippet = this.node.expression ? this.node.expression.manipulate(block) : block.renderer.reference(this.node.handler_name);
if (this.node.reassigned) {
block.maintain_context = true;
@ -47,9 +45,11 @@ export default class EventHandlerWrapper {
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) {
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) {
args.push(FALSE);
}
@ -59,6 +59,8 @@ export default class EventHandlerWrapper {
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'],
filter: (node: Element, _name: string) =>
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'],
filter: (node: Element, name: string) =>
(name === 'textContent' || name === 'innerHTML') &&
node.attributes.some((attribute) => attribute.name === 'contenteditable'),
node.attributes.some(attribute => attribute.name === 'contenteditable')
},
{
event_names: ['change'],
filter: (node: Element, _name: string) =>
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'],
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'],
filter: (_node: Element, name: string) => dimensions.test(name),
filter: (_node: Element, name: string) =>
dimensions.test(name)
},
// media events
{
event_names: ['timeupdate'],
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'],
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'],
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'],
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'],
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'],
@ -88,25 +98,34 @@ const events = [
},
{
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'],
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'],
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'],
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
{
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);
this.var = {
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);
@ -143,14 +162,14 @@ export default class ElementWrapper extends Wrapper {
this.class_dependencies = [];
if (this.node.children.length) {
this.node.lets.forEach((l) => {
extract_names(l.value || l.name).forEach((name) => {
this.node.lets.forEach(l => {
extract_names(l.value || l.name).forEach(name => {
renderer.add_to_context(name, true);
});
});
}
this.attributes = this.node.attributes.map((attribute) => {
this.attributes = this.node.attributes.map(attribute => {
if (attribute.name === 'slot') {
// TODO make separate subclass for this?
let owner = this.parent;
@ -169,28 +188,28 @@ export default class ElementWrapper extends Wrapper {
if (owner && owner.node.type === 'InlineComponent') {
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({
comment: create_debugging_comment(node, this.renderer.component),
name: this.renderer.component.get_unique_name(`create_${sanitize(name)}_slot`),
type: 'slot',
type: 'slot'
});
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);
});
((owner as unknown) as InlineComponentWrapper).slots.set(
(owner as unknown as InlineComponentWrapper).slots.set(
name,
get_slot_definition(child_block, scope, lets)
);
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;
}
}
@ -203,9 +222,9 @@ export default class ElementWrapper extends Wrapper {
// ordinarily, there'll only be one... but we need to handle
// the rare case where an element can have multiple bindings,
// 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) block.add_intro(node.intro.is_local);
@ -217,29 +236,27 @@ export default class ElementWrapper extends Wrapper {
}
// 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) {
block.add_dependencies(directive.expression.dependencies);
}
});
node.handlers.forEach((handler) => {
node.handlers.forEach(handler => {
if (handler.expression) {
block.add_dependencies(handler.expression.dependencies);
}
});
if (this.parent) {
if (
renderer.options.dev ||
node.actions.length ||
node.bindings.length ||
node.handlers.length ||
node.classes.length ||
node.intro ||
node.outro ||
if (node.actions.length > 0 ||
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.not_static_content();
@ -273,22 +290,32 @@ export default class ElementWrapper extends Wrapper {
block.add_variable(node);
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 (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) {
block.chunks.claim.push(b`var ${nodes} = ${children};`);
block.chunks.claim.push(b`
var ${nodes} = ${children};
`);
}
} else {
block.chunks.claim.push(b`${node} = ${render_statement};`);
block.chunks.claim.push(
b`${node} = ${render_statement};`
);
}
}
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)) {
block.chunks.destroy.push(b`@detach(${node});`);
@ -313,38 +340,39 @@ export default class ElementWrapper extends Wrapper {
const state = {
quasi: {
type: 'TemplateElement',
value: { raw: '' },
},
value: { raw: '' }
}
};
const literal = {
type: 'TemplateLiteral',
expressions: [],
quasis: [],
quasis: []
};
const can_use_raw_text = !this.can_use_innerhtml && can_use_textcontent;
to_html(
(this.fragment.nodes as unknown) as Array<ElementWrapper | TextWrapper>,
block,
literal,
state,
can_use_raw_text
);
to_html((this.fragment.nodes as unknown as Array<ElementWrapper | TextWrapper>), block, literal, state, can_use_raw_text);
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 {
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 =
this.bindings.some((binding) => binding.handler.uses_context) ||
this.node.handlers.some((handler) => handler.uses_context) ||
this.node.actions.some((action) => action.uses_context);
const event_handler_or_binding_uses_context = (
this.bindings.some(binding => binding.handler.uses_context) ||
this.node.handlers.some(handler => handler.uses_context) ||
this.node.actions.some(action => action.uses_context)
);
if (event_handler_or_binding_uses_context) {
block.maintain_context = true;
@ -368,7 +396,9 @@ export default class ElementWrapper extends Wrapper {
this.add_manual_style_scoping(block);
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) {
@ -380,10 +410,7 @@ export default class ElementWrapper extends Wrapper {
}
can_use_textcontent() {
return (
this.is_static_content &&
this.fragment.nodes.every((node) => node.node.type === 'Text' || node.node.type === 'MustacheTag')
);
return this.is_static_content && this.fragment.nodes.every(node => node.node.type === 'Text' || node.node.type === 'MustacheTag');
}
get_render_statement(block: Block) {
@ -397,7 +424,7 @@ export default class ElementWrapper extends Wrapper {
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) {
return x`@element_is("${name}", ${is.render_chunks(block).reduce((lhs, rhs) => x`${lhs} + ${rhs}`)})`;
}
@ -410,14 +437,16 @@ export default class ElementWrapper extends Wrapper {
.filter((attr) => attr.type === 'Attribute')
.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;
return x`@claim_element(${nodes}, "${name}", { ${attributes} }, ${svg})`;
}
add_directives_in_order(block: Block) {
add_directives_in_order (block: Block) {
interface BindingGroup {
events: string[];
bindings: Binding[];
@ -426,17 +455,17 @@ export default class ElementWrapper extends Wrapper {
type OrderedAttribute = EventHandler | BindingGroup | Binding | Action;
const bindingGroups = events
.map((event) => ({
.map(event => ({
events: event.event_names,
bindings: this.bindings
.filter((binding) => binding.node.name !== 'this')
.filter((binding) => event.filter(this.node, binding.node.name)),
.filter(binding => binding.node.name !== 'this')
.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) {
return item.node.start;
} else if (item instanceof Binding) {
@ -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)
.sort((a, b) => getOrder(a) - getOrder(b))
.forEach((item) => {
.forEach(item => {
if (item instanceof EventHandler) {
add_event_handler(block, this.var, item);
} else if (item instanceof Binding) {
@ -471,23 +505,23 @@ export default class ElementWrapper extends Wrapper {
renderer.component.has_reactive_assignments = true;
const lock = bindingGroup.bindings.some((binding) => binding.needs_lock)
? block.get_unique_name(`${this.var.name}_updating`)
: null;
const lock = bindingGroup.bindings.some(binding => binding.needs_lock) ?
block.get_unique_name(`${this.var.name}_updating`) :
null;
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`);
renderer.add_to_context(handler.name);
// 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 contextual_dependencies: Set<string> = new Set();
group.bindings.forEach((binding) => {
group.bindings.forEach(binding => {
// TODO this is a mess
add_to_set(dependencies, binding.get_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
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
if (animation_frame) {
@ -539,20 +573,20 @@ export default class ElementWrapper extends Wrapper {
const params = Array.from(contextual_dependencies).map((name) => ({
type: 'Identifier',
name,
name
}));
this.renderer.component.partly_hoisted.push(b`
function ${handler}(${params}) {
${group.bindings.map((b) => b.handler.mutation)}
${group.bindings.map(b => b.handler.mutation)}
${Array.from(dependencies)
.filter((dep) => dep[0] !== '$')
.filter((dep) => !contextual_dependencies.has(dep))
.map((dep) => b`${this.renderer.invalidate(dep)};`)}
.filter(dep => dep[0] !== '$')
.filter(dep => !contextual_dependencies.has(dep))
.map(dep => b`${this.renderer.invalidate(dep)};`)}
}
`);
group.events.forEach((name) => {
group.events.forEach(name => {
if (name === 'elementresize') {
// special case
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}));`
);
block.chunks.destroy.push(b`${resize_listener}();`);
block.chunks.destroy.push(
b`${resize_listener}();`
);
} 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
.map((binding) => x`${binding.snippet} === void 0`)
.map(binding => x`${binding.snippet} === void 0`)
.reduce((lhs, rhs) => x`${lhs} || ${rhs}`);
const should_initialise =
this.node.name === 'select' ||
group.bindings.find((binding) => {
group.bindings.find(binding => {
return (
binding.node.name === 'indeterminate' ||
binding.node.name === 'textContent' ||
@ -585,11 +623,15 @@ export default class ElementWrapper extends Wrapper {
if (should_initialise) {
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') {
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) {
// Get all the class dependencies first
this.attributes.forEach((attribute) => {
this.attributes.forEach(attribute => {
if (attribute.node.name === 'class') {
const dependencies = attribute.node.get_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);
return;
}
this.attributes.forEach((attribute) => {
this.attributes.forEach(attribute => {
attribute.render(block);
});
}
@ -633,9 +675,11 @@ export default class ElementWrapper extends Wrapper {
const initial_props = [];
const updates = [];
this.attributes.forEach((attr) => {
const condition =
attr.node.dependencies.size > 0 ? block.renderer.dirty(Array.from(attr.node.dependencies)) : null;
this.attributes
.forEach(attr => {
const condition = attr.node.dependencies.size > 0
? block.renderer.dirty(Array.from(attr.node.dependencies))
: null;
if (attr.node.is_spread) {
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`;
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) {
const name = block.get_unique_name(`${this.var.name}_transition`);
@ -767,8 +817,8 @@ export default class ElementWrapper extends Wrapper {
}
add_classes(block: Block) {
const has_spread = this.node.attributes.some((attr) => attr.is_spread);
this.node.classes.forEach((class_directive) => {
const has_spread = this.node.attributes.some(attr => attr.is_spread);
this.node.classes.forEach(class_directive => {
const { expression, name } = class_directive;
let snippet;
let dependencies;
@ -806,20 +856,18 @@ export default class ElementWrapper extends Wrapper {
}
}
function to_html(
wrappers: Array<ElementWrapper | TextWrapper | TagWrapper>,
block: Block,
literal: any,
state: any,
can_use_raw_text?: boolean
) {
wrappers.forEach((wrapper) => {
function to_html(wrappers: Array<ElementWrapper | TextWrapper | TagWrapper>, block: Block, literal: any, state: any, can_use_raw_text?: boolean) {
wrappers.forEach(wrapper => {
if (wrapper.node.type === 'Text') {
if ((wrapper as TextWrapper).use_space()) state.quasi.value.raw += ' ';
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))
.replace(/\\/g, '\\\\')
@ -830,7 +878,7 @@ function to_html(
literal.expressions.push(wrapper.node.expression.manipulate(block));
state.quasi = {
type: 'TemplateElement',
value: { raw: '' },
value: { raw: '' }
};
} else if (wrapper.node.name === 'noscript') {
// do nothing
@ -841,7 +889,7 @@ function to_html(
(wrapper as ElementWrapper).attributes.forEach((attr: AttributeWrapper) => {
state.quasi.value.raw += ` ${fix_attribute_casing(attr.node.name)}="`;
attr.node.chunks.forEach((chunk) => {
attr.node.chunks.forEach(chunk => {
if (chunk.type === 'Text') {
state.quasi.value.raw += escape_html(chunk.data);
} else {
@ -850,7 +898,7 @@ function to_html(
state.quasi = {
type: 'TemplateElement',
value: { raw: '' },
value: { raw: '' }
};
}
});
@ -861,12 +909,7 @@ function to_html(
state.quasi.value.raw += '>';
if (!(wrapper as ElementWrapper).void) {
to_html(
(wrapper as ElementWrapper).fragment.nodes as Array<ElementWrapper | TextWrapper>,
block,
literal,
state
);
to_html((wrapper as ElementWrapper).fragment.nodes as Array<ElementWrapper | TextWrapper>, block, literal, state);
state.quasi.value.raw += `</${wrapper.node.name}>`;
}

@ -36,17 +36,14 @@ const wrappers = {
Slot,
Text,
Title,
Window,
Window
};
function trimmable_at(child: INode, next_sibling: Wrapper): boolean {
// 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 next sibling's previous node is an each block
return (
next_sibling.node.find_nearest(/EachBlock/) === child.find_nearest(/EachBlock/) ||
next_sibling.node.prev.type === 'EachBlock'
);
return (next_sibling.node.find_nearest(/EachBlock/) === child.find_nearest(/EachBlock/)) || next_sibling.node.prev.type === 'EachBlock';
}
export default class FragmentWrapper {
@ -90,11 +87,9 @@ export default class FragmentWrapper {
// We want to remove trailing whitespace inside an element/component/block,
// *unless* there is no whitespace between this node and its next sibling
if (this.nodes.length === 0) {
const should_trim = next_sibling
? next_sibling.node.type === 'Text' &&
/^\s/.test(next_sibling.node.data) &&
trimmable_at(child, next_sibling)
: !child.has_ancestor('EachBlock');
const should_trim = (
next_sibling ? (next_sibling.node.type === 'Text' && /^\s/.test(next_sibling.node.data) && trimmable_at(child, next_sibling)) : !child.has_ancestor('EachBlock')
);
if (should_trim) {
data = trim_end(data);
@ -113,7 +108,7 @@ export default class FragmentWrapper {
this.nodes.unshift(wrapper);
link(last_child, (last_child = wrapper));
link(last_child, last_child = wrapper);
} else {
const Wrapper = wrappers[child.type];
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);
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.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) {
@ -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) {
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';
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 {
@ -35,7 +37,7 @@ class IfBlockBranch extends Wrapper {
) {
super(renderer, block, parent, node);
const { expression } = node as IfBlock;
const { expression } = (node as IfBlock);
const is_else = !expression;
if (expression) {
@ -49,12 +51,12 @@ class IfBlockBranch extends Wrapper {
if (node.type === 'CallExpression' || node.type === 'NewExpression') {
should_cache = true;
}
},
}
});
if (should_cache) {
this.condition = block.get_unique_name(`show_if`);
this.snippet = expression.manipulate(block) as Node;
this.snippet = (expression.manipulate(block) as Node);
} else {
this.condition = expression.manipulate(block);
}
@ -62,8 +64,10 @@ class IfBlockBranch extends Wrapper {
this.block = block.child({
comment: create_debugging_comment(node, parent.renderer.component),
name: parent.renderer.component.get_unique_name(is_else ? `create_else_block` : `create_if_block`),
type: (node as IfBlock).expression ? 'if' : 'else',
name: parent.renderer.component.get_unique_name(
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);
@ -100,7 +104,14 @@ export default class IfBlockWrapper extends Wrapper {
let has_outros = false;
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);
@ -124,7 +135,14 @@ export default class IfBlockWrapper extends Wrapper {
if (is_else_if(node.else)) {
create_branches(node.else.children[0] as IfBlock);
} 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);
@ -142,7 +160,7 @@ export default class IfBlockWrapper extends Wrapper {
create_branches(this.node);
blocks.forEach((block) => {
blocks.forEach(block => {
block.has_update_method = is_dynamic;
block.has_intro_method = has_intros;
block.has_outro_method = has_outros;
@ -151,7 +169,11 @@ export default class IfBlockWrapper extends Wrapper {
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 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`)
: (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 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';
if (this.node.else) {
this.branches.forEach((branch) => {
this.branches.forEach(branch => {
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 (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 {
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) {
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);
});
}
@ -235,26 +266,23 @@ export default class IfBlockWrapper extends Wrapper {
if (this.needs_update) {
block.chunks.init.push(b`
function ${select_block_type}(#ctx, #dirty) {
${this.branches.map(({ dependencies, condition, snippet, block }) =>
condition
${this.branches.map(({ dependencies, condition, snippet, block }) => condition
? b`
${
snippet &&
(dependencies.length > 0
? b`if (${condition} == null || ${block.renderer.dirty(dependencies)}) ${condition} = !!${snippet};`
: b`if (${condition} == null) ${condition} = !!${snippet};`)
}
if (${condition}) return ${block.name};`
: b`return ${block.name};`
${snippet && (
dependencies.length > 0
? b`if (${condition} == null || ${block.renderer.dirty(dependencies)}) ${condition} = !!${snippet}`
: b`if (${condition} == null) ${condition} = !!${snippet}`
)}
if (${condition}) return ${block.name};`
: b`return ${block.name};`)}
}
`);
} else {
block.chunks.init.push(b`
function ${select_block_type}(#ctx, #dirty) {
${this.branches.map(({ condition, snippet, block }) =>
condition ? b`if (${snippet || condition}) return ${block.name};` : b`return ${block.name};`
)}
${this.branches.map(({ condition, snippet, block }) => condition
? 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';
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 {
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) {
@ -339,47 +371,42 @@ export default class IfBlockWrapper extends Wrapper {
const if_blocks = block.get_unique_name(`if_blocks`);
const if_current_block_type_index = has_else
? (nodes) => nodes
: (nodes) => b`if (~${current_block_type_index}) { ${nodes} }`;
? nodes => nodes
: nodes => b`if (~${current_block_type_index}) { ${nodes} }`;
block.add_variable(current_block_type_index);
block.add_variable(name);
block.chunks.init.push(b`
const ${if_block_creators} = [
${this.branches.map((branch) => branch.block.name)}
${this.branches.map(branch => branch.block.name)}
];
const ${if_blocks} = [];
${
this.needs_update
${this.needs_update
? b`
function ${select_block_type}(#ctx, #dirty) {
${this.branches.map(({ dependencies, condition, snippet }, i) =>
condition
${this.branches.map(({ dependencies, condition, snippet }, i) => condition
? b`
${
snippet &&
(dependencies.length > 0
${snippet && (
dependencies.length > 0
? b`if (${block.renderer.dirty(dependencies)}) ${condition} = !!${snippet}`
: b`if (${condition} == null) ${condition} = !!${snippet}`
)}
if (${condition}) return ${i};`
: b`return ${i};`
)}
: b`return ${i};`)}
${!has_else && b`return -1;`}
}
`
: b`
function ${select_block_type}(#ctx, #dirty) {
${this.branches.map(({ condition, snippet }, i) =>
condition ? b`if (${snippet || condition}) return ${i};` : b`return ${i};`
)}
${this.branches.map(({ condition, snippet }, i) => condition
? b`if (${snippet || condition}) return ${i};`
: b`return ${i};`)}
${!has_else && b`return -1;`}
}
`
}
`}
`);
if (has_else) {
@ -490,7 +517,9 @@ export default class IfBlockWrapper extends Wrapper {
const initial_mount_node = parent_node || '#target';
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) {
const update_mount_node = this.get_update_mount_node(anchor);
@ -513,9 +542,7 @@ export default class IfBlockWrapper extends Wrapper {
`;
if (branch.snippet) {
block.chunks.update.push(
b`if (${block.renderer.dirty(branch.dependencies)}) ${branch.condition} = ${branch.snippet}`
);
block.chunks.update.push(b`if (${block.renderer.dirty(branch.dependencies)}) ${branch.condition} = ${branch.snippet}`);
}
// 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);
}
this.node.attributes.forEach((attr) => {
this.node.attributes.forEach(attr => {
block.add_dependencies(attr.dependencies);
});
this.node.bindings.forEach((binding) => {
this.node.bindings.forEach(binding => {
if (binding.is_contextual) {
// we need to ensure that the each block creates a context including
// 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);
});
this.node.handlers.forEach((handler) => {
this.node.handlers.forEach(handler => {
if (handler.expression) {
block.add_dependencies(handler.expression.dependencies);
}
@ -66,17 +66,16 @@ export default class InlineComponentWrapper extends Wrapper {
this.var = {
type: 'Identifier',
name: (this.node.name === 'svelte:self'
? renderer.component.name.name
: this.node.name === 'svelte:component'
? 'switch_instance'
: sanitize(this.node.name)
).toLowerCase(),
name: (
this.node.name === 'svelte:self' ? renderer.component.name.name :
this.node.name === 'svelte:component' ? 'switch_instance' :
sanitize(this.node.name)
).toLowerCase()
};
if (this.node.children.length) {
this.node.lets.forEach((l) => {
extract_names(l.value || l.name).forEach((name) => {
this.node.lets.forEach(l => {
extract_names(l.value || l.name).forEach(name => {
renderer.add_to_context(name, true);
});
});
@ -84,7 +83,7 @@ export default class InlineComponentWrapper extends Wrapper {
const default_slot = block.child({
comment: create_debugging_comment(node, renderer.component),
name: renderer.component.get_unique_name(`create_default_slot`),
type: 'slot',
type: '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();
const { renderer } = this;
@ -140,14 +143,14 @@ export default class InlineComponentWrapper extends Wrapper {
const default_slot = this.slots.get('default');
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;
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
for (const slot of this.slots.keys()) {
@ -157,8 +160,7 @@ export default class InlineComponentWrapper extends Wrapper {
}
}
const initial_props =
this.slots.size > 0
const initial_props = this.slots.size > 0
? [
p`$$slots: {
${Array.from(this.slots).map(([name, slot]) => {
@ -174,7 +176,7 @@ export default class InlineComponentWrapper extends Wrapper {
const attribute_object = uses_spread
? x`{ ${initial_props} }`
: 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}
}`;
@ -196,8 +198,8 @@ export default class InlineComponentWrapper extends Wrapper {
}
const fragment_dependencies = new Set(this.fragment ? ['$$scope'] : []);
this.slots.forEach((slot) => {
slot.block.dependencies.forEach((name) => {
this.slots.forEach(slot => {
slot.block.dependencies.forEach(name => {
const is_let = slot.scope.is_let(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 (
!uses_spread &&
(dynamic_attributes.length > 0 || this.node.bindings.length > 0 || fragment_dependencies.size > 0)
) {
if (!uses_spread && (dynamic_attributes.length > 0 || this.node.bindings.length > 0 || fragment_dependencies.size > 0)) {
updates.push(b`const ${name_changes} = {};`);
}
@ -223,15 +222,14 @@ export default class InlineComponentWrapper extends Wrapper {
const all_dependencies: Set<string> = new Set();
this.node.attributes.forEach((attr) => {
this.node.attributes.forEach(attr => {
add_to_set(all_dependencies, attr.dependencies);
});
this.node.attributes.forEach((attr, i) => {
const { name, dependencies } = attr;
const condition =
dependencies.size > 0 && dependencies.size !== all_dependencies.size
const condition = dependencies.size > 0 && (dependencies.size !== all_dependencies.size)
? renderer.dirty(Array.from(dependencies))
: null;
const unchanged = dependencies.size === 0;
@ -251,11 +249,19 @@ export default class InlineComponentWrapper extends Wrapper {
}
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`
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;
if (binding.name === 'this') {
@ -312,7 +318,11 @@ export default class InlineComponentWrapper extends Wrapper {
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`
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}));`;
});
const munged_handlers = this.node.handlers.map((handler) => {
const munged_handlers = this.node.handlers.map(handler => {
const event_handler = new EventHandler(handler, this);
let snippet = event_handler.get_snippet(block);
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) {
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`
@ -444,18 +458,19 @@ export default class InlineComponentWrapper extends Wrapper {
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'});`);
} 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`
${
(this.node.attributes.length > 0 || this.node.bindings.length > 0) &&
b`
${props && b`let ${props} = ${attribute_object};`}`
}
${(this.node.attributes.length > 0 || this.node.bindings.length > 0) && b`
${props && b`let ${props} = ${attribute_object};`}`}
${statements}
const ${name} = new ${expression}(${component_opts});
@ -488,7 +503,9 @@ export default class InlineComponentWrapper extends Wrapper {
@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({
comment: create_debugging_comment(this.node.children[0], this.renderer.component),
name: this.renderer.component.get_unique_name(`fallback_block`),
type: 'fallback',
type: '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);
});
@ -54,7 +61,11 @@ export default class SlotWrapper extends Wrapper {
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 { slot_name } = this.node;
@ -70,20 +81,20 @@ export default class SlotWrapper extends Wrapper {
const dependencies = new Set();
this.node.values.forEach((attribute) => {
attribute.chunks.forEach((chunk) => {
this.node.values.forEach(attribute => {
attribute.chunks.forEach(chunk => {
if ((chunk as Expression).dependencies) {
add_to_set(dependencies, (chunk as Expression).contextual_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);
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;
const variable = renderer.component.var_lookup.get(name);
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}
`);
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) {
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`
@ -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 (this.node.scope.is_let(name)) return true;
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 Action from '../../../nodes/Action';
export default function add_actions(block: Block, target: string, actions: Action[]) {
actions.forEach((action) => add_action(block, target, action));
export default function add_actions(
block: Block,
target: string,
actions: Action[]
) {
actions.forEach(action => add_action(block, target, 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();
}
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);
@ -32,6 +38,8 @@ export function add_action(block: Block, target: string, action: Action) {
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 remove_whitespace_children from './utils/remove_whitespace_children';
export default function (
node: Element,
renderer: Renderer,
options: RenderOptions & {
export default function(node: Element, renderer: Renderer, options: RenderOptions & {
slot_scopes: Map<any, any>;
}
) {
}) {
const children = remove_whitespace_children(node.children, node.next);
// awkward special case
let node_contents;
const contenteditable =
const contenteditable = (
node.name !== 'textarea' &&
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 nearest_inline_component = node.find_nearest(/InlineComponent/);
@ -34,7 +32,7 @@ export default function (
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 snippet = expression ? expression.node : x`#ctx.${name}`; // TODO is this right?
return x`${snippet} ? "${name}" : ""`;
@ -43,12 +41,13 @@ export default function (
class_expression_list.push(x`"${node.component.stylesheet.id}"`);
}
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
const args = [];
node.attributes.forEach((attribute) => {
node.attributes.forEach(attribute => {
if (attribute.is_spread) {
args.push(attribute.expression.node);
} else {
@ -79,7 +78,11 @@ export default function (
node_contents = get_attribute_value(attribute);
} else if (attribute.is_true) {
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
renderer.add_string(` `);
renderer.add_expression(x`${(attribute.chunks[0] as Expression).node} ? "${attribute.name}" : ""`);
@ -90,9 +93,7 @@ export default function (
renderer.add_string(`"`);
} else if (attribute.chunks.length === 1 && attribute.chunks[0].type !== 'Text') {
const snippet = (attribute.chunks[0] as Expression).node;
renderer.add_expression(
x`@add_attribute("${attribute.name}", ${snippet}, ${boolean_attributes.has(name) ? 1 : 0})`
);
renderer.add_expression(x`@add_attribute("${attribute.name}", ${snippet}, ${boolean_attributes.has(name) ? 1 : 0})`);
} else {
renderer.add_string(` ${attribute.name}="`);
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;
if (binding.is_readonly) {
@ -155,15 +156,15 @@ export default function (
}
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);
});
options.slot_scopes.set(slot, {
input: get_slot_scope(node.lets),
output: renderer.pop(),
output: renderer.pop()
});
} else {
renderer.render(children, options);

@ -10,18 +10,18 @@ function get_prop_value(attribute) {
if (attribute.chunks.length === 0) return x`''`;
return attribute.chunks
.map((chunk) => {
.map(chunk => {
if (chunk.type === 'Text') return string_literal(chunk.data);
return chunk.node;
})
.reduce((lhs, rhs) => x`${lhs} + ${rhs}`);
}
export default function (node: InlineComponent, renderer: Renderer, options: RenderOptions) {
export default function(node: InlineComponent, renderer: Renderer, options: RenderOptions) {
const binding_props = [];
const binding_fns = [];
node.bindings.forEach((binding) => {
node.bindings.forEach(binding => {
renderer.has_bindings = true;
// 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 }`);
});
const uses_spread = node.attributes.find((attr) => attr.is_spread);
const uses_spread = node.attributes.find(attr => attr.is_spread);
let props;
if (uses_spread) {
props = x`@_Object.assign(${node.attributes
.map((attribute) => {
props = x`@_Object.assign(${
node.attributes
.map(attribute => {
if (attribute.is_spread) {
return attribute.expression.node;
} else {
return x`{ ${attribute.name}: ${get_prop_value(attribute)} }`;
}
})
.concat(binding_props.map((p) => x`{ ${p} }`))})`;
.concat(binding_props.map(p => x`{ ${p} }`))
})`;
} else {
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}
}`;
}
@ -56,13 +58,13 @@ export default function (node: InlineComponent, renderer: Renderer, options: Ren
${binding_fns}
}`;
const expression =
const expression = (
node.name === 'svelte:self'
? renderer.name
: node.name === 'svelte: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 children = remove_whitespace_children(node.children, node.next);
@ -72,21 +74,20 @@ export default function (node: InlineComponent, renderer: Renderer, options: Ren
renderer.push();
renderer.render(
children,
Object.assign({}, options, {
slot_scopes,
})
);
renderer.render(children, Object.assign({}, options, {
slot_scopes
}));
slot_scopes.set('default', {
input: get_slot_scope(node.lets),
output: renderer.pop(),
output: renderer.pop()
});
slot_scopes.forEach(({ input, output }, name) => {
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}
}`;
renderer.add_expression(
x`@validate_component(${expression}, "${node.name}").$$render($$result, ${props}, ${bindings}, ${slots})`
);
renderer.add_expression(x`@validate_component(${expression}, "${node.name}").$$render($$result, ${props}, ${bindings}, ${slots})`);
}
function is_empty_template_literal(template_literal) {
return (
template_literal.expressions.length === 0 &&
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
.map((chunk) => {
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})`;
})
.reduce((lhs, rhs) => x`${lhs} + ${rhs}`);

@ -1,4 +1,5 @@
import { INode } from '../../../nodes/interfaces';
import { trim_end, trim_start } from '../../../../utils/trim';
import { link } from '../../../../utils/link';
// 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) {
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');
if (should_trim) {
data = data.trimRight();
data = trim_end(data);
if (!data) continue;
}
}
@ -36,16 +39,16 @@ export default function remove_whitespace_children(children: INode[], next?: INo
}
nodes.unshift(child);
link(last_child, (last_child = child));
link(last_child, last_child = child);
} else {
nodes.unshift(child);
link(last_child, (last_child = child));
link(last_child, last_child = child);
}
}
const first = nodes[0];
if (first && first.type === 'Text') {
first.data = first.data.trimLeft();
first.data = trim_start(first.data);
if (!first.data) {
first.var = null;
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 next sibling's previous node is an each block
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 { 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({
name: component.name,
name: component.name
});
const { name } = component;
// 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
const literal = renderer.pop();
// 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 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
? b`
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 = {};
for (#k in $$props){ if (!#keys.has(#k) && #k[0] !== '$'){ $$restProps[#k] = $$props[#k];}}`
: 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
.map(({ name }) => {
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
const parent_bindings = instance_javascript
? component.vars
.filter((variable) => !variable.module && variable.export_name)
.map((prop) => {
.filter(variable => !variable.module && variable.export_name)
.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});`;
})
: [];
const reactive_declarations = component.reactive_declarations.map((d) => {
const reactive_declarations = component.reactive_declarations.map(d => {
const body: Statement = (d.node as LabeledStatement).body;
let statement = b`${body}`;
if (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;
});
const self_dependencies = injected.filter((name) => d.dependencies.has(name));
const self_dependencies = injected.filter(name => d.dependencies.has(name));
if (injected.length) {
// in some cases we need to do `let foo; [expression]`, in
// 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;
statement = separate
? b`
${injected.map((name) => b`let ${name};`)}
${injected.map(name => b`let ${name};`)}
${statement}`
: b`let ${left} = ${right}`;
}
} else {
// TODO do not add label if it's not referenced
} else { // TODO do not add label if it's not referenced
statement = b`$: { ${statement} }`;
}
@ -128,23 +137,22 @@ export default function ssr(component: Component, options: CompileOptions): { js
...reactive_stores.map(({ name }) => {
const store_name = name.slice(1);
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,
...parent_bindings,
css.code && b`$$result.css.add(#css);`,
main,
main
].filter(Boolean);
const js = b`
${
css.code
? b`const #css = {
${css.code ? b`
const #css = {
code: "${css.code}",
map: ${css.map ? string_literal(css.map.toString()) : 'null'}
};`
: null
}
};` : null}
${component.extract_javascript(component.ast.module)}
@ -155,7 +163,7 @@ export default function ssr(component: Component, options: CompileOptions): { js
});
`;
return { js, css };
return {js, css};
}
function trim(nodes: TemplateNode[]) {

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

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

@ -49,12 +49,7 @@ export default function mustache(parser: Parser) {
block = parser.current();
}
if (
block.type === 'ElseBlock' ||
block.type === 'PendingBlock' ||
block.type === 'ThenBlock' ||
block.type === 'CatchBlock'
) {
if (block.type === 'ElseBlock' || block.type === 'PendingBlock' || block.type === 'ThenBlock' || block.type === 'CatchBlock') {
block.end = start;
parser.stack.pop();
block = parser.current();
@ -71,7 +66,7 @@ export default function mustache(parser: Parser) {
} else {
parser.error({
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')) {
parser.error({
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') {
parser.error({
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`
: `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') {
parser.error({
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`
: `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') {
parser.error({
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`
: `Cannot have an {:then} block outside an {#await ...} block`,
: `Cannot have an {:then} block outside an {#await ...} block`
});
}
} else {
if (block.type !== 'ThenBlock' && block.type !== 'PendingBlock') {
parser.error({
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`
: `Cannot have an {:catch} block outside an {#await ...} block`,
: `Cannot have an {:catch} block outside an {#await ...} block`
});
}
}
@ -209,9 +204,9 @@ export default function mustache(parser: Parser) {
const new_block: TemplateNode = {
start,
end: null,
type: is_then ? 'ThenBlock' : 'CatchBlock',
type: is_then ? 'ThenBlock': 'CatchBlock',
children: [],
skip: false,
skip: false
};
await_block[is_then ? 'then' : 'catch'] = new_block;
@ -229,7 +224,7 @@ export default function mustache(parser: Parser) {
} else {
parser.error({
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 block: TemplateNode =
type === 'AwaitBlock'
? {
const block: TemplateNode = type === 'AwaitBlock' ?
{
start,
end: null,
type,
@ -251,24 +245,24 @@ export default function mustache(parser: Parser) {
end: null,
type: 'PendingBlock',
children: [],
skip: true,
skip: true
},
then: {
start: null,
end: null,
type: 'ThenBlock',
children: [],
skip: true,
skip: true
},
catch: {
start: null,
end: null,
type: 'CatchBlock',
children: [],
skip: true,
skip: true
},
}
: {
} :
{
start,
end: null,
type,
@ -290,10 +284,9 @@ export default function mustache(parser: Parser) {
if (parser.eat(',')) {
parser.allow_whitespace();
block.index = parser.read_identifier();
if (!block.index)
parser.error({
if (!block.index) parser.error({
code: `expected-name`,
message: `Expected name`,
message: `Expected name`
});
parser.allow_whitespace();
@ -368,17 +361,16 @@ export default function mustache(parser: Parser) {
} else {
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') {
parser.error(
{
parser.error({
code: 'invalid-debug-args',
message: '{@debug ...} arguments must be identifiers, not arbitrary expressions',
},
node.start
);
message: '{@debug ...} arguments must be identifiers, not arbitrary expressions'
}, node.start);
}
});
@ -390,7 +382,7 @@ export default function mustache(parser: Parser) {
start,
end: parser.index,
type: 'DebugTag',
identifiers,
identifiers
});
} else {
const expression = read_expression(parser);

@ -35,7 +35,10 @@ const windows_1252 = [
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) {
return html.replace(entity_pattern, (match, entity) => {
@ -57,7 +60,7 @@ export function decode_character_references(html: string) {
return String.fromCodePoint(validate_code(code));
});
}
// this is necessary
const NUL = 0;
// some code points are verboten. If we were inserting HTML, the browser would replace the illegal

@ -156,7 +156,11 @@ class FuzzySet {
let results = [];
// 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);
if (results) {
return results;
@ -200,7 +204,10 @@ class FuzzySet {
// build a results list of [score, str]
for (const match_index in matches) {
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);
@ -209,7 +216,10 @@ class FuzzySet {
const end_index = Math.min(50, results.length);
// truncate somewhat arbitrarily to 50
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.sort(sort_descending);

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

@ -1,2 +1,6 @@
export default (items: string[], conjunction = 'or') =>
items.length === 1 ? items[0] : items.slice(0, -1).join(', ') + ` ${conjunction} ${items[items.length - 1]}`;
export default function list(items: string[], conjunction = 'or') {
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;
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));
};
@ -66,7 +67,7 @@ export const setFrameTimeout = (callback: (t: number) => void, timestamp: number
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 = (
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 = (
callback: (seconds_elapsed: number) => boolean,

@ -47,7 +47,7 @@ export const group_transition_out = (fn) => {
});
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;
};

Loading…
Cancel
Save