more tidying up

pull/2252/head
Richard Harris 7 years ago
parent 6ffb1ea00b
commit cc92f8fdcd

@ -15,7 +15,7 @@ export default class ReplProxy {
} }
iframeCommand(command, args) { iframeCommand(command, args) {
return new Promise( (resolve, reject) => { return new Promise((resolve, reject) => {
this.cmdId += 1; this.cmdId += 1;
this.pendingCmds.set(this.cmdId, { resolve, reject }); this.pendingCmds.set(this.cmdId, { resolve, reject });
@ -23,7 +23,7 @@ export default class ReplProxy {
action: command, action: command,
cmdId: this.cmdId, cmdId: this.cmdId,
args args
}, '*') }, '*');
}); });
} }

@ -3,8 +3,8 @@ import { walk, childKeys } from 'estree-walker';
import { getLocator } from 'locate-character'; import { getLocator } from 'locate-character';
import Stats from '../Stats'; import Stats from '../Stats';
import { globals, reserved } from '../utils/names'; import { globals, reserved } from '../utils/names';
import { namespaces, validNamespaces } from '../utils/namespaces'; import { namespaces, valid_namespaces } from '../utils/namespaces';
import wrapModule from './wrapModule'; import create_module from './create_module';
import { create_scopes, extract_names, Scope } from './utils/scope'; import { create_scopes, extract_names, Scope } from './utils/scope';
import Stylesheet from './css/Stylesheet'; import Stylesheet from './css/Stylesheet';
import { test } from '../config'; import { test } from '../config';
@ -70,13 +70,13 @@ export default class Component {
source: string; source: string;
code: MagicString; code: MagicString;
name: string; name: string;
compileOptions: CompileOptions; compile_options: CompileOptions;
fragment: Fragment; fragment: Fragment;
module_scope: Scope; module_scope: Scope;
instance_scope: Scope; instance_scope: Scope;
instance_scope_map: WeakMap<Node, Scope>; instance_scope_map: WeakMap<Node, Scope>;
componentOptions: ComponentOptions; component_options: ComponentOptions;
namespace: string; namespace: string;
tag: string; tag: string;
accessors: boolean; accessors: boolean;
@ -98,7 +98,7 @@ export default class Component {
injected_reactive_declaration_vars: Set<string> = new Set(); injected_reactive_declaration_vars: Set<string> = new Set();
helpers: Set<string> = new Set(); helpers: Set<string> = new Set();
indirectDependencies: Map<string, Set<string>> = new Map(); indirect_dependencies: Map<string, Set<string>> = new Map();
file: string; file: string;
locate: (c: number) => { line: number, column: number }; locate: (c: number) => { line: number, column: number };
@ -112,13 +112,13 @@ export default class Component {
stylesheet: Stylesheet; stylesheet: Stylesheet;
aliases: Map<string, string> = new Map(); aliases: Map<string, string> = new Map();
usedNames: Set<string> = new Set(); used_names: Set<string> = new Set();
constructor( constructor(
ast: Ast, ast: Ast,
source: string, source: string,
name: string, name: string,
compileOptions: CompileOptions, compile_options: CompileOptions,
stats: Stats, stats: Stats,
warnings: Warning[] warnings: Warning[]
) { ) {
@ -128,24 +128,24 @@ export default class Component {
this.warnings = warnings; this.warnings = warnings;
this.ast = ast; this.ast = ast;
this.source = source; this.source = source;
this.compileOptions = compileOptions; this.compile_options = compile_options;
this.file = compileOptions.filename && ( this.file = compile_options.filename && (
typeof process !== 'undefined' ? compileOptions.filename.replace(process.cwd(), '').replace(/^[\/\\]/, '') : compileOptions.filename typeof process !== 'undefined' ? compile_options.filename.replace(process.cwd(), '').replace(/^[\/\\]/, '') : compile_options.filename
); );
this.locate = getLocator(this.source); this.locate = getLocator(this.source);
this.code = new MagicString(source); this.code = new MagicString(source);
// styles // styles
this.stylesheet = new Stylesheet(source, ast, compileOptions.filename, compileOptions.dev); this.stylesheet = new Stylesheet(source, ast, compile_options.filename, compile_options.dev);
this.stylesheet.validate(this); this.stylesheet.validate(this);
this.componentOptions = process_component_options(this, this.ast.html.children); this.component_options = process_component_options(this, this.ast.html.children);
this.namespace = namespaces[this.componentOptions.namespace] || this.componentOptions.namespace; this.namespace = namespaces[this.component_options.namespace] || this.component_options.namespace;
if (compileOptions.customElement) { if (compile_options.customElement) {
this.tag = this.componentOptions.tag || compileOptions.tag; this.tag = this.component_options.tag || compile_options.tag;
if (!this.tag) { if (!this.tag) {
throw new Error(`Cannot compile to a custom element without specifying a tag name via options.tag or <svelte:options>`); throw new Error(`Cannot compile to a custom element without specifying a tag name via options.tag or <svelte:options>`);
} }
@ -157,11 +157,11 @@ export default class Component {
this.walk_instance_js_pre_template(); this.walk_instance_js_pre_template();
this.fragment = new Fragment(this, ast.html); this.fragment = new Fragment(this, ast.html);
this.name = this.getUniqueName(name); this.name = this.get_unique_name(name);
this.walk_instance_js_post_template(); this.walk_instance_js_post_template();
if (!compileOptions.customElement) this.stylesheet.reify(); if (!compile_options.customElement) this.stylesheet.reify();
this.stylesheet.warnOnUnusedSelectors(this); this.stylesheet.warnOnUnusedSelectors(this);
} }
@ -197,11 +197,11 @@ export default class Component {
const variable = this.var_lookup.get(subscribable_name); const variable = this.var_lookup.get(subscribable_name);
if (variable) variable.subscribable = true; if (variable) variable.subscribable = true;
} else { } else {
this.usedNames.add(name); this.used_names.add(name);
} }
} }
addSourcemapLocations(node: Node) { add_sourcemap_locations(node: Node) {
walk(node, { walk(node, {
enter: (node: Node) => { enter: (node: Node) => {
this.code.addSourcemapLocation(node.start); this.code.addSourcemapLocation(node.start);
@ -212,7 +212,7 @@ export default class Component {
alias(name: string) { alias(name: string) {
if (!this.aliases.has(name)) { if (!this.aliases.has(name)) {
this.aliases.set(name, this.getUniqueName(name)); this.aliases.set(name, this.get_unique_name(name));
} }
return this.aliases.get(name); return this.aliases.get(name);
@ -228,17 +228,17 @@ export default class Component {
let css = null; let css = null;
if (result) { if (result) {
const { compileOptions, name } = this; const { compile_options, name } = this;
const { format = 'esm' } = compileOptions; const { format = 'esm' } = compile_options;
const banner = `/* ${this.file ? `${this.file} ` : ``}generated by Svelte v${"__VERSION__"} */`; const banner = `/* ${this.file ? `${this.file} ` : ``}generated by Svelte v${"__VERSION__"} */`;
result = result result = result
.replace(/__svelte:self__/g, this.name) .replace(/__svelte:self__/g, this.name)
.replace(compileOptions.generate === 'ssr' ? /(@+|#+)(\w*(?:-\w*)?)/g : /(@+)(\w*(?:-\w*)?)/g, (match: string, sigil: string, name: string) => { .replace(compile_options.generate === 'ssr' ? /(@+|#+)(\w*(?:-\w*)?)/g : /(@+)(\w*(?:-\w*)?)/g, (match: string, sigil: string, name: string) => {
if (sigil === '@') { if (sigil === '@') {
if (internal_exports.has(name)) { if (internal_exports.has(name)) {
if (compileOptions.dev && internal_exports.has(`${name}Dev`)) name = `${name}Dev`; if (compile_options.dev && internal_exports.has(`${name}Dev`)) name = `${name}Dev`;
this.helpers.add(name); this.helpers.add(name);
} }
@ -248,21 +248,20 @@ export default class Component {
return sigil.slice(1) + name; return sigil.slice(1) + name;
}); });
const importedHelpers = Array.from(this.helpers) const imported_helpers = Array.from(this.helpers)
.sort() .sort()
.map(name => { .map(name => {
const alias = this.alias(name); const alias = this.alias(name);
return { name, alias }; return { name, alias };
}); });
const module = wrapModule( const module = create_module(
result, result,
format, format,
name, name,
compileOptions,
banner, banner,
compileOptions.sveltePath, compile_options.sveltePath,
importedHelpers, imported_helpers,
this.imports, this.imports,
this.vars.filter(variable => variable.module && variable.export_name).map(variable => ({ this.vars.filter(variable => variable.module && variable.export_name).map(variable => ({
name: variable.name, name: variable.name,
@ -272,17 +271,17 @@ export default class Component {
); );
const parts = module.split('✂]'); const parts = module.split('✂]');
const finalChunk = parts.pop(); const final_chunk = parts.pop();
const compiled = new Bundle({ separator: '' }); const compiled = new Bundle({ separator: '' });
function addString(str: string) { function add_string(str: string) {
compiled.addSource({ compiled.addSource({
content: new MagicString(str), content: new MagicString(str),
}); });
} }
const { filename } = compileOptions; const { filename } = compile_options;
// special case — the source file doesn't actually get used anywhere. we need // special case — the source file doesn't actually get used anywhere. we need
// to add an empty file to populate map.sources and map.sourcesContent // to add an empty file to populate map.sources and map.sourcesContent
@ -297,7 +296,7 @@ export default class Component {
parts.forEach((str: string) => { parts.forEach((str: string) => {
const chunk = str.replace(pattern, ''); const chunk = str.replace(pattern, '');
if (chunk) addString(chunk); if (chunk) add_string(chunk);
const match = pattern.exec(str); const match = pattern.exec(str);
@ -309,17 +308,17 @@ export default class Component {
}); });
}); });
addString(finalChunk); add_string(final_chunk);
css = compileOptions.customElement ? css = compile_options.customElement ?
{ code: null, map: null } : { code: null, map: null } :
this.stylesheet.render(compileOptions.cssOutputFilename, true); this.stylesheet.render(compile_options.cssOutputFilename, true);
js = { js = {
code: compiled.toString(), code: compiled.toString(),
map: compiled.generateMap({ map: compiled.generateMap({
includeContent: true, includeContent: true,
file: compileOptions.outputFilename, file: compile_options.outputFilename,
}) })
}; };
} }
@ -343,25 +342,25 @@ export default class Component {
}; };
} }
getUniqueName(name: string) { get_unique_name(name: string) {
if (test) name = `${name}$`; if (test) name = `${name}$`;
let alias = name; let alias = name;
for ( for (
let i = 1; let i = 1;
reserved.has(alias) || reserved.has(alias) ||
this.var_lookup.has(alias) || this.var_lookup.has(alias) ||
this.usedNames.has(alias); this.used_names.has(alias);
alias = `${name}_${i++}` alias = `${name}_${i++}`
); );
this.usedNames.add(alias); this.used_names.add(alias);
return alias; return alias;
} }
getUniqueNameMaker() { get_unique_name_maker() {
const localUsedNames = new Set(); const local_used_names = new Set();
function add(name: string) { function add(name: string) {
localUsedNames.add(name); local_used_names.add(name);
} }
reserved.forEach(add); reserved.forEach(add);
@ -373,11 +372,11 @@ export default class Component {
let alias = name; let alias = name;
for ( for (
let i = 1; let i = 1;
this.usedNames.has(alias) || this.used_names.has(alias) ||
localUsedNames.has(alias); local_used_names.has(alias);
alias = `${name}_${i++}` alias = `${name}_${i++}`
); );
localUsedNames.add(alias); local_used_names.add(alias);
return alias; return alias;
}; };
} }
@ -398,7 +397,7 @@ export default class Component {
source: this.source, source: this.source,
start: pos.start, start: pos.start,
end: pos.end, end: pos.end,
filename: this.compileOptions.filename filename: this.compile_options.filename
}); });
} }
@ -428,7 +427,7 @@ export default class Component {
start, start,
end, end,
pos: pos.start, pos: pos.start,
filename: this.compileOptions.filename, filename: this.compile_options.filename,
toString: () => `${warning.message} (${start.line + 1}:${start.column})\n${frame}`, toString: () => `${warning.message} (${start.line + 1}:${start.column})\n${frame}`,
}); });
} }
@ -536,7 +535,7 @@ export default class Component {
const script = this.ast.module; const script = this.ast.module;
if (!script) return; if (!script) return;
this.addSourcemapLocations(script.content); this.add_sourcemap_locations(script.content);
let { scope, globals } = create_scopes(script.content); let { scope, globals } = create_scopes(script.content);
this.module_scope = scope; this.module_scope = scope;
@ -581,7 +580,7 @@ export default class Component {
const script = this.ast.instance; const script = this.ast.instance;
if (!script) return; if (!script) return;
this.addSourcemapLocations(script.content); this.add_sourcemap_locations(script.content);
// inject vars for reactive declarations // inject vars for reactive declarations
script.content.body.forEach(node => { script.content.body.forEach(node => {
@ -759,7 +758,7 @@ export default class Component {
rewrite_props(get_insert: (variable: Var) => string) { rewrite_props(get_insert: (variable: Var) => string) {
const component = this; const component = this;
const { code, instance_scope, instance_scope_map: map, componentOptions } = this; const { code, instance_scope, instance_scope_map: map, component_options } = this;
let scope = instance_scope; let scope = instance_scope;
const coalesced_declarations = []; const coalesced_declarations = [];
@ -1189,11 +1188,11 @@ export default class Component {
} }
function process_component_options(component: Component, nodes) { function process_component_options(component: Component, nodes) {
const componentOptions: ComponentOptions = { const component_options: ComponentOptions = {
immutable: component.compileOptions.immutable || false, immutable: component.compile_options.immutable || false,
accessors: 'accessors' in component.compileOptions accessors: 'accessors' in component.compile_options
? component.compileOptions.accessors ? component.compile_options.accessors
: !!component.compileOptions.customElement : !!component.compile_options.customElement
}; };
const node = nodes.find(node => node.name === 'svelte:options'); const node = nodes.find(node => node.name === 'svelte:options');
@ -1237,7 +1236,7 @@ function process_component_options(component: Component, nodes) {
}); });
} }
componentOptions.tag = tag; component_options.tag = tag;
break; break;
} }
@ -1248,8 +1247,8 @@ function process_component_options(component: Component, nodes) {
if (typeof ns !== 'string') component.error(attribute, { code, message }); if (typeof ns !== 'string') component.error(attribute, { code, message });
if (validNamespaces.indexOf(ns) === -1) { if (valid_namespaces.indexOf(ns) === -1) {
const match = fuzzymatch(ns, validNamespaces); const match = fuzzymatch(ns, valid_namespaces);
if (match) { if (match) {
component.error(attribute, { component.error(attribute, {
code: `invalid-namespace-property`, code: `invalid-namespace-property`,
@ -1263,7 +1262,7 @@ function process_component_options(component: Component, nodes) {
} }
} }
componentOptions.namespace = ns; component_options.namespace = ns;
break; break;
} }
@ -1275,7 +1274,7 @@ function process_component_options(component: Component, nodes) {
if (typeof value !== 'boolean') component.error(attribute, { code, message }); if (typeof value !== 'boolean') component.error(attribute, { code, message });
componentOptions[name] = value; component_options[name] = value;
break; break;
default: default:
@ -1295,5 +1294,5 @@ function process_component_options(component: Component, nodes) {
}); });
} }
return componentOptions; return component_options;
} }

@ -1,14 +1,8 @@
import deindent from './utils/deindent'; import deindent from './utils/deindent';
import list from '../utils/list'; import list from '../utils/list';
import { CompileOptions, ModuleFormat, Node } from '../interfaces'; import { ModuleFormat, Node } from '../interfaces';
import { stringify_props } from './utils/stringify_props'; import { stringify_props } from './utils/stringify_props';
interface Dependency {
name: string;
statements: string[];
source: string;
}
const wrappers = { esm, cjs }; const wrappers = { esm, cjs };
type Export = { type Export = {
@ -16,11 +10,10 @@ type Export = {
as: string; as: string;
}; };
export default function wrapModule( export default function create_module(
code: string, code: string,
format: ModuleFormat, format: ModuleFormat,
name: string, name: string,
options: CompileOptions,
banner: string, banner: string,
sveltePath = 'svelte', sveltePath = 'svelte',
helpers: { name: string, alias: string }[], helpers: { name: string, alias: string }[],
@ -28,18 +21,18 @@ export default function wrapModule(
module_exports: Export[], module_exports: Export[],
source: string source: string
): string { ): string {
const internalPath = `${sveltePath}/internal`; const internal_path = `${sveltePath}/internal`;
if (format === 'esm') { if (format === 'esm') {
return esm(code, name, options, banner, sveltePath, internalPath, helpers, imports, module_exports, source); return esm(code, name, banner, sveltePath, internal_path, helpers, imports, module_exports, source);
} }
if (format === 'cjs') return cjs(code, name, banner, sveltePath, internalPath, helpers, imports, module_exports); if (format === 'cjs') return cjs(code, name, banner, sveltePath, internal_path, helpers, imports, module_exports);
throw new Error(`options.format is invalid (must be ${list(Object.keys(wrappers))})`); throw new Error(`options.format is invalid (must be ${list(Object.keys(wrappers))})`);
} }
function editSource(source, sveltePath) { function edit_source(source, sveltePath) {
return source === 'svelte' || source.startsWith('svelte/') return source === 'svelte' || source.startsWith('svelte/')
? source.replace('svelte', sveltePath) ? source.replace('svelte', sveltePath)
: source; : source;
@ -48,23 +41,22 @@ function editSource(source, sveltePath) {
function esm( function esm(
code: string, code: string,
name: string, name: string,
options: CompileOptions,
banner: string, banner: string,
sveltePath: string, sveltePath: string,
internalPath: string, internal_path: string,
helpers: { name: string, alias: string }[], helpers: { name: string, alias: string }[],
imports: Node[], imports: Node[],
module_exports: Export[], module_exports: Export[],
source: string source: string
) { ) {
const importHelpers = helpers.length > 0 && ( const internal_imports = helpers.length > 0 && (
`import ${stringify_props(helpers.map(h => h.name === h.alias ? h.name : `${h.name} as ${h.alias}`).sort())} from ${JSON.stringify(internalPath)};` `import ${stringify_props(helpers.map(h => h.name === h.alias ? h.name : `${h.name} as ${h.alias}`).sort())} from ${JSON.stringify(internal_path)};`
); );
const importBlock = imports.length > 0 && ( const user_imports = imports.length > 0 && (
imports imports
.map((declaration: Node) => { .map((declaration: Node) => {
const import_source = editSource(declaration.source.value, sveltePath); const import_source = edit_source(declaration.source.value, sveltePath);
return ( return (
source.slice(declaration.start, declaration.source.start) + source.slice(declaration.start, declaration.source.start) +
@ -77,8 +69,8 @@ function esm(
return deindent` return deindent`
${banner} ${banner}
${importHelpers} ${internal_imports}
${importBlock} ${user_imports}
${code} ${code}
@ -91,15 +83,15 @@ function cjs(
name: string, name: string,
banner: string, banner: string,
sveltePath: string, sveltePath: string,
internalPath: string, internal_path: string,
helpers: { name: string, alias: string }[], helpers: { name: string, alias: string }[],
imports: Node[], imports: Node[],
module_exports: Export[] module_exports: Export[]
) { ) {
const helperDeclarations = helpers.map(h => `${h.alias === h.name ? h.name : `${h.name}: ${h.alias}`}`).sort(); const declarations = helpers.map(h => `${h.alias === h.name ? h.name : `${h.name}: ${h.alias}`}`).sort();
const helperBlock = helpers.length > 0 && ( const internal_imports = helpers.length > 0 && (
`const ${stringify_props(helperDeclarations)} = require(${JSON.stringify(internalPath)});\n` `const ${stringify_props(declarations)} = require(${JSON.stringify(internal_path)});\n`
); );
const requires = imports.map(node => { const requires = imports.map(node => {
@ -121,7 +113,7 @@ function cjs(
lhs = `{ ${properties.join(', ')} }`; lhs = `{ ${properties.join(', ')} }`;
} }
const source = editSource(node.source.value, sveltePath); const source = edit_source(node.source.value, sveltePath);
return `const ${lhs} = require("${source}");` return `const ${lhs} = require("${source}");`
}); });
@ -134,7 +126,7 @@ function cjs(
${banner} ${banner}
"use strict"; "use strict";
${helperBlock} ${internal_imports}
${requires} ${requires}
${code} ${code}

@ -1,6 +1,6 @@
import MagicString from 'magic-string'; import MagicString from 'magic-string';
import Stylesheet from './Stylesheet'; import Stylesheet from './Stylesheet';
import { gatherPossibleValues, UNKNOWN } from './gatherPossibleValues'; import { gather_possible_values, UNKNOWN } from './gather_possible_values';
import { Node } from '../interfaces'; import { Node } from '../interfaces';
import Component from '../compile/Component'; import Component from '../compile/Component';
@ -8,14 +8,14 @@ export default class Selector {
node: Node; node: Node;
stylesheet: Stylesheet; stylesheet: Stylesheet;
blocks: Block[]; blocks: Block[];
localBlocks: Block[]; local_blocks: Block[];
used: boolean; used: boolean;
constructor(node: Node, stylesheet: Stylesheet) { constructor(node: Node, stylesheet: Stylesheet) {
this.node = node; this.node = node;
this.stylesheet = stylesheet; this.stylesheet = stylesheet;
this.blocks = groupSelectors(node); this.blocks = group_selectors(node);
// take trailing :global(...) selectors out of consideration // take trailing :global(...) selectors out of consideration
let i = this.blocks.length; let i = this.blocks.length;
@ -24,19 +24,19 @@ export default class Selector {
i -= 1; i -= 1;
} }
this.localBlocks = this.blocks.slice(0, i); this.local_blocks = this.blocks.slice(0, i);
this.used = this.blocks[0].global; this.used = this.blocks[0].global;
} }
apply(node: Node, stack: Node[]) { apply(node: Node, stack: Node[]) {
const toEncapsulate: Node[] = []; const to_encapsulate: Node[] = [];
applySelector(this.stylesheet, this.localBlocks.slice(), node, stack.slice(), toEncapsulate); apply_selector(this.stylesheet, this.local_blocks.slice(), node, stack.slice(), to_encapsulate);
if (toEncapsulate.length > 0) { if (to_encapsulate.length > 0) {
toEncapsulate.filter((_, i) => i === 0 || i === toEncapsulate.length - 1).forEach(({ node, block }) => { to_encapsulate.filter((_, i) => i === 0 || i === to_encapsulate.length - 1).forEach(({ node, block }) => {
this.stylesheet.nodesWithCssClass.add(node); this.stylesheet.nodes_with_css_class.add(node);
block.shouldEncapsulate = true; block.should_encapsulate = true;
}); });
this.used = true; this.used = true;
@ -57,7 +57,7 @@ export default class Selector {
} }
transform(code: MagicString, attr: string) { transform(code: MagicString, attr: string) {
function encapsulateBlock(block: Block) { function encapsulate_block(block: Block) {
let i = block.selectors.length; let i = block.selectors.length;
while (i--) { while (i--) {
const selector = block.selectors[i]; const selector = block.selectors[i];
@ -81,7 +81,7 @@ export default class Selector {
code.remove(selector.start, first.start).remove(last.end, selector.end); code.remove(selector.start, first.start).remove(last.end, selector.end);
} }
if (block.shouldEncapsulate) encapsulateBlock(block); if (block.should_encapsulate) encapsulate_block(block);
}); });
} }
@ -121,7 +121,7 @@ export default class Selector {
} }
} }
function applySelector(stylesheet: Stylesheet, blocks: Block[], node: Node, stack: Node[], toEncapsulate: any[]): boolean { function apply_selector(stylesheet: Stylesheet, blocks: Block[], node: Node, stack: Node[], to_encapsulate: any[]): boolean {
const block = blocks.pop(); const block = blocks.pop();
if (!block) return false; if (!block) return false;
@ -145,15 +145,15 @@ function applySelector(stylesheet: Stylesheet, blocks: Block[], node: Node, stac
} }
if (selector.type === 'ClassSelector') { if (selector.type === 'ClassSelector') {
if (!attributeMatches(node, 'class', selector.name, '~=', false) && !classMatches(node, selector.name)) return false; if (!attribute_matches(node, 'class', selector.name, '~=', false) && !class_matches(node, selector.name)) return false;
} }
else if (selector.type === 'IdSelector') { else if (selector.type === 'IdSelector') {
if (!attributeMatches(node, 'id', selector.name, '=', false)) return false; if (!attribute_matches(node, 'id', selector.name, '=', false)) return false;
} }
else if (selector.type === 'AttributeSelector') { else if (selector.type === 'AttributeSelector') {
if (!attributeMatches(node, selector.name.name, selector.value && unquote(selector.value), selector.matcher, selector.flags)) return false; if (!attribute_matches(node, selector.name.name, selector.value && unquote(selector.value), selector.matcher, selector.flags)) return false;
} }
else if (selector.type === 'TypeSelector') { else if (selector.type === 'TypeSelector') {
@ -163,7 +163,7 @@ function applySelector(stylesheet: Stylesheet, blocks: Block[], node: Node, stac
else { else {
// bail. TODO figure out what these could be // bail. TODO figure out what these could be
toEncapsulate.push({ node, block }); to_encapsulate.push({ node, block });
return true; return true;
} }
} }
@ -171,21 +171,21 @@ function applySelector(stylesheet: Stylesheet, blocks: Block[], node: Node, stac
if (block.combinator) { if (block.combinator) {
if (block.combinator.type === 'WhiteSpace') { if (block.combinator.type === 'WhiteSpace') {
while (stack.length) { while (stack.length) {
if (applySelector(stylesheet, blocks.slice(), stack.pop(), stack, toEncapsulate)) { if (apply_selector(stylesheet, blocks.slice(), stack.pop(), stack, to_encapsulate)) {
toEncapsulate.push({ node, block }); to_encapsulate.push({ node, block });
return true; return true;
} }
} }
if (blocks.every(block => block.global)) { if (blocks.every(block => block.global)) {
toEncapsulate.push({ node, block }); to_encapsulate.push({ node, block });
return true; return true;
} }
return false; return false;
} else if (block.combinator.name === '>') { } else if (block.combinator.name === '>') {
if (applySelector(stylesheet, blocks, stack.pop(), stack, toEncapsulate)) { if (apply_selector(stylesheet, blocks, stack.pop(), stack, to_encapsulate)) {
toEncapsulate.push({ node, block }); to_encapsulate.push({ node, block });
return true; return true;
} }
@ -193,11 +193,11 @@ function applySelector(stylesheet: Stylesheet, blocks: Block[], node: Node, stac
} }
// TODO other combinators // TODO other combinators
toEncapsulate.push({ node, block }); to_encapsulate.push({ node, block });
return true; return true;
} }
toEncapsulate.push({ node, block }); to_encapsulate.push({ node, block });
return true; return true;
} }
@ -210,43 +210,39 @@ const operators = {
'*=': (value: string, flags: string) => new RegExp(value, flags) '*=': (value: string, flags: string) => new RegExp(value, flags)
}; };
function attributeMatches(node: Node, name: string, expectedValue: string, operator: string, caseInsensitive: boolean) { function attribute_matches(node: Node, name: string, expected_value: string, operator: string, caseInsensitive: boolean) {
const spread = node.attributes.find(attr => attr.type === 'Spread'); const spread = node.attributes.find(attr => attr.type === 'Spread');
if (spread) return true; if (spread) return true;
const attr = node.attributes.find((attr: Node) => attr.name === name); const attr = node.attributes.find((attr: Node) => attr.name === name);
if (!attr) return false; if (!attr) return false;
if (attr.isTrue) return operator === null; if (attr.is_true) return operator === null;
if (attr.chunks.length > 1) return true; if (attr.chunks.length > 1) return true;
if (!expectedValue) return true; if (!expected_value) return true;
const pattern = operators[operator](expectedValue, caseInsensitive ? 'i' : ''); const pattern = operators[operator](expected_value, caseInsensitive ? 'i' : '');
const value = attr.chunks[0]; const value = attr.chunks[0];
if (!value) return false; if (!value) return false;
if (value.type === 'Text') return pattern.test(value.data); if (value.type === 'Text') return pattern.test(value.data);
const possibleValues = new Set(); const possible_values = new Set();
gatherPossibleValues(value.node, possibleValues); gather_possible_values(value.node, possible_values);
if (possibleValues.has(UNKNOWN)) return true; if (possible_values.has(UNKNOWN)) return true;
for (const x of Array.from(possibleValues)) { // TypeScript for-of is slightly unlike JS for (const x of Array.from(possible_values)) { // TypeScript for-of is slightly unlike JS
if (pattern.test(x)) return true; if (pattern.test(x)) return true;
} }
return false; return false;
} }
function classMatches(node, name: string) { function class_matches(node, name: string) {
return node.classes.some(function(classDir) { return node.classes.some(function(class_directive) {
return classDir.name === name; return class_directive.name === name;
}); });
} }
function isDynamic(value: Node) {
return value.length > 1 || value[0].type !== 'Text';
}
function unquote(value: Node) { function unquote(value: Node) {
if (value.type === 'Identifier') return value.name; if (value.type === 'Identifier') return value.name;
const str = value.value; const str = value.value;
@ -262,7 +258,7 @@ class Block {
selectors: Node[] selectors: Node[]
start: number; start: number;
end: number; end: number;
shouldEncapsulate: boolean; should_encapsulate: boolean;
constructor(combinator: Node) { constructor(combinator: Node) {
this.combinator = combinator; this.combinator = combinator;
@ -272,7 +268,7 @@ class Block {
this.start = null; this.start = null;
this.end = null; this.end = null;
this.shouldEncapsulate = false; this.should_encapsulate = false;
} }
add(selector: Node) { add(selector: Node) {
@ -286,7 +282,7 @@ class Block {
} }
} }
function groupSelectors(selector: Node) { function group_selectors(selector: Node) {
let block: Block = new Block(null); let block: Block = new Block(null);
const blocks = [block]; const blocks = [block];

@ -9,7 +9,7 @@ function remove_css_prefox(name: string): string {
return name.replace(/^-((webkit)|(moz)|(o)|(ms))-/, ''); return name.replace(/^-((webkit)|(moz)|(o)|(ms))-/, '');
} }
const isKeyframesNode = (node: Node) => remove_css_prefox(node.name) === 'keyframes'; const is_keyframes_node = (node: Node) => remove_css_prefox(node.name) === 'keyframes';
// https://github.com/darkskyapp/string-hash/blob/master/index.js // https://github.com/darkskyapp/string-hash/blob/master/index.js
function hash(str: string): string { function hash(str: string): string {
@ -37,8 +37,8 @@ class Rule {
this.selectors.forEach(selector => selector.apply(node, stack)); // TODO move the logic in here? this.selectors.forEach(selector => selector.apply(node, stack)); // TODO move the logic in here?
} }
isUsed(dev: boolean) { is_used(dev: boolean) {
if (this.parent && this.parent.node.type === 'Atrule' && isKeyframesNode(this.parent.node)) return true; if (this.parent && this.parent.node.type === 'Atrule' && is_keyframes_node(this.parent.node)) return true;
if (this.declarations.length === 0) return dev; if (this.declarations.length === 0) return dev;
return this.selectors.some(s => s.used); return this.selectors.some(s => s.used);
} }
@ -79,7 +79,7 @@ class Rule {
} }
transform(code: MagicString, id: string, keyframes: Map<string, string>) { transform(code: MagicString, id: string, keyframes: Map<string, string>) {
if (this.parent && this.parent.node.type === 'Atrule' && isKeyframesNode(this.parent.node)) return true; if (this.parent && this.parent.node.type === 'Atrule' && is_keyframes_node(this.parent.node)) return true;
const attr = `.${id}`; const attr = `.${id}`;
@ -93,7 +93,7 @@ class Rule {
}); });
} }
warnOnUnusedSelector(handler: (selector: Selector) => void) { warn_on_unused_selector(handler: (selector: Selector) => void) {
this.selectors.forEach(selector => { this.selectors.forEach(selector => {
if (!selector.used) handler(selector); if (!selector.used) handler(selector);
}); });
@ -154,7 +154,7 @@ class Atrule {
}); });
} }
else if (isKeyframesNode(this.node)) { else if (is_keyframes_node(this.node)) {
this.children.forEach((rule: Rule) => { this.children.forEach((rule: Rule) => {
rule.selectors.forEach(selector => { rule.selectors.forEach(selector => {
selector.used = true; selector.used = true;
@ -163,14 +163,14 @@ class Atrule {
} }
} }
isUsed(dev: boolean) { is_used(dev: boolean) {
return true; // TODO return true; // TODO
} }
minify(code: MagicString, dev: boolean) { minify(code: MagicString, dev: boolean) {
if (this.node.name === 'media') { if (this.node.name === 'media') {
const expressionChar = code.original[this.node.expression.start]; const expression_char = code.original[this.node.expression.start];
let c = this.node.start + (expressionChar === '(' ? 6 : 7); let c = this.node.start + (expression_char === '(' ? 6 : 7);
if (this.node.expression.start > c) code.remove(c, this.node.expression.start); if (this.node.expression.start > c) code.remove(c, this.node.expression.start);
this.node.expression.children.forEach((query: Node) => { this.node.expression.children.forEach((query: Node) => {
@ -179,7 +179,7 @@ class Atrule {
}); });
code.remove(c, this.node.block.start); code.remove(c, this.node.block.start);
} else if (isKeyframesNode(this.node)) { } else if (is_keyframes_node(this.node)) {
let c = this.node.start + this.node.name.length + 1; let c = this.node.start + this.node.name.length + 1;
if (this.node.expression.start - c > 1) code.overwrite(c, this.node.expression.start, ' '); if (this.node.expression.start - c > 1) code.overwrite(c, this.node.expression.start, ' ');
c = this.node.expression.end; c = this.node.expression.end;
@ -200,7 +200,7 @@ class Atrule {
let c = this.node.block.start + 1; let c = this.node.block.start + 1;
this.children.forEach(child => { this.children.forEach(child => {
if (child.isUsed(dev)) { if (child.is_used(dev)) {
code.remove(c, child.node.start); code.remove(c, child.node.start);
child.minify(code, dev); child.minify(code, dev);
c = child.node.end; c = child.node.end;
@ -212,7 +212,7 @@ class Atrule {
} }
transform(code: MagicString, id: string, keyframes: Map<string, string>) { transform(code: MagicString, id: string, keyframes: Map<string, string>) {
if (isKeyframesNode(this.node)) { if (is_keyframes_node(this.node)) {
this.node.expression.children.forEach(({ type, name, start, end }: Node) => { this.node.expression.children.forEach(({ type, name, start, end }: Node) => {
if (type === 'Identifier') { if (type === 'Identifier') {
if (name.startsWith('-global-')) { if (name.startsWith('-global-')) {
@ -226,7 +226,7 @@ class Atrule {
this.children.forEach(child => { this.children.forEach(child => {
child.transform(code, id, keyframes); child.transform(code, id, keyframes);
}) });
} }
validate(component: Component) { validate(component: Component) {
@ -235,31 +235,28 @@ class Atrule {
}); });
} }
warnOnUnusedSelector(handler: (selector: Selector) => void) { warn_on_unused_selector(handler: (selector: Selector) => void) {
if (this.node.name !== 'media') return; if (this.node.name !== 'media') return;
this.children.forEach(child => { this.children.forEach(child => {
child.warnOnUnusedSelector(handler); child.warn_on_unused_selector(handler);
}); });
} }
} }
const keys = {};
export default class Stylesheet { export default class Stylesheet {
source: string; source: string;
ast: Ast; ast: Ast;
filename: string; filename: string;
dev: boolean; dev: boolean;
hasStyles: boolean; has_styles: boolean;
id: string; id: string;
children: (Rule|Atrule)[]; children: (Rule|Atrule)[] = [];
keyframes: Map<string, string>; keyframes: Map<string, string> = new Map();
nodesWithCssClass: Set<Node>; nodes_with_css_class: Set<Node> = new Set();
nodesWithRefCssClass: Map<String, Node>;
constructor(source: string, ast: Ast, filename: string, dev: boolean) { constructor(source: string, ast: Ast, filename: string, dev: boolean) {
this.source = source; this.source = source;
@ -267,19 +264,13 @@ export default class Stylesheet {
this.filename = filename; this.filename = filename;
this.dev = dev; this.dev = dev;
this.children = [];
this.keyframes = new Map();
this.nodesWithCssClass = new Set();
this.nodesWithRefCssClass = new Map();
if (ast.css && ast.css.children.length) { if (ast.css && ast.css.children.length) {
this.id = `svelte-${hash(ast.css.content.styles)}`; this.id = `svelte-${hash(ast.css.content.styles)}`;
this.hasStyles = true; this.has_styles = true;
const stack: (Rule | Atrule)[] = []; const stack: (Rule | Atrule)[] = [];
let currentAtrule: Atrule = null; let current_atrule: Atrule = null;
walk(ast.css, { walk(ast.css, {
enter: (node: Node) => { enter: (node: Node) => {
@ -293,13 +284,13 @@ export default class Stylesheet {
// possibly other future constructs) // possibly other future constructs)
if (last && !(last instanceof Atrule)) return; if (last && !(last instanceof Atrule)) return;
if (currentAtrule) { if (current_atrule) {
currentAtrule.children.push(atrule); current_atrule.children.push(atrule);
} else { } else {
this.children.push(atrule); this.children.push(atrule);
} }
if (isKeyframesNode(node)) { if (is_keyframes_node(node)) {
node.expression.children.forEach((expression: Node) => { node.expression.children.forEach((expression: Node) => {
if (expression.type === 'Identifier' && !expression.name.startsWith('-global-')) { if (expression.type === 'Identifier' && !expression.name.startsWith('-global-')) {
this.keyframes.set(expression.name, `${this.id}-${expression.name}`); this.keyframes.set(expression.name, `${this.id}-${expression.name}`);
@ -307,15 +298,15 @@ export default class Stylesheet {
}); });
} }
currentAtrule = atrule; current_atrule = atrule;
} }
if (node.type === 'Rule') { if (node.type === 'Rule') {
const rule = new Rule(node, this, currentAtrule); const rule = new Rule(node, this, current_atrule);
stack.push(rule); stack.push(rule);
if (currentAtrule) { if (current_atrule) {
currentAtrule.children.push(rule); current_atrule.children.push(rule);
} else { } else {
this.children.push(rule); this.children.push(rule);
} }
@ -324,16 +315,16 @@ export default class Stylesheet {
leave: (node: Node) => { leave: (node: Node) => {
if (node.type === 'Rule' || node.type === 'Atrule') stack.pop(); if (node.type === 'Rule' || node.type === 'Atrule') stack.pop();
if (node.type === 'Atrule') currentAtrule = stack[stack.length - 1] as Atrule; if (node.type === 'Atrule') current_atrule = stack[stack.length - 1] as Atrule;
} }
}); });
} else { } else {
this.hasStyles = false; this.has_styles = false;
} }
} }
apply(node: Element) { apply(node: Element) {
if (!this.hasStyles) return; if (!this.has_styles) return;
const stack: Element[] = []; const stack: Element[] = [];
let parent: Node = node; let parent: Node = node;
@ -348,13 +339,13 @@ export default class Stylesheet {
} }
reify() { reify() {
this.nodesWithCssClass.forEach((node: Node) => { this.nodes_with_css_class.forEach((node: Node) => {
node.addCssClass(); node.add_css_class();
}); });
} }
render(cssOutputFilename: string, shouldTransformSelectors: boolean) { render(file: string, should_transform_selectors: boolean) {
if (!this.hasStyles) { if (!this.has_styles) {
return { code: null, map: null }; return { code: null, map: null };
} }
@ -367,7 +358,7 @@ export default class Stylesheet {
} }
}); });
if (shouldTransformSelectors) { if (should_transform_selectors) {
this.children.forEach((child: (Atrule|Rule)) => { this.children.forEach((child: (Atrule|Rule)) => {
child.transform(code, this.id, this.keyframes); child.transform(code, this.id, this.keyframes);
}); });
@ -375,7 +366,7 @@ export default class Stylesheet {
let c = 0; let c = 0;
this.children.forEach(child => { this.children.forEach(child => {
if (child.isUsed(this.dev)) { if (child.is_used(this.dev)) {
code.remove(c, child.node.start); code.remove(c, child.node.start);
child.minify(code, this.dev); child.minify(code, this.dev);
c = child.node.end; c = child.node.end;
@ -389,7 +380,7 @@ export default class Stylesheet {
map: code.generateMap({ map: code.generateMap({
includeContent: true, includeContent: true,
source: this.filename, source: this.filename,
file: cssOutputFilename file
}) })
}; };
} }
@ -402,7 +393,7 @@ export default class Stylesheet {
warnOnUnusedSelectors(component: Component) { warnOnUnusedSelectors(component: Component) {
this.children.forEach(child => { this.children.forEach(child => {
child.warnOnUnusedSelector((selector: Selector) => { child.warn_on_unused_selector((selector: Selector) => {
component.warn(selector.node, { component.warn(selector.node, {
code: `css-unused-selector`, code: `css-unused-selector`,
message: `Unused CSS selector` message: `Unused CSS selector`

@ -2,14 +2,14 @@ import { Node } from '../interfaces';
export const UNKNOWN = {}; export const UNKNOWN = {};
export function gatherPossibleValues(node: Node, set: Set<string|{}>) { export function gather_possible_values(node: Node, set: Set<string|{}>) {
if (node.type === 'Literal') { if (node.type === 'Literal') {
set.add(node.value); set.add(node.value);
} }
else if (node.type === 'ConditionalExpression') { else if (node.type === 'ConditionalExpression') {
gatherPossibleValues(node.consequent, set); gather_possible_values(node.consequent, set);
gatherPossibleValues(node.alternate, set); gather_possible_values(node.alternate, set);
} }
else { else {

@ -6,7 +6,7 @@ export default class Action extends Node {
type: 'Action'; type: 'Action';
name: string; name: string;
expression: Expression; expression: Expression;
usesContext: boolean; uses_context: boolean;
constructor(component: Component, parent, scope, info) { constructor(component: Component, parent, scope, info) {
super(component, parent, scope, info); super(component, parent, scope, info);
@ -20,6 +20,6 @@ export default class Action extends Node {
? new Expression(component, this, scope, info.expression) ? new Expression(component, this, scope, info.expression)
: null; : null;
this.usesContext = this.expression && this.expression.usesContext; this.uses_context = this.expression && this.expression.uses_context;
} }
} }

@ -31,7 +31,7 @@ export default class Animation extends Node {
}); });
} }
block.hasAnimation = true; block.has_animation = true;
this.expression = info.expression this.expression = info.expression
? new Expression(component, this, scope, info.expression) ? new Expression(component, this, scope, info.expression)

@ -14,12 +14,12 @@ export default class Attribute extends Node {
component: Component; component: Component;
parent: Element; parent: Element;
name: string; name: string;
isSpread: boolean; is_spread: boolean;
isTrue: boolean; is_true: boolean;
isDynamic: boolean; is_dynamic: boolean;
isStatic: boolean; is_static: boolean;
isSynthetic: boolean; is_synthetic: boolean;
shouldCache: boolean; should_cache: boolean;
expression?: Expression; expression?: Expression;
chunks: (Text | Expression)[]; chunks: (Text | Expression)[];
dependencies: Set<string>; dependencies: Set<string>;
@ -29,33 +29,33 @@ export default class Attribute extends Node {
if (info.type === 'Spread') { if (info.type === 'Spread') {
this.name = null; this.name = null;
this.isSpread = true; this.is_spread = true;
this.isTrue = false; this.is_true = false;
this.isSynthetic = false; this.is_synthetic = false;
this.expression = new Expression(component, this, scope, info.expression); this.expression = new Expression(component, this, scope, info.expression);
this.dependencies = this.expression.dependencies; this.dependencies = this.expression.dependencies;
this.chunks = null; this.chunks = null;
this.isDynamic = true; // TODO not necessarily this.is_dynamic = true; // TODO not necessarily
this.isStatic = false; this.is_static = false;
this.shouldCache = false; // TODO does this mean anything here? this.should_cache = false; // TODO does this mean anything here?
} }
else { else {
this.name = info.name; this.name = info.name;
this.isTrue = info.value === true; this.is_true = info.value === true;
this.isStatic = true; this.is_static = true;
this.isSynthetic = info.synthetic; this.is_synthetic = info.synthetic;
this.dependencies = new Set(); this.dependencies = new Set();
this.chunks = this.isTrue this.chunks = this.is_true
? [] ? []
: info.value.map(node => { : info.value.map(node => {
if (node.type === 'Text') return node; if (node.type === 'Text') return node;
this.isStatic = false; this.is_static = false;
const expression = new Expression(component, this, scope, node.expression); const expression = new Expression(component, this, scope, node.expression);
@ -63,9 +63,9 @@ export default class Attribute extends Node {
return expression; return expression;
}); });
this.isDynamic = this.dependencies.size > 0; this.is_dynamic = this.dependencies.size > 0;
this.shouldCache = this.isDynamic this.should_cache = this.is_dynamic
? this.chunks.length === 1 ? this.chunks.length === 1
? this.chunks[0].node.type !== 'Identifier' || scope.names.has(this.chunks[0].node.name) ? this.chunks[0].node.type !== 'Identifier' || scope.names.has(this.chunks[0].node.name)
: true : true
@ -74,7 +74,7 @@ export default class Attribute extends Node {
} }
get_dependencies() { get_dependencies() {
if (this.isSpread) return this.expression.dynamic_dependencies(); if (this.is_spread) return this.expression.dynamic_dependencies();
const dependencies = new Set(); const dependencies = new Set();
this.chunks.forEach(chunk => { this.chunks.forEach(chunk => {
@ -86,8 +86,8 @@ export default class Attribute extends Node {
return Array.from(dependencies); return Array.from(dependencies);
} }
getValue(block) { get_value(block) {
if (this.isTrue) return true; if (this.is_true) return true;
if (this.chunks.length === 0) return `""`; if (this.chunks.length === 0) return `""`;
if (this.chunks.length === 1) { if (this.chunks.length === 1) {
@ -102,16 +102,16 @@ export default class Attribute extends Node {
if (chunk.type === 'Text') { if (chunk.type === 'Text') {
return stringify(chunk.data); return stringify(chunk.data);
} else { } else {
return chunk.getPrecedence() <= 13 ? `(${chunk.render()})` : chunk.render(); return chunk.get_precedence() <= 13 ? `(${chunk.render()})` : chunk.render();
} }
}) })
.join(' + '); .join(' + ');
} }
getStaticValue() { get_static_value() {
if (this.isSpread || this.isDynamic) return null; if (this.is_spread || this.is_dynamic) return null;
return this.isTrue return this.is_true
? true ? true
: this.chunks[0] : this.chunks[0]
? this.chunks[0].data ? this.chunks[0].data

@ -7,7 +7,7 @@ import TemplateScope from './shared/TemplateScope';
export default class Binding extends Node { export default class Binding extends Node {
name: string; name: string;
expression: Expression; expression: Expression;
isContextual: boolean; is_contextual: boolean;
obj: string; obj: string;
prop: string; prop: string;
@ -28,11 +28,11 @@ export default class Binding extends Node {
let prop; let prop;
const { name } = get_object(this.expression.node); const { name } = get_object(this.expression.node);
this.isContextual = scope.names.has(name); this.is_contextual = scope.names.has(name);
// make sure we track this as a mutable ref // make sure we track this as a mutable ref
if (this.isContextual) { if (this.is_contextual) {
scope.dependenciesForName.get(name).forEach(name => { scope.dependencies_for_name.get(name).forEach(name => {
const variable = component.var_lookup.get(name); const variable = component.var_lookup.get(name);
variable[this.expression.node.type === 'MemberExpression' ? 'mutated' : 'reassigned'] = true; variable[this.expression.node.type === 'MemberExpression' ? 'mutated' : 'reassigned'] = true;
}); });

@ -1,6 +1,6 @@
import Node from './shared/Node'; import Node from './shared/Node';
import Block from '../render-dom/Block'; import Block from '../render-dom/Block';
import mapChildren from './shared/mapChildren'; import map_children from './shared/map_children';
import TemplateScope from './shared/TemplateScope'; import TemplateScope from './shared/TemplateScope';
export default class CatchBlock extends Node { export default class CatchBlock extends Node {
@ -13,8 +13,8 @@ export default class CatchBlock extends Node {
this.scope = scope.child(); this.scope = scope.child();
this.scope.add(parent.error, parent.expression.dependencies, this); this.scope.add(parent.error, parent.expression.dependencies, this);
this.children = mapChildren(component, parent, this.scope, info.children); this.children = map_children(component, parent, this.scope, info.children);
this.warnIfEmptyBlock(); this.warn_if_empty_block();
} }
} }

@ -2,7 +2,7 @@ import Node from './shared/Node';
import ElseBlock from './ElseBlock'; import ElseBlock from './ElseBlock';
import Block from '../render-dom/Block'; import Block from '../render-dom/Block';
import Expression from './shared/Expression'; import Expression from './shared/Expression';
import mapChildren from './shared/mapChildren'; import map_children from './shared/map_children';
import TemplateScope from './shared/TemplateScope'; import TemplateScope from './shared/TemplateScope';
import { Node as INode } from '../../interfaces'; import { Node as INode } from '../../interfaces';
@ -38,7 +38,7 @@ export default class EachBlock extends Node {
key: Expression; key: Expression;
scope: TemplateScope; scope: TemplateScope;
contexts: Array<{ name: string, tail: string }>; contexts: Array<{ name: string, tail: string }>;
hasAnimation: boolean; has_animation: boolean;
has_binding = false; has_binding = false;
children: Node[]; children: Node[];
@ -71,11 +71,11 @@ export default class EachBlock extends Node {
this.scope.add(this.index, dependencies, this); this.scope.add(this.index, dependencies, this);
} }
this.hasAnimation = false; this.has_animation = false;
this.children = mapChildren(component, this, this.scope, info.children); this.children = map_children(component, this, this.scope, info.children);
if (this.hasAnimation) { if (this.has_animation) {
if (this.children.length !== 1) { if (this.children.length !== 1) {
const child = this.children.find(child => !!child.animation); const child = this.children.find(child => !!child.animation);
component.error(child.animation, { component.error(child.animation, {
@ -85,7 +85,7 @@ export default class EachBlock extends Node {
} }
} }
this.warnIfEmptyBlock(); // TODO would be better if EachBlock, IfBlock etc extended an abstract Block class this.warn_if_empty_block(); // TODO would be better if EachBlock, IfBlock etc extended an abstract Block class
this.else = info.else this.else = info.else
? new ElseBlock(component, this, this.scope, info.else) ? new ElseBlock(component, this, this.scope, info.else)

@ -9,7 +9,7 @@ import Action from './Action';
import Class from './Class'; import Class from './Class';
import Text from './Text'; import Text from './Text';
import { namespaces } from '../../utils/namespaces'; import { namespaces } from '../../utils/namespaces';
import mapChildren from './shared/mapChildren'; import map_children from './shared/map_children';
import { dimensions } from '../../utils/patterns'; import { dimensions } from '../../utils/patterns';
import fuzzymatch from '../../utils/fuzzymatch'; import fuzzymatch from '../../utils/fuzzymatch';
import list from '../../utils/list'; import list from '../../utils/list';
@ -18,13 +18,13 @@ import TemplateScope from './shared/TemplateScope';
const svg = /^(?:altGlyph|altGlyphDef|altGlyphItem|animate|animateColor|animateMotion|animateTransform|circle|clipPath|color-profile|cursor|defs|desc|discard|ellipse|feBlend|feColorMatrix|feComponentTransfer|feComposite|feConvolveMatrix|feDiffuseLighting|feDisplacementMap|feDistantLight|feDropShadow|feFlood|feFuncA|feFuncB|feFuncG|feFuncR|feGaussianBlur|feImage|feMerge|feMergeNode|feMorphology|feOffset|fePointLight|feSpecularLighting|feSpotLight|feTile|feTurbulence|filter|font|font-face|font-face-format|font-face-name|font-face-src|font-face-uri|foreignObject|g|glyph|glyphRef|hatch|hatchpath|hkern|image|line|linearGradient|marker|mask|mesh|meshgradient|meshpatch|meshrow|metadata|missing-glyph|mpath|path|pattern|polygon|polyline|radialGradient|rect|set|solidcolor|stop|switch|symbol|text|textPath|tref|tspan|unknown|use|view|vkern)$/; const svg = /^(?:altGlyph|altGlyphDef|altGlyphItem|animate|animateColor|animateMotion|animateTransform|circle|clipPath|color-profile|cursor|defs|desc|discard|ellipse|feBlend|feColorMatrix|feComponentTransfer|feComposite|feConvolveMatrix|feDiffuseLighting|feDisplacementMap|feDistantLight|feDropShadow|feFlood|feFuncA|feFuncB|feFuncG|feFuncR|feGaussianBlur|feImage|feMerge|feMergeNode|feMorphology|feOffset|fePointLight|feSpecularLighting|feSpotLight|feTile|feTurbulence|filter|font|font-face|font-face-format|font-face-name|font-face-src|font-face-uri|foreignObject|g|glyph|glyphRef|hatch|hatchpath|hkern|image|line|linearGradient|marker|mask|mesh|meshgradient|meshpatch|meshrow|metadata|missing-glyph|mpath|path|pattern|polygon|polyline|radialGradient|rect|set|solidcolor|stop|switch|symbol|text|textPath|tref|tspan|unknown|use|view|vkern)$/;
const ariaAttributes = 'activedescendant atomic autocomplete busy checked colindex controls current describedby details disabled dropeffect errormessage expanded flowto grabbed haspopup hidden invalid keyshortcuts label labelledby level live modal multiline multiselectable orientation owns placeholder posinset pressed readonly relevant required roledescription rowindex selected setsize sort valuemax valuemin valuenow valuetext'.split(' '); const aria_attributes = 'activedescendant atomic autocomplete busy checked colindex controls current describedby details disabled dropeffect errormessage expanded flowto grabbed haspopup hidden invalid keyshortcuts label labelledby level live modal multiline multiselectable orientation owns placeholder posinset pressed readonly relevant required roledescription rowindex selected setsize sort valuemax valuemin valuenow valuetext'.split(' ');
const ariaAttributeSet = new Set(ariaAttributes); const aria_attribute_set = new Set(aria_attributes);
const ariaRoles = 'alert alertdialog application article banner button cell checkbox columnheader combobox command complementary composite contentinfo definition dialog directory document feed figure form grid gridcell group heading img input landmark link list listbox listitem log main marquee math menu menubar menuitem menuitemcheckbox menuitemradio navigation none note option presentation progressbar radio radiogroup range region roletype row rowgroup rowheader scrollbar search searchbox section sectionhead select separator slider spinbutton status structure switch tab table tablist tabpanel term textbox timer toolbar tooltip tree treegrid treeitem widget window'.split(' '); const aria_roles = 'alert alertdialog application article banner button cell checkbox columnheader combobox command complementary composite contentinfo definition dialog directory document feed figure form grid gridcell group heading img input landmark link list listbox listitem log main marquee math menu menubar menuitem menuitemcheckbox menuitemradio navigation none note option presentation progressbar radio radiogroup range region roletype row rowgroup rowheader scrollbar search searchbox section sectionhead select separator slider spinbutton status structure switch tab table tablist tabpanel term textbox timer toolbar tooltip tree treegrid treeitem widget window'.split(' ');
const ariaRoleSet = new Set(ariaRoles); const aria_role_set = new Set(aria_roles);
const a11yRequiredAttributes = { const a11y_required_attributes = {
a: ['href'], a: ['href'],
area: ['alt', 'aria-label', 'aria-labelledby'], area: ['alt', 'aria-label', 'aria-labelledby'],
@ -37,12 +37,12 @@ const a11yRequiredAttributes = {
object: ['title', 'aria-label', 'aria-labelledby'] object: ['title', 'aria-label', 'aria-labelledby']
}; };
const a11yDistractingElements = new Set([ const a11y_distracting_elements = new Set([
'blink', 'blink',
'marquee' 'marquee'
]); ]);
const a11yRequiredContent = new Set([ const a11y_required_content = new Set([
// anchor-has-content // anchor-has-content
'a', 'a',
@ -55,9 +55,9 @@ const a11yRequiredContent = new Set([
'h6' 'h6'
]) ])
const invisibleElements = new Set(['meta', 'html', 'script', 'style']); const invisible_elements = new Set(['meta', 'html', 'script', 'style']);
const validModifiers = new Set([ const valid_modifiers = new Set([
'preventDefault', 'preventDefault',
'stopPropagation', 'stopPropagation',
'capture', 'capture',
@ -65,7 +65,7 @@ const validModifiers = new Set([
'passive' 'passive'
]); ]);
const passiveEvents = new Set([ const passive_events = new Set([
'wheel', 'wheel',
'touchstart', 'touchstart',
'touchmove', 'touchmove',
@ -93,10 +93,10 @@ export default class Element extends Node {
super(component, parent, scope, info); super(component, parent, scope, info);
this.name = info.name; this.name = info.name;
const parentElement = parent.findNearest(/^Element/); const parent_element = parent.find_nearest(/^Element/);
this.namespace = this.name === 'svg' ? this.namespace = this.name === 'svg' ?
namespaces.svg : namespaces.svg :
parentElement ? parentElement.namespace : this.component.namespace; parent_element ? parent_element.namespace : this.component.namespace;
if (!this.namespace && svg.test(this.name)) { if (!this.namespace && svg.test(this.name)) {
this.component.warn(this, { this.component.warn(this, {
@ -107,9 +107,9 @@ export default class Element extends Node {
if (this.name === 'textarea') { if (this.name === 'textarea') {
if (info.children.length > 0) { if (info.children.length > 0) {
const valueAttribute = info.attributes.find(node => node.name === 'value'); const value_attribute = info.attributes.find(node => node.name === 'value');
if (valueAttribute) { if (value_attribute) {
component.error(valueAttribute, { component.error(value_attribute, {
code: `textarea-duplicate-value`, code: `textarea-duplicate-value`,
message: `A <textarea> can have either a value attribute or (equivalently) child content, but not both` message: `A <textarea> can have either a value attribute or (equivalently) child content, but not both`
}); });
@ -131,9 +131,9 @@ export default class Element extends Node {
// Special case — treat these the same way: // Special case — treat these the same way:
// <option>{foo}</option> // <option>{foo}</option>
// <option value={foo}>{foo}</option> // <option value={foo}>{foo}</option>
const valueAttribute = info.attributes.find((attribute: Node) => attribute.name === 'value'); const value_attribute = info.attributes.find((attribute: Node) => attribute.name === 'value');
if (!valueAttribute) { if (!value_attribute) {
info.attributes.push({ info.attributes.push({
type: 'Attribute', type: 'Attribute',
name: 'value', name: 'value',
@ -202,7 +202,7 @@ export default class Element extends Node {
this.scope = scope; this.scope = scope;
} }
this.children = mapChildren(component, this, this.scope, info.children); this.children = map_children(component, this, this.scope, info.children);
this.validate(); this.validate();
@ -210,7 +210,7 @@ export default class Element extends Node {
} }
validate() { validate() {
if (a11yDistractingElements.has(this.name)) { if (a11y_distracting_elements.has(this.name)) {
// no-distracting-elements // no-distracting-elements
this.component.warn(this, { this.component.warn(this, {
code: `a11y-distracting-elements`, code: `a11y-distracting-elements`,
@ -244,25 +244,25 @@ export default class Element extends Node {
} }
} }
this.validateAttributes(); this.validate_attributes();
this.validateBindings(); this.validate_bindings();
this.validateContent(); this.validate_content();
this.validateEventHandlers(); this.validate_event_handlers();
} }
validateAttributes() { validate_attributes() {
const { component } = this; const { component } = this;
const attributeMap = new Map(); const attribute_map = new Map();
this.attributes.forEach(attribute => { this.attributes.forEach(attribute => {
if (attribute.isSpread) return; if (attribute.is_spread) return;
const name = attribute.name.toLowerCase(); const name = attribute.name.toLowerCase();
// aria-props // aria-props
if (name.startsWith('aria-')) { if (name.startsWith('aria-')) {
if (invisibleElements.has(this.name)) { if (invisible_elements.has(this.name)) {
// aria-unsupported-elements // aria-unsupported-elements
component.warn(attribute, { component.warn(attribute, {
code: `a11y-aria-attributes`, code: `a11y-aria-attributes`,
@ -271,8 +271,8 @@ export default class Element extends Node {
} }
const type = name.slice(5); const type = name.slice(5);
if (!ariaAttributeSet.has(type)) { if (!aria_attribute_set.has(type)) {
const match = fuzzymatch(type, ariaAttributes); const match = fuzzymatch(type, aria_attributes);
let message = `A11y: Unknown aria attribute 'aria-${type}'`; let message = `A11y: Unknown aria attribute 'aria-${type}'`;
if (match) message += ` (did you mean '${match}'?)`; if (match) message += ` (did you mean '${match}'?)`;
@ -292,7 +292,7 @@ export default class Element extends Node {
// aria-role // aria-role
if (name === 'role') { if (name === 'role') {
if (invisibleElements.has(this.name)) { if (invisible_elements.has(this.name)) {
// aria-unsupported-elements // aria-unsupported-elements
component.warn(attribute, { component.warn(attribute, {
code: `a11y-misplaced-role`, code: `a11y-misplaced-role`,
@ -300,9 +300,9 @@ export default class Element extends Node {
}); });
} }
const value = attribute.getStaticValue(); const value = attribute.get_static_value();
if (value && !ariaRoleSet.has(value)) { if (value && !aria_role_set.has(value)) {
const match = fuzzymatch(value, ariaRoles); const match = fuzzymatch(value, aria_roles);
let message = `A11y: Unknown role '${value}'`; let message = `A11y: Unknown role '${value}'`;
if (match) message += ` (did you mean '${match}'?)`; if (match) message += ` (did you mean '${match}'?)`;
@ -339,7 +339,7 @@ export default class Element extends Node {
// tabindex-no-positive // tabindex-no-positive
if (name === 'tabindex') { if (name === 'tabindex') {
const value = attribute.getStaticValue(); const value = attribute.get_static_value();
if (!isNaN(value) && +value > 0) { if (!isNaN(value) && +value > 0) {
component.warn(attribute, { component.warn(attribute, {
code: `a11y-positive-tabindex`, code: `a11y-positive-tabindex`,
@ -349,7 +349,7 @@ export default class Element extends Node {
} }
if (name === 'slot') { if (name === 'slot') {
if (!attribute.isStatic) { if (!attribute.is_static) {
component.error(attribute, { component.error(attribute, {
code: `invalid-slot-attribute`, code: `invalid-slot-attribute`,
message: `slot attribute cannot have a dynamic value` message: `slot attribute cannot have a dynamic value`
@ -380,15 +380,15 @@ export default class Element extends Node {
} }
} }
attributeMap.set(attribute.name, attribute); attribute_map.set(attribute.name, attribute);
}); });
// handle special cases // handle special cases
if (this.name === 'a') { if (this.name === 'a') {
const attribute = attributeMap.get('href') || attributeMap.get('xlink:href'); const attribute = attribute_map.get('href') || attribute_map.get('xlink:href');
if (attribute) { if (attribute) {
const value = attribute.getStaticValue(); const value = attribute.get_static_value();
if (value === '' || value === '#') { if (value === '' || value === '#') {
component.warn(attribute, { component.warn(attribute, {
@ -405,19 +405,19 @@ export default class Element extends Node {
} }
else { else {
const requiredAttributes = a11yRequiredAttributes[this.name]; const required_attributes = a11y_required_attributes[this.name];
if (requiredAttributes) { if (required_attributes) {
const hasAttribute = requiredAttributes.some(name => attributeMap.has(name)); const has_attribute = required_attributes.some(name => attribute_map.has(name));
if (!hasAttribute) { if (!has_attribute) {
shouldHaveAttribute(this, requiredAttributes); should_have_attribute(this, required_attributes);
} }
} }
if (this.name === 'input') { if (this.name === 'input') {
const type = attributeMap.get('type'); const type = attribute_map.get('type');
if (type && type.getStaticValue() === 'image') { if (type && type.get_static_value() === 'image') {
shouldHaveAttribute( should_have_attribute(
this, this,
['alt', 'aria-label', 'aria-labelledby'], ['alt', 'aria-label', 'aria-labelledby'],
'input type="image"' 'input type="image"'
@ -427,24 +427,24 @@ export default class Element extends Node {
} }
} }
validateBindings() { validate_bindings() {
const { component } = this; const { component } = this;
const checkTypeAttribute = () => { const check_type_attribute = () => {
const attribute = this.attributes.find( const attribute = this.attributes.find(
(attribute: Attribute) => attribute.name === 'type' (attribute: Attribute) => attribute.name === 'type'
); );
if (!attribute) return null; if (!attribute) return null;
if (!attribute.isStatic) { if (!attribute.is_static) {
component.error(attribute, { component.error(attribute, {
code: `invalid-type`, code: `invalid-type`,
message: `'type' attribute cannot be dynamic if input uses two-way binding` message: `'type' attribute cannot be dynamic if input uses two-way binding`
}); });
} }
const value = attribute.getStaticValue(); const value = attribute.get_static_value();
if (value === true) { if (value === true) {
component.error(attribute, { component.error(attribute, {
@ -476,14 +476,14 @@ export default class Element extends Node {
(attribute: Attribute) => attribute.name === 'multiple' (attribute: Attribute) => attribute.name === 'multiple'
); );
if (attribute && !attribute.isStatic) { if (attribute && !attribute.is_static) {
component.error(attribute, { component.error(attribute, {
code: `dynamic-multiple-attribute`, code: `dynamic-multiple-attribute`,
message: `'multiple' attribute cannot be dynamic if select uses two-way binding` message: `'multiple' attribute cannot be dynamic if select uses two-way binding`
}); });
} }
} else { } else {
checkTypeAttribute(); check_type_attribute();
} }
} else if (name === 'checked' || name === 'indeterminate') { } else if (name === 'checked' || name === 'indeterminate') {
if (this.name !== 'input') { if (this.name !== 'input') {
@ -493,7 +493,7 @@ export default class Element extends Node {
}); });
} }
if (checkTypeAttribute() !== 'checkbox') { if (check_type_attribute() !== 'checkbox') {
component.error(binding, { component.error(binding, {
code: `invalid-binding`, code: `invalid-binding`,
message: `'${name}' binding can only be used with <input type="checkbox">` message: `'${name}' binding can only be used with <input type="checkbox">`
@ -507,7 +507,7 @@ export default class Element extends Node {
}); });
} }
const type = checkTypeAttribute(); const type = check_type_attribute();
if (type !== 'checkbox' && type !== 'radio') { if (type !== 'checkbox' && type !== 'radio') {
component.error(binding, { component.error(binding, {
@ -523,7 +523,7 @@ export default class Element extends Node {
}); });
} }
const type = checkTypeAttribute(); const type = check_type_attribute();
if (type !== 'file') { if (type !== 'file') {
component.error(binding, { component.error(binding, {
@ -572,8 +572,8 @@ export default class Element extends Node {
}); });
} }
validateContent() { validate_content() {
if (!a11yRequiredContent.has(this.name)) return; if (!a11y_required_content.has(this.name)) return;
if (this.children.length === 0) { if (this.children.length === 0) {
this.component.warn(this, { this.component.warn(this, {
@ -583,7 +583,7 @@ export default class Element extends Node {
} }
} }
validateEventHandlers() { validate_event_handlers() {
const { component } = this; const { component } = this;
this.handlers.forEach(handler => { this.handlers.forEach(handler => {
@ -595,16 +595,16 @@ export default class Element extends Node {
} }
handler.modifiers.forEach(modifier => { handler.modifiers.forEach(modifier => {
if (!validModifiers.has(modifier)) { if (!valid_modifiers.has(modifier)) {
component.error(handler, { component.error(handler, {
code: 'invalid-event-modifier', code: 'invalid-event-modifier',
message: `Valid event modifiers are ${list(Array.from(validModifiers))}` message: `Valid event modifiers are ${list(Array.from(valid_modifiers))}`
}); });
} }
if (modifier === 'passive') { if (modifier === 'passive') {
if (passiveEvents.has(handler.name)) { if (passive_events.has(handler.name)) {
if (handler.canMakePassive) { if (handler.can_make_passive) {
component.warn(handler, { component.warn(handler, {
code: 'redundant-event-modifier', code: 'redundant-event-modifier',
message: `Touch event handlers that don't use the 'event' object are passive by default` message: `Touch event handlers that don't use the 'event' object are passive by default`
@ -618,7 +618,7 @@ export default class Element extends Node {
} }
} }
if (component.compileOptions.legacy && (modifier === 'once' || modifier === 'passive')) { if (component.compile_options.legacy && (modifier === 'once' || modifier === 'passive')) {
// TODO this could be supported, but it would need a few changes to // TODO this could be supported, but it would need a few changes to
// how event listeners work // how event listeners work
component.error(handler, { component.error(handler, {
@ -628,21 +628,21 @@ export default class Element extends Node {
} }
}); });
if (passiveEvents.has(handler.name) && handler.canMakePassive && !handler.modifiers.has('preventDefault')) { if (passive_events.has(handler.name) && handler.can_make_passive && !handler.modifiers.has('preventDefault')) {
// touch/wheel events should be passive by default // touch/wheel events should be passive by default
handler.modifiers.add('passive'); handler.modifiers.add('passive');
} }
}); });
} }
getStaticAttributeValue(name: string) { get_static_attribute_value(name: string) {
const attribute = this.attributes.find( const attribute = this.attributes.find(
(attr: Attribute) => attr.type === 'Attribute' && attr.name.toLowerCase() === name (attr: Attribute) => attr.type === 'Attribute' && attr.name.toLowerCase() === name
); );
if (!attribute) return null; if (!attribute) return null;
if (attribute.isTrue) return true; if (attribute.is_true) return true;
if (attribute.chunks.length === 0) return ''; if (attribute.chunks.length === 0) return '';
if (attribute.chunks.length === 1 && attribute.chunks[0].type === 'Text') { if (attribute.chunks.length === 1 && attribute.chunks[0].type === 'Text') {
@ -652,20 +652,20 @@ export default class Element extends Node {
return null; return null;
} }
isMediaNode() { is_media_node() {
return this.name === 'audio' || this.name === 'video'; return this.name === 'audio' || this.name === 'video';
} }
addCssClass(className = this.component.stylesheet.id) { add_css_class(class_name = this.component.stylesheet.id) {
const classAttribute = this.attributes.find(a => a.name === 'class'); const class_attribute = this.attributes.find(a => a.name === 'class');
if (classAttribute && !classAttribute.isTrue) { if (class_attribute && !class_attribute.is_true) {
if (classAttribute.chunks.length === 1 && classAttribute.chunks[0].type === 'Text') { if (class_attribute.chunks.length === 1 && class_attribute.chunks[0].type === 'Text') {
(classAttribute.chunks[0] as Text).data += ` ${className}`; (class_attribute.chunks[0] as Text).data += ` ${class_name}`;
} else { } else {
(<Node[]>classAttribute.chunks).push( (<Node[]>class_attribute.chunks).push(
new Text(this.component, this, this.scope, { new Text(this.component, this, this.scope, {
type: 'Text', type: 'Text',
data: ` ${className}` data: ` ${class_name}`
}) })
); );
} }
@ -674,14 +674,14 @@ export default class Element extends Node {
new Attribute(this.component, this, this.scope, { new Attribute(this.component, this, this.scope, {
type: 'Attribute', type: 'Attribute',
name: 'class', name: 'class',
value: [{ type: 'Text', data: className }] value: [{ type: 'Text', data: class_name }]
}) })
); );
} }
} }
} }
function shouldHaveAttribute( function should_have_attribute(
node, node,
attributes: string[], attributes: string[],
name = node.name name = node.name

@ -1,6 +1,6 @@
import Node from './shared/Node'; import Node from './shared/Node';
import Block from '../render-dom/Block'; import Block from '../render-dom/Block';
import mapChildren from './shared/mapChildren'; import map_children from './shared/map_children';
export default class ElseBlock extends Node { export default class ElseBlock extends Node {
type: 'ElseBlock'; type: 'ElseBlock';
@ -9,8 +9,8 @@ export default class ElseBlock extends Node {
constructor(component, parent, scope, info) { constructor(component, parent, scope, info) {
super(component, parent, scope, info); super(component, parent, scope, info);
this.children = mapChildren(component, this, scope, info.children); this.children = map_children(component, this, scope, info.children);
this.warnIfEmptyBlock(); this.warn_if_empty_block();
} }
} }

@ -9,8 +9,8 @@ export default class EventHandler extends Node {
modifiers: Set<string>; modifiers: Set<string>;
expression: Expression; expression: Expression;
handler_name: string; handler_name: string;
usesContext = false; uses_context = false;
canMakePassive = false; can_make_passive = false;
constructor(component: Component, parent, template_scope, info) { constructor(component: Component, parent, template_scope, info) {
super(component, parent, template_scope, info); super(component, parent, template_scope, info);
@ -20,12 +20,12 @@ export default class EventHandler extends Node {
if (info.expression) { if (info.expression) {
this.expression = new Expression(component, this, template_scope, info.expression); this.expression = new Expression(component, this, template_scope, info.expression);
this.usesContext = this.expression.usesContext; this.uses_context = this.expression.uses_context;
if (/FunctionExpression/.test(info.expression.type) && info.expression.params.length === 0) { if (/FunctionExpression/.test(info.expression.type) && info.expression.params.length === 0) {
// TODO make this detection more accurate — if `event.preventDefault` isn't called, and // TODO make this detection more accurate — if `event.preventDefault` isn't called, and
// `event` is passed to another function, we can make it passive // `event` is passed to another function, we can make it passive
this.canMakePassive = true; this.can_make_passive = true;
} else if (info.expression.type === 'Identifier') { } else if (info.expression.type === 'Identifier') {
let node = component.node_for_declaration.get(info.expression.name); let node = component.node_for_declaration.get(info.expression.name);
@ -36,11 +36,11 @@ export default class EventHandler extends Node {
} }
if (node && /Function/.test(node.type) && node.params.length === 0) { if (node && /Function/.test(node.type) && node.params.length === 0) {
this.canMakePassive = true; this.can_make_passive = true;
} }
} }
} else { } else {
const name = component.getUniqueName(`${this.name}_handler`); const name = component.get_unique_name(`${this.name}_handler`);
component.add_var({ component.add_var({
name, name,

@ -1,6 +1,6 @@
import Node from './shared/Node'; import Node from './shared/Node';
import Component from '../Component'; import Component from '../Component';
import mapChildren from './shared/mapChildren'; import map_children from './shared/map_children';
import Block from '../render-dom/Block'; import Block from '../render-dom/Block';
import TemplateScope from './shared/TemplateScope'; import TemplateScope from './shared/TemplateScope';
@ -14,6 +14,6 @@ export default class Fragment extends Node {
super(component, null, scope, info); super(component, null, scope, info);
this.scope = scope; this.scope = scope;
this.children = mapChildren(component, this, scope, info.children); this.children = map_children(component, this, scope, info.children);
} }
} }

@ -1,6 +1,6 @@
import Node from './shared/Node'; import Node from './shared/Node';
import Block from '../render-dom/Block'; import Block from '../render-dom/Block';
import mapChildren from './shared/mapChildren'; import map_children from './shared/map_children';
export default class Head extends Node { export default class Head extends Node {
type: 'Head'; type: 'Head';
@ -16,7 +16,7 @@ export default class Head extends Node {
}); });
} }
this.children = mapChildren(component, parent, scope, info.children.filter(child => { this.children = map_children(component, parent, scope, info.children.filter(child => {
return (child.type !== 'Text' || /\S/.test(child.data)); return (child.type !== 'Text' || /\S/.test(child.data));
})); }));
} }

@ -2,7 +2,7 @@ import Node from './shared/Node';
import ElseBlock from './ElseBlock'; import ElseBlock from './ElseBlock';
import Block from '../render-dom/Block'; import Block from '../render-dom/Block';
import Expression from './shared/Expression'; import Expression from './shared/Expression';
import mapChildren from './shared/mapChildren'; import map_children from './shared/map_children';
export default class IfBlock extends Node { export default class IfBlock extends Node {
type: 'IfBlock'; type: 'IfBlock';
@ -16,12 +16,12 @@ export default class IfBlock extends Node {
super(component, parent, scope, info); super(component, parent, scope, info);
this.expression = new Expression(component, this, scope, info.expression); this.expression = new Expression(component, this, scope, info.expression);
this.children = mapChildren(component, this, scope, info.children); this.children = map_children(component, this, scope, info.children);
this.else = info.else this.else = info.else
? new ElseBlock(component, this, scope, info.else) ? new ElseBlock(component, this, scope, info.else)
: null; : null;
this.warnIfEmptyBlock(); this.warn_if_empty_block();
} }
} }

@ -1,6 +1,6 @@
import Node from './shared/Node'; import Node from './shared/Node';
import Attribute from './Attribute'; import Attribute from './Attribute';
import mapChildren from './shared/mapChildren'; import map_children from './shared/map_children';
import Binding from './Binding'; import Binding from './Binding';
import EventHandler from './EventHandler'; import EventHandler from './EventHandler';
import Expression from './shared/Expression'; import Expression from './shared/Expression';
@ -96,6 +96,6 @@ export default class InlineComponent extends Node {
this.scope = scope; this.scope = scope;
} }
this.children = mapChildren(component, this, this.scope, info.children); this.children = map_children(component, this, this.scope, info.children);
} }
} }

@ -1,6 +1,6 @@
import Node from './shared/Node'; import Node from './shared/Node';
import Block from '../render-dom/Block'; import Block from '../render-dom/Block';
import mapChildren from './shared/mapChildren'; import map_children from './shared/map_children';
export default class PendingBlock extends Node { export default class PendingBlock extends Node {
block: Block; block: Block;
@ -8,8 +8,8 @@ export default class PendingBlock extends Node {
constructor(component, parent, scope, info) { constructor(component, parent, scope, info) {
super(component, parent, scope, info); super(component, parent, scope, info);
this.children = mapChildren(component, parent, scope, info.children); this.children = map_children(component, parent, scope, info.children);
this.warnIfEmptyBlock(); this.warn_if_empty_block();
} }
} }

@ -19,13 +19,6 @@ export default class Slot extends Element {
}); });
} }
// if (attr.name !== 'name') {
// component.error(attr, {
// code: `invalid-slot-attribute`,
// message: `"name" is the only attribute permitted on <slot> elements`
// });
// }
if (attr.name === 'name') { if (attr.name === 'name') {
if (attr.value.length !== 1 || attr.value[0].type !== 'Text') { if (attr.value.length !== 1 || attr.value[0].type !== 'Text') {
component.error(attr, { component.error(attr, {
@ -61,14 +54,14 @@ export default class Slot extends Element {
// } // }
} }
getStaticAttributeValue(name: string) { get_static_attribute_value(name: string) {
const attribute = this.attributes.find( const attribute = this.attributes.find(
attr => attr.name.toLowerCase() === name attr => attr.name.toLowerCase() === name
); );
if (!attribute) return null; if (!attribute) return null;
if (attribute.isTrue) return true; if (attribute.is_true) return true;
if (attribute.chunks.length === 0) return ''; if (attribute.chunks.length === 0) return '';
if (attribute.chunks.length === 1 && attribute.chunks[0].type === 'Text') { if (attribute.chunks.length === 1 && attribute.chunks[0].type === 'Text') {

@ -3,7 +3,6 @@ import Node from './shared/Node';
export default class Text extends Node { export default class Text extends Node {
type: 'Text'; type: 'Text';
data: string; data: string;
shouldSkip: boolean;
constructor(component, parent, scope, info) { constructor(component, parent, scope, info) {
super(component, parent, scope, info); super(component, parent, scope, info);

@ -1,6 +1,6 @@
import Node from './shared/Node'; import Node from './shared/Node';
import Block from '../render-dom/Block'; import Block from '../render-dom/Block';
import mapChildren from './shared/mapChildren'; import map_children from './shared/map_children';
import TemplateScope from './shared/TemplateScope'; import TemplateScope from './shared/TemplateScope';
export default class ThenBlock extends Node { export default class ThenBlock extends Node {
@ -13,8 +13,8 @@ export default class ThenBlock extends Node {
this.scope = scope.child(); this.scope = scope.child();
this.scope.add(parent.value, parent.expression.dependencies, this); this.scope.add(parent.value, parent.expression.dependencies, this);
this.children = mapChildren(component, parent, this.scope, info.children); this.children = map_children(component, parent, this.scope, info.children);
this.warnIfEmptyBlock(); this.warn_if_empty_block();
} }
} }

@ -1,14 +1,14 @@
import Node from './shared/Node'; import Node from './shared/Node';
import mapChildren from './shared/mapChildren'; import map_children from './shared/map_children';
export default class Title extends Node { export default class Title extends Node {
type: 'Title'; type: 'Title';
children: any[]; // TODO children: any[]; // TODO
shouldCache: boolean; should_cache: boolean;
constructor(component, parent, scope, info) { constructor(component, parent, scope, info) {
super(component, parent, scope, info); super(component, parent, scope, info);
this.children = mapChildren(component, parent, scope, info.children); this.children = map_children(component, parent, scope, info.children);
if (info.attributes.length > 0) { if (info.attributes.length > 0) {
component.error(info.attributes[0], { component.error(info.attributes[0], {
@ -26,7 +26,7 @@ export default class Title extends Node {
} }
}); });
this.shouldCache = info.children.length === 1 this.should_cache = info.children.length === 1
? ( ? (
info.children[0].type !== 'Identifier' || info.children[0].type !== 'Identifier' ||
scope.names.has(info.children[0].name) scope.names.has(info.children[0].name)

@ -21,11 +21,11 @@ export default class Transition extends Node {
this.is_local = info.modifiers.includes('local'); this.is_local = info.modifiers.includes('local');
if ((info.intro && parent.intro) || (info.outro && parent.outro)) { if ((info.intro && parent.intro) || (info.outro && parent.outro)) {
const parentTransition = (parent.intro || parent.outro); const parent_transition = (parent.intro || parent.outro);
const message = this.directive === parentTransition.directive const message = this.directive === parent_transition.directive
? `An element can only have one '${this.directive}' directive` ? `An element can only have one '${this.directive}' directive`
: `An element cannot have both ${describe(parentTransition)} directive and ${describe(this)} directive`; : `An element cannot have both ${describe(parent_transition)} directive and ${describe(this)} directive`;
component.error(info, { component.error(info, {
code: `duplicate-transition`, code: `duplicate-transition`,

@ -6,7 +6,7 @@ import fuzzymatch from '../../utils/fuzzymatch';
import list from '../../utils/list'; import list from '../../utils/list';
import Action from './Action'; import Action from './Action';
const validBindings = [ const valid_bindings = [
'innerWidth', 'innerWidth',
'innerHeight', 'innerHeight',
'outerWidth', 'outerWidth',
@ -41,11 +41,11 @@ export default class Window extends Node {
}); });
} }
if (!~validBindings.indexOf(node.name)) { if (!~valid_bindings.indexOf(node.name)) {
const match = ( const match = (
node.name === 'width' ? 'innerWidth' : node.name === 'width' ? 'innerWidth' :
node.name === 'height' ? 'innerHeight' : node.name === 'height' ? 'innerHeight' :
fuzzymatch(node.name, validBindings) fuzzymatch(node.name, valid_bindings)
); );
const message = `'${node.name}' is not a valid binding on <svelte:window>`; const message = `'${node.name}' is not a valid binding on <svelte:window>`;
@ -58,7 +58,7 @@ export default class Window extends Node {
} else { } else {
component.error(node, { component.error(node, {
code: `invalid-binding`, code: `invalid-binding`,
message: `${message} — valid bindings are ${list(validBindings)}` message: `${message} — valid bindings are ${list(valid_bindings)}`
}); });
} }
} }

@ -77,8 +77,7 @@ export default class Expression {
is_synthetic: boolean; is_synthetic: boolean;
declarations: string[] = []; declarations: string[] = [];
usesContext = false; uses_context = false;
usesEvent = false;
rendered: string; rendered: string;
@ -93,7 +92,7 @@ export default class Expression {
this.node = info; this.node = info;
this.template_scope = template_scope; this.template_scope = template_scope;
this.owner = owner; this.owner = owner;
this.is_synthetic = owner.isSynthetic; this.is_synthetic = owner.is_synthetic;
const { dependencies, contextual_dependencies } = this; const { dependencies, contextual_dependencies } = this;
@ -137,12 +136,12 @@ export default class Expression {
dependencies.add(name); dependencies.add(name);
} }
} else if (template_scope.names.has(name)) { } else if (template_scope.names.has(name)) {
expression.usesContext = true; expression.uses_context = true;
contextual_dependencies.add(name); contextual_dependencies.add(name);
if (!function_expression) { if (!function_expression) {
template_scope.dependenciesForName.get(name).forEach(name => dependencies.add(name)); template_scope.dependencies_for_name.get(name).forEach(name => dependencies.add(name));
} }
} else { } else {
if (!function_expression) { if (!function_expression) {
@ -175,7 +174,7 @@ export default class Expression {
if (names) { if (names) {
names.forEach(name => { names.forEach(name => {
if (template_scope.names.has(name)) { if (template_scope.names.has(name)) {
template_scope.dependenciesForName.get(name).forEach(name => { template_scope.dependencies_for_name.get(name).forEach(name => {
const variable = component.var_lookup.get(name); const variable = component.var_lookup.get(name);
if (variable) variable[deep ? 'mutated' : 'reassigned'] = true; if (variable) variable[deep ? 'mutated' : 'reassigned'] = true;
}); });
@ -214,7 +213,7 @@ export default class Expression {
}); });
} }
getPrecedence() { get_precedence() {
return this.node.type in precedence ? precedence[this.node.type](this.node) : 0; return this.node.type in precedence ? precedence[this.node.type](this.node) : 0;
} }
@ -263,14 +262,14 @@ export default class Expression {
if (template_scope.names.has(name)) { if (template_scope.names.has(name)) {
contextual_dependencies.add(name); contextual_dependencies.add(name);
template_scope.dependenciesForName.get(name).forEach(dependency => { template_scope.dependencies_for_name.get(name).forEach(dependency => {
dependencies.add(dependency); dependencies.add(dependency);
}); });
} else { } else {
dependencies.add(name); dependencies.add(name);
component.add_reference(name); component.add_reference(name);
} }
} else if (!is_synthetic && isContextual(component, template_scope, name)) { } else if (!is_synthetic && is_contextual(component, template_scope, name)) {
code.prependRight(node.start, key === 'key' && parent.shorthand code.prependRight(node.start, key === 'key' && parent.shorthand
? `${name}: ctx.` ? `${name}: ctx.`
: 'ctx.'); : 'ctx.');
@ -347,7 +346,7 @@ export default class Expression {
// the return value doesn't matter // the return value doesn't matter
} }
const name = component.getUniqueName( const name = component.get_unique_name(
sanitize(get_function_name(node, owner)) sanitize(get_function_name(node, owner))
); );
@ -465,7 +464,7 @@ export default class Expression {
}); });
if (declarations.length > 0) { if (declarations.length > 0) {
block.maintainContext = true; block.maintain_context = true;
declarations.forEach(declaration => { declarations.forEach(declaration => {
block.builders.init.add_block(declaration); block.builders.init.add_block(declaration);
}); });
@ -487,11 +486,11 @@ function get_function_name(node, parent) {
return 'func'; return 'func';
} }
function isContextual(component: Component, scope: TemplateScope, name: string) { function is_contextual(component: Component, scope: TemplateScope, name: string) {
if (name === '$$props') return true; if (name === '$$props') return true;
// if it's a name below root scope, it's contextual // if it's a name below root scope, it's contextual
if (!scope.isTopLevel(name)) return true; if (!scope.is_top_level(name)) return true;
const variable = component.var_lookup.get(name); const variable = component.var_lookup.get(name);

@ -10,7 +10,7 @@ export default class Node {
prev?: Node; prev?: Node;
next?: Node; next?: Node;
canUseInnerHTML: boolean; can_use_innerhtml: boolean;
var: string; var: string;
constructor(component: Component, parent, scope, info: any) { constructor(component: Component, parent, scope, info: any) {
@ -30,25 +30,25 @@ export default class Node {
}); });
} }
cannotUseInnerHTML() { cannot_use_innerhtml() {
if (this.canUseInnerHTML !== false) { if (this.can_use_innerhtml !== false) {
this.canUseInnerHTML = false; this.can_use_innerhtml = false;
if (this.parent) this.parent.cannotUseInnerHTML(); if (this.parent) this.parent.cannot_use_innerhtml();
} }
} }
hasAncestor(type: string) { has_ancestor(type: string) {
return this.parent ? return this.parent ?
this.parent.type === type || this.parent.hasAncestor(type) : this.parent.type === type || this.parent.has_ancestor(type) :
false; false;
} }
findNearest(selector: RegExp) { find_nearest(selector: RegExp) {
if (selector.test(this.type)) return this; if (selector.test(this.type)) return this;
if (this.parent) return this.parent.findNearest(selector); if (this.parent) return this.parent.find_nearest(selector);
} }
warnIfEmptyBlock() { warn_if_empty_block() {
if (!/Block$/.test(this.type) || !this.children) return; if (!/Block$/.test(this.type) || !this.children) return;
if (this.children.length > 1) return; if (this.children.length > 1) return;

@ -3,13 +3,13 @@ import Expression from './Expression';
export default class Tag extends Node { export default class Tag extends Node {
expression: Expression; expression: Expression;
shouldCache: boolean; should_cache: boolean;
constructor(component, parent, scope, info) { constructor(component, parent, scope, info) {
super(component, parent, scope, info); super(component, parent, scope, info);
this.expression = new Expression(component, this, scope, info.expression); this.expression = new Expression(component, this, scope, info.expression);
this.shouldCache = ( this.should_cache = (
info.expression.type !== 'Identifier' || info.expression.type !== 'Identifier' ||
(this.expression.dependencies.size && scope.names.has(info.expression.name)) (this.expression.dependencies.size && scope.names.has(info.expression.name))
); );

@ -1,4 +1,3 @@
import Node from './Node';
import EachBlock from '../EachBlock'; import EachBlock from '../EachBlock';
import ThenBlock from '../ThenBlock'; import ThenBlock from '../ThenBlock';
import CatchBlock from '../CatchBlock'; import CatchBlock from '../CatchBlock';
@ -8,19 +7,19 @@ type NodeWithScope = EachBlock | ThenBlock | CatchBlock | InlineComponent | Elem
export default class TemplateScope { export default class TemplateScope {
names: Set<string>; names: Set<string>;
dependenciesForName: Map<string, Set<string>>; dependencies_for_name: Map<string, Set<string>>;
owners: Map<string, NodeWithScope> = new Map(); owners: Map<string, NodeWithScope> = new Map();
parent?: TemplateScope; parent?: TemplateScope;
constructor(parent?: TemplateScope) { constructor(parent?: TemplateScope) {
this.parent = parent; this.parent = parent;
this.names = new Set(parent ? parent.names : []); this.names = new Set(parent ? parent.names : []);
this.dependenciesForName = new Map(parent ? parent.dependenciesForName : []); this.dependencies_for_name = new Map(parent ? parent.dependencies_for_name : []);
} }
add(name, dependencies: Set<string>, owner) { add(name, dependencies: Set<string>, owner) {
this.names.add(name); this.names.add(name);
this.dependenciesForName.set(name, dependencies); this.dependencies_for_name.set(name, dependencies);
this.owners.set(name, owner); this.owners.set(name, owner);
return this; return this;
} }
@ -30,16 +29,16 @@ export default class TemplateScope {
return child; return child;
} }
isTopLevel(name: string) { is_top_level(name: string) {
return !this.parent || !this.names.has(name) && this.parent.isTopLevel(name); return !this.parent || !this.names.has(name) && this.parent.is_top_level(name);
} }
getOwner(name: string): NodeWithScope { get_owner(name: string): NodeWithScope {
return this.owners.get(name) || (this.parent && this.parent.getOwner(name)); return this.owners.get(name) || (this.parent && this.parent.get_owner(name));
} }
is_let(name: string) { is_let(name: string) {
const owner = this.getOwner(name); const owner = this.get_owner(name);
return owner && (owner.type === 'Element' || owner.type === 'InlineComponent'); return owner && (owner.type === 'Element' || owner.type === 'InlineComponent');
} }
} }

@ -16,7 +16,7 @@ import Title from '../Title';
import Window from '../Window'; import Window from '../Window';
import Node from './Node'; import Node from './Node';
function getConstructor(type): typeof Node { function get_constructor(type): typeof Node {
switch (type) { switch (type) {
case 'AwaitBlock': return AwaitBlock; case 'AwaitBlock': return AwaitBlock;
case 'Body': return Body; case 'Body': return Body;
@ -38,10 +38,10 @@ function getConstructor(type): typeof Node {
} }
} }
export default function mapChildren(component, parent, scope, children: any[]) { export default function map_children(component, parent, scope, children: any[]) {
let last = null; let last = null;
return children.map(child => { return children.map(child => {
const constructor = getConstructor(child.type); const constructor = get_constructor(child.type);
const node = new constructor(component, parent, scope, child); const node = new constructor(component, parent, scope, child);
if (last) last.next = node; if (last) last.next = node;

@ -2,9 +2,6 @@ import CodeBuilder from '../utils/CodeBuilder';
import deindent from '../utils/deindent'; import deindent from '../utils/deindent';
import Renderer from './Renderer'; import Renderer from './Renderer';
import Wrapper from './wrappers/shared/Wrapper'; import Wrapper from './wrappers/shared/Wrapper';
import EachBlockWrapper from './wrappers/EachBlock';
import InlineComponentWrapper from './wrappers/InlineComponent';
import ElementWrapper from './wrappers/Element';
export interface BlockOptions { export interface BlockOptions {
parent?: Block; parent?: Block;
@ -48,19 +45,19 @@ export default class Block {
event_listeners: string[] = []; event_listeners: string[] = [];
maintainContext: boolean; maintain_context: boolean;
hasAnimation: boolean; has_animation: boolean;
hasIntros: boolean; has_intros: boolean;
hasOutros: boolean; has_outros: boolean;
hasIntroMethod: boolean; // could have the method without the transition, due to siblings has_intro_method: boolean; // could have the method without the transition, due to siblings
hasOutroMethod: boolean; has_outro_method: boolean;
outros: number; outros: number;
aliases: Map<string, string>; aliases: Map<string, string>;
variables: Map<string, string>; variables: Map<string, string>;
getUniqueName: (name: string) => string; get_unique_name: (name: string) => string;
hasUpdateMethod = false; has_update_method = false;
autofocus: string; autofocus: string;
constructor(options: BlockOptions) { constructor(options: BlockOptions) {
@ -94,19 +91,19 @@ export default class Block {
destroy: new CodeBuilder(), destroy: new CodeBuilder(),
}; };
this.hasAnimation = false; this.has_animation = false;
this.hasIntroMethod = false; // a block could have an intro method but not intro transitions, e.g. if a sibling block has intros this.has_intro_method = false; // a block could have an intro method but not intro transitions, e.g. if a sibling block has intros
this.hasOutroMethod = false; this.has_outro_method = false;
this.outros = 0; this.outros = 0;
this.getUniqueName = this.renderer.component.getUniqueNameMaker(); this.get_unique_name = this.renderer.component.get_unique_name_maker();
this.variables = new Map(); this.variables = new Map();
this.aliases = new Map().set('ctx', this.getUniqueName('ctx')); this.aliases = new Map().set('ctx', this.get_unique_name('ctx'));
if (this.key) this.aliases.set('key', this.getUniqueName('key')); if (this.key) this.aliases.set('key', this.get_unique_name('key'));
} }
assignVariableNames() { assign_variable_names() {
const seen = new Set(); const seen = new Set();
const dupes = new Set(); const dupes = new Set();
@ -116,7 +113,7 @@ export default class Block {
const wrapper = this.wrappers[i]; const wrapper = this.wrappers[i];
if (!wrapper.var) continue; if (!wrapper.var) continue;
if (wrapper.parent && wrapper.parent.canUseInnerHTML) continue; if (wrapper.parent && wrapper.parent.can_use_innerhtml) continue;
if (seen.has(wrapper.var)) { if (seen.has(wrapper.var)) {
dupes.add(wrapper.var); dupes.add(wrapper.var);
@ -136,60 +133,60 @@ export default class Block {
if (dupes.has(wrapper.var)) { if (dupes.has(wrapper.var)) {
const i = counts.get(wrapper.var) || 0; const i = counts.get(wrapper.var) || 0;
counts.set(wrapper.var, i + 1); counts.set(wrapper.var, i + 1);
wrapper.var = this.getUniqueName(wrapper.var + i); wrapper.var = this.get_unique_name(wrapper.var + i);
} else { } else {
wrapper.var = this.getUniqueName(wrapper.var); wrapper.var = this.get_unique_name(wrapper.var);
} }
} }
} }
addDependencies(dependencies: Set<string>) { add_dependencies(dependencies: Set<string>) {
dependencies.forEach(dependency => { dependencies.forEach(dependency => {
this.dependencies.add(dependency); this.dependencies.add(dependency);
}); });
this.hasUpdateMethod = true; this.has_update_method = true;
} }
addElement( add_element(
name: string, name: string,
renderStatement: string, render_statement: string,
claimStatement: string, claim_statement: string,
parentNode: string, parent_node: string,
noDetach?: boolean no_detach?: boolean
) { ) {
this.addVariable(name); this.add_variable(name);
this.builders.create.add_line(`${name} = ${renderStatement};`); this.builders.create.add_line(`${name} = ${render_statement};`);
if (this.renderer.options.hydratable) { if (this.renderer.options.hydratable) {
this.builders.claim.add_line(`${name} = ${claimStatement || renderStatement};`); this.builders.claim.add_line(`${name} = ${claim_statement || render_statement};`);
} }
if (parentNode) { if (parent_node) {
this.builders.mount.add_line(`@append(${parentNode}, ${name});`); this.builders.mount.add_line(`@append(${parent_node}, ${name});`);
if (parentNode === 'document.head') this.builders.destroy.add_line(`@detach(${name});`); if (parent_node === 'document.head') this.builders.destroy.add_line(`@detach(${name});`);
} else { } else {
this.builders.mount.add_line(`@insert(#target, ${name}, anchor);`); this.builders.mount.add_line(`@insert(#target, ${name}, anchor);`);
if (!noDetach) this.builders.destroy.add_conditional('detaching', `@detach(${name});`); if (!no_detach) this.builders.destroy.add_conditional('detaching', `@detach(${name});`);
} }
} }
addIntro(local?: boolean) { add_intro(local?: boolean) {
this.hasIntros = this.hasIntroMethod = this.renderer.hasIntroTransitions = true; this.has_intros = this.has_intro_method = true;
if (!local && this.parent) this.parent.addIntro(); if (!local && this.parent) this.parent.add_intro();
} }
addOutro(local?: boolean) { add_outro(local?: boolean) {
this.hasOutros = this.hasOutroMethod = this.renderer.hasOutroTransitions = true; this.has_outros = this.has_outro_method = true;
this.outros += 1; this.outros += 1;
if (!local && this.parent) this.parent.addOutro(); if (!local && this.parent) this.parent.add_outro();
} }
addAnimation() { add_animation() {
this.hasAnimation = true; this.has_animation = true;
} }
addVariable(name: string, init?: string) { add_variable(name: string, init?: string) {
if (name[0] === '#') { if (name[0] === '#') {
name = this.alias(name.slice(1)); name = this.alias(name.slice(1));
} }
@ -205,7 +202,7 @@ export default class Block {
alias(name: string) { alias(name: string) {
if (!this.aliases.has(name)) { if (!this.aliases.has(name)) {
this.aliases.set(name, this.getUniqueName(name)); this.aliases.set(name, this.get_unique_name(name));
} }
return this.aliases.get(name); return this.aliases.get(name);
@ -215,11 +212,11 @@ export default class Block {
return new Block(Object.assign({}, this, { key: null }, options, { parent: this })); return new Block(Object.assign({}, this, { key: null }, options, { parent: this }));
} }
getContents(localKey?: string) { get_contents(local_key?: string) {
const { dev } = this.renderer.options; const { dev } = this.renderer.options;
if (this.hasOutros) { if (this.has_outros) {
this.addVariable('#current'); this.add_variable('#current');
if (!this.builders.intro.is_empty()) { if (!this.builders.intro.is_empty()) {
this.builders.intro.add_line(`#current = true;`); this.builders.intro.add_line(`#current = true;`);
@ -235,14 +232,14 @@ export default class Block {
this.builders.mount.add_line(`${this.autofocus}.focus();`); this.builders.mount.add_line(`${this.autofocus}.focus();`);
} }
this.renderListeners(); this.render_listeners();
const properties = new CodeBuilder(); const properties = new CodeBuilder();
const methodName = (short: string, long: string) => dev ? `${short}: function ${this.getUniqueName(long)}` : short; const method_name = (short: string, long: string) => dev ? `${short}: function ${this.get_unique_name(long)}` : short;
if (localKey) { if (local_key) {
properties.add_block(`key: ${localKey},`); properties.add_block(`key: ${local_key},`);
} }
if (this.first) { if (this.first) {
@ -260,7 +257,7 @@ export default class Block {
); );
properties.add_block(deindent` properties.add_block(deindent`
${methodName('c', 'create')}() { ${method_name('c', 'create')}() {
${this.builders.create} ${this.builders.create}
${hydrate} ${hydrate}
}, },
@ -272,7 +269,7 @@ export default class Block {
properties.add_line(`l: @noop,`); properties.add_line(`l: @noop,`);
} else { } else {
properties.add_block(deindent` properties.add_block(deindent`
${methodName('l', 'claim')}(nodes) { ${method_name('l', 'claim')}(nodes) {
${this.builders.claim} ${this.builders.claim}
${this.renderer.options.hydratable && !this.builders.hydrate.is_empty() && `this.h();`} ${this.renderer.options.hydratable && !this.builders.hydrate.is_empty() && `this.h();`}
}, },
@ -282,7 +279,7 @@ export default class Block {
if (this.renderer.options.hydratable && !this.builders.hydrate.is_empty()) { if (this.renderer.options.hydratable && !this.builders.hydrate.is_empty()) {
properties.add_block(deindent` properties.add_block(deindent`
${methodName('h', 'hydrate')}() { ${method_name('h', 'hydrate')}() {
${this.builders.hydrate} ${this.builders.hydrate}
}, },
`); `);
@ -292,48 +289,48 @@ export default class Block {
properties.add_line(`m: @noop,`); properties.add_line(`m: @noop,`);
} else { } else {
properties.add_block(deindent` properties.add_block(deindent`
${methodName('m', 'mount')}(#target, anchor) { ${method_name('m', 'mount')}(#target, anchor) {
${this.builders.mount} ${this.builders.mount}
}, },
`); `);
} }
if (this.hasUpdateMethod || this.maintainContext) { if (this.has_update_method || this.maintain_context) {
if (this.builders.update.is_empty() && !this.maintainContext) { if (this.builders.update.is_empty() && !this.maintain_context) {
properties.add_line(`p: @noop,`); properties.add_line(`p: @noop,`);
} else { } else {
properties.add_block(deindent` properties.add_block(deindent`
${methodName('p', 'update')}(changed, ${this.maintainContext ? 'new_ctx' : 'ctx'}) { ${method_name('p', 'update')}(changed, ${this.maintain_context ? 'new_ctx' : 'ctx'}) {
${this.maintainContext && `ctx = new_ctx;`} ${this.maintain_context && `ctx = new_ctx;`}
${this.builders.update} ${this.builders.update}
}, },
`); `);
} }
} }
if (this.hasAnimation) { if (this.has_animation) {
properties.add_block(deindent` properties.add_block(deindent`
${methodName('r', 'measure')}() { ${method_name('r', 'measure')}() {
${this.builders.measure} ${this.builders.measure}
}, },
${methodName('f', 'fix')}() { ${method_name('f', 'fix')}() {
${this.builders.fix} ${this.builders.fix}
}, },
${methodName('a', 'animate')}() { ${method_name('a', 'animate')}() {
${this.builders.animate} ${this.builders.animate}
}, },
`); `);
} }
if (this.hasIntroMethod || this.hasOutroMethod) { if (this.has_intro_method || this.has_outro_method) {
if (this.builders.intro.is_empty()) { if (this.builders.intro.is_empty()) {
properties.add_line(`i: @noop,`); properties.add_line(`i: @noop,`);
} else { } else {
properties.add_block(deindent` properties.add_block(deindent`
${methodName('i', 'intro')}(#local) { ${method_name('i', 'intro')}(#local) {
${this.hasOutros && `if (#current) return;`} ${this.has_outros && `if (#current) return;`}
${this.builders.intro} ${this.builders.intro}
}, },
`); `);
@ -343,7 +340,7 @@ export default class Block {
properties.add_line(`o: @noop,`); properties.add_line(`o: @noop,`);
} else { } else {
properties.add_block(deindent` properties.add_block(deindent`
${methodName('o', 'outro')}(#local) { ${method_name('o', 'outro')}(#local) {
${this.builders.outro} ${this.builders.outro}
}, },
`); `);
@ -354,7 +351,7 @@ export default class Block {
properties.add_line(`d: @noop`); properties.add_line(`d: @noop`);
} else { } else {
properties.add_block(deindent` properties.add_block(deindent`
${methodName('d', 'destroy')}(detaching) { ${method_name('d', 'destroy')}(detaching) {
${this.builders.destroy} ${this.builders.destroy}
} }
`); `);
@ -379,9 +376,9 @@ export default class Block {
}); });
} }
renderListeners(chunk: string = '') { render_listeners(chunk: string = '') {
if (this.event_listeners.length > 0) { if (this.event_listeners.length > 0) {
this.addVariable(`#dispose${chunk}`); this.add_variable(`#dispose${chunk}`);
if (this.event_listeners.length === 1) { if (this.event_listeners.length === 1) {
this.builders.hydrate.add_line( this.builders.hydrate.add_line(
@ -406,12 +403,12 @@ export default class Block {
} }
toString() { toString() {
const localKey = this.key && this.getUniqueName('key'); const local_key = this.key && this.get_unique_name('key');
return deindent` return deindent`
${this.comment && `// ${this.comment}`} ${this.comment && `// ${this.comment}`}
function ${this.name}(${this.key ? `${localKey}, ` : ''}ctx) { function ${this.name}(${this.key ? `${local_key}, ` : ''}ctx) {
${this.getContents(localKey)} ${this.get_contents(local_key)}
} }
`; `;
} }

@ -11,16 +11,13 @@ export default class Renderer {
blocks: (Block | string)[]; blocks: (Block | string)[];
readonly: Set<string>; readonly: Set<string>;
slots: Set<string>; slots: Set<string>;
metaBindings: CodeBuilder; meta_bindings: CodeBuilder;
bindingGroups: string[]; binding_groups: string[];
block: Block; block: Block;
fragment: FragmentWrapper; fragment: FragmentWrapper;
fileVar: string; file_var: string;
hasIntroTransitions: boolean;
hasOutroTransitions: boolean;
constructor(component: Component, options: CompileOptions) { constructor(component: Component, options: CompileOptions) {
this.component = component; this.component = component;
@ -30,12 +27,12 @@ export default class Renderer {
this.readonly = new Set(); this.readonly = new Set();
this.slots = new Set(); this.slots = new Set();
this.fileVar = options.dev && this.component.getUniqueName('file'); this.file_var = options.dev && this.component.get_unique_name('file');
// initial values for e.g. window.innerWidth, if there's a <svelte:window> meta tag // initial values for e.g. window.innerWidth, if there's a <svelte:window> meta tag
this.metaBindings = new CodeBuilder(); this.meta_bindings = new CodeBuilder();
this.bindingGroups = []; this.binding_groups = [];
// main block // main block
this.block = new Block({ this.block = new Block({
@ -48,7 +45,7 @@ export default class Renderer {
dependencies: new Set(), dependencies: new Set(),
}); });
this.block.hasUpdateMethod = true; this.block.has_update_method = true;
this.blocks = []; this.blocks = [];
this.fragment = new FragmentWrapper( this.fragment = new FragmentWrapper(
@ -62,11 +59,11 @@ export default class Renderer {
this.blocks.forEach(block => { this.blocks.forEach(block => {
if (typeof block !== 'string') { if (typeof block !== 'string') {
block.assignVariableNames(); block.assign_variable_names();
} }
}); });
this.block.assignVariableNames(); this.block.assign_variable_names();
this.fragment.render(this.block, null, 'nodes'); this.fragment.render(this.block, null, 'nodes');
} }

@ -21,23 +21,23 @@ export default function dom(
const renderer = new Renderer(component, options); const renderer = new Renderer(component, options);
const { block } = renderer; const { block } = renderer;
block.hasOutroMethod = true; block.has_outro_method = true;
// prevent fragment being created twice (#1063) // prevent fragment being created twice (#1063)
if (options.customElement) block.builders.create.add_line(`this.c = @noop;`); if (options.customElement) block.builders.create.add_line(`this.c = @noop;`);
const builder = new CodeBuilder(); const builder = new CodeBuilder();
if (component.compileOptions.dev) { if (component.compile_options.dev) {
builder.add_line(`const ${renderer.fileVar} = ${JSON.stringify(component.file)};`); builder.add_line(`const ${renderer.file_var} = ${JSON.stringify(component.file)};`);
} }
const css = component.stylesheet.render(options.filename, !options.customElement); const css = component.stylesheet.render(options.filename, !options.customElement);
const styles = component.stylesheet.hasStyles && stringify(options.dev ? const styles = component.stylesheet.has_styles && stringify(options.dev ?
`${css.code}\n/*# sourceMappingURL=${css.map.toUrl()} */` : `${css.code}\n/*# sourceMappingURL=${css.map.toUrl()} */` :
css.code, { onlyEscapeAtSymbol: true }); css.code, { onlyEscapeAtSymbol: true });
if (styles && component.compileOptions.css !== false && !options.customElement) { if (styles && component.compile_options.css !== false && !options.customElement) {
builder.add_block(deindent` builder.add_block(deindent`
function @add_css() { function @add_css() {
var style = @element("style"); var style = @element("style");
@ -66,7 +66,7 @@ export default function dom(
// explicit opt-in, or something? // explicit opt-in, or something?
const should_add_css = ( const should_add_css = (
!options.customElement && !options.customElement &&
component.stylesheet.hasStyles && component.stylesheet.has_styles &&
options.css !== false options.css !== false
); );
@ -90,7 +90,7 @@ export default function dom(
const body = []; const body = [];
const not_equal = component.componentOptions.immutable ? `@not_equal` : `@safe_not_equal`; const not_equal = component.component_options.immutable ? `@not_equal` : `@safe_not_equal`;
let dev_props_check; let dev_props_check;
props.forEach(x => { props.forEach(x => {
@ -102,7 +102,7 @@ export default function dom(
return ${x.name}; return ${x.name};
} }
`); `);
} else if (component.componentOptions.accessors) { } else if (component.component_options.accessors) {
body.push(deindent` body.push(deindent`
get ${x.export_name}() { get ${x.export_name}() {
return this.$$.ctx.${x.name}; return this.$$.ctx.${x.name};
@ -111,7 +111,7 @@ export default function dom(
} }
if (variable.writable && !renderer.readonly.has(x.export_name)) { if (variable.writable && !renderer.readonly.has(x.export_name)) {
if (component.componentOptions.accessors) { if (component.component_options.accessors) {
body.push(deindent` body.push(deindent`
set ${x.export_name}(${x.name}) { set ${x.export_name}(${x.name}) {
this.$set({ ${x.name} }); this.$set({ ${x.name} });
@ -119,7 +119,7 @@ export default function dom(
} }
`); `);
} }
} else if (component.compileOptions.dev) { } else if (component.compile_options.dev) {
body.push(deindent` body.push(deindent`
set ${x.export_name}(value) { set ${x.export_name}(value) {
throw new Error("<${component.tag}>: Cannot set read-only property '${x.export_name}'"); throw new Error("<${component.tag}>: Cannot set read-only property '${x.export_name}'");
@ -128,7 +128,7 @@ export default function dom(
} }
}); });
if (component.compileOptions.dev) { if (component.compile_options.dev) {
// TODO check no uunexpected props were passed, as well as // TODO check no uunexpected props were passed, as well as
// checking that expected ones were passed // checking that expected ones were passed
const expected = props.filter(prop => !prop.initialised); const expected = props.filter(prop => !prop.initialised);
@ -258,7 +258,7 @@ export default function dom(
const subscribe = component.helper('subscribe'); const subscribe = component.helper('subscribe');
let insert = `${subscribe}($$self, ${name}, $${callback})`; let insert = `${subscribe}($$self, ${name}, $${callback})`;
if (component.compileOptions.dev) { if (component.compile_options.dev) {
const validate_store = component.helper('validate_store'); const validate_store = component.helper('validate_store');
insert = `${validate_store}(${name}, '${name}'); ${insert}`; insert = `${validate_store}(${name}, '${name}'); ${insert}`;
} }
@ -274,7 +274,7 @@ export default function dom(
builder.add_block(deindent` builder.add_block(deindent`
function create_fragment(ctx) { function create_fragment(ctx) {
${block.getContents()} ${block.get_contents()}
} }
${component.module_javascript} ${component.module_javascript}
@ -303,7 +303,7 @@ export default function dom(
filtered_declarations.push(...arr.map(name => `$$slot_${sanitize(name)}`), '$$scope'); filtered_declarations.push(...arr.map(name => `$$slot_${sanitize(name)}`), '$$scope');
} }
if (renderer.bindingGroups.length > 0) { if (renderer.binding_groups.length > 0) {
filtered_declarations.push(`$$binding_groups`); filtered_declarations.push(`$$binding_groups`);
} }
@ -331,7 +331,7 @@ export default function dom(
return !variable || variable.hoistable; return !variable || variable.hoistable;
}) })
.map(({ name }) => deindent` .map(({ name }) => deindent`
${component.compileOptions.dev && `@validate_store(${name.slice(1)}, '${name.slice(1)}');`} ${component.compile_options.dev && `@validate_store(${name.slice(1)}, '${name.slice(1)}');`}
@subscribe($$self, ${name.slice(1)}, $$value => { ${name} = $$value; $$invalidate('${name}', ${name}); }); @subscribe($$self, ${name.slice(1)}, $$value => { ${name} = $$value; $$invalidate('${name}', ${name}); });
`); `);
@ -391,7 +391,7 @@ export default function dom(
${renderer.slots.size && `let { ${[...renderer.slots].map(name => `$$slot_${sanitize(name)}`).join(', ')}, $$scope } = $$props;`} ${renderer.slots.size && `let { ${[...renderer.slots].map(name => `$$slot_${sanitize(name)}`).join(', ')}, $$scope } = $$props;`}
${renderer.bindingGroups.length > 0 && `const $$binding_groups = [${renderer.bindingGroups.map(_ => `[]`).join(', ')}];`} ${renderer.binding_groups.length > 0 && `const $$binding_groups = [${renderer.binding_groups.map(_ => `[]`).join(', ')}];`}
${component.partly_hoisted.length > 0 && component.partly_hoisted.join('\n\n')} ${component.partly_hoisted.length > 0 && component.partly_hoisted.join('\n\n')}

@ -13,7 +13,7 @@ class AwaitBlockBranch extends Wrapper {
node: PendingBlock | ThenBlock | CatchBlock; node: PendingBlock | ThenBlock | CatchBlock;
block: Block; block: Block;
fragment: FragmentWrapper; fragment: FragmentWrapper;
isDynamic: boolean; is_dynamic: boolean;
var = null; var = null;
@ -23,14 +23,14 @@ class AwaitBlockBranch extends Wrapper {
block: Block, block: Block,
parent: Wrapper, parent: Wrapper,
node: AwaitBlock, node: AwaitBlock,
stripWhitespace: boolean, strip_whitespace: boolean,
nextSibling: Wrapper next_sibling: Wrapper
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.block = block.child({ this.block = block.child({
comment: create_debugging_comment(node, this.renderer.component), comment: create_debugging_comment(node, this.renderer.component),
name: this.renderer.component.getUniqueName(`create_${status}_block`) name: this.renderer.component.get_unique_name(`create_${status}_block`)
}); });
this.fragment = new FragmentWrapper( this.fragment = new FragmentWrapper(
@ -38,11 +38,11 @@ class AwaitBlockBranch extends Wrapper {
this.block, this.block,
this.node.children, this.node.children,
parent, parent,
stripWhitespace, strip_whitespace,
nextSibling next_sibling
); );
this.isDynamic = this.block.dependencies.size > 0; this.is_dynamic = this.block.dependencies.size > 0;
} }
} }
@ -60,18 +60,18 @@ export default class AwaitBlockWrapper extends Wrapper {
block: Block, block: Block,
parent: Wrapper, parent: Wrapper,
node: AwaitBlock, node: AwaitBlock,
stripWhitespace: boolean, strip_whitespace: boolean,
nextSibling: Wrapper next_sibling: Wrapper
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.cannotUseInnerHTML(); this.cannot_use_innerhtml();
block.addDependencies(this.node.expression.dependencies); block.add_dependencies(this.node.expression.dependencies);
let isDynamic = false; let is_dynamic = false;
let hasIntros = false; let has_intros = false;
let hasOutros = false; let has_outros = false;
['pending', 'then', 'catch'].forEach(status => { ['pending', 'then', 'catch'].forEach(status => {
const child = this.node[status]; const child = this.node[status];
@ -82,59 +82,59 @@ export default class AwaitBlockWrapper extends Wrapper {
block, block,
this, this,
child, child,
stripWhitespace, strip_whitespace,
nextSibling next_sibling
); );
renderer.blocks.push(branch.block); renderer.blocks.push(branch.block);
if (branch.isDynamic) { if (branch.is_dynamic) {
isDynamic = true; is_dynamic = true;
// TODO should blocks update their own parents? // TODO should blocks update their own parents?
block.addDependencies(branch.block.dependencies); block.add_dependencies(branch.block.dependencies);
} }
if (branch.block.hasIntros) hasIntros = true; if (branch.block.has_intros) has_intros = true;
if (branch.block.hasOutros) hasOutros = true; if (branch.block.has_outros) has_outros = true;
this[status] = branch; this[status] = branch;
}); });
this.pending.block.hasUpdateMethod = isDynamic; this.pending.block.has_update_method = is_dynamic;
this.then.block.hasUpdateMethod = isDynamic; this.then.block.has_update_method = is_dynamic;
this.catch.block.hasUpdateMethod = isDynamic; this.catch.block.has_update_method = is_dynamic;
this.pending.block.hasIntroMethod = hasIntros; this.pending.block.has_intro_method = has_intros;
this.then.block.hasIntroMethod = hasIntros; this.then.block.has_intro_method = has_intros;
this.catch.block.hasIntroMethod = hasIntros; this.catch.block.has_intro_method = has_intros;
this.pending.block.hasOutroMethod = hasOutros; this.pending.block.has_outro_method = has_outros;
this.then.block.hasOutroMethod = hasOutros; this.then.block.has_outro_method = has_outros;
this.catch.block.hasOutroMethod = hasOutros; this.catch.block.has_outro_method = has_outros;
if (hasOutros) { if (has_outros) {
block.addOutro(); block.add_outro();
} }
} }
render( render(
block: Block, block: Block,
parentNode: string, parent_node: string,
parentNodes: string parent_nodes: string
) { ) {
const anchor = this.getOrCreateAnchor(block, parentNode, parentNodes); const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes);
const updateMountNode = this.getUpdateMountNode(anchor); const update_mount_node = this.get_update_mount_node(anchor);
const snippet = this.node.expression.render(block); const snippet = this.node.expression.render(block);
const info = block.getUniqueName(`info`); const info = block.get_unique_name(`info`);
const promise = block.getUniqueName(`promise`); const promise = block.get_unique_name(`promise`);
block.addVariable(promise); block.add_variable(promise);
block.maintainContext = true; block.maintain_context = true;
const infoProps = [ const info_props = [
'ctx', 'ctx',
'current: null', 'current: null',
this.pending.block.name && `pending: ${this.pending.block.name}`, this.pending.block.name && `pending: ${this.pending.block.name}`,
@ -142,12 +142,12 @@ export default class AwaitBlockWrapper extends Wrapper {
this.catch.block.name && `catch: ${this.catch.block.name}`, this.catch.block.name && `catch: ${this.catch.block.name}`,
this.then.block.name && `value: '${this.node.value}'`, this.then.block.name && `value: '${this.node.value}'`,
this.catch.block.name && `error: '${this.node.error}'`, this.catch.block.name && `error: '${this.node.error}'`,
this.pending.block.hasOutroMethod && `blocks: Array(3)` this.pending.block.has_outro_method && `blocks: Array(3)`
].filter(Boolean); ].filter(Boolean);
block.builders.init.add_block(deindent` block.builders.init.add_block(deindent`
let ${info} = { let ${info} = {
${infoProps.join(',\n')} ${info_props.join(',\n')}
}; };
`); `);
@ -159,24 +159,24 @@ export default class AwaitBlockWrapper extends Wrapper {
${info}.block.c(); ${info}.block.c();
`); `);
if (parentNodes && this.renderer.options.hydratable) { if (parent_nodes && this.renderer.options.hydratable) {
block.builders.claim.add_block(deindent` block.builders.claim.add_block(deindent`
${info}.block.l(${parentNodes}); ${info}.block.l(${parent_nodes});
`); `);
} }
const initialMountNode = parentNode || '#target'; const initial_mount_node = parent_node || '#target';
const anchorNode = parentNode ? 'null' : 'anchor'; const anchor_node = parent_node ? 'null' : 'anchor';
const hasTransitions = this.pending.block.hasIntroMethod || this.pending.block.hasOutroMethod; const has_transitions = this.pending.block.has_intro_method || this.pending.block.has_outro_method;
block.builders.mount.add_block(deindent` block.builders.mount.add_block(deindent`
${info}.block.m(${initialMountNode}, ${info}.anchor = ${anchorNode}); ${info}.block.m(${initial_mount_node}, ${info}.anchor = ${anchor_node});
${info}.mount = () => ${updateMountNode}; ${info}.mount = () => ${update_mount_node};
${info}.anchor = ${anchor}; ${info}.anchor = ${anchor};
`); `);
if (hasTransitions) { if (has_transitions) {
block.builders.intro.add_line(`${info}.block.i();`); block.builders.intro.add_line(`${info}.block.i();`);
} }
@ -198,7 +198,7 @@ export default class AwaitBlockWrapper extends Wrapper {
`${info}.ctx = ctx;` `${info}.ctx = ctx;`
); );
if (this.pending.block.hasUpdateMethod) { if (this.pending.block.has_update_method) {
block.builders.update.add_block(deindent` block.builders.update.add_block(deindent`
if (${conditions.join(' && ')}) { if (${conditions.join(' && ')}) {
// nothing // nothing
@ -212,7 +212,7 @@ export default class AwaitBlockWrapper extends Wrapper {
`); `);
} }
if (this.pending.block.hasOutroMethod) { if (this.pending.block.has_outro_method) {
block.builders.outro.add_block(deindent` block.builders.outro.add_block(deindent`
for (let #i = 0; #i < 3; #i += 1) { for (let #i = 0; #i < 3; #i += 1) {
const block = ${info}.blocks[#i]; const block = ${info}.blocks[#i];
@ -222,7 +222,7 @@ export default class AwaitBlockWrapper extends Wrapper {
} }
block.builders.destroy.add_block(deindent` block.builders.destroy.add_block(deindent`
${info}.block.d(${parentNode ? '' : 'detaching'}); ${info}.block.d(${parent_node ? '' : 'detaching'});
${info} = null; ${info} = null;
`); `);

@ -6,7 +6,7 @@ import Body from '../../nodes/Body';
export default class BodyWrapper extends Wrapper { export default class BodyWrapper extends Wrapper {
node: Body; node: Body;
render(block: Block, parentNode: string, parentNodes: string) { render(block: Block, parent_node: string, parent_nodes: string) {
this.node.handlers.forEach(handler => { this.node.handlers.forEach(handler => {
const snippet = handler.render(block); const snippet = handler.render(block);

@ -13,13 +13,13 @@ export default class DebugTagWrapper extends Wrapper {
block: Block, block: Block,
parent: Wrapper, parent: Wrapper,
node: DebugTag, node: DebugTag,
stripWhitespace: boolean, strip_whitespace: boolean,
nextSibling: Wrapper next_sibling: Wrapper
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
} }
render(block: Block, parentNode: string, parentNodes: string) { render(block: Block, parent_node: string, parent_nodes: string) {
const { renderer } = this; const { renderer } = this;
const { component } = renderer; const { component } = renderer;

@ -11,7 +11,7 @@ class ElseBlockWrapper extends Wrapper {
node: ElseBlock; node: ElseBlock;
block: Block; block: Block;
fragment: FragmentWrapper; fragment: FragmentWrapper;
isDynamic: boolean; is_dynamic: boolean;
var = null; var = null;
@ -20,14 +20,14 @@ class ElseBlockWrapper extends Wrapper {
block: Block, block: Block,
parent: Wrapper, parent: Wrapper,
node: ElseBlock, node: ElseBlock,
stripWhitespace: boolean, strip_whitespace: boolean,
nextSibling: Wrapper next_sibling: Wrapper
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.block = block.child({ this.block = block.child({
comment: create_debugging_comment(node, this.renderer.component), comment: create_debugging_comment(node, this.renderer.component),
name: this.renderer.component.getUniqueName(`create_else_block`) name: this.renderer.component.get_unique_name(`create_else_block`)
}); });
this.fragment = new FragmentWrapper( this.fragment = new FragmentWrapper(
@ -35,11 +35,11 @@ class ElseBlockWrapper extends Wrapper {
this.block, this.block,
this.node.children, this.node.children,
parent, parent,
stripWhitespace, strip_whitespace,
nextSibling next_sibling
); );
this.isDynamic = this.block.dependencies.size > 0; this.is_dynamic = this.block.dependencies.size > 0;
} }
} }
@ -60,8 +60,8 @@ export default class EachBlockWrapper extends Wrapper {
length: string; length: string;
} }
contextProps: string[]; context_props: string[];
indexName: string; index_name: string;
var = 'each'; var = 'each';
@ -70,27 +70,27 @@ export default class EachBlockWrapper extends Wrapper {
block: Block, block: Block,
parent: Wrapper, parent: Wrapper,
node: EachBlock, node: EachBlock,
stripWhitespace: boolean, strip_whitespace: boolean,
nextSibling: Wrapper next_sibling: Wrapper
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.cannotUseInnerHTML(); this.cannot_use_innerhtml();
const { dependencies } = node.expression; const { dependencies } = node.expression;
block.addDependencies(dependencies); block.add_dependencies(dependencies);
this.block = block.child({ this.block = block.child({
comment: create_debugging_comment(this.node, this.renderer.component), comment: create_debugging_comment(this.node, this.renderer.component),
name: renderer.component.getUniqueName('create_each_block'), name: renderer.component.get_unique_name('create_each_block'),
key: node.key as string, key: node.key as string,
bindings: new Map(block.bindings) bindings: new Map(block.bindings)
}); });
// TODO this seems messy // TODO this seems messy
this.block.hasAnimation = this.node.hasAnimation; this.block.has_animation = this.node.has_animation;
this.indexName = this.node.index || renderer.component.getUniqueName(`${this.node.context}_index`); this.index_name = this.node.index || renderer.component.get_unique_name(`${this.node.context}_index`);
const fixed_length = node.expression.node.type === 'ArrayExpression' const fixed_length = node.expression.node.type === 'ArrayExpression'
? node.expression.node.elements.length ? node.expression.node.elements.length
@ -102,13 +102,13 @@ export default class EachBlockWrapper extends Wrapper {
while (renderer.component.source[c] !== 'e') c += 1; while (renderer.component.source[c] !== 'e') c += 1;
renderer.component.code.overwrite(c, c + 4, 'length'); renderer.component.code.overwrite(c, c + 4, 'length');
const each_block_value = renderer.component.getUniqueName(`${this.var}_value`); const each_block_value = renderer.component.get_unique_name(`${this.var}_value`);
const iterations = block.getUniqueName(`${this.var}_blocks`); const iterations = block.get_unique_name(`${this.var}_blocks`);
this.vars = { this.vars = {
create_each_block: this.block.name, create_each_block: this.block.name,
each_block_value, each_block_value,
get_each_context: renderer.component.getUniqueName(`get_${this.var}_context`), get_each_context: renderer.component.get_unique_name(`get_${this.var}_context`),
iterations, iterations,
length: `[✂${c}-${c+4}✂]`, length: `[✂${c}-${c+4}✂]`,
@ -124,18 +124,18 @@ export default class EachBlockWrapper extends Wrapper {
node.contexts.forEach(prop => { node.contexts.forEach(prop => {
this.block.bindings.set(prop.key.name, { this.block.bindings.set(prop.key.name, {
object: this.vars.each_block_value, object: this.vars.each_block_value,
property: this.indexName, property: this.index_name,
snippet: `${this.vars.each_block_value}[${this.indexName}]${prop.tail}` snippet: `${this.vars.each_block_value}[${this.index_name}]${prop.tail}`
}); });
}); });
if (this.node.index) { if (this.node.index) {
this.block.getUniqueName(this.node.index); // this prevents name collisions (#1254) this.block.get_unique_name(this.node.index); // this prevents name collisions (#1254)
} }
renderer.blocks.push(this.block); renderer.blocks.push(this.block);
this.fragment = new FragmentWrapper(renderer, this.block, node.children, this, stripWhitespace, nextSibling); this.fragment = new FragmentWrapper(renderer, this.block, node.children, this, strip_whitespace, next_sibling);
if (this.node.else) { if (this.node.else) {
this.else = new ElseBlockWrapper( this.else = new ElseBlockWrapper(
@ -143,42 +143,42 @@ export default class EachBlockWrapper extends Wrapper {
block, block,
this, this,
this.node.else, this.node.else,
stripWhitespace, strip_whitespace,
nextSibling next_sibling
); );
renderer.blocks.push(this.else.block); renderer.blocks.push(this.else.block);
if (this.else.isDynamic) { if (this.else.is_dynamic) {
this.block.addDependencies(this.else.block.dependencies); this.block.add_dependencies(this.else.block.dependencies);
} }
} }
block.addDependencies(this.block.dependencies); block.add_dependencies(this.block.dependencies);
if (this.block.hasOutros || (this.else && this.else.block.hasOutros)) { if (this.block.has_outros || (this.else && this.else.block.has_outros)) {
block.addOutro(); block.add_outro();
} }
} }
render(block: Block, parentNode: string, parentNodes: string) { render(block: Block, parent_node: string, parent_nodes: string) {
if (this.fragment.nodes.length === 0) return; if (this.fragment.nodes.length === 0) return;
const { renderer } = this; const { renderer } = this;
const { component } = renderer; const { component } = renderer;
const needsAnchor = this.next const needs_anchor = this.next
? !this.next.isDomNode() : ? !this.next.is_dom_node() :
!parentNode || !this.parent.isDomNode(); !parent_node || !this.parent.is_dom_node();
this.vars.anchor = needsAnchor this.vars.anchor = needs_anchor
? block.getUniqueName(`${this.var}_anchor`) ? block.get_unique_name(`${this.var}_anchor`)
: (this.next && this.next.var) || 'null'; : (this.next && this.next.var) || 'null';
this.contextProps = this.node.contexts.map(prop => `child_ctx.${prop.key.name} = list[i]${prop.tail};`); this.context_props = this.node.contexts.map(prop => `child_ctx.${prop.key.name} = list[i]${prop.tail};`);
if (this.node.has_binding) this.contextProps.push(`child_ctx.${this.vars.each_block_value} = list;`); if (this.node.has_binding) this.context_props.push(`child_ctx.${this.vars.each_block_value} = list;`);
if (this.node.has_binding || this.node.index) this.contextProps.push(`child_ctx.${this.indexName} = i;`); if (this.node.has_binding || this.node.index) this.context_props.push(`child_ctx.${this.index_name} = i;`);
const snippet = this.node.expression.render(block); const snippet = this.node.expression.render(block);
@ -187,34 +187,34 @@ export default class EachBlockWrapper extends Wrapper {
renderer.blocks.push(deindent` renderer.blocks.push(deindent`
function ${this.vars.get_each_context}(ctx, list, i) { function ${this.vars.get_each_context}(ctx, list, i) {
const child_ctx = Object.create(ctx); const child_ctx = Object.create(ctx);
${this.contextProps} ${this.context_props}
return child_ctx; return child_ctx;
} }
`); `);
if (this.node.key) { if (this.node.key) {
this.renderKeyed(block, parentNode, parentNodes, snippet); this.render_keyed(block, parent_node, parent_nodes, snippet);
} else { } else {
this.renderUnkeyed(block, parentNode, parentNodes, snippet); this.render_unkeyed(block, parent_node, parent_nodes, snippet);
} }
if (this.block.hasIntroMethod || this.block.hasOutroMethod) { if (this.block.has_intro_method || this.block.has_outro_method) {
block.builders.intro.add_block(deindent` block.builders.intro.add_block(deindent`
for (var #i = 0; #i < ${this.vars.data_length}; #i += 1) ${this.vars.iterations}[#i].i(); for (var #i = 0; #i < ${this.vars.data_length}; #i += 1) ${this.vars.iterations}[#i].i();
`); `);
} }
if (needsAnchor) { if (needs_anchor) {
block.addElement( block.add_element(
this.vars.anchor, this.vars.anchor,
`@comment()`, `@comment()`,
parentNodes && `@comment()`, parent_nodes && `@comment()`,
parentNode parent_node
); );
} }
if (this.else) { if (this.else) {
const each_block_else = component.getUniqueName(`${this.var}_else`); const each_block_else = component.get_unique_name(`${this.var}_else`);
block.builders.init.add_line(`var ${each_block_else} = null;`); block.builders.init.add_line(`var ${each_block_else} = null;`);
@ -228,20 +228,20 @@ export default class EachBlockWrapper extends Wrapper {
block.builders.mount.add_block(deindent` block.builders.mount.add_block(deindent`
if (${each_block_else}) { if (${each_block_else}) {
${each_block_else}.m(${parentNode || '#target'}, null); ${each_block_else}.m(${parent_node || '#target'}, null);
} }
`); `);
const initialMountNode = parentNode || `${this.vars.anchor}.parentNode`; const initial_mount_node = parent_node || `${this.vars.anchor}.parentNode`;
if (this.else.block.hasUpdateMethod) { if (this.else.block.has_update_method) {
block.builders.update.add_block(deindent` block.builders.update.add_block(deindent`
if (!${this.vars.data_length} && ${each_block_else}) { if (!${this.vars.data_length} && ${each_block_else}) {
${each_block_else}.p(changed, ctx); ${each_block_else}.p(changed, ctx);
} else if (!${this.vars.data_length}) { } else if (!${this.vars.data_length}) {
${each_block_else} = ${this.else.block.name}(ctx); ${each_block_else} = ${this.else.block.name}(ctx);
${each_block_else}.c(); ${each_block_else}.c();
${each_block_else}.m(${initialMountNode}, ${this.vars.anchor}); ${each_block_else}.m(${initial_mount_node}, ${this.vars.anchor});
} else if (${each_block_else}) { } else if (${each_block_else}) {
${each_block_else}.d(1); ${each_block_else}.d(1);
${each_block_else} = null; ${each_block_else} = null;
@ -257,13 +257,13 @@ export default class EachBlockWrapper extends Wrapper {
} else if (!${each_block_else}) { } else if (!${each_block_else}) {
${each_block_else} = ${this.else.block.name}(ctx); ${each_block_else} = ${this.else.block.name}(ctx);
${each_block_else}.c(); ${each_block_else}.c();
${each_block_else}.m(${initialMountNode}, ${this.vars.anchor}); ${each_block_else}.m(${initial_mount_node}, ${this.vars.anchor});
} }
`); `);
} }
block.builders.destroy.add_block(deindent` block.builders.destroy.add_block(deindent`
if (${each_block_else}) ${each_block_else}.d(${parentNode ? '' : 'detaching'}); if (${each_block_else}) ${each_block_else}.d(${parent_node ? '' : 'detaching'});
`); `);
} }
@ -274,10 +274,10 @@ export default class EachBlockWrapper extends Wrapper {
} }
} }
renderKeyed( render_keyed(
block: Block, block: Block,
parentNode: string, parent_node: string,
parentNodes: string, parent_nodes: string,
snippet: string snippet: string
) { ) {
const { const {
@ -288,20 +288,20 @@ export default class EachBlockWrapper extends Wrapper {
view_length view_length
} = this.vars; } = this.vars;
const get_key = block.getUniqueName('get_key'); const get_key = block.get_unique_name('get_key');
const lookup = block.getUniqueName(`${this.var}_lookup`); const lookup = block.get_unique_name(`${this.var}_lookup`);
block.addVariable(iterations, '[]'); block.add_variable(iterations, '[]');
block.addVariable(lookup, `@blank_object()`); block.add_variable(lookup, `@blank_object()`);
if (this.fragment.nodes[0].isDomNode()) { if (this.fragment.nodes[0].is_dom_node()) {
this.block.first = this.fragment.nodes[0].var; this.block.first = this.fragment.nodes[0].var;
} else { } else {
this.block.first = this.block.getUniqueName('first'); this.block.first = this.block.get_unique_name('first');
this.block.addElement( this.block.add_element(
this.block.first, this.block.first,
`@comment()`, `@comment()`,
parentNodes && `@comment()`, parent_nodes && `@comment()`,
null null
); );
} }
@ -316,58 +316,57 @@ export default class EachBlockWrapper extends Wrapper {
} }
`); `);
const initialMountNode = parentNode || '#target'; const initial_mount_node = parent_node || '#target';
const updateMountNode = this.getUpdateMountNode(anchor); const update_mount_node = this.get_update_mount_node(anchor);
const anchorNode = parentNode ? 'null' : 'anchor'; const anchor_node = parent_node ? 'null' : 'anchor';
block.builders.create.add_block(deindent` block.builders.create.add_block(deindent`
for (#i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].c(); for (#i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].c();
`); `);
if (parentNodes && this.renderer.options.hydratable) { if (parent_nodes && this.renderer.options.hydratable) {
block.builders.claim.add_block(deindent` block.builders.claim.add_block(deindent`
for (#i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].l(${parentNodes}); for (#i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].l(${parent_nodes});
`); `);
} }
block.builders.mount.add_block(deindent` block.builders.mount.add_block(deindent`
for (#i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].m(${initialMountNode}, ${anchorNode}); for (#i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].m(${initial_mount_node}, ${anchor_node});
`); `);
const dynamic = this.block.hasUpdateMethod; const dynamic = this.block.has_update_method;
const rects = block.getUniqueName('rects'); const destroy = this.node.has_animation
const destroy = this.node.hasAnimation
? `@fix_and_outro_and_destroy_block` ? `@fix_and_outro_and_destroy_block`
: this.block.hasOutros : this.block.has_outros
? `@outro_and_destroy_block` ? `@outro_and_destroy_block`
: `@destroy_block`; : `@destroy_block`;
block.builders.update.add_block(deindent` block.builders.update.add_block(deindent`
const ${this.vars.each_block_value} = ${snippet}; const ${this.vars.each_block_value} = ${snippet};
${this.block.hasOutros && `@group_outros();`} ${this.block.has_outros && `@group_outros();`}
${this.node.hasAnimation && `for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].r();`} ${this.node.has_animation && `for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].r();`}
${iterations} = @update_keyed_each(${iterations}, changed, ${get_key}, ${dynamic ? '1' : '0'}, ctx, ${this.vars.each_block_value}, ${lookup}, ${updateMountNode}, ${destroy}, ${create_each_block}, ${anchor}, ${this.vars.get_each_context}); ${iterations} = @update_keyed_each(${iterations}, changed, ${get_key}, ${dynamic ? '1' : '0'}, ctx, ${this.vars.each_block_value}, ${lookup}, ${update_mount_node}, ${destroy}, ${create_each_block}, ${anchor}, ${this.vars.get_each_context});
${this.node.hasAnimation && `for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].a();`} ${this.node.has_animation && `for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].a();`}
${this.block.hasOutros && `@check_outros();`} ${this.block.has_outros && `@check_outros();`}
`); `);
if (this.block.hasOutros) { if (this.block.has_outros) {
block.builders.outro.add_block(deindent` block.builders.outro.add_block(deindent`
for (#i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].o(); for (#i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].o();
`); `);
} }
block.builders.destroy.add_block(deindent` block.builders.destroy.add_block(deindent`
for (#i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].d(${parentNode ? '' : 'detaching'}); for (#i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].d(${parent_node ? '' : 'detaching'});
`); `);
} }
renderUnkeyed( render_unkeyed(
block: Block, block: Block,
parentNode: string, parent_node: string,
parentNodes: string, parent_nodes: string,
snippet: string snippet: string
) { ) {
const { const {
@ -388,9 +387,9 @@ export default class EachBlockWrapper extends Wrapper {
} }
`); `);
const initialMountNode = parentNode || '#target'; const initial_mount_node = parent_node || '#target';
const updateMountNode = this.getUpdateMountNode(anchor); const update_mount_node = this.get_update_mount_node(anchor);
const anchorNode = parentNode ? 'null' : 'anchor'; const anchor_node = parent_node ? 'null' : 'anchor';
block.builders.create.add_block(deindent` block.builders.create.add_block(deindent`
for (var #i = 0; #i < ${view_length}; #i += 1) { for (var #i = 0; #i < ${view_length}; #i += 1) {
@ -398,30 +397,30 @@ export default class EachBlockWrapper extends Wrapper {
} }
`); `);
if (parentNodes && this.renderer.options.hydratable) { if (parent_nodes && this.renderer.options.hydratable) {
block.builders.claim.add_block(deindent` block.builders.claim.add_block(deindent`
for (var #i = 0; #i < ${view_length}; #i += 1) { for (var #i = 0; #i < ${view_length}; #i += 1) {
${iterations}[#i].l(${parentNodes}); ${iterations}[#i].l(${parent_nodes});
} }
`); `);
} }
block.builders.mount.add_block(deindent` block.builders.mount.add_block(deindent`
for (var #i = 0; #i < ${view_length}; #i += 1) { for (var #i = 0; #i < ${view_length}; #i += 1) {
${iterations}[#i].m(${initialMountNode}, ${anchorNode}); ${iterations}[#i].m(${initial_mount_node}, ${anchor_node});
} }
`); `);
const allDependencies = new Set(this.block.dependencies); const all_dependencies = new Set(this.block.dependencies);
const { dependencies } = this.node.expression; const { dependencies } = this.node.expression;
dependencies.forEach((dependency: string) => { dependencies.forEach((dependency: string) => {
allDependencies.add(dependency); all_dependencies.add(dependency);
}); });
const outroBlock = this.block.hasOutros && block.getUniqueName('outroBlock') const outro_block = this.block.has_outros && block.get_unique_name('outro_block')
if (outroBlock) { if (outro_block) {
block.builders.init.add_block(deindent` block.builders.init.add_block(deindent`
function ${outroBlock}(i, detaching, local) { function ${outro_block}(i, detaching, local) {
if (${iterations}[i]) { if (${iterations}[i]) {
if (detaching) { if (detaching) {
@on_outro(() => { @on_outro(() => {
@ -436,14 +435,14 @@ export default class EachBlockWrapper extends Wrapper {
`); `);
} }
const condition = Array.from(allDependencies) const condition = Array.from(all_dependencies)
.map(dependency => `changed.${dependency}`) .map(dependency => `changed.${dependency}`)
.join(' || '); .join(' || ');
const has_transitions = !!(this.block.hasIntroMethod || this.block.hasOutroMethod); const has_transitions = !!(this.block.has_intro_method || this.block.has_outro_method);
if (condition !== '') { if (condition !== '') {
const forLoopBody = this.block.hasUpdateMethod const for_loop_body = this.block.has_update_method
? deindent` ? deindent`
if (${iterations}[#i]) { if (${iterations}[#i]) {
${iterations}[#i].p(changed, child_ctx); ${iterations}[#i].p(changed, child_ctx);
@ -452,29 +451,29 @@ export default class EachBlockWrapper extends Wrapper {
${iterations}[#i] = ${create_each_block}(child_ctx); ${iterations}[#i] = ${create_each_block}(child_ctx);
${iterations}[#i].c(); ${iterations}[#i].c();
${has_transitions && `${iterations}[#i].i(1);`} ${has_transitions && `${iterations}[#i].i(1);`}
${iterations}[#i].m(${updateMountNode}, ${anchor}); ${iterations}[#i].m(${update_mount_node}, ${anchor});
} }
` `
: deindent` : deindent`
${iterations}[#i] = ${create_each_block}(child_ctx); ${iterations}[#i] = ${create_each_block}(child_ctx);
${iterations}[#i].c(); ${iterations}[#i].c();
${has_transitions && `${iterations}[#i].i(1);`} ${has_transitions && `${iterations}[#i].i(1);`}
${iterations}[#i].m(${updateMountNode}, ${anchor}); ${iterations}[#i].m(${update_mount_node}, ${anchor});
`; `;
const start = this.block.hasUpdateMethod ? '0' : `${view_length}`; const start = this.block.has_update_method ? '0' : `${view_length}`;
let remove_old_blocks; let remove_old_blocks;
if (this.block.hasOutros) { if (this.block.has_outros) {
remove_old_blocks = deindent` remove_old_blocks = deindent`
@group_outros(); @group_outros();
for (; #i < ${view_length}; #i += 1) ${outroBlock}(#i, 1, 1); for (; #i < ${view_length}; #i += 1) ${outro_block}(#i, 1, 1);
@check_outros(); @check_outros();
`; `;
} else { } else {
remove_old_blocks = deindent` remove_old_blocks = deindent`
for (${this.block.hasUpdateMethod ? `` : `#i = ${this.vars.each_block_value}.${length}`}; #i < ${view_length}; #i += 1) { for (${this.block.has_update_method ? `` : `#i = ${this.vars.each_block_value}.${length}`}; #i < ${view_length}; #i += 1) {
${iterations}[#i].d(1); ${iterations}[#i].d(1);
} }
${!fixed_length && `${view_length} = ${this.vars.each_block_value}.${length};`} ${!fixed_length && `${view_length} = ${this.vars.each_block_value}.${length};`}
@ -487,7 +486,7 @@ export default class EachBlockWrapper extends Wrapper {
for (var #i = ${start}; #i < ${this.vars.each_block_value}.${length}; #i += 1) { for (var #i = ${start}; #i < ${this.vars.each_block_value}.${length}; #i += 1) {
const child_ctx = ${this.vars.get_each_context}(ctx, ${this.vars.each_block_value}, #i); const child_ctx = ${this.vars.get_each_context}(ctx, ${this.vars.each_block_value}, #i);
${forLoopBody} ${for_loop_body}
} }
${remove_old_blocks} ${remove_old_blocks}
@ -500,10 +499,10 @@ export default class EachBlockWrapper extends Wrapper {
`); `);
} }
if (outroBlock) { if (outro_block) {
block.builders.outro.add_block(deindent` block.builders.outro.add_block(deindent`
${iterations} = ${iterations}.filter(Boolean); ${iterations} = ${iterations}.filter(Boolean);
for (let #i = 0; #i < ${view_length}; #i += 1) ${outroBlock}(#i, 0);` for (let #i = 0; #i < ${view_length}; #i += 1) ${outro_block}(#i, 0);`
); );
} }

@ -14,19 +14,19 @@ export default class AttributeWrapper {
this.parent = parent; this.parent = parent;
if (node.dependencies.size > 0) { if (node.dependencies.size > 0) {
parent.cannotUseInnerHTML(); parent.cannot_use_innerhtml();
block.addDependencies(node.dependencies); block.add_dependencies(node.dependencies);
// special case — <option value={foo}> — see below // special case — <option value={foo}> — see below
if (this.parent.node.name === 'option' && node.name === 'value') { if (this.parent.node.name === 'option' && node.name === 'value') {
let select: ElementWrapper = this.parent; let select: ElementWrapper = this.parent;
while (select && (select.node.type !== 'Element' || select.node.name !== 'select')) select = select.parent; while (select && (select.node.type !== 'Element' || select.node.name !== 'select')) select = select.parent;
if (select && select.selectBindingDependencies) { if (select && select.select_binding_dependencies) {
select.selectBindingDependencies.forEach(prop => { select.select_binding_dependencies.forEach(prop => {
this.node.dependencies.forEach((dependency: string) => { this.node.dependencies.forEach((dependency: string) => {
this.parent.renderer.component.indirectDependencies.get(prop).add(dependency); this.parent.renderer.component.indirect_dependencies.get(prop).add(dependency);
}); });
}); });
} }
@ -38,11 +38,11 @@ export default class AttributeWrapper {
const element = this.parent; const element = this.parent;
const name = fix_attribute_casing(this.node.name); const name = fix_attribute_casing(this.node.name);
let metadata = element.node.namespace ? null : attributeLookup[name]; let metadata = element.node.namespace ? null : attribute_lookup[name];
if (metadata && metadata.appliesTo && !~metadata.appliesTo.indexOf(element.node.name)) if (metadata && metadata.applies_to && !~metadata.applies_to.indexOf(element.node.name))
metadata = null; metadata = null;
const isIndirectlyBoundValue = const is_indirectly_bound_value =
name === 'value' && name === 'value' &&
(element.node.name === 'option' || // TODO check it's actually bound (element.node.name === 'option' || // TODO check it's actually bound
(element.node.name === 'input' && (element.node.name === 'input' &&
@ -51,9 +51,9 @@ export default class AttributeWrapper {
/checked|group/.test(binding.name) /checked|group/.test(binding.name)
))); )));
const propertyName = isIndirectlyBoundValue const property_name = is_indirectly_bound_value
? '__value' ? '__value'
: metadata && metadata.propertyName; : metadata && metadata.property_name;
// xlink is a special case... we could maybe extend this to generic // xlink is a special case... we could maybe extend this to generic
// namespaced attributes but I'm not sure that's applicable in // namespaced attributes but I'm not sure that's applicable in
@ -64,14 +64,14 @@ export default class AttributeWrapper {
? '@xlink_attr' ? '@xlink_attr'
: '@attr'; : '@attr';
const isLegacyInputType = element.renderer.component.compileOptions.legacy && name === 'type' && this.parent.node.name === 'input'; const is_legacy_input_type = element.renderer.component.compile_options.legacy && name === 'type' && this.parent.node.name === 'input';
const isDataSet = /^data-/.test(name) && !element.renderer.component.compileOptions.legacy && !element.node.namespace; const is_dataset = /^data-/.test(name) && !element.renderer.component.compile_options.legacy && !element.node.namespace;
const camelCaseName = isDataSet ? name.replace('data-', '').replace(/(-\w)/g, function (m) { const camel_case_name = is_dataset ? name.replace('data-', '').replace(/(-\w)/g, function (m) {
return m[1].toUpperCase(); return m[1].toUpperCase();
}) : name; }) : name;
if (this.node.isDynamic) { if (this.node.is_dynamic) {
let value; let value;
// TODO some of this code is repeated in Tag.ts — would be good to // TODO some of this code is repeated in Tag.ts — would be good to
@ -88,7 +88,7 @@ export default class AttributeWrapper {
if (chunk.type === 'Text') { if (chunk.type === 'Text') {
return stringify(chunk.data); return stringify(chunk.data);
} else { } else {
return chunk.getPrecedence() <= 13 return chunk.get_precedence() <= 13
? `(${chunk.render()})` ? `(${chunk.render()})`
: chunk.render(); : chunk.render();
} }
@ -96,32 +96,32 @@ export default class AttributeWrapper {
.join(' + '); .join(' + ');
} }
const isSelectValueAttribute = const is_select_value_attribute =
name === 'value' && element.node.name === 'select'; name === 'value' && element.node.name === 'select';
const shouldCache = (this.node.shouldCache || isSelectValueAttribute); const should_cache = (this.node.should_cache || is_select_value_attribute);
const last = shouldCache && block.getUniqueName( const last = should_cache && block.get_unique_name(
`${element.var}_${name.replace(/[^a-zA-Z_$]/g, '_')}_value` `${element.var}_${name.replace(/[^a-zA-Z_$]/g, '_')}_value`
); );
if (shouldCache) block.addVariable(last); if (should_cache) block.add_variable(last);
let updater; let updater;
const init = shouldCache ? `${last} = ${value}` : value; const init = should_cache ? `${last} = ${value}` : value;
if (isLegacyInputType) { if (is_legacy_input_type) {
block.builders.hydrate.add_line( block.builders.hydrate.add_line(
`@set_input_type(${element.var}, ${init});` `@set_input_type(${element.var}, ${init});`
); );
updater = `@set_input_type(${element.var}, ${shouldCache ? last : value});`; updater = `@set_input_type(${element.var}, ${should_cache ? last : value});`;
} else if (isSelectValueAttribute) { } else if (is_select_value_attribute) {
// annoying special case // annoying special case
const isMultipleSelect = element.getStaticAttributeValue('multiple'); const is_multiple_select = element.get_static_attribute_value('multiple');
const i = block.getUniqueName('i'); const i = block.get_unique_name('i');
const option = block.getUniqueName('option'); const option = block.get_unique_name('option');
const ifStatement = isMultipleSelect const if_statement = is_multiple_select
? deindent` ? deindent`
${option}.selected = ~${last}.indexOf(${option}.__value);` ${option}.selected = ~${last}.indexOf(${option}.__value);`
: deindent` : deindent`
@ -134,7 +134,7 @@ export default class AttributeWrapper {
for (var ${i} = 0; ${i} < ${element.var}.options.length; ${i} += 1) { for (var ${i} = 0; ${i} < ${element.var}.options.length; ${i} += 1) {
var ${option} = ${element.var}.options[${i}]; var ${option} = ${element.var}.options[${i}];
${ifStatement} ${if_statement}
} }
`; `;
@ -142,36 +142,36 @@ export default class AttributeWrapper {
${last} = ${value}; ${last} = ${value};
${updater} ${updater}
`); `);
} else if (propertyName) { } else if (property_name) {
block.builders.hydrate.add_line( block.builders.hydrate.add_line(
`${element.var}.${propertyName} = ${init};` `${element.var}.${property_name} = ${init};`
); );
updater = `${element.var}.${propertyName} = ${shouldCache ? last : value};`; updater = `${element.var}.${property_name} = ${should_cache ? last : value};`;
} else if (isDataSet) { } else if (is_dataset) {
block.builders.hydrate.add_line( block.builders.hydrate.add_line(
`${element.var}.dataset.${camelCaseName} = ${init};` `${element.var}.dataset.${camel_case_name} = ${init};`
); );
updater = `${element.var}.dataset.${camelCaseName} = ${shouldCache ? last : value};`; updater = `${element.var}.dataset.${camel_case_name} = ${should_cache ? last : value};`;
} else { } else {
block.builders.hydrate.add_line( block.builders.hydrate.add_line(
`${method}(${element.var}, "${name}", ${init});` `${method}(${element.var}, "${name}", ${init});`
); );
updater = `${method}(${element.var}, "${name}", ${shouldCache ? last : value});`; updater = `${method}(${element.var}, "${name}", ${should_cache ? last : value});`;
} }
// only add an update if mutations are involved (or it's a select?) // only add an update if mutations are involved (or it's a select?)
const dependencies = this.node.get_dependencies(); const dependencies = this.node.get_dependencies();
if (dependencies.length > 0 || isSelectValueAttribute) { if (dependencies.length > 0 || is_select_value_attribute) {
const changedCheck = ( const changed_check = (
(block.hasOutros ? `!#current || ` : '') + (block.has_outros ? `!#current || ` : '') +
dependencies.map(dependency => `changed.${dependency}`).join(' || ') dependencies.map(dependency => `changed.${dependency}`).join(' || ')
); );
const updateCachedValue = `${last} !== (${last} = ${value})`; const update_cached_value = `${last} !== (${last} = ${value})`;
const condition = shouldCache const condition = should_cache
? (dependencies.length ? `(${changedCheck}) && ${updateCachedValue}` : updateCachedValue) ? (dependencies.length ? `(${changed_check}) && ${update_cached_value}` : update_cached_value)
: changedCheck; : changed_check;
block.builders.update.add_conditional( block.builders.update.add_conditional(
condition, condition,
@ -179,36 +179,36 @@ export default class AttributeWrapper {
); );
} }
} else { } else {
const value = this.node.getValue(block); const value = this.node.get_value(block);
const statement = ( const statement = (
isLegacyInputType is_legacy_input_type
? `@set_input_type(${element.var}, ${value});` ? `@set_input_type(${element.var}, ${value});`
: propertyName : property_name
? `${element.var}.${propertyName} = ${value};` ? `${element.var}.${property_name} = ${value};`
: isDataSet : is_dataset
? `${element.var}.dataset.${camelCaseName} = ${value};` ? `${element.var}.dataset.${camel_case_name} = ${value};`
: `${method}(${element.var}, "${name}", ${value === true ? '""' : value});` : `${method}(${element.var}, "${name}", ${value === true ? '""' : value});`
); );
block.builders.hydrate.add_line(statement); block.builders.hydrate.add_line(statement);
// special case autofocus. has to be handled in a bit of a weird way // special case autofocus. has to be handled in a bit of a weird way
if (this.node.isTrue && name === 'autofocus') { if (this.node.is_true && name === 'autofocus') {
block.autofocus = element.var; block.autofocus = element.var;
} }
} }
if (isIndirectlyBoundValue) { if (is_indirectly_bound_value) {
const updateValue = `${element.var}.value = ${element.var}.__value;`; const update_value = `${element.var}.value = ${element.var}.__value;`;
block.builders.hydrate.add_line(updateValue); block.builders.hydrate.add_line(update_value);
if (this.node.isDynamic) block.builders.update.add_line(updateValue); if (this.node.is_dynamic) block.builders.update.add_line(update_value);
} }
} }
stringify() { stringify() {
if (this.node.isTrue) return ''; if (this.node.is_true) return '';
const value = this.node.chunks; const value = this.node.chunks;
if (value.length === 0) return `=""`; if (value.length === 0) return `=""`;
@ -222,13 +222,13 @@ export default class AttributeWrapper {
} }
// source: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes // source: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes
const attributeLookup = { const attribute_lookup = {
accept: { appliesTo: ['form', 'input'] }, accept: { applies_to: ['form', 'input'] },
'accept-charset': { propertyName: 'acceptCharset', appliesTo: ['form'] }, 'accept-charset': { property_name: 'acceptCharset', applies_to: ['form'] },
accesskey: { propertyName: 'accessKey' }, accesskey: { property_name: 'accessKey' },
action: { appliesTo: ['form'] }, action: { applies_to: ['form'] },
align: { align: {
appliesTo: [ applies_to: [
'applet', 'applet',
'caption', 'caption',
'col', 'col',
@ -245,16 +245,16 @@ const attributeLookup = {
'tr', 'tr',
], ],
}, },
allowfullscreen: { propertyName: 'allowFullscreen', appliesTo: ['iframe'] }, allowfullscreen: { property_name: 'allowFullscreen', applies_to: ['iframe'] },
alt: { appliesTo: ['applet', 'area', 'img', 'input'] }, alt: { applies_to: ['applet', 'area', 'img', 'input'] },
async: { appliesTo: ['script'] }, async: { applies_to: ['script'] },
autocomplete: { appliesTo: ['form', 'input'] }, autocomplete: { applies_to: ['form', 'input'] },
autofocus: { appliesTo: ['button', 'input', 'keygen', 'select', 'textarea'] }, autofocus: { applies_to: ['button', 'input', 'keygen', 'select', 'textarea'] },
autoplay: { appliesTo: ['audio', 'video'] }, autoplay: { applies_to: ['audio', 'video'] },
autosave: { appliesTo: ['input'] }, autosave: { applies_to: ['input'] },
bgcolor: { bgcolor: {
propertyName: 'bgColor', property_name: 'bgColor',
appliesTo: [ applies_to: [
'body', 'body',
'col', 'col',
'colgroup', 'colgroup',
@ -267,31 +267,31 @@ const attributeLookup = {
'tr', 'tr',
], ],
}, },
border: { appliesTo: ['img', 'object', 'table'] }, border: { applies_to: ['img', 'object', 'table'] },
buffered: { appliesTo: ['audio', 'video'] }, buffered: { applies_to: ['audio', 'video'] },
challenge: { appliesTo: ['keygen'] }, challenge: { applies_to: ['keygen'] },
charset: { appliesTo: ['meta', 'script'] }, charset: { applies_to: ['meta', 'script'] },
checked: { appliesTo: ['command', 'input'] }, checked: { applies_to: ['command', 'input'] },
cite: { appliesTo: ['blockquote', 'del', 'ins', 'q'] }, cite: { applies_to: ['blockquote', 'del', 'ins', 'q'] },
class: { propertyName: 'className' }, class: { property_name: 'className' },
code: { appliesTo: ['applet'] }, code: { applies_to: ['applet'] },
codebase: { propertyName: 'codeBase', appliesTo: ['applet'] }, codebase: { property_name: 'codeBase', applies_to: ['applet'] },
color: { appliesTo: ['basefont', 'font', 'hr'] }, color: { applies_to: ['basefont', 'font', 'hr'] },
cols: { appliesTo: ['textarea'] }, cols: { applies_to: ['textarea'] },
colspan: { propertyName: 'colSpan', appliesTo: ['td', 'th'] }, colspan: { property_name: 'colSpan', applies_to: ['td', 'th'] },
content: { appliesTo: ['meta'] }, content: { applies_to: ['meta'] },
contenteditable: { propertyName: 'contentEditable' }, contenteditable: { property_name: 'contentEditable' },
contextmenu: {}, contextmenu: {},
controls: { appliesTo: ['audio', 'video'] }, controls: { applies_to: ['audio', 'video'] },
coords: { appliesTo: ['area'] }, coords: { applies_to: ['area'] },
data: { appliesTo: ['object'] }, data: { applies_to: ['object'] },
datetime: { propertyName: 'dateTime', appliesTo: ['del', 'ins', 'time'] }, datetime: { property_name: 'dateTime', applies_to: ['del', 'ins', 'time'] },
default: { appliesTo: ['track'] }, default: { applies_to: ['track'] },
defer: { appliesTo: ['script'] }, defer: { applies_to: ['script'] },
dir: {}, dir: {},
dirname: { propertyName: 'dirName', appliesTo: ['input', 'textarea'] }, dirname: { property_name: 'dirName', applies_to: ['input', 'textarea'] },
disabled: { disabled: {
appliesTo: [ applies_to: [
'button', 'button',
'command', 'command',
'fieldset', 'fieldset',
@ -303,13 +303,13 @@ const attributeLookup = {
'textarea', 'textarea',
], ],
}, },
download: { appliesTo: ['a', 'area'] }, download: { applies_to: ['a', 'area'] },
draggable: {}, draggable: {},
dropzone: {}, dropzone: {},
enctype: { appliesTo: ['form'] }, enctype: { applies_to: ['form'] },
for: { propertyName: 'htmlFor', appliesTo: ['label', 'output'] }, for: { property_name: 'htmlFor', applies_to: ['label', 'output'] },
form: { form: {
appliesTo: [ applies_to: [
'button', 'button',
'fieldset', 'fieldset',
'input', 'input',
@ -323,38 +323,38 @@ const attributeLookup = {
'textarea', 'textarea',
], ],
}, },
formaction: { appliesTo: ['input', 'button'] }, formaction: { applies_to: ['input', 'button'] },
headers: { appliesTo: ['td', 'th'] }, headers: { applies_to: ['td', 'th'] },
height: { height: {
appliesTo: ['canvas', 'embed', 'iframe', 'img', 'input', 'object', 'video'], applies_to: ['canvas', 'embed', 'iframe', 'img', 'input', 'object', 'video'],
}, },
hidden: {}, hidden: {},
high: { appliesTo: ['meter'] }, high: { applies_to: ['meter'] },
href: { appliesTo: ['a', 'area', 'base', 'link'] }, href: { applies_to: ['a', 'area', 'base', 'link'] },
hreflang: { appliesTo: ['a', 'area', 'link'] }, hreflang: { applies_to: ['a', 'area', 'link'] },
'http-equiv': { propertyName: 'httpEquiv', appliesTo: ['meta'] }, 'http-equiv': { property_name: 'httpEquiv', applies_to: ['meta'] },
icon: { appliesTo: ['command'] }, icon: { applies_to: ['command'] },
id: {}, id: {},
indeterminate: { appliesTo: ['input'] }, indeterminate: { applies_to: ['input'] },
ismap: { propertyName: 'isMap', appliesTo: ['img'] }, ismap: { property_name: 'isMap', applies_to: ['img'] },
itemprop: {}, itemprop: {},
keytype: { appliesTo: ['keygen'] }, keytype: { applies_to: ['keygen'] },
kind: { appliesTo: ['track'] }, kind: { applies_to: ['track'] },
label: { appliesTo: ['track'] }, label: { applies_to: ['track'] },
lang: {}, lang: {},
language: { appliesTo: ['script'] }, language: { applies_to: ['script'] },
loop: { appliesTo: ['audio', 'bgsound', 'marquee', 'video'] }, loop: { applies_to: ['audio', 'bgsound', 'marquee', 'video'] },
low: { appliesTo: ['meter'] }, low: { applies_to: ['meter'] },
manifest: { appliesTo: ['html'] }, manifest: { applies_to: ['html'] },
max: { appliesTo: ['input', 'meter', 'progress'] }, max: { applies_to: ['input', 'meter', 'progress'] },
maxlength: { propertyName: 'maxLength', appliesTo: ['input', 'textarea'] }, maxlength: { property_name: 'maxLength', applies_to: ['input', 'textarea'] },
media: { appliesTo: ['a', 'area', 'link', 'source', 'style'] }, media: { applies_to: ['a', 'area', 'link', 'source', 'style'] },
method: { appliesTo: ['form'] }, method: { applies_to: ['form'] },
min: { appliesTo: ['input', 'meter'] }, min: { applies_to: ['input', 'meter'] },
multiple: { appliesTo: ['input', 'select'] }, multiple: { applies_to: ['input', 'select'] },
muted: { appliesTo: ['audio', 'video'] }, muted: { applies_to: ['audio', 'video'] },
name: { name: {
appliesTo: [ applies_to: [
'button', 'button',
'form', 'form',
'fieldset', 'fieldset',
@ -370,33 +370,33 @@ const attributeLookup = {
'param', 'param',
], ],
}, },
novalidate: { propertyName: 'noValidate', appliesTo: ['form'] }, novalidate: { property_name: 'noValidate', applies_to: ['form'] },
open: { appliesTo: ['details'] }, open: { applies_to: ['details'] },
optimum: { appliesTo: ['meter'] }, optimum: { applies_to: ['meter'] },
pattern: { appliesTo: ['input'] }, pattern: { applies_to: ['input'] },
ping: { appliesTo: ['a', 'area'] }, ping: { applies_to: ['a', 'area'] },
placeholder: { appliesTo: ['input', 'textarea'] }, placeholder: { applies_to: ['input', 'textarea'] },
poster: { appliesTo: ['video'] }, poster: { applies_to: ['video'] },
preload: { appliesTo: ['audio', 'video'] }, preload: { applies_to: ['audio', 'video'] },
radiogroup: { appliesTo: ['command'] }, radiogroup: { applies_to: ['command'] },
readonly: { propertyName: 'readOnly', appliesTo: ['input', 'textarea'] }, readonly: { property_name: 'readOnly', applies_to: ['input', 'textarea'] },
rel: { appliesTo: ['a', 'area', 'link'] }, rel: { applies_to: ['a', 'area', 'link'] },
required: { appliesTo: ['input', 'select', 'textarea'] }, required: { applies_to: ['input', 'select', 'textarea'] },
reversed: { appliesTo: ['ol'] }, reversed: { applies_to: ['ol'] },
rows: { appliesTo: ['textarea'] }, rows: { applies_to: ['textarea'] },
rowspan: { propertyName: 'rowSpan', appliesTo: ['td', 'th'] }, rowspan: { property_name: 'rowSpan', applies_to: ['td', 'th'] },
sandbox: { appliesTo: ['iframe'] }, sandbox: { applies_to: ['iframe'] },
scope: { appliesTo: ['th'] }, scope: { applies_to: ['th'] },
scoped: { appliesTo: ['style'] }, scoped: { applies_to: ['style'] },
seamless: { appliesTo: ['iframe'] }, seamless: { applies_to: ['iframe'] },
selected: { appliesTo: ['option'] }, selected: { applies_to: ['option'] },
shape: { appliesTo: ['a', 'area'] }, shape: { applies_to: ['a', 'area'] },
size: { appliesTo: ['input', 'select'] }, size: { applies_to: ['input', 'select'] },
sizes: { appliesTo: ['link', 'img', 'source'] }, sizes: { applies_to: ['link', 'img', 'source'] },
span: { appliesTo: ['col', 'colgroup'] }, span: { applies_to: ['col', 'colgroup'] },
spellcheck: {}, spellcheck: {},
src: { src: {
appliesTo: [ applies_to: [
'audio', 'audio',
'embed', 'embed',
'iframe', 'iframe',
@ -408,18 +408,18 @@ const attributeLookup = {
'video', 'video',
], ],
}, },
srcdoc: { appliesTo: ['iframe'] }, srcdoc: { applies_to: ['iframe'] },
srclang: { appliesTo: ['track'] }, srclang: { applies_to: ['track'] },
srcset: { appliesTo: ['img'] }, srcset: { applies_to: ['img'] },
start: { appliesTo: ['ol'] }, start: { applies_to: ['ol'] },
step: { appliesTo: ['input'] }, step: { applies_to: ['input'] },
style: { propertyName: 'style.cssText' }, style: { property_name: 'style.cssText' },
summary: { appliesTo: ['table'] }, summary: { applies_to: ['table'] },
tabindex: { propertyName: 'tabIndex' }, tabindex: { property_name: 'tabIndex' },
target: { appliesTo: ['a', 'area', 'base', 'form'] }, target: { applies_to: ['a', 'area', 'base', 'form'] },
title: {}, title: {},
type: { type: {
appliesTo: [ applies_to: [
'button', 'button',
'command', 'command',
'embed', 'embed',
@ -430,9 +430,9 @@ const attributeLookup = {
'menu', 'menu',
], ],
}, },
usemap: { propertyName: 'useMap', appliesTo: ['img', 'input', 'object'] }, usemap: { property_name: 'useMap', applies_to: ['img', 'input', 'object'] },
value: { value: {
appliesTo: [ applies_to: [
'button', 'button',
'option', 'option',
'input', 'input',
@ -444,14 +444,14 @@ const attributeLookup = {
'textarea', 'textarea',
], ],
}, },
volume: { appliesTo: ['audio', 'video'] }, volume: { applies_to: ['audio', 'video'] },
width: { width: {
appliesTo: ['canvas', 'embed', 'iframe', 'img', 'input', 'object', 'video'], applies_to: ['canvas', 'embed', 'iframe', 'img', 'input', 'object', 'video'],
}, },
wrap: { appliesTo: ['textarea'] }, wrap: { applies_to: ['textarea'] },
}; };
Object.keys(attributeLookup).forEach(name => { Object.keys(attribute_lookup).forEach(name => {
const metadata = attributeLookup[name]; const metadata = attribute_lookup[name];
if (!metadata.propertyName) metadata.propertyName = name; if (!metadata.property_name) metadata.property_name = name;
}); });

@ -10,7 +10,7 @@ import EachBlock from '../../../nodes/EachBlock';
import { Node as INode } from '../../../../interfaces'; import { Node as INode } from '../../../../interfaces';
// TODO this should live in a specific binding // TODO this should live in a specific binding
const readOnlyMediaAttributes = new Set([ const read_only_media_attributes = new Set([
'duration', 'duration',
'buffered', 'buffered',
'seekable', 'seekable',
@ -29,15 +29,14 @@ export default class BindingWrapper {
object: string; object: string;
handler: { handler: {
usesContext: boolean; uses_context: boolean;
mutation: string; mutation: string;
contextual_dependencies: Set<string>, contextual_dependencies: Set<string>,
snippet?: string snippet?: string
}; };
snippet: string; snippet: string;
initialUpdate: string; is_readonly: boolean;
isReadOnly: boolean; needs_lock: boolean;
needsLock: boolean;
constructor(block: Block, node: Binding, parent: ElementWrapper) { constructor(block: Block, node: Binding, parent: ElementWrapper) {
this.node = node; this.node = node;
@ -45,23 +44,23 @@ export default class BindingWrapper {
const { dependencies } = this.node.expression; const { dependencies } = this.node.expression;
block.addDependencies(dependencies); block.add_dependencies(dependencies);
// TODO does this also apply to e.g. `<input type='checkbox' bind:group='foo'>`? // TODO does this also apply to e.g. `<input type='checkbox' bind:group='foo'>`?
if (parent.node.name === 'select') { if (parent.node.name === 'select') {
parent.selectBindingDependencies = dependencies; parent.select_binding_dependencies = dependencies;
dependencies.forEach((prop: string) => { dependencies.forEach((prop: string) => {
parent.renderer.component.indirectDependencies.set(prop, new Set()); parent.renderer.component.indirect_dependencies.set(prop, new Set());
}); });
} }
if (node.isContextual) { if (node.is_contextual) {
// we need to ensure that the each block creates a context including // we need to ensure that the each block creates a context including
// the list and the index, if they're not otherwise referenced // the list and the index, if they're not otherwise referenced
const { name } = get_object(this.node.expression.node); const { name } = get_object(this.node.expression.node);
const eachBlock = this.parent.node.scope.getOwner(name); const each_block = this.parent.node.scope.get_owner(name);
(eachBlock as EachBlock).has_binding = true; (each_block as EachBlock).has_binding = true;
} }
this.object = get_object(this.node.expression.node).name; this.object = get_object(this.node.expression.node).name;
@ -71,28 +70,28 @@ export default class BindingWrapper {
const contextless_snippet = this.parent.renderer.component.source.slice(this.node.expression.node.start, this.node.expression.node.end); const contextless_snippet = this.parent.renderer.component.source.slice(this.node.expression.node.start, this.node.expression.node.end);
// view to model // view to model
this.handler = getEventHandler(this, parent.renderer, block, this.object, contextless_snippet); this.handler = get_event_handler(this, parent.renderer, block, this.object, contextless_snippet);
this.snippet = this.node.expression.render(block); this.snippet = this.node.expression.render(block);
const type = parent.node.getStaticAttributeValue('type'); const type = parent.node.get_static_attribute_value('type');
this.isReadOnly = ( this.is_readonly = (
dimensions.test(this.node.name) || dimensions.test(this.node.name) ||
(parent.node.isMediaNode() && readOnlyMediaAttributes.has(this.node.name)) || (parent.node.is_media_node() && read_only_media_attributes.has(this.node.name)) ||
(parent.node.name === 'input' && type === 'file') // TODO others? (parent.node.name === 'input' && type === 'file') // TODO others?
); );
this.needsLock = this.node.name === 'currentTime'; // TODO others? this.needs_lock = this.node.name === 'currentTime'; // TODO others?
} }
get_dependencies() { get_dependencies() {
const dependencies = new Set(this.node.expression.dependencies); const dependencies = new Set(this.node.expression.dependencies);
this.node.expression.dependencies.forEach((prop: string) => { this.node.expression.dependencies.forEach((prop: string) => {
const indirectDependencies = this.parent.renderer.component.indirectDependencies.get(prop); const indirect_dependencies = this.parent.renderer.component.indirect_dependencies.get(prop);
if (indirectDependencies) { if (indirect_dependencies) {
indirectDependencies.forEach(indirectDependency => { indirect_dependencies.forEach(indirectDependency => {
dependencies.add(indirectDependency); dependencies.add(indirectDependency);
}); });
} }
@ -101,34 +100,34 @@ export default class BindingWrapper {
return dependencies; return dependencies;
} }
isReadOnlyMediaAttribute() { is_readonly_media_attribute() {
return readOnlyMediaAttributes.has(this.node.name); return read_only_media_attributes.has(this.node.name);
} }
render(block: Block, lock: string) { render(block: Block, lock: string) {
if (this.isReadOnly) return; if (this.is_readonly) return;
const { parent } = this; const { parent } = this;
let updateConditions: string[] = this.needsLock ? [`!${lock}`] : []; let update_conditions: string[] = this.needs_lock ? [`!${lock}`] : [];
const dependencyArray = [...this.node.expression.dependencies] const dependency_array = [...this.node.expression.dependencies]
if (dependencyArray.length === 1) { if (dependency_array.length === 1) {
updateConditions.push(`changed.${dependencyArray[0]}`) update_conditions.push(`changed.${dependency_array[0]}`)
} else if (dependencyArray.length > 1) { } else if (dependency_array.length > 1) {
updateConditions.push( update_conditions.push(
`(${dependencyArray.map(prop => `changed.${prop}`).join(' || ')})` `(${dependency_array.map(prop => `changed.${prop}`).join(' || ')})`
) )
} }
// model to view // model to view
let updateDom = getDomUpdater(parent, this); let update_dom = get_dom_updater(parent, this);
// special cases // special cases
switch (this.node.name) { switch (this.node.name) {
case 'group': case 'group':
const bindingGroup = getBindingGroup(parent.renderer, this.node.expression.node); const bindingGroup = get_binding_group(parent.renderer, this.node.expression.node);
block.builders.hydrate.add_line( block.builders.hydrate.add_line(
`ctx.$$binding_groups[${bindingGroup}].push(${parent.var});` `ctx.$$binding_groups[${bindingGroup}].push(${parent.var});`
@ -141,43 +140,43 @@ export default class BindingWrapper {
case 'currentTime': case 'currentTime':
case 'volume': case 'volume':
updateConditions.push(`!isNaN(${this.snippet})`); update_conditions.push(`!isNaN(${this.snippet})`);
break; break;
case 'paused': case 'paused':
// this is necessary to prevent audio restarting by itself // this is necessary to prevent audio restarting by itself
const last = block.getUniqueName(`${parent.var}_is_paused`); const last = block.get_unique_name(`${parent.var}_is_paused`);
block.addVariable(last, 'true'); block.add_variable(last, 'true');
updateConditions.push(`${last} !== (${last} = ${this.snippet})`); update_conditions.push(`${last} !== (${last} = ${this.snippet})`);
updateDom = `${parent.var}[${last} ? "pause" : "play"]();`; update_dom = `${parent.var}[${last} ? "pause" : "play"]();`;
break; break;
case 'value': case 'value':
if (parent.getStaticAttributeValue('type') === 'file') { if (parent.get_static_attribute_value('type') === 'file') {
updateDom = null; update_dom = null;
} }
} }
if (updateDom) { if (update_dom) {
block.builders.update.add_line( block.builders.update.add_line(
updateConditions.length ? `if (${updateConditions.join(' && ')}) ${updateDom}` : updateDom update_conditions.length ? `if (${update_conditions.join(' && ')}) ${update_dom}` : update_dom
); );
} }
if (!/(currentTime|paused)/.test(this.node.name)) { if (!/(currentTime|paused)/.test(this.node.name)) {
block.builders.mount.add_block(updateDom); block.builders.mount.add_block(update_dom);
} }
} }
} }
function getDomUpdater( function get_dom_updater(
element: ElementWrapper, element: ElementWrapper,
binding: BindingWrapper binding: BindingWrapper
) { ) {
const { node } = element; const { node } = element;
if (binding.isReadOnlyMediaAttribute()) { if (binding.is_readonly_media_attribute()) {
return null; return null;
} }
@ -186,13 +185,13 @@ function getDomUpdater(
} }
if (node.name === 'select') { if (node.name === 'select') {
return node.getStaticAttributeValue('multiple') === true ? return node.get_static_attribute_value('multiple') === true ?
`@select_options(${element.var}, ${binding.snippet})` : `@select_options(${element.var}, ${binding.snippet})` :
`@select_option(${element.var}, ${binding.snippet})`; `@select_option(${element.var}, ${binding.snippet})`;
} }
if (binding.node.name === 'group') { if (binding.node.name === 'group') {
const type = node.getStaticAttributeValue('type'); const type = node.get_static_attribute_value('type');
const condition = type === 'checkbox' const condition = type === 'checkbox'
? `~${binding.snippet}.indexOf(${element.var}.__value)` ? `~${binding.snippet}.indexOf(${element.var}.__value)`
@ -204,16 +203,16 @@ function getDomUpdater(
return `${element.var}.${binding.node.name} = ${binding.snippet};`; return `${element.var}.${binding.node.name} = ${binding.snippet};`;
} }
function getBindingGroup(renderer: Renderer, value: Node) { function get_binding_group(renderer: Renderer, value: Node) {
const { parts } = flatten_reference(value); // TODO handle cases involving computed member expressions const { parts } = flatten_reference(value); // TODO handle cases involving computed member expressions
const keypath = parts.join('.'); const keypath = parts.join('.');
// TODO handle contextual bindings — `keypath` should include unique ID of // TODO handle contextual bindings — `keypath` should include unique ID of
// each block that provides context // each block that provides context
let index = renderer.bindingGroups.indexOf(keypath); let index = renderer.binding_groups.indexOf(keypath);
if (index === -1) { if (index === -1) {
index = renderer.bindingGroups.length; index = renderer.binding_groups.length;
renderer.bindingGroups.push(keypath); renderer.binding_groups.push(keypath);
} }
return index; return index;
@ -225,14 +224,14 @@ function mutate_store(store, value, tail) {
: `${store}.set(${value});`; : `${store}.set(${value});`;
} }
function getEventHandler( function get_event_handler(
binding: BindingWrapper, binding: BindingWrapper,
renderer: Renderer, renderer: Renderer,
block: Block, block: Block,
name: string, name: string,
snippet: string snippet: string
) { ) {
const value = getValueFromDom(renderer, binding.parent, binding); const value = get_value_from_dom(renderer, binding.parent, binding);
const store = binding.object[0] === '$' ? binding.object.slice(1) : null; const store = binding.object[0] === '$' ? binding.object.slice(1) : null;
let tail = ''; let tail = '';
@ -241,11 +240,11 @@ function getEventHandler(
tail = renderer.component.source.slice(start, end); tail = renderer.component.source.slice(start, end);
} }
if (binding.node.isContextual) { if (binding.node.is_contextual) {
const { object, property, snippet } = block.bindings.get(name); const { object, property, snippet } = block.bindings.get(name);
return { return {
usesContext: true, uses_context: true,
mutation: store mutation: store
? mutate_store(store, value, tail) ? mutate_store(store, value, tail)
: `${snippet}${tail} = ${value};`, : `${snippet}${tail} = ${value};`,
@ -259,7 +258,7 @@ function getEventHandler(
if (binding.node.expression.node.type === 'MemberExpression') { if (binding.node.expression.node.type === 'MemberExpression') {
return { return {
usesContext: binding.node.expression.usesContext, uses_context: binding.node.expression.uses_context,
mutation, mutation,
contextual_dependencies: binding.node.expression.contextual_dependencies, contextual_dependencies: binding.node.expression.contextual_dependencies,
snippet snippet
@ -267,13 +266,13 @@ function getEventHandler(
} }
return { return {
usesContext: false, uses_context: false,
mutation, mutation,
contextual_dependencies: new Set() contextual_dependencies: new Set()
}; };
} }
function getValueFromDom( function get_value_from_dom(
renderer: Renderer, renderer: Renderer,
element: ElementWrapper, element: ElementWrapper,
binding: BindingWrapper binding: BindingWrapper
@ -287,16 +286,16 @@ function getValueFromDom(
// <select bind:value='selected> // <select bind:value='selected>
if (node.name === 'select') { if (node.name === 'select') {
return node.getStaticAttributeValue('multiple') === true ? return node.get_static_attribute_value('multiple') === true ?
`@select_multiple_value(this)` : `@select_multiple_value(this)` :
`@select_value(this)`; `@select_value(this)`;
} }
const type = node.getStaticAttributeValue('type'); const type = node.get_static_attribute_value('type');
// <input type='checkbox' bind:group='foo'> // <input type='checkbox' bind:group='foo'>
if (name === 'group') { if (name === 'group') {
const bindingGroup = getBindingGroup(renderer, binding.node.expression.node); const bindingGroup = get_binding_group(renderer, binding.node.expression.node);
if (type === 'checkbox') { if (type === 'checkbox') {
return `@get_binding_group_value($$binding_groups[${bindingGroup}])`; return `@get_binding_group_value($$binding_groups[${bindingGroup}])`;
} }

@ -16,15 +16,14 @@ export default class StyleAttributeWrapper extends AttributeWrapper {
parent: ElementWrapper; parent: ElementWrapper;
render(block: Block) { render(block: Block) {
const styleProps = optimizeStyle(this.node.chunks); const style_props = optimize_style(this.node.chunks);
if (!styleProps) return super.render(block); if (!style_props) return super.render(block);
styleProps.forEach((prop: StyleProp) => { style_props.forEach((prop: StyleProp) => {
let value; let value;
if (isDynamic(prop.value)) { if (is_dynamic(prop.value)) {
const propDependencies = new Set(); const prop_dependencies = new Set();
let shouldCache;
value = value =
((prop.value.length === 1 || prop.value[0].type === 'Text') ? '' : `"" + `) + ((prop.value.length === 1 || prop.value[0].type === 'Text') ? '' : `"" + `) +
@ -35,17 +34,17 @@ export default class StyleAttributeWrapper extends AttributeWrapper {
} else { } else {
const snippet = chunk.render(); const snippet = chunk.render();
add_to_set(propDependencies, chunk.dependencies); add_to_set(prop_dependencies, chunk.dependencies);
return chunk.getPrecedence() <= 13 ? `(${snippet})` : snippet; return chunk.get_precedence() <= 13 ? `(${snippet})` : snippet;
} }
}) })
.join(' + '); .join(' + ');
if (propDependencies.size) { if (prop_dependencies.size) {
const dependencies = Array.from(propDependencies); const dependencies = Array.from(prop_dependencies);
const condition = ( const condition = (
(block.hasOutros ? `!#current || ` : '') + (block.has_outros ? `!#current || ` : '') +
dependencies.map(dependency => `changed.${dependency}`).join(' || ') dependencies.map(dependency => `changed.${dependency}`).join(' || ')
); );
@ -65,7 +64,7 @@ export default class StyleAttributeWrapper extends AttributeWrapper {
} }
} }
function optimizeStyle(value: Node[]) { function optimize_style(value: Node[]) {
const props: { key: string, value: Node[] }[] = []; const props: { key: string, value: Node[] }[] = [];
let chunks = value.slice(); let chunks = value.slice();
@ -74,26 +73,26 @@ function optimizeStyle(value: Node[]) {
if (chunk.type !== 'Text') return null; if (chunk.type !== 'Text') return null;
const keyMatch = /^\s*([\w-]+):\s*/.exec(chunk.data); const key_match = /^\s*([\w-]+):\s*/.exec(chunk.data);
if (!keyMatch) return null; if (!key_match) return null;
const key = keyMatch[1]; const key = key_match[1];
const offset = keyMatch.index + keyMatch[0].length; const offset = key_match.index + key_match[0].length;
const remainingData = chunk.data.slice(offset); const remaining_data = chunk.data.slice(offset);
if (remainingData) { if (remaining_data) {
chunks[0] = { chunks[0] = {
start: chunk.start + offset, start: chunk.start + offset,
end: chunk.end, end: chunk.end,
type: 'Text', type: 'Text',
data: remainingData data: remaining_data
}; };
} else { } else {
chunks.shift(); chunks.shift();
} }
const result = getStyleValue(chunks); const result = get_style_value(chunks);
if (!result) return null; if (!result) return null;
props.push({ key, value: result.value }); props.push({ key, value: result.value });
@ -103,11 +102,11 @@ function optimizeStyle(value: Node[]) {
return props; return props;
} }
function getStyleValue(chunks: Node[]) { function get_style_value(chunks: Node[]) {
const value: Node[] = []; const value: Node[] = [];
let inUrl = false; let in_url = false;
let quoteMark = null; let quote_mark = null;
let escaped = false; let escaped = false;
while (chunks.length) { while (chunks.length) {
@ -122,15 +121,15 @@ function getStyleValue(chunks: Node[]) {
escaped = false; escaped = false;
} else if (char === '\\') { } else if (char === '\\') {
escaped = true; escaped = true;
} else if (char === quoteMark) { } else if (char === quote_mark) {
quoteMark === null; quote_mark === null;
} else if (char === '"' || char === "'") { } else if (char === '"' || char === "'") {
quoteMark = char; quote_mark = char;
} else if (char === ')' && inUrl) { } else if (char === ')' && in_url) {
inUrl = false; in_url = false;
} else if (char === 'u' && chunk.data.slice(c, c + 4) === 'url(') { } else if (char === 'u' && chunk.data.slice(c, c + 4) === 'url(') {
inUrl = true; in_url = true;
} else if (char === ';' && !inUrl && !quoteMark) { } else if (char === ';' && !in_url && !quote_mark) {
break; break;
} }
@ -147,14 +146,14 @@ function getStyleValue(chunks: Node[]) {
} }
while (/[;\s]/.test(chunk.data[c])) c += 1; while (/[;\s]/.test(chunk.data[c])) c += 1;
const remainingData = chunk.data.slice(c); const remaining_data = chunk.data.slice(c);
if (remainingData) { if (remaining_data) {
chunks.unshift({ chunks.unshift({
start: chunk.start + c, start: chunk.start + c,
end: chunk.end, end: chunk.end,
type: 'Text', type: 'Text',
data: remainingData data: remaining_data
}); });
break; break;
@ -172,6 +171,6 @@ function getStyleValue(chunks: Node[]) {
}; };
} }
function isDynamic(value: Node[]) { function is_dynamic(value: Node[]) {
return value.length > 1 || value[0].type !== 'Text'; return value.length > 1 || value[0].type !== 'Text';
} }

@ -16,71 +16,71 @@ import { dimensions } from '../../../../utils/patterns';
import Binding from './Binding'; import Binding from './Binding';
import InlineComponentWrapper from '../InlineComponent'; import InlineComponentWrapper from '../InlineComponent';
import add_to_set from '../../../utils/add_to_set'; import add_to_set from '../../../utils/add_to_set';
import addEventHandlers from '../shared/addEventHandlers'; import add_event_handlers from '../shared/add_event_handlers';
import addActions from '../shared/addActions'; import add_actions from '../shared/add_actions';
import create_debugging_comment from '../shared/create_debugging_comment'; import create_debugging_comment from '../shared/create_debugging_comment';
import { get_context_merger } from '../shared/get_context_merger'; import { get_context_merger } from '../shared/get_context_merger';
const events = [ const events = [
{ {
eventNames: ['input'], event_names: ['input'],
filter: (node: Element, name: string) => filter: (node: Element, name: string) =>
node.name === 'textarea' || node.name === 'textarea' ||
node.name === 'input' && !/radio|checkbox|range/.test(node.getStaticAttributeValue('type')) node.name === 'input' && !/radio|checkbox|range/.test(node.get_static_attribute_value('type'))
}, },
{ {
eventNames: ['change'], event_names: ['change'],
filter: (node: Element, name: string) => filter: (node: Element, name: string) =>
node.name === 'select' || node.name === 'select' ||
node.name === 'input' && /radio|checkbox/.test(node.getStaticAttributeValue('type')) node.name === 'input' && /radio|checkbox/.test(node.get_static_attribute_value('type'))
}, },
{ {
eventNames: ['change', 'input'], event_names: ['change', 'input'],
filter: (node: Element, name: string) => filter: (node: Element, name: string) =>
node.name === 'input' && node.getStaticAttributeValue('type') === 'range' node.name === 'input' && node.get_static_attribute_value('type') === 'range'
}, },
{ {
eventNames: ['resize'], event_names: ['resize'],
filter: (node: Element, name: string) => filter: (node: Element, name: string) =>
dimensions.test(name) dimensions.test(name)
}, },
// media events // media events
{ {
eventNames: ['timeupdate'], event_names: ['timeupdate'],
filter: (node: Element, name: string) => filter: (node: Element, name: string) =>
node.isMediaNode() && node.is_media_node() &&
(name === 'currentTime' || name === 'played') (name === 'currentTime' || name === 'played')
}, },
{ {
eventNames: ['durationchange'], event_names: ['durationchange'],
filter: (node: Element, name: string) => filter: (node: Element, name: string) =>
node.isMediaNode() && node.is_media_node() &&
name === 'duration' name === 'duration'
}, },
{ {
eventNames: ['play', 'pause'], event_names: ['play', 'pause'],
filter: (node: Element, name: string) => filter: (node: Element, name: string) =>
node.isMediaNode() && node.is_media_node() &&
name === 'paused' name === 'paused'
}, },
{ {
eventNames: ['progress'], event_names: ['progress'],
filter: (node: Element, name: string) => filter: (node: Element, name: string) =>
node.isMediaNode() && node.is_media_node() &&
name === 'buffered' name === 'buffered'
}, },
{ {
eventNames: ['loadedmetadata'], event_names: ['loadedmetadata'],
filter: (node: Element, name: string) => filter: (node: Element, name: string) =>
node.isMediaNode() && node.is_media_node() &&
(name === 'buffered' || name === 'seekable') (name === 'buffered' || name === 'seekable')
}, },
{ {
eventNames: ['volumechange'], event_names: ['volumechange'],
filter: (node: Element, name: string) => filter: (node: Element, name: string) =>
node.isMediaNode() && node.is_media_node() &&
name === 'volume' name === 'volume'
} }
]; ];
@ -90,10 +90,10 @@ export default class ElementWrapper extends Wrapper {
fragment: FragmentWrapper; fragment: FragmentWrapper;
attributes: AttributeWrapper[]; attributes: AttributeWrapper[];
bindings: Binding[]; bindings: Binding[];
classDependencies: string[]; class_dependencies: string[];
slot_block: Block; slot_block: Block;
selectBindingDependencies?: Set<string>; select_binding_dependencies?: Set<string>;
var: string; var: string;
@ -102,13 +102,13 @@ export default class ElementWrapper extends Wrapper {
block: Block, block: Block,
parent: Wrapper, parent: Wrapper,
node: Element, node: Element,
stripWhitespace: boolean, strip_whitespace: boolean,
nextSibling: Wrapper next_sibling: Wrapper
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.var = node.name.replace(/[^a-zA-Z0-9_$]/g, '_') this.var = node.name.replace(/[^a-zA-Z0-9_$]/g, '_')
this.classDependencies = []; this.class_dependencies = [];
this.attributes = this.node.attributes.map(attribute => { this.attributes = this.node.attributes.map(attribute => {
if (attribute.name === 'slot') { if (attribute.name === 'slot') {
@ -127,12 +127,12 @@ export default class ElementWrapper extends Wrapper {
} }
if (owner && owner.node.type === 'InlineComponent') { if (owner && owner.node.type === 'InlineComponent') {
const name = attribute.getStaticValue(); const name = attribute.get_static_value();
if (!(owner as InlineComponentWrapper).slots.has(name)) { if (!(owner as InlineComponentWrapper).slots.has(name)) {
const child_block = block.child({ const child_block = block.child({
comment: create_debugging_comment(node, this.renderer.component), comment: create_debugging_comment(node, this.renderer.component),
name: this.renderer.component.getUniqueName(`create_${sanitize(name)}_slot`) name: this.renderer.component.get_unique_name(`create_${sanitize(name)}_slot`)
}); });
const fn = get_context_merger(this.node.lets); const fn = get_context_merger(this.node.lets);
@ -161,46 +161,46 @@ export default class ElementWrapper extends Wrapper {
this.bindings = this.node.bindings.map(binding => new Binding(block, binding, this)); this.bindings = this.node.bindings.map(binding => new Binding(block, binding, this));
if (node.intro || node.outro) { if (node.intro || node.outro) {
if (node.intro) block.addIntro(node.intro.is_local); if (node.intro) block.add_intro(node.intro.is_local);
if (node.outro) block.addOutro(node.outro.is_local); if (node.outro) block.add_outro(node.outro.is_local);
} }
if (node.animation) { if (node.animation) {
block.addAnimation(); block.add_animation();
} }
// add directive and handler dependencies // add directive and handler dependencies
[node.animation, node.outro, ...node.actions, ...node.classes].forEach(directive => { [node.animation, node.outro, ...node.actions, ...node.classes].forEach(directive => {
if (directive && directive.expression) { if (directive && directive.expression) {
block.addDependencies(directive.expression.dependencies); block.add_dependencies(directive.expression.dependencies);
} }
}); });
node.handlers.forEach(handler => { node.handlers.forEach(handler => {
if (handler.expression) { if (handler.expression) {
block.addDependencies(handler.expression.dependencies); block.add_dependencies(handler.expression.dependencies);
} }
}); });
if (this.parent) { if (this.parent) {
if (node.actions.length > 0) this.parent.cannotUseInnerHTML(); if (node.actions.length > 0) this.parent.cannot_use_innerhtml();
if (node.animation) this.parent.cannotUseInnerHTML(); if (node.animation) this.parent.cannot_use_innerhtml();
if (node.bindings.length > 0) this.parent.cannotUseInnerHTML(); if (node.bindings.length > 0) this.parent.cannot_use_innerhtml();
if (node.classes.length > 0) this.parent.cannotUseInnerHTML(); if (node.classes.length > 0) this.parent.cannot_use_innerhtml();
if (node.intro || node.outro) this.parent.cannotUseInnerHTML(); if (node.intro || node.outro) this.parent.cannot_use_innerhtml();
if (node.handlers.length > 0) this.parent.cannotUseInnerHTML(); if (node.handlers.length > 0) this.parent.cannot_use_innerhtml();
if (this.node.name === 'option') this.parent.cannotUseInnerHTML(); if (this.node.name === 'option') this.parent.cannot_use_innerhtml();
if (renderer.options.dev) { if (renderer.options.dev) {
this.parent.cannotUseInnerHTML(); // need to use add_location this.parent.cannot_use_innerhtml(); // need to use add_location
} }
} }
this.fragment = new FragmentWrapper(renderer, block, node.children, this, stripWhitespace, nextSibling); this.fragment = new FragmentWrapper(renderer, block, node.children, this, strip_whitespace, next_sibling);
if (this.slot_block) { if (this.slot_block) {
block.parent.addDependencies(block.dependencies); block.parent.add_dependencies(block.dependencies);
// appalling hack // appalling hack
const index = block.parent.wrappers.indexOf(this); const index = block.parent.wrappers.indexOf(this);
@ -209,11 +209,11 @@ export default class ElementWrapper extends Wrapper {
} }
} }
render(block: Block, parentNode: string, parentNodes: string) { render(block: Block, parent_node: string, parent_nodes: string) {
const { renderer } = this; const { renderer } = this;
if (this.node.name === 'slot') { if (this.node.name === 'slot') {
const slotName = this.getStaticAttributeValue('name') || 'default'; const slotName = this.get_static_attribute_value('name') || 'default';
renderer.slots.add(slotName); renderer.slots.add(slotName);
} }
@ -224,33 +224,33 @@ export default class ElementWrapper extends Wrapper {
} }
const node = this.var; const node = this.var;
const nodes = parentNodes && block.getUniqueName(`${this.var}_nodes`) // if we're in unclaimable territory, i.e. <head>, parentNodes is null const nodes = parent_nodes && block.get_unique_name(`${this.var}_nodes`) // if we're in unclaimable territory, i.e. <head>, parent_nodes is null
block.addVariable(node); block.add_variable(node);
const renderStatement = this.getRenderStatement(); const render_statement = this.get_render_statement();
block.builders.create.add_line( block.builders.create.add_line(
`${node} = ${renderStatement};` `${node} = ${render_statement};`
); );
if (renderer.options.hydratable) { if (renderer.options.hydratable) {
if (parentNodes) { if (parent_nodes) {
block.builders.claim.add_block(deindent` block.builders.claim.add_block(deindent`
${node} = ${this.getClaimStatement(parentNodes)}; ${node} = ${this.get_claim_statement(parent_nodes)};
var ${nodes} = @children(${this.node.name === 'template' ? `${node}.content` : node}); var ${nodes} = @children(${this.node.name === 'template' ? `${node}.content` : node});
`); `);
} else { } else {
block.builders.claim.add_line( block.builders.claim.add_line(
`${node} = ${renderStatement};` `${node} = ${render_statement};`
); );
} }
} }
if (parentNode) { if (parent_node) {
block.builders.mount.add_line( block.builders.mount.add_line(
`@append(${parentNode}, ${node});` `@append(${parent_node}, ${node});`
); );
if (parentNode === 'document.head') { if (parent_node === 'document.head') {
block.builders.destroy.add_line(`@detach(${node});`); block.builders.destroy.add_line(`@detach(${node});`);
} }
} else { } else {
@ -262,20 +262,20 @@ export default class ElementWrapper extends Wrapper {
} }
// insert static children with textContent or innerHTML // insert static children with textContent or innerHTML
if (!this.node.namespace && this.canUseInnerHTML && this.fragment.nodes.length > 0) { if (!this.node.namespace && this.can_use_innerhtml && this.fragment.nodes.length > 0) {
if (this.fragment.nodes.length === 1 && this.fragment.nodes[0].node.type === 'Text') { if (this.fragment.nodes.length === 1 && this.fragment.nodes[0].node.type === 'Text') {
block.builders.create.add_line( block.builders.create.add_line(
`${node}.textContent = ${stringify(this.fragment.nodes[0].data)};` `${node}.textContent = ${stringify(this.fragment.nodes[0].data)};`
); );
} else { } else {
const innerHTML = escape( const inner_html = escape(
this.fragment.nodes this.fragment.nodes
.map(toHTML) .map(to_html)
.join('') .join('')
); );
block.builders.create.add_line( block.builders.create.add_line(
`${node}.innerHTML = \`${innerHTML}\`;` `${node}.innerHTML = \`${inner_html}\`;`
); );
} }
} else { } else {
@ -288,23 +288,23 @@ export default class ElementWrapper extends Wrapper {
}); });
} }
const eventHandlerOrBindingUsesContext = ( const event_handler_or_binding_uses_context = (
this.bindings.some(binding => binding.handler.usesContext) || this.bindings.some(binding => binding.handler.uses_context) ||
this.node.handlers.some(handler => handler.usesContext) || this.node.handlers.some(handler => handler.uses_context) ||
this.node.actions.some(action => action.usesContext) this.node.actions.some(action => action.uses_context)
); );
if (eventHandlerOrBindingUsesContext) { if (event_handler_or_binding_uses_context) {
block.maintainContext = true; block.maintain_context = true;
} }
this.addBindings(block); this.add_bindings(block);
this.addEventHandlers(block); this.add_event_handlers(block);
this.addAttributes(block); this.add_attributes(block);
this.addTransitions(block); this.add_transitions(block);
this.addAnimation(block); this.add_animation(block);
this.addActions(block); this.add_actions(block);
this.addClasses(block); this.add_classes(block);
if (nodes && this.renderer.options.hydratable) { if (nodes && this.renderer.options.hydratable) {
block.builders.claim.add_line( block.builders.claim.add_line(
@ -312,7 +312,7 @@ export default class ElementWrapper extends Wrapper {
); );
} }
function toHTML(wrapper: ElementWrapper | TextWrapper) { function to_html(wrapper: ElementWrapper | TextWrapper) {
if (wrapper.node.type === 'Text') { if (wrapper.node.type === 'Text') {
const { parent } = wrapper.node; const { parent } = wrapper.node;
@ -339,18 +339,18 @@ export default class ElementWrapper extends Wrapper {
if (is_void(wrapper.node.name)) return open + '>'; if (is_void(wrapper.node.name)) return open + '>';
return `${open}>${wrapper.fragment.nodes.map(toHTML).join('')}</${wrapper.node.name}>`; return `${open}>${wrapper.fragment.nodes.map(to_html).join('')}</${wrapper.node.name}>`;
} }
if (renderer.options.dev) { if (renderer.options.dev) {
const loc = renderer.locate(this.node.start); const loc = renderer.locate(this.node.start);
block.builders.hydrate.add_line( block.builders.hydrate.add_line(
`@add_location(${this.var}, ${renderer.fileVar}, ${loc.line}, ${loc.column}, ${this.node.start});` `@add_location(${this.var}, ${renderer.file_var}, ${loc.line}, ${loc.column}, ${this.node.start});`
); );
} }
} }
getRenderStatement() { get_render_statement() {
const { name, namespace } = this.node; const { name, namespace } = this.node;
if (namespace === 'http://www.w3.org/2000/svg') { if (namespace === 'http://www.w3.org/2000/svg') {
@ -364,7 +364,7 @@ export default class ElementWrapper extends Wrapper {
return `@element("${name}")`; return `@element("${name}")`;
} }
getClaimStatement(nodes: string) { get_claim_statement(nodes: string) {
const attributes = this.node.attributes const attributes = this.node.attributes
.filter((attr: Node) => attr.type === 'Attribute') .filter((attr: Node) => attr.type === 'Attribute')
.map((attr: Node) => `${quote_name_if_necessary(attr.name)}: true`) .map((attr: Node) => `${quote_name_if_necessary(attr.name)}: true`)
@ -379,22 +379,22 @@ export default class ElementWrapper extends Wrapper {
: `{}`}, ${this.node.namespace === namespaces.svg ? true : false})`; : `{}`}, ${this.node.namespace === namespaces.svg ? true : false})`;
} }
addBindings(block: Block) { add_bindings(block: Block) {
const { renderer } = this; const { renderer } = this;
if (this.bindings.length === 0) return; if (this.bindings.length === 0) return;
renderer.component.has_reactive_assignments = true; renderer.component.has_reactive_assignments = true;
const lock = this.bindings.some(binding => binding.needsLock) ? const lock = this.bindings.some(binding => binding.needs_lock) ?
block.getUniqueName(`${this.var}_updating`) : block.get_unique_name(`${this.var}_updating`) :
null; null;
if (lock) block.addVariable(lock, 'false'); if (lock) block.add_variable(lock, 'false');
const groups = events const groups = events
.map(event => ({ .map(event => ({
events: event.eventNames, events: event.event_names,
bindings: this.bindings bindings: this.bindings
.filter(binding => binding.node.name !== 'this') .filter(binding => binding.node.name !== 'this')
.filter(binding => event.filter(this.node, binding.node.name)) .filter(binding => event.filter(this.node, binding.node.name))
@ -402,7 +402,7 @@ export default class ElementWrapper extends Wrapper {
.filter(group => group.bindings.length); .filter(group => group.bindings.length);
groups.forEach(group => { groups.forEach(group => {
const handler = renderer.component.getUniqueName(`${this.var}_${group.events.join('_')}_handler`); const handler = renderer.component.get_unique_name(`${this.var}_${group.events.join('_')}_handler`);
renderer.component.add_var({ renderer.component.add_var({
name: handler, name: handler,
@ -411,7 +411,7 @@ export default class ElementWrapper extends Wrapper {
}); });
// TODO figure out how to handle locks // TODO figure out how to handle locks
const needsLock = group.bindings.some(binding => binding.needsLock); const needs_lock = group.bindings.some(binding => binding.needs_lock);
const dependencies = new Set(); const dependencies = new Set();
const contextual_dependencies = new Set(); const contextual_dependencies = new Set();
@ -430,11 +430,11 @@ export default class ElementWrapper extends Wrapper {
// own hands // own hands
let animation_frame; let animation_frame;
if (group.events[0] === 'timeupdate') { if (group.events[0] === 'timeupdate') {
animation_frame = block.getUniqueName(`${this.var}_animationframe`); animation_frame = block.get_unique_name(`${this.var}_animationframe`);
block.addVariable(animation_frame); block.add_variable(animation_frame);
} }
const has_local_function = contextual_dependencies.size > 0 || needsLock || animation_frame; const has_local_function = contextual_dependencies.size > 0 || needs_lock || animation_frame;
let callee; let callee;
@ -446,7 +446,7 @@ export default class ElementWrapper extends Wrapper {
${animation_frame && deindent` ${animation_frame && deindent`
cancelAnimationFrame(${animation_frame}); cancelAnimationFrame(${animation_frame});
if (!${this.var}.paused) ${animation_frame} = requestAnimationFrame(${handler});`} if (!${this.var}.paused) ${animation_frame} = requestAnimationFrame(${handler});`}
${needsLock && `${lock} = true;`} ${needs_lock && `${lock} = true;`}
ctx.${handler}.call(${this.var}${contextual_dependencies.size > 0 ? ', ctx' : ''}); ctx.${handler}.call(${this.var}${contextual_dependencies.size > 0 ? ', ctx' : ''});
} }
`); `);
@ -466,8 +466,8 @@ export default class ElementWrapper extends Wrapper {
group.events.forEach(name => { group.events.forEach(name => {
if (name === 'resize') { if (name === 'resize') {
// special case // special case
const resize_listener = block.getUniqueName(`${this.var}_resize_listener`); const resize_listener = block.get_unique_name(`${this.var}_resize_listener`);
block.addVariable(resize_listener); block.add_variable(resize_listener);
block.builders.mount.add_line( block.builders.mount.add_line(
`${resize_listener} = @add_resize_listener(${this.var}, ${callee}.bind(${this.var}));` `${resize_listener} = @add_resize_listener(${this.var}, ${callee}.bind(${this.var}));`
@ -483,14 +483,14 @@ export default class ElementWrapper extends Wrapper {
} }
}); });
const someInitialStateIsUndefined = group.bindings const some_initial_state_is_undefined = group.bindings
.map(binding => `${binding.snippet} === void 0`) .map(binding => `${binding.snippet} === void 0`)
.join(' || '); .join(' || ');
if (this.node.name === 'select' || group.bindings.find(binding => binding.node.name === 'indeterminate' || binding.isReadOnlyMediaAttribute())) { if (this.node.name === 'select' || group.bindings.find(binding => binding.node.name === 'indeterminate' || binding.is_readonly_media_attribute())) {
const callback = has_local_function ? handler : `() => ${callee}.call(${this.var})`; const callback = has_local_function ? handler : `() => ${callee}.call(${this.var})`;
block.builders.hydrate.add_line( block.builders.hydrate.add_line(
`if (${someInitialStateIsUndefined}) @add_render_callback(${callback});` `if (${some_initial_state_is_undefined}) @add_render_callback(${callback});`
); );
} }
@ -507,7 +507,7 @@ export default class ElementWrapper extends Wrapper {
const this_binding = this.bindings.find(b => b.node.name === 'this'); const this_binding = this.bindings.find(b => b.node.name === 'this');
if (this_binding) { if (this_binding) {
const name = renderer.component.getUniqueName(`${this.var}_binding`); const name = renderer.component.get_unique_name(`${this.var}_binding`);
renderer.component.add_var({ renderer.component.add_var({
name, name,
@ -520,7 +520,7 @@ export default class ElementWrapper extends Wrapper {
const args = []; const args = [];
for (const arg of handler.contextual_dependencies) { for (const arg of handler.contextual_dependencies) {
args.push(arg); args.push(arg);
block.addVariable(arg, `ctx.${arg}`); block.add_variable(arg, `ctx.${arg}`);
} }
renderer.component.partly_hoisted.push(deindent` renderer.component.partly_hoisted.push(deindent`
@ -542,25 +542,25 @@ export default class ElementWrapper extends Wrapper {
} }
} }
addAttributes(block: Block) { add_attributes(block: Block) {
if (this.node.attributes.find(attr => attr.type === 'Spread')) { if (this.node.attributes.find(attr => attr.type === 'Spread')) {
this.addSpreadAttributes(block); this.add_spread_attributes(block);
return; return;
} }
this.attributes.forEach((attribute: Attribute) => { this.attributes.forEach((attribute: Attribute) => {
if (attribute.node.name === 'class' && attribute.node.isDynamic) { if (attribute.node.name === 'class' && attribute.node.is_dynamic) {
this.classDependencies.push(...attribute.node.dependencies); this.class_dependencies.push(...attribute.node.dependencies);
} }
attribute.render(block); attribute.render(block);
}); });
} }
addSpreadAttributes(block: Block) { add_spread_attributes(block: Block) {
const levels = block.getUniqueName(`${this.var}_levels`); const levels = block.get_unique_name(`${this.var}_levels`);
const data = block.getUniqueName(`${this.var}_data`); const data = block.get_unique_name(`${this.var}_data`);
const initialProps = []; const initial_props = [];
const updates = []; const updates = [];
this.node.attributes this.node.attributes
@ -570,15 +570,15 @@ export default class ElementWrapper extends Wrapper {
? `(${[...attr.dependencies].map(d => `changed.${d}`).join(' || ')})` ? `(${[...attr.dependencies].map(d => `changed.${d}`).join(' || ')})`
: null; : null;
if (attr.isSpread) { if (attr.is_spread) {
const snippet = attr.expression.render(block); const snippet = attr.expression.render(block);
initialProps.push(snippet); initial_props.push(snippet);
updates.push(condition ? `${condition} && ${snippet}` : snippet); updates.push(condition ? `${condition} && ${snippet}` : snippet);
} else { } else {
const snippet = `{ ${quote_name_if_necessary(attr.name)}: ${attr.getValue(block)} }`; const snippet = `{ ${quote_name_if_necessary(attr.name)}: ${attr.get_value(block)} }`;
initialProps.push(snippet); initial_props.push(snippet);
updates.push(condition ? `${condition} && ${snippet}` : snippet); updates.push(condition ? `${condition} && ${snippet}` : snippet);
} }
@ -586,7 +586,7 @@ export default class ElementWrapper extends Wrapper {
block.builders.init.add_block(deindent` block.builders.init.add_block(deindent`
var ${levels} = [ var ${levels} = [
${initialProps.join(',\n')} ${initial_props.join(',\n')}
]; ];
var ${data} = {}; var ${data} = {};
@ -606,11 +606,11 @@ export default class ElementWrapper extends Wrapper {
`); `);
} }
addEventHandlers(block: Block) { add_event_handlers(block: Block) {
addEventHandlers(block, this.var, this.node.handlers); add_event_handlers(block, this.var, this.node.handlers);
} }
addTransitions( add_transitions(
block: Block block: Block
) { ) {
const { intro, outro } = this.node; const { intro, outro } = this.node;
@ -620,12 +620,12 @@ export default class ElementWrapper extends Wrapper {
if (intro === outro) { if (intro === outro) {
// bidirectional transition // bidirectional transition
const name = block.getUniqueName(`${this.var}_transition`); const name = block.get_unique_name(`${this.var}_transition`);
const snippet = intro.expression const snippet = intro.expression
? intro.expression.render(block) ? intro.expression.render(block)
: '{}'; : '{}';
block.addVariable(name); block.add_variable(name);
const fn = component.qualify(intro.name); const fn = component.qualify(intro.name);
@ -662,11 +662,11 @@ export default class ElementWrapper extends Wrapper {
} }
else { else {
const introName = intro && block.getUniqueName(`${this.var}_intro`); const intro_name = intro && block.get_unique_name(`${this.var}_intro`);
const outroName = outro && block.getUniqueName(`${this.var}_outro`); const outro_name = outro && block.get_unique_name(`${this.var}_outro`);
if (intro) { if (intro) {
block.addVariable(introName); block.add_variable(intro_name);
const snippet = intro.expression const snippet = intro.expression
? intro.expression.render(block) ? intro.expression.render(block)
: '{}'; : '{}';
@ -678,19 +678,19 @@ export default class ElementWrapper extends Wrapper {
if (outro) { if (outro) {
intro_block = deindent` intro_block = deindent`
@add_render_callback(() => { @add_render_callback(() => {
if (${outroName}) ${outroName}.end(1); if (${outro_name}) ${outro_name}.end(1);
if (!${introName}) ${introName} = @create_in_transition(${this.var}, ${fn}, ${snippet}); if (!${intro_name}) ${intro_name} = @create_in_transition(${this.var}, ${fn}, ${snippet});
${introName}.start(); ${intro_name}.start();
}); });
`; `;
block.builders.outro.add_line(`if (${introName}) ${introName}.invalidate();`); block.builders.outro.add_line(`if (${intro_name}) ${intro_name}.invalidate();`);
} else { } else {
intro_block = deindent` intro_block = deindent`
if (!${introName}) { if (!${intro_name}) {
@add_render_callback(() => { @add_render_callback(() => {
${introName} = @create_in_transition(${this.var}, ${fn}, ${snippet}); ${intro_name} = @create_in_transition(${this.var}, ${fn}, ${snippet});
${introName}.start(); ${intro_name}.start();
}); });
} }
`; `;
@ -708,7 +708,7 @@ export default class ElementWrapper extends Wrapper {
} }
if (outro) { if (outro) {
block.addVariable(outroName); block.add_variable(outro_name);
const snippet = outro.expression const snippet = outro.expression
? outro.expression.render(block) ? outro.expression.render(block)
: '{}'; : '{}';
@ -717,14 +717,14 @@ export default class ElementWrapper extends Wrapper {
if (!intro) { if (!intro) {
block.builders.intro.add_block(deindent` block.builders.intro.add_block(deindent`
if (${outroName}) ${outroName}.end(1); if (${outro_name}) ${outro_name}.end(1);
`); `);
} }
// TODO hide elements that have outro'd (unless they belong to a still-outroing // TODO hide elements that have outro'd (unless they belong to a still-outroing
// group) prior to their removal from the DOM // group) prior to their removal from the DOM
let outro_block = deindent` let outro_block = deindent`
${outroName} = @create_out_transition(${this.var}, ${fn}, ${snippet}); ${outro_name} = @create_out_transition(${this.var}, ${fn}, ${snippet});
`; `;
if (outro_block) { if (outro_block) {
@ -737,21 +737,21 @@ export default class ElementWrapper extends Wrapper {
block.builders.outro.add_block(outro_block); block.builders.outro.add_block(outro_block);
block.builders.destroy.add_conditional('detaching', `if (${outroName}) ${outroName}.end();`); block.builders.destroy.add_conditional('detaching', `if (${outro_name}) ${outro_name}.end();`);
} }
} }
} }
addAnimation(block: Block) { add_animation(block: Block) {
if (!this.node.animation) return; if (!this.node.animation) return;
const { component } = this.renderer; const { component } = this.renderer;
const rect = block.getUniqueName('rect'); const rect = block.get_unique_name('rect');
const stop_animation = block.getUniqueName('stop_animation'); const stop_animation = block.get_unique_name('stop_animation');
block.addVariable(rect); block.add_variable(rect);
block.addVariable(stop_animation, '@noop'); block.add_variable(stop_animation, '@noop');
block.builders.measure.add_block(deindent` block.builders.measure.add_block(deindent`
${rect} = ${this.var}.getBoundingClientRect(); ${rect} = ${this.var}.getBoundingClientRect();
@ -772,11 +772,11 @@ export default class ElementWrapper extends Wrapper {
`); `);
} }
addActions(block: Block) { add_actions(block: Block) {
addActions(this.renderer.component, block, this.var, this.node.actions); add_actions(this.renderer.component, block, this.var, this.node.actions);
} }
addClasses(block: Block) { add_classes(block: Block) {
this.node.classes.forEach(classDir => { this.node.classes.forEach(classDir => {
const { expression, name } = classDir; const { expression, name } = classDir;
let snippet, dependencies; let snippet, dependencies;
@ -791,10 +791,10 @@ export default class ElementWrapper extends Wrapper {
block.builders.hydrate.add_line(updater); block.builders.hydrate.add_line(updater);
if ((dependencies && dependencies.size > 0) || this.classDependencies.length) { if ((dependencies && dependencies.size > 0) || this.class_dependencies.length) {
const allDeps = this.classDependencies.concat(...dependencies); const all_dependencies = this.class_dependencies.concat(...dependencies);
const deps = allDeps.map(dependency => `changed${quote_prop_if_necessary(dependency)}`).join(' || '); const deps = all_dependencies.map(dependency => `changed${quote_prop_if_necessary(dependency)}`).join(' || ');
const condition = allDeps.length > 1 ? `(${deps})` : deps; const condition = all_dependencies.length > 1 ? `(${deps})` : deps;
block.builders.update.add_conditional( block.builders.update.add_conditional(
condition, condition,
@ -804,14 +804,14 @@ export default class ElementWrapper extends Wrapper {
}); });
} }
getStaticAttributeValue(name: string) { get_static_attribute_value(name: string) {
const attribute = this.node.attributes.find( const attribute = this.node.attributes.find(
(attr: Attribute) => attr.type === 'Attribute' && attr.name.toLowerCase() === name (attr: Attribute) => attr.type === 'Attribute' && attr.name.toLowerCase() === name
); );
if (!attribute) return null; if (!attribute) return null;
if (attribute.isTrue) return true; if (attribute.is_true) return true;
if (attribute.chunks.length === 0) return ''; if (attribute.chunks.length === 0) return '';
if (attribute.chunks.length === 1 && attribute.chunks[0].type === 'Text') { if (attribute.chunks.length === 1 && attribute.chunks[0].type === 'Text') {
@ -821,13 +821,13 @@ export default class ElementWrapper extends Wrapper {
return null; return null;
} }
addCssClass(className = this.component.stylesheet.id) { add_css_class(className = this.component.stylesheet.id) {
const classAttribute = this.attributes.find(a => a.name === 'class'); const class_attribute = this.attributes.find(a => a.name === 'class');
if (classAttribute && !classAttribute.isTrue) { if (class_attribute && !class_attribute.is_true) {
if (classAttribute.chunks.length === 1 && classAttribute.chunks[0].type === 'Text') { if (class_attribute.chunks.length === 1 && class_attribute.chunks[0].type === 'Text') {
(classAttribute.chunks[0] as Text).data += ` ${className}`; (class_attribute.chunks[0] as Text).data += ` ${className}`;
} else { } else {
(classAttribute.chunks as Node[]).push( (class_attribute.chunks as Node[]).push(
new Text(this.component, this, this.scope, { new Text(this.component, this, this.scope, {
type: 'Text', type: 'Text',
data: ` ${className}` data: ` ${className}`

@ -14,10 +14,10 @@ import Text from './Text';
import Title from './Title'; import Title from './Title';
import Window from './Window'; import Window from './Window';
import Node from '../../nodes/shared/Node'; import Node from '../../nodes/shared/Node';
import { trim_start, trim_end } from '../../../utils/trim';
import TextWrapper from './Text'; import TextWrapper from './Text';
import Renderer from '../Renderer'; import Renderer from '../Renderer';
import Block from '../Block'; import Block from '../Block';
import { trim_start, trim_end } from '../../../utils/trim';
const wrappers = { const wrappers = {
AwaitBlock, AwaitBlock,
@ -51,13 +51,13 @@ export default class FragmentWrapper {
block: Block, block: Block,
nodes: Node[], nodes: Node[],
parent: Wrapper, parent: Wrapper,
stripWhitespace: boolean, strip_whitespace: boolean,
nextSibling: Wrapper next_sibling: Wrapper
) { ) {
this.nodes = []; this.nodes = [];
let lastChild: Wrapper; let last_child: Wrapper;
let windowWrapper; let window_wrapper;
let i = nodes.length; let i = nodes.length;
while (i--) { while (i--) {
@ -74,7 +74,7 @@ export default class FragmentWrapper {
// special case — this is an easy way to remove whitespace surrounding // special case — this is an easy way to remove whitespace surrounding
// <svelte:window/>. lil hacky but it works // <svelte:window/>. lil hacky but it works
if (child.type === 'Window') { if (child.type === 'Window') {
windowWrapper = new Window(renderer, block, parent, child); window_wrapper = new Window(renderer, block, parent, child);
continue; continue;
} }
@ -84,19 +84,19 @@ export default class FragmentWrapper {
// We want to remove trailing whitespace inside an element/component/block, // We want to remove trailing whitespace inside an element/component/block,
// *unless* there is no whitespace between this node and its next sibling // *unless* there is no whitespace between this node and its next sibling
if (this.nodes.length === 0) { if (this.nodes.length === 0) {
const shouldTrim = ( const should_trim = (
nextSibling ? (nextSibling.node.type === 'Text' && /^\s/.test(nextSibling.data)) : !child.hasAncestor('EachBlock') next_sibling ? (next_sibling.node.type === 'Text' && /^\s/.test(next_sibling.data)) : !child.has_ancestor('EachBlock')
); );
if (shouldTrim) { if (should_trim) {
data = trim_end(data); data = trim_end(data);
if (!data) continue; if (!data) continue;
} }
} }
// glue text nodes (which could e.g. be separated by comments) together // glue text nodes (which could e.g. be separated by comments) together
if (lastChild && lastChild.node.type === 'Text') { if (last_child && last_child.node.type === 'Text') {
lastChild.data = data + lastChild.data; last_child.data = data + last_child.data;
continue; continue;
} }
@ -105,19 +105,19 @@ export default class FragmentWrapper {
this.nodes.unshift(wrapper); this.nodes.unshift(wrapper);
link(lastChild, lastChild = wrapper); link(last_child, last_child = wrapper);
} else { } else {
const Wrapper = wrappers[child.type]; const Wrapper = wrappers[child.type];
if (!Wrapper) continue; if (!Wrapper) continue;
const wrapper = new Wrapper(renderer, block, parent, child, stripWhitespace, lastChild || nextSibling); const wrapper = new Wrapper(renderer, block, parent, child, strip_whitespace, last_child || next_sibling);
this.nodes.unshift(wrapper); this.nodes.unshift(wrapper);
link(lastChild, lastChild = wrapper); link(last_child, last_child = wrapper);
} }
} }
if (stripWhitespace) { if (strip_whitespace) {
const first = this.nodes[0] as TextWrapper; const first = this.nodes[0] as TextWrapper;
if (first && first.node.type === 'Text') { if (first && first.node.type === 'Text') {
@ -133,15 +133,15 @@ export default class FragmentWrapper {
} }
} }
if (windowWrapper) { if (window_wrapper) {
this.nodes.unshift(windowWrapper); this.nodes.unshift(window_wrapper);
link(lastChild, windowWrapper); link(last_child, window_wrapper);
} }
} }
render(block: Block, parentNode: string, parentNodes: string) { render(block: Block, parentNode: string, parent_nodes: string) {
for (let i = 0; i < this.nodes.length; i += 1) { for (let i = 0; i < this.nodes.length; i += 1) {
this.nodes[i].render(block, parentNode, parentNodes); this.nodes[i].render(block, parentNode, parent_nodes);
} }
} }
} }

@ -12,24 +12,24 @@ export default class HeadWrapper extends Wrapper {
block: Block, block: Block,
parent: Wrapper, parent: Wrapper,
node: Head, node: Head,
stripWhitespace: boolean, strip_whitespace: boolean,
nextSibling: Wrapper next_sibling: Wrapper
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.canUseInnerHTML = false; this.can_use_innerhtml = false;
this.fragment = new FragmentWrapper( this.fragment = new FragmentWrapper(
renderer, renderer,
block, block,
node.children, node.children,
this, this,
stripWhitespace, strip_whitespace,
nextSibling next_sibling
); );
} }
render(block: Block, parentNode: string, parentNodes: string) { render(block: Block, parent_node: string, parent_nodes: string) {
this.fragment.render(block, 'document.head', null); this.fragment.render(block, 'document.head', null);
} }
} }

@ -8,7 +8,7 @@ import ElseBlock from '../../nodes/ElseBlock';
import FragmentWrapper from './Fragment'; import FragmentWrapper from './Fragment';
import deindent from '../../utils/deindent'; import deindent from '../../utils/deindent';
function isElseIf(node: ElseBlock) { function is_else_if(node: ElseBlock) {
return ( return (
node && node.children.length === 1 && node.children[0].type === 'IfBlock' node && node.children.length === 1 && node.children[0].type === 'IfBlock'
); );
@ -18,7 +18,7 @@ class IfBlockBranch extends Wrapper {
block: Block; block: Block;
fragment: FragmentWrapper; fragment: FragmentWrapper;
condition: string; condition: string;
isDynamic: boolean; is_dynamic: boolean;
var = null; var = null;
@ -27,8 +27,8 @@ class IfBlockBranch extends Wrapper {
block: Block, block: Block,
parent: IfBlockWrapper, parent: IfBlockWrapper,
node: IfBlock | ElseBlock, node: IfBlock | ElseBlock,
stripWhitespace: boolean, strip_whitespace: boolean,
nextSibling: Wrapper next_sibling: Wrapper
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
@ -36,14 +36,14 @@ class IfBlockBranch extends Wrapper {
this.block = block.child({ this.block = block.child({
comment: create_debugging_comment(node, parent.renderer.component), comment: create_debugging_comment(node, parent.renderer.component),
name: parent.renderer.component.getUniqueName( name: parent.renderer.component.get_unique_name(
(node as IfBlock).expression ? `create_if_block` : `create_else_block` (node as IfBlock).expression ? `create_if_block` : `create_else_block`
) )
}); });
this.fragment = new FragmentWrapper(renderer, this.block, node.children, parent, stripWhitespace, nextSibling); this.fragment = new FragmentWrapper(renderer, this.block, node.children, parent, strip_whitespace, next_sibling);
this.isDynamic = this.block.dependencies.size > 0; this.is_dynamic = this.block.dependencies.size > 0;
} }
} }
@ -58,55 +58,53 @@ export default class IfBlockWrapper extends Wrapper {
block: Block, block: Block,
parent: Wrapper, parent: Wrapper,
node: EachBlock, node: EachBlock,
stripWhitespace: boolean, strip_whitespace: boolean,
nextSibling: Wrapper next_sibling: Wrapper
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
const { component } = renderer; this.cannot_use_innerhtml();
this.cannotUseInnerHTML();
this.branches = []; this.branches = [];
const blocks: Block[] = []; const blocks: Block[] = [];
let isDynamic = false; let is_dynamic = false;
let hasIntros = false; let has_intros = false;
let hasOutros = false; let has_outros = false;
const createBranches = (node: IfBlock) => { const create_branches = (node: IfBlock) => {
const branch = new IfBlockBranch( const branch = new IfBlockBranch(
renderer, renderer,
block, block,
this, this,
node, node,
stripWhitespace, strip_whitespace,
nextSibling next_sibling
); );
this.branches.push(branch); this.branches.push(branch);
blocks.push(branch.block); blocks.push(branch.block);
block.addDependencies(node.expression.dependencies); block.add_dependencies(node.expression.dependencies);
if (branch.block.dependencies.size > 0) { if (branch.block.dependencies.size > 0) {
isDynamic = true; is_dynamic = true;
block.addDependencies(branch.block.dependencies); block.add_dependencies(branch.block.dependencies);
} }
if (branch.block.hasIntros) hasIntros = true; if (branch.block.has_intros) has_intros = true;
if (branch.block.hasOutros) hasOutros = true; if (branch.block.has_outros) has_outros = true;
if (isElseIf(node.else)) { if (is_else_if(node.else)) {
createBranches(node.else.children[0]); create_branches(node.else.children[0]);
} else if (node.else) { } else if (node.else) {
const branch = new IfBlockBranch( const branch = new IfBlockBranch(
renderer, renderer,
block, block,
this, this,
node.else, node.else,
stripWhitespace, strip_whitespace,
nextSibling next_sibling
); );
this.branches.push(branch); this.branches.push(branch);
@ -114,21 +112,21 @@ export default class IfBlockWrapper extends Wrapper {
blocks.push(branch.block); blocks.push(branch.block);
if (branch.block.dependencies.size > 0) { if (branch.block.dependencies.size > 0) {
isDynamic = true; is_dynamic = true;
block.addDependencies(branch.block.dependencies); block.add_dependencies(branch.block.dependencies);
} }
if (branch.block.hasIntros) hasIntros = true; if (branch.block.has_intros) has_intros = true;
if (branch.block.hasOutros) hasOutros = true; if (branch.block.has_outros) has_outros = true;
} }
}; };
createBranches(this.node); create_branches(this.node);
blocks.forEach(block => { blocks.forEach(block => {
block.hasUpdateMethod = isDynamic; block.has_update_method = is_dynamic;
block.hasIntroMethod = hasIntros; block.has_intro_method = has_intros;
block.hasOutroMethod = hasOutros; block.has_outro_method = has_outros;
}); });
renderer.blocks.push(...blocks); renderer.blocks.push(...blocks);
@ -136,60 +134,60 @@ export default class IfBlockWrapper extends Wrapper {
render( render(
block: Block, block: Block,
parentNode: string, parent_node: string,
parentNodes: string parent_nodes: string
) { ) {
const name = this.var; const name = this.var;
const needsAnchor = this.next ? !this.next.isDomNode() : !parentNode || !this.parent.isDomNode(); const needs_anchor = this.next ? !this.next.is_dom_node() : !parent_node || !this.parent.is_dom_node();
const anchor = needsAnchor const anchor = needs_anchor
? block.getUniqueName(`${name}_anchor`) ? block.get_unique_name(`${name}_anchor`)
: (this.next && this.next.var) || 'null'; : (this.next && this.next.var) || 'null';
const hasElse = !(this.branches[this.branches.length - 1].condition); const has_else = !(this.branches[this.branches.length - 1].condition);
const if_name = hasElse ? '' : `if (${name}) `; const if_name = has_else ? '' : `if (${name}) `;
const dynamic = this.branches[0].block.hasUpdateMethod; // can use [0] as proxy for all, since they necessarily have the same value const dynamic = this.branches[0].block.has_update_method; // can use [0] as proxy for all, since they necessarily have the same value
const hasIntros = this.branches[0].block.hasIntroMethod; const has_intros = this.branches[0].block.has_intro_method;
const hasOutros = this.branches[0].block.hasOutroMethod; const has_outros = this.branches[0].block.has_outro_method;
const has_transitions = hasIntros || hasOutros; const has_transitions = has_intros || has_outros;
const vars = { name, anchor, if_name, hasElse, has_transitions }; const vars = { name, anchor, if_name, has_else, has_transitions };
if (this.node.else) { if (this.node.else) {
if (hasOutros) { if (has_outros) {
this.renderCompoundWithOutros(block, parentNode, parentNodes, dynamic, vars); this.render_compound_with_outros(block, parent_node, parent_nodes, dynamic, vars);
block.builders.outro.add_line(`if (${name}) ${name}.o();`); block.builders.outro.add_line(`if (${name}) ${name}.o();`);
} else { } else {
this.renderCompound(block, parentNode, parentNodes, dynamic, vars); this.render_compound(block, parent_node, parent_nodes, dynamic, vars);
} }
} else { } else {
this.renderSimple(block, parentNode, parentNodes, dynamic, vars); this.render_simple(block, parent_node, parent_nodes, dynamic, vars);
if (hasOutros) { if (has_outros) {
block.builders.outro.add_line(`if (${name}) ${name}.o();`); block.builders.outro.add_line(`if (${name}) ${name}.o();`);
} }
} }
block.builders.create.add_line(`${if_name}${name}.c();`); block.builders.create.add_line(`${if_name}${name}.c();`);
if (parentNodes && this.renderer.options.hydratable) { if (parent_nodes && this.renderer.options.hydratable) {
block.builders.claim.add_line( block.builders.claim.add_line(
`${if_name}${name}.l(${parentNodes});` `${if_name}${name}.l(${parent_nodes});`
); );
} }
if (hasIntros || hasOutros) { if (has_intros || has_outros) {
block.builders.intro.add_line(`if (${name}) ${name}.i();`); block.builders.intro.add_line(`if (${name}) ${name}.i();`);
} }
if (needsAnchor) { if (needs_anchor) {
block.addElement( block.add_element(
anchor, anchor,
`@comment()`, `@comment()`,
parentNodes && `@comment()`, parent_nodes && `@comment()`,
parentNode parent_node
); );
} }
@ -198,16 +196,16 @@ export default class IfBlockWrapper extends Wrapper {
}); });
} }
renderCompound( render_compound(
block: Block, block: Block,
parentNode: string, parent_node: string,
parentNodes: string, parent_nodes: string,
dynamic, dynamic,
{ name, anchor, hasElse, if_name, has_transitions } { name, anchor, has_else, if_name, has_transitions }
) { ) {
const select_block_type = this.renderer.component.getUniqueName(`select_block_type`); const select_block_type = this.renderer.component.get_unique_name(`select_block_type`);
const current_block_type = block.getUniqueName(`current_block_type`); const current_block_type = block.get_unique_name(`current_block_type`);
const current_block_type_and = hasElse ? '' : `${current_block_type} && `; const current_block_type_and = has_else ? '' : `${current_block_type} && `;
block.builders.init.add_block(deindent` block.builders.init.add_block(deindent`
function ${select_block_type}(ctx) { function ${select_block_type}(ctx) {
@ -222,21 +220,21 @@ export default class IfBlockWrapper extends Wrapper {
var ${name} = ${current_block_type_and}${current_block_type}(ctx); var ${name} = ${current_block_type_and}${current_block_type}(ctx);
`); `);
const initialMountNode = parentNode || '#target'; const initial_mount_node = parent_node || '#target';
const anchorNode = parentNode ? 'null' : 'anchor'; const anchor_node = parent_node ? 'null' : 'anchor';
block.builders.mount.add_line( block.builders.mount.add_line(
`${if_name}${name}.m(${initialMountNode}, ${anchorNode});` `${if_name}${name}.m(${initial_mount_node}, ${anchor_node});`
); );
const updateMountNode = this.getUpdateMountNode(anchor); const update_mount_node = this.get_update_mount_node(anchor);
const changeBlock = deindent` const change_block = deindent`
${if_name}${name}.d(1); ${if_name}${name}.d(1);
${name} = ${current_block_type_and}${current_block_type}(ctx); ${name} = ${current_block_type_and}${current_block_type}(ctx);
if (${name}) { if (${name}) {
${name}.c(); ${name}.c();
${has_transitions && `${name}.i(1);`} ${has_transitions && `${name}.i(1);`}
${name}.m(${updateMountNode}, ${anchor}); ${name}.m(${update_mount_node}, ${anchor});
} }
`; `;
@ -245,41 +243,41 @@ export default class IfBlockWrapper extends Wrapper {
if (${current_block_type} === (${current_block_type} = ${select_block_type}(ctx)) && ${name}) { if (${current_block_type} === (${current_block_type} = ${select_block_type}(ctx)) && ${name}) {
${name}.p(changed, ctx); ${name}.p(changed, ctx);
} else { } else {
${changeBlock} ${change_block}
} }
`); `);
} else { } else {
block.builders.update.add_block(deindent` block.builders.update.add_block(deindent`
if (${current_block_type} !== (${current_block_type} = ${select_block_type}(ctx))) { if (${current_block_type} !== (${current_block_type} = ${select_block_type}(ctx))) {
${changeBlock} ${change_block}
} }
`); `);
} }
block.builders.destroy.add_line(`${if_name}${name}.d(${parentNode ? '' : 'detaching'});`); block.builders.destroy.add_line(`${if_name}${name}.d(${parent_node ? '' : 'detaching'});`);
} }
// if any of the siblings have outros, we need to keep references to the blocks // if any of the siblings have outros, we need to keep references to the blocks
// (TODO does this only apply to bidi transitions?) // (TODO does this only apply to bidi transitions?)
renderCompoundWithOutros( render_compound_with_outros(
block: Block, block: Block,
parentNode: string, parent_node: string,
parentNodes: string, parent_nodes: string,
dynamic, dynamic,
{ name, anchor, hasElse, has_transitions } { name, anchor, has_else, has_transitions }
) { ) {
const select_block_type = this.renderer.component.getUniqueName(`select_block_type`); const select_block_type = this.renderer.component.get_unique_name(`select_block_type`);
const current_block_type_index = block.getUniqueName(`current_block_type_index`); const current_block_type_index = block.get_unique_name(`current_block_type_index`);
const previous_block_index = block.getUniqueName(`previous_block_index`); const previous_block_index = block.get_unique_name(`previous_block_index`);
const if_block_creators = block.getUniqueName(`if_block_creators`); const if_block_creators = block.get_unique_name(`if_block_creators`);
const if_blocks = block.getUniqueName(`if_blocks`); const if_blocks = block.get_unique_name(`if_blocks`);
const if_current_block_type_index = hasElse const if_current_block_type_index = has_else
? '' ? ''
: `if (~${current_block_type_index}) `; : `if (~${current_block_type_index}) `;
block.addVariable(current_block_type_index); block.add_variable(current_block_type_index);
block.addVariable(name); block.add_variable(name);
block.builders.init.add_block(deindent` block.builders.init.add_block(deindent`
var ${if_block_creators} = [ var ${if_block_creators} = [
@ -292,11 +290,11 @@ export default class IfBlockWrapper extends Wrapper {
${this.branches ${this.branches
.map(({ condition }, i) => `${condition ? `if (${condition}) ` : ''}return ${i};`) .map(({ condition }, i) => `${condition ? `if (${condition}) ` : ''}return ${i};`)
.join('\n')} .join('\n')}
${!hasElse && `return -1;`} ${!has_else && `return -1;`}
} }
`); `);
if (hasElse) { if (has_else) {
block.builders.init.add_block(deindent` block.builders.init.add_block(deindent`
${current_block_type_index} = ${select_block_type}(ctx); ${current_block_type_index} = ${select_block_type}(ctx);
${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](ctx); ${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](ctx);
@ -309,16 +307,16 @@ export default class IfBlockWrapper extends Wrapper {
`); `);
} }
const initialMountNode = parentNode || '#target'; const initial_mount_node = parent_node || '#target';
const anchorNode = parentNode ? 'null' : 'anchor'; const anchor_node = parent_node ? 'null' : 'anchor';
block.builders.mount.add_line( block.builders.mount.add_line(
`${if_current_block_type_index}${if_blocks}[${current_block_type_index}].m(${initialMountNode}, ${anchorNode});` `${if_current_block_type_index}${if_blocks}[${current_block_type_index}].m(${initial_mount_node}, ${anchor_node});`
); );
const updateMountNode = this.getUpdateMountNode(anchor); const update_mount_node = this.get_update_mount_node(anchor);
const destroyOldBlock = deindent` const destroy_old_block = deindent`
@group_outros(); @group_outros();
@on_outro(() => { @on_outro(() => {
${if_blocks}[${previous_block_index}].d(1); ${if_blocks}[${previous_block_index}].d(1);
@ -328,29 +326,29 @@ export default class IfBlockWrapper extends Wrapper {
@check_outros(); @check_outros();
`; `;
const createNewBlock = deindent` const create_new_block = deindent`
${name} = ${if_blocks}[${current_block_type_index}]; ${name} = ${if_blocks}[${current_block_type_index}];
if (!${name}) { if (!${name}) {
${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](ctx); ${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](ctx);
${name}.c(); ${name}.c();
} }
${has_transitions && `${name}.i(1);`} ${has_transitions && `${name}.i(1);`}
${name}.m(${updateMountNode}, ${anchor}); ${name}.m(${update_mount_node}, ${anchor});
`; `;
const changeBlock = hasElse const change_block = has_else
? deindent` ? deindent`
${destroyOldBlock} ${destroy_old_block}
${createNewBlock} ${create_new_block}
` `
: deindent` : deindent`
if (${name}) { if (${name}) {
${destroyOldBlock} ${destroy_old_block}
} }
if (~${current_block_type_index}) { if (~${current_block_type_index}) {
${createNewBlock} ${create_new_block}
} else { } else {
${name} = null; ${name} = null;
} }
@ -363,7 +361,7 @@ export default class IfBlockWrapper extends Wrapper {
if (${current_block_type_index} === ${previous_block_index}) { if (${current_block_type_index} === ${previous_block_index}) {
${if_current_block_type_index}${if_blocks}[${current_block_type_index}].p(changed, ctx); ${if_current_block_type_index}${if_blocks}[${current_block_type_index}].p(changed, ctx);
} else { } else {
${changeBlock} ${change_block}
} }
`); `);
} else { } else {
@ -371,20 +369,20 @@ export default class IfBlockWrapper extends Wrapper {
var ${previous_block_index} = ${current_block_type_index}; var ${previous_block_index} = ${current_block_type_index};
${current_block_type_index} = ${select_block_type}(ctx); ${current_block_type_index} = ${select_block_type}(ctx);
if (${current_block_type_index} !== ${previous_block_index}) { if (${current_block_type_index} !== ${previous_block_index}) {
${changeBlock} ${change_block}
} }
`); `);
} }
block.builders.destroy.add_line(deindent` block.builders.destroy.add_line(deindent`
${if_current_block_type_index}${if_blocks}[${current_block_type_index}].d(${parentNode ? '' : 'detaching'}); ${if_current_block_type_index}${if_blocks}[${current_block_type_index}].d(${parent_node ? '' : 'detaching'});
`); `);
} }
renderSimple( render_simple(
block: Block, block: Block,
parentNode: string, parent_node: string,
parentNodes: string, parent_nodes: string,
dynamic, dynamic,
{ name, anchor, if_name, has_transitions } { name, anchor, if_name, has_transitions }
) { ) {
@ -394,14 +392,14 @@ export default class IfBlockWrapper extends Wrapper {
var ${name} = (${branch.condition}) && ${branch.block.name}(ctx); var ${name} = (${branch.condition}) && ${branch.block.name}(ctx);
`); `);
const initialMountNode = parentNode || '#target'; const initial_mount_node = parent_node || '#target';
const anchorNode = parentNode ? 'null' : 'anchor'; const anchor_node = parent_node ? 'null' : 'anchor';
block.builders.mount.add_line( block.builders.mount.add_line(
`if (${name}) ${name}.m(${initialMountNode}, ${anchorNode});` `if (${name}) ${name}.m(${initial_mount_node}, ${anchor_node});`
); );
const updateMountNode = this.getUpdateMountNode(anchor); const update_mount_node = this.get_update_mount_node(anchor);
const enter = dynamic const enter = dynamic
? deindent` ? deindent`
@ -412,7 +410,7 @@ export default class IfBlockWrapper extends Wrapper {
${name} = ${branch.block.name}(ctx); ${name} = ${branch.block.name}(ctx);
${name}.c(); ${name}.c();
${has_transitions && `${name}.i(1);`} ${has_transitions && `${name}.i(1);`}
${name}.m(${updateMountNode}, ${anchor}); ${name}.m(${update_mount_node}, ${anchor});
} }
` `
: deindent` : deindent`
@ -420,7 +418,7 @@ export default class IfBlockWrapper extends Wrapper {
${name} = ${branch.block.name}(ctx); ${name} = ${branch.block.name}(ctx);
${name}.c(); ${name}.c();
${has_transitions && `${name}.i(1);`} ${has_transitions && `${name}.i(1);`}
${name}.m(${updateMountNode}, ${anchor}); ${name}.m(${update_mount_node}, ${anchor});
${has_transitions && `} else { ${has_transitions && `} else {
${name}.i(1);`} ${name}.i(1);`}
} }
@ -428,7 +426,7 @@ export default class IfBlockWrapper extends Wrapper {
// no `p()` here — we don't want to update outroing nodes, // no `p()` here — we don't want to update outroing nodes,
// as that will typically result in glitching // as that will typically result in glitching
const exit = branch.block.hasOutroMethod const exit = branch.block.has_outro_method
? deindent` ? deindent`
@group_outros(); @group_outros();
@on_outro(() => { @on_outro(() => {
@ -452,6 +450,6 @@ export default class IfBlockWrapper extends Wrapper {
} }
`); `);
block.builders.destroy.add_line(`${if_name}${name}.d(${parentNode ? '' : 'detaching'});`); block.builders.destroy.add_line(`${if_name}${name}.d(${parent_node ? '' : 'detaching'});`);
} }
} }

@ -26,37 +26,37 @@ export default class InlineComponentWrapper extends Wrapper {
block: Block, block: Block,
parent: Wrapper, parent: Wrapper,
node: InlineComponent, node: InlineComponent,
stripWhitespace: boolean, strip_whitespace: boolean,
nextSibling: Wrapper next_sibling: Wrapper
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.cannotUseInnerHTML(); this.cannot_use_innerhtml();
if (this.node.expression) { if (this.node.expression) {
block.addDependencies(this.node.expression.dependencies); block.add_dependencies(this.node.expression.dependencies);
} }
this.node.attributes.forEach(attr => { this.node.attributes.forEach(attr => {
block.addDependencies(attr.dependencies); block.add_dependencies(attr.dependencies);
}); });
this.node.bindings.forEach(binding => { this.node.bindings.forEach(binding => {
if (binding.isContextual) { if (binding.is_contextual) {
// we need to ensure that the each block creates a context including // we need to ensure that the each block creates a context including
// the list and the index, if they're not otherwise referenced // the list and the index, if they're not otherwise referenced
const { name } = get_object(binding.expression.node); const { name } = get_object(binding.expression.node);
const eachBlock = this.node.scope.getOwner(name); const each_block = this.node.scope.get_owner(name);
(eachBlock as EachBlock).has_binding = true; (each_block as EachBlock).has_binding = true;
} }
block.addDependencies(binding.expression.dependencies); block.add_dependencies(binding.expression.dependencies);
}); });
this.node.handlers.forEach(handler => { this.node.handlers.forEach(handler => {
if (handler.expression) { if (handler.expression) {
block.addDependencies(handler.expression.dependencies); block.add_dependencies(handler.expression.dependencies);
} }
}); });
@ -69,7 +69,7 @@ export default class InlineComponentWrapper extends Wrapper {
if (this.node.children.length) { if (this.node.children.length) {
const default_slot = block.child({ const default_slot = block.child({
comment: create_debugging_comment(node, renderer.component), comment: create_debugging_comment(node, renderer.component),
name: renderer.component.getUniqueName(`create_default_slot`) name: renderer.component.get_unique_name(`create_default_slot`)
}); });
this.renderer.blocks.push(default_slot); this.renderer.blocks.push(default_slot);
@ -81,7 +81,7 @@ export default class InlineComponentWrapper extends Wrapper {
scope: this.node.scope, scope: this.node.scope,
fn fn
}); });
this.fragment = new FragmentWrapper(renderer, default_slot, node.children, this, stripWhitespace, nextSibling); this.fragment = new FragmentWrapper(renderer, default_slot, node.children, this, strip_whitespace, next_sibling);
const dependencies = new Set(); const dependencies = new Set();
@ -92,16 +92,16 @@ export default class InlineComponentWrapper extends Wrapper {
} }
}); });
block.addDependencies(dependencies); block.add_dependencies(dependencies);
} }
block.addOutro(); block.add_outro();
} }
render( render(
block: Block, block: Block,
parentNode: string, parentNode: string,
parentNodes: string parent_nodes: string
) { ) {
const { renderer } = this; const { renderer } = this;
const { component } = renderer; const { component } = renderer;
@ -115,24 +115,24 @@ export default class InlineComponentWrapper extends Wrapper {
const postupdates: string[] = []; const postupdates: string[] = [];
let props; let props;
const name_changes = block.getUniqueName(`${name}_changes`); const name_changes = block.get_unique_name(`${name}_changes`);
const usesSpread = !!this.node.attributes.find(a => a.isSpread); const uses_spread = !!this.node.attributes.find(a => a.is_spread);
const slot_props = Array.from(this.slots).map(([name, slot]) => `$$slot_${sanitize(name)}: [${slot.block.name}${slot.fn ? `, ${slot.fn}` : ''}]`); const slot_props = Array.from(this.slots).map(([name, slot]) => `$$slot_${sanitize(name)}: [${slot.block.name}${slot.fn ? `, ${slot.fn}` : ''}]`);
if (slot_props.length > 0) slot_props.push(`$$scope: { ctx }`); if (slot_props.length > 0) slot_props.push(`$$scope: { ctx }`);
const attributeObject = usesSpread const attributeObject = uses_spread
? stringify_props(slot_props) ? stringify_props(slot_props)
: stringify_props( : stringify_props(
this.node.attributes.map(attr => `${quote_name_if_necessary(attr.name)}: ${attr.getValue(block)}`).concat(slot_props) this.node.attributes.map(attr => `${quote_name_if_necessary(attr.name)}: ${attr.get_value(block)}`).concat(slot_props)
); );
if (this.node.attributes.length || this.node.bindings.length || slot_props.length) { if (this.node.attributes.length || this.node.bindings.length || slot_props.length) {
if (!usesSpread && this.node.bindings.length === 0) { if (!uses_spread && this.node.bindings.length === 0) {
component_opts.push(`props: ${attributeObject}`); component_opts.push(`props: ${attributeObject}`);
} else { } else {
props = block.getUniqueName(`${name}_props`); props = block.get_unique_name(`${name}_props`);
component_opts.push(`props: ${props}`); component_opts.push(`props: ${props}`);
} }
} }
@ -145,7 +145,7 @@ export default class InlineComponentWrapper extends Wrapper {
}); });
} }
if (component.compileOptions.dev) { if (component.compile_options.dev) {
// TODO this is a terrible hack, but without it the component // TODO this is a terrible hack, but without it the component
// will complain that options.target is missing. This would // will complain that options.target is missing. This would
// work better if components had separate public and private // work better if components had separate public and private
@ -169,38 +169,38 @@ export default class InlineComponentWrapper extends Wrapper {
const non_let_dependencies = Array.from(fragment_dependencies).filter(name => !this.node.scope.is_let(name)); const non_let_dependencies = Array.from(fragment_dependencies).filter(name => !this.node.scope.is_let(name));
if (!usesSpread && (this.node.attributes.filter(a => a.isDynamic).length || this.node.bindings.length || non_let_dependencies.length > 0)) { if (!uses_spread && (this.node.attributes.filter(a => a.is_dynamic).length || this.node.bindings.length || non_let_dependencies.length > 0)) {
updates.push(`var ${name_changes} = {};`); updates.push(`var ${name_changes} = {};`);
} }
if (this.node.attributes.length) { if (this.node.attributes.length) {
if (usesSpread) { if (uses_spread) {
const levels = block.getUniqueName(`${this.var}_spread_levels`); const levels = block.get_unique_name(`${this.var}_spread_levels`);
const initialProps = []; const initial_props = [];
const changes = []; const changes = [];
const allDependencies = new Set(); const all_dependencies = new Set();
this.node.attributes.forEach(attr => { this.node.attributes.forEach(attr => {
add_to_set(allDependencies, attr.dependencies); add_to_set(all_dependencies, attr.dependencies);
}); });
this.node.attributes.forEach(attr => { this.node.attributes.forEach(attr => {
const { name, dependencies } = attr; const { name, dependencies } = attr;
const condition = dependencies.size > 0 && (dependencies.size !== allDependencies.size) const condition = dependencies.size > 0 && (dependencies.size !== all_dependencies.size)
? `(${Array.from(dependencies).map(d => `changed.${d}`).join(' || ')})` ? `(${Array.from(dependencies).map(d => `changed.${d}`).join(' || ')})`
: null; : null;
if (attr.isSpread) { if (attr.is_spread) {
const value = attr.expression.render(block); const value = attr.expression.render(block);
initialProps.push(value); initial_props.push(value);
changes.push(condition ? `${condition} && ${value}` : value); changes.push(condition ? `${condition} && ${value}` : value);
} else { } else {
const obj = `{ ${quote_name_if_necessary(name)}: ${attr.getValue(block)} }`; const obj = `{ ${quote_name_if_necessary(name)}: ${attr.get_value(block)} }`;
initialProps.push(obj); initial_props.push(obj);
changes.push(condition ? `${condition} && ${obj}` : obj); changes.push(condition ? `${condition} && ${obj}` : obj);
} }
@ -208,7 +208,7 @@ export default class InlineComponentWrapper extends Wrapper {
block.builders.init.add_block(deindent` block.builders.init.add_block(deindent`
var ${levels} = [ var ${levels} = [
${initialProps.join(',\n')} ${initial_props.join(',\n')}
]; ];
`); `);
@ -218,22 +218,22 @@ export default class InlineComponentWrapper extends Wrapper {
} }
`); `);
const conditions = Array.from(allDependencies).map(dep => `changed.${dep}`).join(' || '); const conditions = Array.from(all_dependencies).map(dep => `changed.${dep}`).join(' || ');
updates.push(deindent` updates.push(deindent`
var ${name_changes} = ${allDependencies.size === 1 ? `${conditions}` : `(${conditions})`} ? @get_spread_update(${levels}, [ var ${name_changes} = ${all_dependencies.size === 1 ? `${conditions}` : `(${conditions})`} ? @get_spread_update(${levels}, [
${changes.join(',\n')} ${changes.join(',\n')}
]) : {}; ]) : {};
`); `);
} else { } else {
this.node.attributes this.node.attributes
.filter((attribute: Attribute) => attribute.isDynamic) .filter((attribute: Attribute) => attribute.is_dynamic)
.forEach((attribute: Attribute) => { .forEach((attribute: Attribute) => {
if (attribute.dependencies.size > 0) { if (attribute.dependencies.size > 0) {
updates.push(deindent` updates.push(deindent`
if (${[...attribute.dependencies] if (${[...attribute.dependencies]
.map(dependency => `changed.${dependency}`) .map(dependency => `changed.${dependency}`)
.join(' || ')}) ${name_changes}${quote_prop_if_necessary(attribute.name)} = ${attribute.getValue(block)}; .join(' || ')}) ${name_changes}${quote_prop_if_necessary(attribute.name)} = ${attribute.get_value(block)};
`); `);
} }
}); });
@ -248,7 +248,7 @@ export default class InlineComponentWrapper extends Wrapper {
component.has_reactive_assignments = true; component.has_reactive_assignments = true;
if (binding.name === 'this') { if (binding.name === 'this') {
const fn = component.getUniqueName(`${this.var}_binding`); const fn = component.get_unique_name(`${this.var}_binding`);
component.add_var({ component.add_var({
name: fn, name: fn,
@ -259,7 +259,7 @@ export default class InlineComponentWrapper extends Wrapper {
let lhs; let lhs;
let object; let object;
if (binding.isContextual && binding.expression.node.type === 'Identifier') { if (binding.is_contextual && binding.expression.node.type === 'Identifier') {
// bind:x={y} — we can't just do `y = x`, we need to // bind:x={y} — we can't just do `y = x`, we need to
// to `array[index] = x; // to `array[index] = x;
const { name } = binding.expression.node; const { name } = binding.expression.node;
@ -283,7 +283,7 @@ export default class InlineComponentWrapper extends Wrapper {
return `@add_binding_callback(() => ctx.${fn}(${this.var}));`; return `@add_binding_callback(() => ctx.${fn}(${this.var}));`;
} }
const name = component.getUniqueName(`${this.var}_${binding.name}_binding`); const name = component.get_unique_name(`${this.var}_${binding.name}_binding`);
component.add_var({ component.add_var({
name, name,
@ -291,8 +291,8 @@ export default class InlineComponentWrapper extends Wrapper {
referenced: true referenced: true
}); });
const updating = block.getUniqueName(`updating_${binding.name}`); const updating = block.get_unique_name(`updating_${binding.name}`);
block.addVariable(updating); block.add_variable(updating);
const snippet = binding.expression.render(block); const snippet = binding.expression.render(block);
@ -315,7 +315,7 @@ export default class InlineComponentWrapper extends Wrapper {
let lhs = component.source.slice(binding.expression.node.start, binding.expression.node.end).trim(); let lhs = component.source.slice(binding.expression.node.start, binding.expression.node.end).trim();
if (binding.isContextual && binding.expression.node.type === 'Identifier') { if (binding.is_contextual && binding.expression.node.type === 'Identifier') {
// bind:x={y} — we can't just do `y = x`, we need to // bind:x={y} — we can't just do `y = x`, we need to
// to `array[index] = x; // to `array[index] = x;
const { name } = binding.expression.node; const { name } = binding.expression.node;
@ -336,7 +336,7 @@ export default class InlineComponentWrapper extends Wrapper {
} }
`); `);
block.maintainContext = true; // TODO put this somewhere more logical block.maintain_context = true; // TODO put this somewhere more logical
} else { } else {
block.builders.init.add_block(deindent` block.builders.init.add_block(deindent`
function ${name}(value) { function ${name}(value) {
@ -365,8 +365,8 @@ export default class InlineComponentWrapper extends Wrapper {
}); });
if (this.node.name === 'svelte:component') { if (this.node.name === 'svelte:component') {
const switch_value = block.getUniqueName('switch_value'); const switch_value = block.get_unique_name('switch_value');
const switch_props = block.getUniqueName('switch_props'); const switch_props = block.get_unique_name('switch_props');
const snippet = this.node.expression.render(block); const snippet = this.node.expression.render(block);
@ -392,9 +392,9 @@ export default class InlineComponentWrapper extends Wrapper {
`if (${name}) ${name}.$$.fragment.c();` `if (${name}) ${name}.$$.fragment.c();`
); );
if (parentNodes && this.renderer.options.hydratable) { if (parent_nodes && this.renderer.options.hydratable) {
block.builders.claim.add_line( block.builders.claim.add_line(
`if (${name}) ${name}.$$.fragment.l(${parentNodes});` `if (${name}) ${name}.$$.fragment.l(${parent_nodes});`
); );
} }
@ -404,8 +404,8 @@ export default class InlineComponentWrapper extends Wrapper {
} }
`); `);
const anchor = this.getOrCreateAnchor(block, parentNode, parentNodes); const anchor = this.get_or_create_anchor(block, parentNode, parent_nodes);
const updateMountNode = this.getUpdateMountNode(anchor); const update_mount_node = this.get_update_mount_node(anchor);
if (updates.length) { if (updates.length) {
block.builders.update.add_block(deindent` block.builders.update.add_block(deindent`
@ -433,7 +433,7 @@ export default class InlineComponentWrapper extends Wrapper {
${name}.$$.fragment.c(); ${name}.$$.fragment.c();
${name}.$$.fragment.i(1); ${name}.$$.fragment.i(1);
@mount_component(${name}, ${updateMountNode}, ${anchor}); @mount_component(${name}, ${update_mount_node}, ${anchor});
} else { } else {
${name} = null; ${name} = null;
} }
@ -476,9 +476,9 @@ export default class InlineComponentWrapper extends Wrapper {
block.builders.create.add_line(`${name}.$$.fragment.c();`); block.builders.create.add_line(`${name}.$$.fragment.c();`);
if (parentNodes && this.renderer.options.hydratable) { if (parent_nodes && this.renderer.options.hydratable) {
block.builders.claim.add_line( block.builders.claim.add_line(
`${name}.$$.fragment.l(${parentNodes});` `${name}.$$.fragment.l(${parent_nodes});`
); );
} }

@ -9,20 +9,20 @@ export default class MustacheTagWrapper extends Tag {
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: Node) { constructor(renderer: Renderer, block: Block, parent: Wrapper, node: Node) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.cannotUseInnerHTML(); this.cannot_use_innerhtml();
} }
render(block: Block, parentNode: string, parentNodes: string) { render(block: Block, parent_node: string, parent_nodes: string) {
const { init } = this.renameThisMethod( const { init } = this.rename_this_method(
block, block,
value => `@set_data(${this.var}, ${value});` value => `@set_data(${this.var}, ${value});`
); );
block.addElement( block.add_element(
this.var, this.var,
`@create_text(${init})`, `@create_text(${init})`,
parentNodes && `@claim_text(${parentNodes}, ${init})`, parent_nodes && `@claim_text(${parent_nodes}, ${init})`,
parentNode parent_node
); );
} }
} }

@ -15,89 +15,89 @@ export default class RawMustacheTagWrapper extends Tag {
node: Node node: Node
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.cannotUseInnerHTML(); this.cannot_use_innerhtml();
} }
render(block: Block, parentNode: string, parentNodes: string) { render(block: Block, parent_node: string, parent_nodes: string) {
const name = this.var; const name = this.var;
// TODO use isDomNode instead of type === 'Element'? // TODO use is_dom_node instead of type === 'Element'?
const needsAnchorBefore = this.prev ? this.prev.node.type !== 'Element' : !parentNode; const needs_anchor_before = this.prev ? this.prev.node.type !== 'Element' : !parent_node;
const needsAnchorAfter = this.next ? this.next.node.type !== 'Element' : !parentNode; const needs_anchor_after = this.next ? this.next.node.type !== 'Element' : !parent_node;
const anchorBefore = needsAnchorBefore const anchor_before = needs_anchor_before
? block.getUniqueName(`${name}_before`) ? block.get_unique_name(`${name}_before`)
: (this.prev && this.prev.var) || 'null'; : (this.prev && this.prev.var) || 'null';
const anchorAfter = needsAnchorAfter const anchor_after = needs_anchor_after
? block.getUniqueName(`${name}_after`) ? block.get_unique_name(`${name}_after`)
: (this.next && this.next.var) || 'null'; : (this.next && this.next.var) || 'null';
let detach: string; let detach: string;
let insert: (content: string) => string; let insert: (content: string) => string;
let useInnerHTML = false; let use_innerhtml = false;
if (anchorBefore === 'null' && anchorAfter === 'null') { if (anchor_before === 'null' && anchor_after === 'null') {
useInnerHTML = true; use_innerhtml = true;
detach = `${parentNode}.innerHTML = '';`; detach = `${parent_node}.innerHTML = '';`;
insert = content => `${parentNode}.innerHTML = ${content};`; insert = content => `${parent_node}.innerHTML = ${content};`;
} else if (anchorBefore === 'null') { } else if (anchor_before === 'null') {
detach = `@detach_before(${anchorAfter});`; detach = `@detach_before(${anchor_after});`;
insert = content => `${anchorAfter}.insertAdjacentHTML("beforebegin", ${content});`; insert = content => `${anchor_after}.insertAdjacentHTML("beforebegin", ${content});`;
} else if (anchorAfter === 'null') { } else if (anchor_after === 'null') {
detach = `@detach_after(${anchorBefore});`; detach = `@detach_after(${anchor_before});`;
insert = content => `${anchorBefore}.insertAdjacentHTML("afterend", ${content});`; insert = content => `${anchor_before}.insertAdjacentHTML("afterend", ${content});`;
} else { } else {
detach = `@detach_between(${anchorBefore}, ${anchorAfter});`; detach = `@detach_between(${anchor_before}, ${anchor_after});`;
insert = content => `${anchorBefore}.insertAdjacentHTML("afterend", ${content});`; insert = content => `${anchor_before}.insertAdjacentHTML("afterend", ${content});`;
} }
const { init } = this.renameThisMethod( const { init } = this.rename_this_method(
block, block,
content => deindent` content => deindent`
${!useInnerHTML && detach} ${!use_innerhtml && detach}
${insert(content)} ${insert(content)}
` `
); );
// we would have used comments here, but the `insertAdjacentHTML` api only // we would have used comments here, but the `insertAdjacentHTML` api only
// exists for `Element`s. // exists for `Element`s.
if (needsAnchorBefore) { if (needs_anchor_before) {
block.addElement( block.add_element(
anchorBefore, anchor_before,
`@element('noscript')`, `@element('noscript')`,
parentNodes && `@element('noscript')`, parent_nodes && `@element('noscript')`,
parentNode, parent_node,
true true
); );
} }
function addAnchorAfter() { function add_anchor_after() {
block.addElement( block.add_element(
anchorAfter, anchor_after,
`@element('noscript')`, `@element('noscript')`,
parentNodes && `@element('noscript')`, parent_nodes && `@element('noscript')`,
parentNode parent_node
); );
} }
if (needsAnchorAfter && anchorBefore === 'null') { if (needs_anchor_after && anchor_before === 'null') {
// anchorAfter needs to be in the DOM before we // anchor_after needs to be in the DOM before we
// insert the HTML... // insert the HTML...
addAnchorAfter(); add_anchor_after();
} }
block.builders.mount.add_line(insert(init)); block.builders.mount.add_line(insert(init));
if (!parentNode) { if (!parent_node) {
block.builders.destroy.add_conditional('detaching', needsAnchorBefore block.builders.destroy.add_conditional('detaching', needs_anchor_before
? `${detach}\n@detach(${anchorBefore});` ? `${detach}\n@detach(${anchor_before});`
: detach); : detach);
} }
if (needsAnchorAfter && anchorBefore !== 'null') { if (needs_anchor_after && anchor_before !== 'null') {
// ...otherwise it should go afterwards // ...otherwise it should go afterwards
addAnchorAfter(); add_anchor_after();
} }
} }
} }

@ -22,36 +22,36 @@ export default class SlotWrapper extends Wrapper {
block: Block, block: Block,
parent: Wrapper, parent: Wrapper,
node: Slot, node: Slot,
stripWhitespace: boolean, strip_whitespace: boolean,
nextSibling: Wrapper next_sibling: Wrapper
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.cannotUseInnerHTML(); this.cannot_use_innerhtml();
this.fragment = new FragmentWrapper( this.fragment = new FragmentWrapper(
renderer, renderer,
block, block,
node.children, node.children,
parent, parent,
stripWhitespace, strip_whitespace,
nextSibling next_sibling
); );
this.node.attributes.forEach(attribute => { this.node.attributes.forEach(attribute => {
add_to_set(this.dependencies, attribute.dependencies); add_to_set(this.dependencies, attribute.dependencies);
}); });
block.addDependencies(this.dependencies); block.add_dependencies(this.dependencies);
} }
render( render(
block: Block, block: Block,
parentNode: string, parent_node: string,
parentNodes: string parent_nodes: string
) { ) {
const { renderer } = this; const { renderer } = this;
const slot_name = this.node.getStaticAttributeValue('name') || 'default'; const slot_name = this.node.get_static_attribute_value('name') || 'default';
renderer.slots.add(slot_name); renderer.slots.add(slot_name);
let get_slot_changes; let get_slot_changes;
@ -60,8 +60,8 @@ export default class SlotWrapper extends Wrapper {
const attributes = this.node.attributes.filter(attribute => attribute.name !== 'name'); const attributes = this.node.attributes.filter(attribute => attribute.name !== 'name');
if (attributes.length > 0) { if (attributes.length > 0) {
get_slot_changes = renderer.component.getUniqueName(`get_${slot_name}_slot_changes`); get_slot_changes = renderer.component.get_unique_name(`get_${slot_name}_slot_changes`);
get_slot_context = renderer.component.getUniqueName(`get_${slot_name}_slot_context`); get_slot_context = renderer.component.get_unique_name(`get_${slot_name}_slot_context`);
const context_props = get_slot_data(attributes, false); const context_props = get_slot_data(attributes, false);
const changes_props = []; const changes_props = [];
@ -91,8 +91,8 @@ export default class SlotWrapper extends Wrapper {
get_slot_context = 'null'; get_slot_context = 'null';
} }
const slot = block.getUniqueName(`${sanitize(slot_name)}_slot`); const slot = block.get_unique_name(`${sanitize(slot_name)}_slot`);
const slot_definition = block.getUniqueName(`${sanitize(slot_name)}_slot`); const slot_definition = block.get_unique_name(`${sanitize(slot_name)}_slot`);
block.builders.init.add_block(deindent` block.builders.init.add_block(deindent`
const ${slot_definition} = ctx.$$slot_${sanitize(slot_name)}; const ${slot_definition} = ctx.$$slot_${sanitize(slot_name)};
@ -109,8 +109,8 @@ export default class SlotWrapper extends Wrapper {
const listeners = block.event_listeners; const listeners = block.event_listeners;
block.event_listeners = []; block.event_listeners = [];
this.fragment.render(block, parentNode, parentNodes); this.fragment.render(block, parent_node, parent_nodes);
block.renderListeners(`_${slot}`); block.render_listeners(`_${slot}`);
block.event_listeners = listeners; block.event_listeners = listeners;
block.builders.create.pop_condition(); block.builders.create.pop_condition();
@ -124,16 +124,16 @@ export default class SlotWrapper extends Wrapper {
); );
block.builders.claim.add_line( block.builders.claim.add_line(
`if (${slot}) ${slot}.l(${parentNodes});` `if (${slot}) ${slot}.l(${parent_nodes});`
); );
const mountLeadin = block.builders.mount.toString() !== mountBefore const mount_leadin = block.builders.mount.toString() !== mountBefore
? `else` ? `else`
: `if (${slot})`; : `if (${slot})`;
block.builders.mount.add_block(deindent` block.builders.mount.add_block(deindent`
${mountLeadin} { ${mount_leadin} {
${slot}.m(${parentNode || '#target'}, ${parentNode ? 'null' : 'anchor'}); ${slot}.m(${parent_node || '#target'}, ${parent_node ? 'null' : 'anchor'});
} }
`); `);

@ -7,7 +7,7 @@ import { stringify } from '../../utils/stringify';
// Whitespace inside one of these elements will not result in // Whitespace inside one of these elements will not result in
// a whitespace node being created in any circumstances. (This // a whitespace node being created in any circumstances. (This
// list is almost certainly very incomplete) // list is almost certainly very incomplete)
const elementsWithoutText = new Set([ const elements_without_text = new Set([
'audio', 'audio',
'datalist', 'datalist',
'dl', 'dl',
@ -17,16 +17,16 @@ const elementsWithoutText = new Set([
]); ]);
// TODO this should probably be in Fragment // TODO this should probably be in Fragment
function shouldSkip(node: Text) { function should_skip(node: Text) {
if (/\S/.test(node.data)) return false; if (/\S/.test(node.data)) return false;
const parentElement = node.findNearest(/(?:Element|InlineComponent|Head)/); const parent_element = node.find_nearest(/(?:Element|InlineComponent|Head)/);
if (!parentElement) return false; if (!parent_element) return false;
if (parentElement.type === 'Head') return true; if (parent_element.type === 'Head') return true;
if (parentElement.type === 'InlineComponent') return parentElement.children.length === 1 && node === parentElement.children[0]; if (parent_element.type === 'InlineComponent') return parent_element.children.length === 1 && node === parent_element.children[0];
return parentElement.namespace || elementsWithoutText.has(parentElement.name); return parent_element.namespace || elements_without_text.has(parent_element.name);
} }
export default class TextWrapper extends Wrapper { export default class TextWrapper extends Wrapper {
@ -44,19 +44,19 @@ export default class TextWrapper extends Wrapper {
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.skip = shouldSkip(this.node); this.skip = should_skip(this.node);
this.data = data; this.data = data;
this.var = this.skip ? null : 't'; this.var = this.skip ? null : 't';
} }
render(block: Block, parentNode: string, parentNodes: string) { render(block: Block, parent_node: string, parent_nodes: string) {
if (this.skip) return; if (this.skip) return;
block.addElement( block.add_element(
this.var, this.var,
`@create_text(${stringify(this.data)})`, `@create_text(${stringify(this.data)})`,
parentNodes && `@claim_text(${parentNodes}, ${stringify(this.data)})`, parent_nodes && `@claim_text(${parent_nodes}, ${stringify(this.data)})`,
parentNode parent_node
); );
} }
} }

@ -13,19 +13,19 @@ export default class TitleWrapper extends Wrapper {
block: Block, block: Block,
parent: Wrapper, parent: Wrapper,
node: Title, node: Title,
stripWhitespace: boolean, strip_whitespace: boolean,
nextSibling: Wrapper next_sibling: Wrapper
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
} }
render(block: Block, parentNode: string, parentNodes: string) { render(block: Block, parent_node: string, parent_nodes: string) {
const isDynamic = !!this.node.children.find(node => node.type !== 'Text'); const is_dynamic = !!this.node.children.find(node => node.type !== 'Text');
if (isDynamic) { if (is_dynamic) {
let value; let value;
const allDependencies = new Set(); const all_dependencies = new Set();
// TODO some of this code is repeated in Tag.ts — would be good to // TODO some of this code is repeated in Tag.ts — would be good to
// DRY it out if that's possible without introducing crazy indirection // DRY it out if that's possible without introducing crazy indirection
@ -33,7 +33,7 @@ export default class TitleWrapper extends Wrapper {
// single {tag} — may be a non-string // single {tag} — may be a non-string
const { expression } = this.node.children[0]; const { expression } = this.node.children[0];
value = expression.render(block); value = expression.render(block);
add_to_set(allDependencies, expression.dependencies); add_to_set(all_dependencies, expression.dependencies);
} else { } else {
// '{foo} {bar}' — treat as string concatenation // '{foo} {bar}' — treat as string concatenation
value = value =
@ -46,41 +46,41 @@ export default class TitleWrapper extends Wrapper {
const snippet = chunk.expression.render(block); const snippet = chunk.expression.render(block);
chunk.expression.dependencies.forEach(d => { chunk.expression.dependencies.forEach(d => {
allDependencies.add(d); all_dependencies.add(d);
}); });
return chunk.expression.getPrecedence() <= 13 ? `(${snippet})` : snippet; return chunk.expression.get_precedence() <= 13 ? `(${snippet})` : snippet;
} }
}) })
.join(' + '); .join(' + ');
} }
const last = this.node.shouldCache && block.getUniqueName( const last = this.node.should_cache && block.get_unique_name(
`title_value` `title_value`
); );
if (this.node.shouldCache) block.addVariable(last); if (this.node.should_cache) block.add_variable(last);
let updater; let updater;
const init = this.node.shouldCache ? `${last} = ${value}` : value; const init = this.node.should_cache ? `${last} = ${value}` : value;
block.builders.init.add_line( block.builders.init.add_line(
`document.title = ${init};` `document.title = ${init};`
); );
updater = `document.title = ${this.node.shouldCache ? last : value};`; updater = `document.title = ${this.node.should_cache ? last : value};`;
if (allDependencies.size) { if (all_dependencies.size) {
const dependencies = Array.from(allDependencies); const dependencies = Array.from(all_dependencies);
const changedCheck = ( const changed_check = (
( block.hasOutros ? `!#current || ` : '' ) + (block.has_outros ? `!#current || ` : '') +
dependencies.map(dependency => `changed.${dependency}`).join(' || ') dependencies.map(dependency => `changed.${dependency}`).join(' || ')
); );
const updateCachedValue = `${last} !== (${last} = ${value})`; const update_cached_value = `${last} !== (${last} = ${value})`;
const condition = this.node.shouldCache ? const condition = this.node.should_cache ?
( dependencies.length ? `(${changedCheck}) && ${updateCachedValue}` : updateCachedValue ) : (dependencies.length ? `(${changed_check}) && ${update_cached_value}` : update_cached_value) :
changedCheck; changed_check;
block.builders.update.add_conditional( block.builders.update.add_conditional(
condition, condition,

@ -3,11 +3,11 @@ import Block from '../Block';
import Node from '../../nodes/shared/Node'; import Node from '../../nodes/shared/Node';
import Wrapper from './shared/Wrapper'; import Wrapper from './shared/Wrapper';
import deindent from '../../utils/deindent'; import deindent from '../../utils/deindent';
import addEventHandlers from './shared/addEventHandlers'; import add_event_handlers from './shared/add_event_handlers';
import Window from '../../nodes/Window'; import Window from '../../nodes/Window';
import addActions from './shared/addActions'; import add_actions from './shared/add_actions';
const associatedEvents = { const associated_events = {
innerWidth: 'resize', innerWidth: 'resize',
innerHeight: 'resize', innerHeight: 'resize',
outerWidth: 'resize', outerWidth: 'resize',
@ -37,15 +37,15 @@ export default class WindowWrapper extends Wrapper {
super(renderer, block, parent, node); super(renderer, block, parent, node);
} }
render(block: Block, parentNode: string, parentNodes: string) { render(block: Block, parent_node: string, parent_nodes: string) {
const { renderer } = this; const { renderer } = this;
const { component } = renderer; const { component } = renderer;
const events = {}; const events = {};
const bindings: Record<string, string> = {}; const bindings: Record<string, string> = {};
addActions(component, block, 'window', this.node.actions); add_actions(component, block, 'window', this.node.actions);
addEventHandlers(block, 'window', this.node.handlers); add_event_handlers(block, 'window', this.node.handlers);
this.node.bindings.forEach(binding => { this.node.bindings.forEach(binding => {
// in dev mode, throw if read-only values are written to // in dev mode, throw if read-only values are written to
@ -58,29 +58,29 @@ export default class WindowWrapper extends Wrapper {
// bind:online is a special case, we need to listen for two separate events // bind:online is a special case, we need to listen for two separate events
if (binding.name === 'online') return; if (binding.name === 'online') return;
const associatedEvent = associatedEvents[binding.name]; const associated_event = associated_events[binding.name];
const property = properties[binding.name] || binding.name; const property = properties[binding.name] || binding.name;
if (!events[associatedEvent]) events[associatedEvent] = []; if (!events[associated_event]) events[associated_event] = [];
events[associatedEvent].push({ events[associated_event].push({
name: binding.expression.node.name, name: binding.expression.node.name,
value: property value: property
}); });
}); });
const scrolling = block.getUniqueName(`scrolling`); const scrolling = block.get_unique_name(`scrolling`);
const clear_scrolling = block.getUniqueName(`clear_scrolling`); const clear_scrolling = block.get_unique_name(`clear_scrolling`);
const scrolling_timeout = block.getUniqueName(`scrolling_timeout`); const scrolling_timeout = block.get_unique_name(`scrolling_timeout`);
Object.keys(events).forEach(event => { Object.keys(events).forEach(event => {
const handler_name = block.getUniqueName(`onwindow${event}`); const handler_name = block.get_unique_name(`onwindow${event}`);
const props = events[event]; const props = events[event];
if (event === 'scroll') { if (event === 'scroll') {
// TODO other bidirectional bindings... // TODO other bidirectional bindings...
block.addVariable(scrolling, 'false'); block.add_variable(scrolling, 'false');
block.addVariable(clear_scrolling, `() => { ${scrolling} = false }`); block.add_variable(clear_scrolling, `() => { ${scrolling} = false }`);
block.addVariable(scrolling_timeout); block.add_variable(scrolling_timeout);
const condition = [ const condition = [
bindings.scrollX && `"${bindings.scrollX}" in this._state`, bindings.scrollX && `"${bindings.scrollX}" in this._state`,
@ -90,7 +90,7 @@ export default class WindowWrapper extends Wrapper {
const x = bindings.scrollX && `this._state.${bindings.scrollX}`; const x = bindings.scrollX && `this._state.${bindings.scrollX}`;
const y = bindings.scrollY && `this._state.${bindings.scrollY}`; const y = bindings.scrollY && `this._state.${bindings.scrollY}`;
renderer.metaBindings.add_block(deindent` renderer.meta_bindings.add_block(deindent`
if (${condition}) { if (${condition}) {
window.scrollTo(${x || 'window.pageXOffset'}, ${y || 'window.pageYOffset'}); window.scrollTo(${x || 'window.pageXOffset'}, ${y || 'window.pageYOffset'});
} }
@ -108,7 +108,7 @@ export default class WindowWrapper extends Wrapper {
`); `);
} else { } else {
props.forEach(prop => { props.forEach(prop => {
renderer.metaBindings.add_line( renderer.meta_bindings.add_line(
`this._state.${prop.name} = window.${prop.value};` `this._state.${prop.name} = window.${prop.value};`
); );
}); });
@ -159,19 +159,19 @@ export default class WindowWrapper extends Wrapper {
// another special case. (I'm starting to think these are all special cases.) // another special case. (I'm starting to think these are all special cases.)
if (bindings.online) { if (bindings.online) {
const handler_name = block.getUniqueName(`onlinestatuschanged`); const handler_name = block.get_unique_name(`onlinestatuschanged`);
block.builders.init.add_block(deindent` block.builders.init.add_block(deindent`
function ${handler_name}(event) { function ${handler_name}(event) {
${component.compileOptions.dev && `component._updatingReadonlyProperty = true;`} ${component.compile_options.dev && `component._updatingReadonlyProperty = true;`}
#component.set({ ${bindings.online}: navigator.onLine }); #component.set({ ${bindings.online}: navigator.onLine });
${component.compileOptions.dev && `component._updatingReadonlyProperty = false;`} ${component.compile_options.dev && `component._updatingReadonlyProperty = false;`}
} }
window.addEventListener("online", ${handler_name}); window.addEventListener("online", ${handler_name});
window.addEventListener("offline", ${handler_name}); window.addEventListener("offline", ${handler_name});
`); `);
// add initial value // add initial value
renderer.metaBindings.add_line( renderer.meta_bindings.add_line(
`this._state.${bindings.online} = navigator.onLine;` `this._state.${bindings.online} = navigator.onLine;`
); );

@ -9,34 +9,34 @@ export default class Tag extends Wrapper {
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: MustacheTag | RawMustacheTag) { constructor(renderer: Renderer, block: Block, parent: Wrapper, node: MustacheTag | RawMustacheTag) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.cannotUseInnerHTML(); this.cannot_use_innerhtml();
block.addDependencies(node.expression.dependencies); block.add_dependencies(node.expression.dependencies);
} }
renameThisMethod( rename_this_method(
block: Block, block: Block,
update: ((value: string) => string) update: ((value: string) => string)
) { ) {
const dependencies = this.node.expression.dynamic_dependencies(); const dependencies = this.node.expression.dynamic_dependencies();
const snippet = this.node.expression.render(block); const snippet = this.node.expression.render(block);
const value = this.node.shouldCache && block.getUniqueName(`${this.var}_value`); const value = this.node.should_cache && block.get_unique_name(`${this.var}_value`);
const content = this.node.shouldCache ? value : snippet; const content = this.node.should_cache ? value : snippet;
if (this.node.shouldCache) block.addVariable(value, snippet); if (this.node.should_cache) block.add_variable(value, snippet);
if (dependencies.length > 0) { if (dependencies.length > 0) {
const changedCheck = ( const changed_check = (
(block.hasOutros ? `!#current || ` : '') + (block.has_outros ? `!#current || ` : '') +
dependencies.map((dependency: string) => `changed.${dependency}`).join(' || ') dependencies.map((dependency: string) => `changed.${dependency}`).join(' || ')
); );
const updateCachedValue = `${value} !== (${value} = ${snippet})`; const update_cached_value = `${value} !== (${value} = ${snippet})`;
const condition =this.node.shouldCache const condition =this.node.should_cache
? `(${changedCheck}) && ${updateCachedValue}` ? `(${changed_check}) && ${update_cached_value}`
: changedCheck; : changed_check;
block.builders.update.add_conditional( block.builders.update.add_conditional(
condition, condition,

@ -11,7 +11,7 @@ export default class Wrapper {
next: Wrapper | null; next: Wrapper | null;
var: string; var: string;
canUseInnerHTML: boolean; can_use_innerhtml: boolean;
constructor( constructor(
renderer: Renderer, renderer: Renderer,
@ -32,43 +32,43 @@ export default class Wrapper {
} }
}); });
this.canUseInnerHTML = !renderer.options.hydratable; this.can_use_innerhtml = !renderer.options.hydratable;
block.wrappers.push(this); block.wrappers.push(this);
} }
cannotUseInnerHTML() { cannot_use_innerhtml() {
this.canUseInnerHTML = false; this.can_use_innerhtml = false;
if (this.parent) this.parent.cannotUseInnerHTML(); if (this.parent) this.parent.cannot_use_innerhtml();
} }
getOrCreateAnchor(block: Block, parentNode: string, parentNodes: string) { get_or_create_anchor(block: Block, parent_node: string, parent_nodes: string) {
// TODO use this in EachBlock and IfBlock — tricky because // TODO use this in EachBlock and IfBlock — tricky because
// children need to be created first // children need to be created first
const needsAnchor = this.next ? !this.next.isDomNode() : !parentNode || !this.parent.isDomNode(); const needs_anchor = this.next ? !this.next.is_dom_node() : !parent_node || !this.parent.is_dom_node();
const anchor = needsAnchor const anchor = needs_anchor
? block.getUniqueName(`${this.var}_anchor`) ? block.get_unique_name(`${this.var}_anchor`)
: (this.next && this.next.var) || 'null'; : (this.next && this.next.var) || 'null';
if (needsAnchor) { if (needs_anchor) {
block.addElement( block.add_element(
anchor, anchor,
`@comment()`, `@comment()`,
parentNodes && `@comment()`, parent_nodes && `@comment()`,
parentNode parent_node
); );
} }
return anchor; return anchor;
} }
getUpdateMountNode(anchor: string) { get_update_mount_node(anchor: string) {
return (this.parent && this.parent.isDomNode()) return (this.parent && this.parent.is_dom_node())
? this.parent.var ? this.parent.var
: `${anchor}.parentNode`; : `${anchor}.parentNode`;
} }
isDomNode() { is_dom_node() {
return ( return (
this.node.type === 'Element' || this.node.type === 'Element' ||
this.node.type === 'Text' || this.node.type === 'Text' ||

@ -2,7 +2,7 @@ import Block from '../../Block';
import Action from '../../../nodes/Action'; import Action from '../../../nodes/Action';
import Component from '../../../Component'; import Component from '../../../Component';
export default function addActions( export default function add_actions(
component: Component, component: Component,
block: Block, block: Block,
target: string, target: string,
@ -17,11 +17,11 @@ export default function addActions(
dependencies = expression.dynamic_dependencies(); dependencies = expression.dynamic_dependencies();
} }
const name = block.getUniqueName( const name = block.get_unique_name(
`${action.name.replace(/[^a-zA-Z0-9_$]/g, '_')}_action` `${action.name.replace(/[^a-zA-Z0-9_$]/g, '_')}_action`
); );
block.addVariable(name); block.add_variable(name);
const fn = component.qualify(action.name); const fn = component.qualify(action.name);

@ -1,7 +1,7 @@
import Block from '../../Block'; import Block from '../../Block';
import EventHandler from '../../../nodes/EventHandler'; import EventHandler from '../../../nodes/EventHandler';
export default function addEventHandlers( export default function add_event_handlers(
block: Block, block: Block,
target: string, target: string,
handlers: EventHandler[] handlers: EventHandler[]
@ -14,12 +14,12 @@ export default function addEventHandlers(
const opts = ['passive', 'once', 'capture'].filter(mod => handler.modifiers.has(mod)); const opts = ['passive', 'once', 'capture'].filter(mod => handler.modifiers.has(mod));
if (opts.length) { if (opts.length) {
const optString = (opts.length === 1 && opts[0] === 'capture') const opts_string = (opts.length === 1 && opts[0] === 'capture')
? 'true' ? 'true'
: `{ ${opts.map(opt => `${opt}: true`).join(', ')} }`; : `{ ${opts.map(opt => `${opt}: true`).join(', ')} }`;
block.event_listeners.push( block.event_listeners.push(
`@listen(${target}, "${handler.name}", ${snippet}, ${optString})` `@listen(${target}, "${handler.name}", ${snippet}, ${opts_string})`
); );
} else { } else {
block.event_listeners.push( block.event_listeners.push(

@ -46,7 +46,7 @@ export default class Renderer {
append(code: string) { append(code: string) {
if (this.targets.length) { if (this.targets.length) {
const target = this.targets[this.targets.length - 1]; const target = this.targets[this.targets.length - 1];
const slotName = target.slotStack[target.slotStack.length - 1]; const slotName = target.slot_stack[target.slot_stack.length - 1];
target.slots[slotName] += code; target.slots[slotName] += code;
} else { } else {
this.code += code; this.code += code;

@ -47,38 +47,38 @@ const boolean_attributes = new Set([
]); ]);
export default function(node, renderer, options) { export default function(node, renderer, options) {
let openingTag = `<${node.name}`; let opening_tag = `<${node.name}`;
let textareaContents; // awkward special case let textarea_contents; // awkward special case
const slot = node.getStaticAttributeValue('slot'); const slot = node.get_static_attribute_value('slot');
if (slot && node.hasAncestor('InlineComponent')) { if (slot && node.has_ancestor('InlineComponent')) {
const slot = node.attributes.find((attribute: Node) => attribute.name === 'slot'); const slot = node.attributes.find((attribute: Node) => attribute.name === 'slot');
const slotName = slot.chunks[0].data; const slotName = slot.chunks[0].data;
const target = renderer.targets[renderer.targets.length - 1]; const target = renderer.targets[renderer.targets.length - 1];
target.slotStack.push(slotName); target.slot_stack.push(slotName);
target.slots[slotName] = ''; target.slots[slotName] = '';
options.slot_scopes.set(slotName, get_slot_scope(node.lets)); options.slot_scopes.set(slotName, get_slot_scope(node.lets));
} }
const classExpr = node.classes.map((classDir: Class) => { const class_expression = node.classes.map((class_directive: Class) => {
const { expression, name } = classDir; const { expression, name } = class_directive;
const snippet = expression ? snip(expression) : `ctx${quote_prop_if_necessary(name)}`; const snippet = expression ? snip(expression) : `ctx${quote_prop_if_necessary(name)}`;
return `${snippet} ? "${name}" : ""`; return `${snippet} ? "${name}" : ""`;
}).join(', '); }).join(', ');
let addClassAttribute = classExpr ? true : false; let add_class_attribute = class_expression ? true : false;
if (node.attributes.find(attr => attr.isSpread)) { if (node.attributes.find(attr => attr.is_spread)) {
// TODO dry this out // TODO dry this out
const args = []; const args = [];
node.attributes.forEach(attribute => { node.attributes.forEach(attribute => {
if (attribute.isSpread) { if (attribute.is_spread) {
args.push(snip(attribute.expression)); args.push(snip(attribute.expression));
} else { } else {
if (attribute.name === 'value' && node.name === 'textarea') { if (attribute.name === 'value' && node.name === 'textarea') {
textareaContents = stringify_attribute(attribute, true); textarea_contents = stringify_attribute(attribute, true);
} else if (attribute.isTrue) { } else if (attribute.is_true) {
args.push(`{ ${quote_name_if_necessary(attribute.name)}: true }`); args.push(`{ ${quote_name_if_necessary(attribute.name)}: true }`);
} else if ( } else if (
boolean_attributes.has(attribute.name) && boolean_attributes.has(attribute.name) &&
@ -93,32 +93,32 @@ export default function(node, renderer, options) {
} }
}); });
openingTag += "${@spread([" + args.join(', ') + "])}"; opening_tag += "${@spread([" + args.join(', ') + "])}";
} else { } else {
node.attributes.forEach((attribute: Attribute) => { node.attributes.forEach((attribute: Attribute) => {
if (attribute.type !== 'Attribute') return; if (attribute.type !== 'Attribute') return;
if (attribute.name === 'value' && node.name === 'textarea') { if (attribute.name === 'value' && node.name === 'textarea') {
textareaContents = stringify_attribute(attribute, true); textarea_contents = stringify_attribute(attribute, true);
} else if (attribute.isTrue) { } else if (attribute.is_true) {
openingTag += ` ${attribute.name}`; opening_tag += ` ${attribute.name}`;
} else if ( } else if (
boolean_attributes.has(attribute.name) && boolean_attributes.has(attribute.name) &&
attribute.chunks.length === 1 && attribute.chunks.length === 1 &&
attribute.chunks[0].type !== 'Text' attribute.chunks[0].type !== 'Text'
) { ) {
// a boolean attribute with one non-Text chunk // a boolean attribute with one non-Text chunk
openingTag += '${' + snip(attribute.chunks[0]) + ' ? " ' + attribute.name + '" : "" }'; opening_tag += '${' + snip(attribute.chunks[0]) + ' ? " ' + attribute.name + '" : "" }';
} else if (attribute.name === 'class' && classExpr) { } else if (attribute.name === 'class' && class_expression) {
addClassAttribute = false; add_class_attribute = false;
openingTag += ` class="\${[\`${stringify_attribute(attribute, true)}\`, ${classExpr}].join(' ').trim() }"`; opening_tag += ` class="\${[\`${stringify_attribute(attribute, true)}\`, ${class_expression}].join(' ').trim() }"`;
} else if (attribute.chunks.length === 1 && attribute.chunks[0].type !== 'Text') { } else if (attribute.chunks.length === 1 && attribute.chunks[0].type !== 'Text') {
const { name } = attribute; const { name } = attribute;
const snippet = snip(attribute.chunks[0]); const snippet = snip(attribute.chunks[0]);
openingTag += '${(v => v == null ? "" : ` ' + name + '="${@escape(' + snippet + ')}"`)(' + snippet + ')}'; opening_tag += '${(v => v == null ? "" : ` ' + name + '="${@escape(' + snippet + ')}"`)(' + snippet + ')}';
} else { } else {
openingTag += ` ${attribute.name}="${stringify_attribute(attribute, true)}"`; opening_tag += ` ${attribute.name}="${stringify_attribute(attribute, true)}"`;
} }
}); });
} }
@ -130,20 +130,20 @@ export default function(node, renderer, options) {
// TODO server-render group bindings // TODO server-render group bindings
} else { } else {
const snippet = snip(expression); const snippet = snip(expression);
openingTag += ' ${(v => v ? ("' + name + '" + (v === true ? "" : "=" + JSON.stringify(v))) : "")(' + snippet + ')}'; opening_tag += ' ${(v => v ? ("' + name + '" + (v === true ? "" : "=" + JSON.stringify(v))) : "")(' + snippet + ')}';
} }
}); });
if (addClassAttribute) { if (add_class_attribute) {
openingTag += `\${((v) => v ? ' class="' + v + '"' : '')([${classExpr}].join(' ').trim())}`; opening_tag += `\${((v) => v ? ' class="' + v + '"' : '')([${class_expression}].join(' ').trim())}`;
} }
openingTag += '>'; opening_tag += '>';
renderer.append(openingTag); renderer.append(opening_tag);
if (node.name === 'textarea' && textareaContents !== undefined) { if (node.name === 'textarea' && textarea_contents !== undefined) {
renderer.append(textareaContents); renderer.append(textarea_contents);
} else { } else {
renderer.render(node.children, options); renderer.render(node.children, options);
} }

@ -7,16 +7,16 @@ import { get_slot_scope } from './shared/get_slot_scope';
type AppendTarget = any; // TODO type AppendTarget = any; // TODO
function stringifyAttribute(chunk: Node) { function stringify_attribute(chunk: Node) {
if (chunk.type === 'Text') { if (chunk.type === 'Text') {
return escape_template(escape(chunk.data)); return escape_template(escape(chunk.data));
} }
return '${@escape( ' + snip(chunk) + ')}'; return '${@escape(' + snip(chunk) + ')}';
} }
function getAttributeValue(attribute) { function get_attribute_value(attribute) {
if (attribute.isTrue) return `true`; if (attribute.is_true) return `true`;
if (attribute.chunks.length === 0) return `''`; if (attribute.chunks.length === 0) return `''`;
if (attribute.chunks.length === 1) { if (attribute.chunks.length === 1) {
@ -28,7 +28,7 @@ function getAttributeValue(attribute) {
return snip(chunk); return snip(chunk);
} }
return '`' + attribute.chunks.map(stringifyAttribute).join('') + '`'; return '`' + attribute.chunks.map(stringify_attribute).join('') + '`';
} }
export default function(node, renderer: Renderer, options) { export default function(node, renderer: Renderer, options) {
@ -45,18 +45,18 @@ export default function(node, renderer: Renderer, options) {
binding_fns.push(`${binding.name}: $$value => { ${snippet} = $$value; $$settled = false }`); binding_fns.push(`${binding.name}: $$value => { ${snippet} = $$value; $$settled = false }`);
}); });
const usesSpread = node.attributes.find(attr => attr.isSpread); const uses_spread = node.attributes.find(attr => attr.is_spread);
let props; let props;
if (usesSpread) { if (uses_spread) {
props = `Object.assign(${ props = `Object.assign(${
node.attributes node.attributes
.map(attribute => { .map(attribute => {
if (attribute.isSpread) { if (attribute.is_spread) {
return snip(attribute.expression); return snip(attribute.expression);
} else { } else {
return `{ ${attribute.name}: ${getAttributeValue(attribute)} }`; return `{ ${attribute.name}: ${get_attribute_value(attribute)} }`;
} }
}) })
.concat(binding_props.map(p => `{ ${p} }`)) .concat(binding_props.map(p => `{ ${p} }`))
@ -65,7 +65,7 @@ export default function(node, renderer: Renderer, options) {
} else { } else {
props = stringify_props( props = stringify_props(
node.attributes node.attributes
.map(attribute => `${attribute.name}: ${getAttributeValue(attribute)}`) .map(attribute => `${attribute.name}: ${get_attribute_value(attribute)}`)
.concat(binding_props) .concat(binding_props)
); );
} }
@ -85,7 +85,7 @@ export default function(node, renderer: Renderer, options) {
if (node.children.length) { if (node.children.length) {
const target: AppendTarget = { const target: AppendTarget = {
slots: { default: '' }, slots: { default: '' },
slotStack: ['default'] slot_stack: ['default']
}; };
renderer.targets.push(target); renderer.targets.push(target);

@ -31,7 +31,7 @@ export default function ssr(
const assignment = `${name} = @get_store_value(${store_name});`; const assignment = `${name} = @get_store_value(${store_name});`;
return component.compileOptions.dev return component.compile_options.dev
? `@validate_store(${store_name}, '${store_name}'); ${assignment}` ? `@validate_store(${store_name}, '${store_name}'); ${assignment}`
: assignment; : assignment;
}); });
@ -46,7 +46,7 @@ export default function ssr(
const get_store_value = component.helper('get_store_value'); const get_store_value = component.helper('get_store_value');
let insert = `${value} = ${get_store_value}(${name})`; let insert = `${value} = ${get_store_value}(${name})`;
if (component.compileOptions.dev) { if (component.compile_options.dev) {
const validate_store = component.helper('validate_store'); const validate_store = component.helper('validate_store');
insert = `${validate_store}(${name}, '${name}'); ${insert}`; insert = `${validate_store}(${name}, '${name}'); ${insert}`;
} }

@ -102,7 +102,7 @@ describe('CodeBuilder', () => {
builder.add_line('// line 1'); builder.add_line('// line 1');
builder.add_line('// line 2'); builder.add_line('// line 2');
builder.add_block(deindent` builder.add_block(deindent`
if ( foo ) { if (foo) {
bar(); bar();
} }
`); `);
@ -115,7 +115,7 @@ describe('CodeBuilder', () => {
// line 1 // line 1
// line 2 // line 2
if ( foo ) { if (foo) {
bar(); bar();
} }
@ -139,7 +139,7 @@ describe('CodeBuilder', () => {
builder.add_line('// line 1'); builder.add_line('// line 1');
builder.add_line('// line 2'); builder.add_line('// line 2');
builder.add_block(deindent` builder.add_block(deindent`
if ( foo ) { if (foo) {
${child} ${child}
} }
`); `);
@ -152,7 +152,7 @@ describe('CodeBuilder', () => {
// line 1 // line 1
// line 2 // line 2
if ( foo ) { if (foo) {
var obj = { var obj = {
answer: 42 answer: 42
}; };

@ -5,7 +5,7 @@ export default function get_slot_data(attributes, is_ssr: boolean) {
return attributes return attributes
.filter(attribute => attribute.name !== 'name') .filter(attribute => attribute.name !== 'name')
.map(attribute => { .map(attribute => {
const value = attribute.isTrue const value = attribute.is_true
? 'true' ? 'true'
: attribute.chunks.length === 0 : attribute.chunks.length === 0
? '""' ? '""'

@ -67,7 +67,7 @@ export interface Visitor {
export interface AppendTarget { export interface AppendTarget {
slots: Record<string, string>; slots: Record<string, string>;
slotStack: string[] slot_stack: string[]
} }
export interface Var { export interface Var {

@ -5,7 +5,7 @@ export const xlink = 'http://www.w3.org/1999/xlink';
export const xml = 'http://www.w3.org/XML/1998/namespace'; export const xml = 'http://www.w3.org/XML/1998/namespace';
export const xmlns = 'http://www.w3.org/2000/xmlns'; export const xmlns = 'http://www.w3.org/2000/xmlns';
export const validNamespaces = [ export const valid_namespaces = [
'html', 'html',
'mathml', 'mathml',
'svg', 'svg',

Loading…
Cancel
Save