pull/2258/head
Rich Harris 5 years ago committed by GitHub
parent e5050a3621
commit 21d56c9ee3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

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

@ -12,27 +12,27 @@ type Timing = {
children: Timing[];
}
function collapseTimings(timings) {
function collapse_timings(timings) {
const result = {};
timings.forEach(timing => {
result[timing.label] = Object.assign({
total: timing.end - timing.start
}, timing.children && collapseTimings(timing.children));
}, timing.children && collapse_timings(timing.children));
});
return result;
}
export default class Stats {
startTime: number;
currentTiming: Timing;
currentChildren: Timing[];
start_time: number;
current_timing: Timing;
current_children: Timing[];
timings: Timing[];
stack: Timing[];
constructor() {
this.startTime = now();
this.start_time = now();
this.stack = [];
this.currentChildren = this.timings = [];
this.current_children = this.timings = [];
}
start(label) {
@ -43,28 +43,28 @@ export default class Stats {
children: []
};
this.currentChildren.push(timing);
this.current_children.push(timing);
this.stack.push(timing);
this.currentTiming = timing;
this.currentChildren = timing.children;
this.current_timing = timing;
this.current_children = timing.children;
}
stop(label) {
if (label !== this.currentTiming.label) {
throw new Error(`Mismatched timing labels (expected ${this.currentTiming.label}, got ${label})`);
if (label !== this.current_timing.label) {
throw new Error(`Mismatched timing labels (expected ${this.current_timing.label}, got ${label})`);
}
this.currentTiming.end = now();
this.current_timing.end = now();
this.stack.pop();
this.currentTiming = this.stack[this.stack.length - 1];
this.currentChildren = this.currentTiming ? this.currentTiming.children : this.timings;
this.current_timing = this.stack[this.stack.length - 1];
this.current_children = this.current_timing ? this.current_timing.children : this.timings;
}
render() {
const timings = Object.assign({
total: now() - this.startTime
}, collapseTimings(this.timings));
total: now() - this.start_time
}, collapse_timings(this.timings));
return {
timings

@ -2,25 +2,23 @@ import MagicString, { Bundle } from 'magic-string';
import { walk, childKeys } from 'estree-walker';
import { getLocator } from 'locate-character';
import Stats from '../Stats';
import reservedNames from '../utils/reservedNames';
import { namespaces, validNamespaces } from '../utils/namespaces';
import { removeNode } from '../utils/removeNode';
import wrapModule from './wrapModule';
import { createScopes, extractNames, Scope } from '../utils/annotateWithScopes';
import { globals, reserved } from '../utils/names';
import { namespaces, valid_namespaces } from '../utils/namespaces';
import create_module from './create_module';
import { create_scopes, extract_names, Scope } from './utils/scope';
import Stylesheet from './css/Stylesheet';
import { test } from '../config';
import Fragment from './nodes/Fragment';
import internal_exports from './internal-exports';
import { Node, Ast, CompileOptions, Var, Warning } from '../interfaces';
import error from '../utils/error';
import getCodeFrame from '../utils/getCodeFrame';
import flattenReference from '../utils/flattenReference';
import isReference from 'is-reference';
import get_code_frame from '../utils/get_code_frame';
import flatten_reference from './utils/flatten_reference';
import is_reference from 'is-reference';
import TemplateScope from './nodes/shared/TemplateScope';
import fuzzymatch from '../utils/fuzzymatch';
import { remove_indentation, add_indentation } from '../utils/indentation';
import getObject from '../utils/getObject';
import globalWhitelist from '../utils/globalWhitelist';
import get_object from './utils/get_object';
type ComponentOptions = {
namespace?: string;
@ -36,6 +34,34 @@ childKeys.EachBlock = childKeys.IfBlock = ['children', 'else'];
childKeys.Attribute = ['value'];
childKeys.ExportNamedDeclaration = ['declaration', 'specifiers'];
function remove_node(code: MagicString, start: number, end: number, body: Node, node: Node) {
const i = body.indexOf(node);
if (i === -1) throw new Error('node not in list');
let a;
let b;
if (body.length === 1) {
// remove everything, leave {}
a = start;
b = end;
} else if (i === 0) {
// remove everything before second node, including comments
a = start;
while (/\s/.test(code.original[a])) a += 1;
b = body[i].end;
while (/[\s,]/.test(code.original[b])) b += 1;
} else {
// remove the end of the previous node to the end of this one
a = body[i - 1].end;
b = node.end;
}
code.remove(a, b);
return;
}
export default class Component {
stats: Stats;
warnings: Warning[];
@ -44,13 +70,13 @@ export default class Component {
source: string;
code: MagicString;
name: string;
compileOptions: CompileOptions;
compile_options: CompileOptions;
fragment: Fragment;
module_scope: Scope;
instance_scope: Scope;
instance_scope_map: WeakMap<Node, Scope>;
componentOptions: ComponentOptions;
component_options: ComponentOptions;
namespace: string;
tag: string;
accessors: boolean;
@ -72,7 +98,7 @@ export default class Component {
injected_reactive_declaration_vars: 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;
locate: (c: number) => { line: number, column: number };
@ -86,13 +112,14 @@ export default class Component {
stylesheet: Stylesheet;
aliases: Map<string, string> = new Map();
usedNames: Set<string> = new Set();
used_names: Set<string> = new Set();
globally_used_names: Set<string> = new Set();
constructor(
ast: Ast,
source: string,
name: string,
compileOptions: CompileOptions,
compile_options: CompileOptions,
stats: Stats,
warnings: Warning[]
) {
@ -102,24 +129,24 @@ export default class Component {
this.warnings = warnings;
this.ast = ast;
this.source = source;
this.compileOptions = compileOptions;
this.compile_options = compile_options;
this.file = compileOptions.filename && (
typeof process !== 'undefined' ? compileOptions.filename.replace(process.cwd(), '').replace(/^[\/\\]/, '') : compileOptions.filename
this.file = compile_options.filename && (
typeof process !== 'undefined' ? compile_options.filename.replace(process.cwd(), '').replace(/^[\/\\]/, '') : compile_options.filename
);
this.locate = getLocator(this.source);
this.code = new MagicString(source);
// 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.componentOptions = process_component_options(this, this.ast.html.children);
this.namespace = namespaces[this.componentOptions.namespace] || this.componentOptions.namespace;
this.component_options = process_component_options(this, this.ast.html.children);
this.namespace = namespaces[this.component_options.namespace] || this.component_options.namespace;
if (compileOptions.customElement) {
this.tag = this.componentOptions.tag || compileOptions.tag;
if (compile_options.customElement) {
this.tag = this.component_options.tag || compile_options.tag;
if (!this.tag) {
throw new Error(`Cannot compile to a custom element without specifying a tag name via options.tag or <svelte:options>`);
}
@ -131,13 +158,13 @@ export default class Component {
this.walk_instance_js_pre_template();
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();
if (!compileOptions.customElement) this.stylesheet.reify();
if (!compile_options.customElement) this.stylesheet.reify();
this.stylesheet.warnOnUnusedSelectors(this);
this.stylesheet.warn_on_unused_selectors(this);
}
add_var(variable: Var) {
@ -171,11 +198,11 @@ export default class Component {
const variable = this.var_lookup.get(subscribable_name);
if (variable) variable.subscribable = true;
} else {
this.usedNames.add(name);
this.used_names.add(name);
}
}
addSourcemapLocations(node: Node) {
add_sourcemap_locations(node: Node) {
walk(node, {
enter: (node: Node) => {
this.code.addSourcemapLocation(node.start);
@ -186,7 +213,7 @@ export default class Component {
alias(name: string) {
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);
@ -202,17 +229,17 @@ export default class Component {
let css = null;
if (result) {
const { compileOptions, name } = this;
const { format = 'esm' } = compileOptions;
const { compile_options, name } = this;
const { format = 'esm' } = compile_options;
const banner = `/* ${this.file ? `${this.file} ` : ``}generated by Svelte v${"__VERSION__"} */`;
result = result
.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 (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);
}
@ -222,21 +249,20 @@ export default class Component {
return sigil.slice(1) + name;
});
const importedHelpers = Array.from(this.helpers)
const imported_helpers = Array.from(this.helpers)
.sort()
.map(name => {
const alias = this.alias(name);
return { name, alias };
});
const module = wrapModule(
const module = create_module(
result,
format,
name,
compileOptions,
banner,
compileOptions.sveltePath,
importedHelpers,
compile_options.sveltePath,
imported_helpers,
this.imports,
this.vars.filter(variable => variable.module && variable.export_name).map(variable => ({
name: variable.name,
@ -246,17 +272,17 @@ export default class Component {
);
const parts = module.split('✂]');
const finalChunk = parts.pop();
const final_chunk = parts.pop();
const compiled = new Bundle({ separator: '' });
function addString(str: string) {
function add_string(str: string) {
compiled.addSource({
content: new MagicString(str),
});
}
const { filename } = compileOptions;
const { filename } = compile_options;
// 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
@ -271,7 +297,7 @@ export default class Component {
parts.forEach((str: string) => {
const chunk = str.replace(pattern, '');
if (chunk) addString(chunk);
if (chunk) add_string(chunk);
const match = pattern.exec(str);
@ -283,17 +309,17 @@ export default class Component {
});
});
addString(finalChunk);
add_string(final_chunk);
css = compileOptions.customElement ?
css = compile_options.customElement ?
{ code: null, map: null } :
this.stylesheet.render(compileOptions.cssOutputFilename, true);
this.stylesheet.render(compile_options.cssOutputFilename, true);
js = {
code: compiled.toString(),
map: compiled.generateMap({
includeContent: true,
file: compileOptions.outputFilename,
file: compile_options.outputFilename,
})
};
}
@ -317,28 +343,30 @@ export default class Component {
};
}
getUniqueName(name: string) {
get_unique_name(name: string) {
if (test) name = `${name}$`;
let alias = name;
for (
let i = 1;
reservedNames.has(alias) ||
reserved.has(alias) ||
this.var_lookup.has(alias) ||
this.usedNames.has(alias);
this.used_names.has(alias) ||
this.globally_used_names.has(alias);
alias = `${name}_${i++}`
);
this.usedNames.add(alias);
this.used_names.add(alias);
return alias;
}
getUniqueNameMaker() {
const localUsedNames = new Set();
get_unique_name_maker() {
const local_used_names = new Set();
function add(name: string) {
localUsedNames.add(name);
local_used_names.add(name);
}
reservedNames.forEach(add);
reserved.forEach(add);
internal_exports.forEach(add);
this.var_lookup.forEach((value, key) => add(key));
return (name: string) => {
@ -346,11 +374,12 @@ export default class Component {
let alias = name;
for (
let i = 1;
this.usedNames.has(alias) ||
localUsedNames.has(alias);
this.used_names.has(alias) ||
local_used_names.has(alias);
alias = `${name}_${i++}`
);
localUsedNames.add(alias);
local_used_names.add(alias);
this.globally_used_names.add(alias);
return alias;
};
}
@ -371,7 +400,7 @@ export default class Component {
source: this.source,
start: pos.start,
end: pos.end,
filename: this.compileOptions.filename
filename: this.compile_options.filename
});
}
@ -392,7 +421,7 @@ export default class Component {
const start = this.locator(pos.start);
const end = this.locator(pos.end);
const frame = getCodeFrame(this.source, start.line - 1, start.column);
const frame = get_code_frame(this.source, start.line - 1, start.column);
this.warnings.push({
code: warning.code,
@ -401,7 +430,7 @@ export default class Component {
start,
end,
pos: pos.start,
filename: this.compileOptions.filename,
filename: this.compile_options.filename,
toString: () => `${warning.message} (${start.line + 1}:${start.column})\n${frame}`,
});
}
@ -412,7 +441,7 @@ export default class Component {
content.body.forEach(node => {
if (node.type === 'ImportDeclaration') {
// imports need to be hoisted out of the IIFE
removeNode(code, content.start, content.end, content.body, node);
remove_node(code, content.start, content.end, content.body, node);
this.imports.push(node);
}
});
@ -439,7 +468,7 @@ export default class Component {
if (node.declaration) {
if (node.declaration.type === 'VariableDeclaration') {
node.declaration.declarations.forEach(declarator => {
extractNames(declarator.id).forEach(name => {
extract_names(declarator.id).forEach(name => {
const variable = this.var_lookup.get(name);
variable.export_name = name;
});
@ -453,7 +482,7 @@ export default class Component {
code.remove(node.start, node.declaration.start);
} else {
removeNode(code, content.start, content.end, content.body, node);
remove_node(code, content.start, content.end, content.body, node);
node.specifiers.forEach(specifier => {
const variable = this.var_lookup.get(specifier.local.name);
@ -509,9 +538,9 @@ export default class Component {
const script = this.ast.module;
if (!script) return;
this.addSourcemapLocations(script.content);
this.add_sourcemap_locations(script.content);
let { scope, globals } = createScopes(script.content);
let { scope, globals } = create_scopes(script.content);
this.module_scope = scope;
scope.declarations.forEach((node, name) => {
@ -554,7 +583,7 @@ export default class Component {
const script = this.ast.instance;
if (!script) return;
this.addSourcemapLocations(script.content);
this.add_sourcemap_locations(script.content);
// inject vars for reactive declarations
script.content.body.forEach(node => {
@ -569,7 +598,7 @@ export default class Component {
}
});
let { scope: instance_scope, map, globals } = createScopes(script.content);
let { scope: instance_scope, map, globals } = create_scopes(script.content);
this.instance_scope = instance_scope;
this.instance_scope_map = map;
@ -662,15 +691,15 @@ export default class Component {
deep = node.left.type === 'MemberExpression';
names = deep
? [getObject(node.left).name]
: extractNames(node.left);
? [get_object(node.left).name]
: extract_names(node.left);
} else if (node.type === 'UpdateExpression') {
names = [getObject(node.argument).name];
names = [get_object(node.argument).name];
}
if (names) {
names.forEach(name => {
if (scope.findOwner(name) === instance_scope) {
if (scope.find_owner(name) === instance_scope) {
const variable = component.var_lookup.get(name);
variable[deep ? 'mutated' : 'reassigned'] = true;
}
@ -698,8 +727,8 @@ export default class Component {
scope = map.get(node);
}
if (isReference(node, parent)) {
const object = getObject(node);
if (is_reference(node, parent)) {
const object = get_object(node);
const { name } = object;
if (name[0] === '$' && !scope.has(name)) {
@ -732,7 +761,7 @@ export default class Component {
rewrite_props(get_insert: (variable: Var) => string) {
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;
const coalesced_declarations = [];
@ -757,7 +786,7 @@ export default class Component {
if (declarator.id.type !== 'Identifier') {
const inserts = [];
extractNames(declarator.id).forEach(name => {
extract_names(declarator.id).forEach(name => {
const variable = component.var_lookup.get(name);
if (variable.export_name) {
@ -946,9 +975,9 @@ export default class Component {
scope = map.get(node);
}
if (isReference(node, parent)) {
const { name } = flattenReference(node);
const owner = scope.findOwner(name);
if (is_reference(node, parent)) {
const { name } = flatten_reference(node);
const owner = scope.find_owner(name);
if (name[0] === '$' && !owner) {
hoistable = false;
@ -1028,17 +1057,17 @@ export default class Component {
}
if (node.type === 'AssignmentExpression') {
const identifier = getObject(node.left)
const identifier = get_object(node.left)
assignee_nodes.add(identifier);
assignees.add(identifier.name);
} else if (node.type === 'UpdateExpression') {
const identifier = getObject(node.argument);
const identifier = get_object(node.argument);
assignees.add(identifier.name);
} else if (isReference(node, parent)) {
const identifier = getObject(node);
} else if (is_reference(node, parent)) {
const identifier = get_object(node);
if (!assignee_nodes.has(identifier)) {
const { name } = identifier;
const owner = scope.findOwner(name);
const owner = scope.find_owner(name);
if (
(!owner || owner === component.instance_scope) &&
(name[0] === '$' || component.var_lookup.has(name) && component.var_lookup.get(name).writable)
@ -1149,7 +1178,7 @@ export default class Component {
if (this.var_lookup.has(name)) return;
if (template_scope && template_scope.names.has(name)) return;
if (globalWhitelist.has(name)) return;
if (globals.has(name)) return;
let message = `'${name}' is not defined`;
if (!this.ast.instance) message += `. Consider adding a <script> block with 'export let ${name}' to declare a prop`;
@ -1162,11 +1191,11 @@ export default class Component {
}
function process_component_options(component: Component, nodes) {
const componentOptions: ComponentOptions = {
immutable: component.compileOptions.immutable || false,
accessors: 'accessors' in component.compileOptions
? component.compileOptions.accessors
: !!component.compileOptions.customElement
const component_options: ComponentOptions = {
immutable: component.compile_options.immutable || false,
accessors: 'accessors' in component.compile_options
? component.compile_options.accessors
: !!component.compile_options.customElement
};
const node = nodes.find(node => node.name === 'svelte:options');
@ -1210,7 +1239,7 @@ function process_component_options(component: Component, nodes) {
});
}
componentOptions.tag = tag;
component_options.tag = tag;
break;
}
@ -1221,8 +1250,8 @@ function process_component_options(component: Component, nodes) {
if (typeof ns !== 'string') component.error(attribute, { code, message });
if (validNamespaces.indexOf(ns) === -1) {
const match = fuzzymatch(ns, validNamespaces);
if (valid_namespaces.indexOf(ns) === -1) {
const match = fuzzymatch(ns, valid_namespaces);
if (match) {
component.error(attribute, {
code: `invalid-namespace-property`,
@ -1236,7 +1265,7 @@ function process_component_options(component: Component, nodes) {
}
}
componentOptions.namespace = ns;
component_options.namespace = ns;
break;
}
@ -1248,7 +1277,7 @@ function process_component_options(component: Component, nodes) {
if (typeof value !== 'boolean') component.error(attribute, { code, message });
componentOptions[name] = value;
component_options[name] = value;
break;
default:
@ -1268,5 +1297,5 @@ function process_component_options(component: Component, nodes) {
});
}
return componentOptions;
return component_options;
}

@ -1,12 +1,7 @@
import deindent from '../utils/deindent';
import deindent from './utils/deindent';
import list from '../utils/list';
import { CompileOptions, ModuleFormat, Node } from '../interfaces';
interface Dependency {
name: string;
statements: string[];
source: string;
}
import { ModuleFormat, Node } from '../interfaces';
import { stringify_props } from './utils/stringify_props';
const wrappers = { esm, cjs };
@ -15,11 +10,10 @@ type Export = {
as: string;
};
export default function wrapModule(
export default function create_module(
code: string,
format: ModuleFormat,
name: string,
options: CompileOptions,
banner: string,
sveltePath = 'svelte',
helpers: { name: string, alias: string }[],
@ -27,18 +21,18 @@ export default function wrapModule(
module_exports: Export[],
source: string
): string {
const internalPath = `${sveltePath}/internal`;
const internal_path = `${sveltePath}/internal`;
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))})`);
}
function editSource(source, sveltePath) {
function edit_source(source, sveltePath) {
return source === 'svelte' || source.startsWith('svelte/')
? source.replace('svelte', sveltePath)
: source;
@ -47,23 +41,22 @@ function editSource(source, sveltePath) {
function esm(
code: string,
name: string,
options: CompileOptions,
banner: string,
sveltePath: string,
internalPath: string,
internal_path: string,
helpers: { name: string, alias: string }[],
imports: Node[],
module_exports: Export[],
source: string
) {
const importHelpers = helpers.length > 0 && (
`import { ${helpers.map(h => h.name === h.alias ? h.name : `${h.name} as ${h.alias}`).join(', ')} } from ${JSON.stringify(internalPath)};`
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(internal_path)};`
);
const importBlock = imports.length > 0 && (
const user_imports = imports.length > 0 && (
imports
.map((declaration: Node) => {
const import_source = editSource(declaration.source.value, sveltePath);
const import_source = edit_source(declaration.source.value, sveltePath);
return (
source.slice(declaration.start, declaration.source.start) +
@ -76,8 +69,8 @@ function esm(
return deindent`
${banner}
${importHelpers}
${importBlock}
${internal_imports}
${user_imports}
${code}
@ -90,15 +83,15 @@ function cjs(
name: string,
banner: string,
sveltePath: string,
internalPath: string,
internal_path: string,
helpers: { name: string, alias: string }[],
imports: Node[],
module_exports: Export[]
) {
const helperDeclarations = helpers.map(h => `${h.alias === h.name ? h.name : `${h.name}: ${h.alias}`}`).join(', ');
const declarations = helpers.map(h => `${h.alias === h.name ? h.name : `${h.name}: ${h.alias}`}`).sort();
const helperBlock = helpers.length > 0 && (
`const { ${helperDeclarations} } = require(${JSON.stringify(internalPath)});\n`
const internal_imports = helpers.length > 0 && (
`const ${stringify_props(declarations)} = require(${JSON.stringify(internal_path)});\n`
);
const requires = imports.map(node => {
@ -120,7 +113,7 @@ function cjs(
lhs = `{ ${properties.join(', ')} }`;
}
const source = editSource(node.source.value, sveltePath);
const source = edit_source(node.source.value, sveltePath);
return `const ${lhs} = require("${source}");`
});
@ -133,7 +126,7 @@ function cjs(
${banner}
"use strict";
${helperBlock}
${internal_imports}
${requires}
${code}

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

@ -1,13 +1,24 @@
import MagicString from 'magic-string';
import { walk } from 'estree-walker';
import Selector from './Selector';
import hash from '../../utils/hash';
import removeCSSPrefix from '../../utils/removeCSSPrefix';
import Element from '../nodes/Element';
import { Node, Ast } from '../../interfaces';
import Component from '../Component';
const isKeyframesNode = (node: Node) => removeCSSPrefix(node.name) === 'keyframes'
function remove_css_prefox(name: string): string {
return name.replace(/^-((webkit)|(moz)|(o)|(ms))-/, '');
}
const is_keyframes_node = (node: Node) => remove_css_prefox(node.name) === 'keyframes';
// https://github.com/darkskyapp/string-hash/blob/master/index.js
function hash(str: string): string {
let hash = 5381;
let i = str.length;
while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i);
return (hash >>> 0).toString(36);
}
class Rule {
selectors: Selector[];
@ -26,8 +37,8 @@ class Rule {
this.selectors.forEach(selector => selector.apply(node, stack)); // TODO move the logic in here?
}
isUsed(dev: boolean) {
if (this.parent && this.parent.node.type === 'Atrule' && isKeyframesNode(this.parent.node)) return true;
is_used(dev: boolean) {
if (this.parent && this.parent.node.type === 'Atrule' && is_keyframes_node(this.parent.node)) return true;
if (this.declarations.length === 0) return dev;
return this.selectors.some(s => s.used);
}
@ -68,7 +79,7 @@ class Rule {
}
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}`;
@ -82,7 +93,7 @@ class Rule {
});
}
warnOnUnusedSelector(handler: (selector: Selector) => void) {
warn_on_unused_selector(handler: (selector: Selector) => void) {
this.selectors.forEach(selector => {
if (!selector.used) handler(selector);
});
@ -97,7 +108,7 @@ class Declaration {
}
transform(code: MagicString, keyframes: Map<string, string>) {
const property = this.node.property && removeCSSPrefix(this.node.property.toLowerCase());
const property = this.node.property && remove_css_prefox(this.node.property.toLowerCase());
if (property === 'animation' || property === 'animation-name') {
this.node.value.children.forEach((block: Node) => {
if (block.type === 'Identifier') {
@ -143,7 +154,7 @@ class Atrule {
});
}
else if (isKeyframesNode(this.node)) {
else if (is_keyframes_node(this.node)) {
this.children.forEach((rule: Rule) => {
rule.selectors.forEach(selector => {
selector.used = true;
@ -152,14 +163,14 @@ class Atrule {
}
}
isUsed(dev: boolean) {
is_used(dev: boolean) {
return true; // TODO
}
minify(code: MagicString, dev: boolean) {
if (this.node.name === 'media') {
const expressionChar = code.original[this.node.expression.start];
let c = this.node.start + (expressionChar === '(' ? 6 : 7);
const expression_char = code.original[this.node.expression.start];
let c = this.node.start + (expression_char === '(' ? 6 : 7);
if (this.node.expression.start > c) code.remove(c, this.node.expression.start);
this.node.expression.children.forEach((query: Node) => {
@ -168,7 +179,7 @@ class Atrule {
});
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;
if (this.node.expression.start - c > 1) code.overwrite(c, this.node.expression.start, ' ');
c = this.node.expression.end;
@ -189,7 +200,7 @@ class Atrule {
let c = this.node.block.start + 1;
this.children.forEach(child => {
if (child.isUsed(dev)) {
if (child.is_used(dev)) {
code.remove(c, child.node.start);
child.minify(code, dev);
c = child.node.end;
@ -201,7 +212,7 @@ class Atrule {
}
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) => {
if (type === 'Identifier') {
if (name.startsWith('-global-')) {
@ -215,7 +226,7 @@ class Atrule {
this.children.forEach(child => {
child.transform(code, id, keyframes);
})
});
}
validate(component: Component) {
@ -224,31 +235,28 @@ class Atrule {
});
}
warnOnUnusedSelector(handler: (selector: Selector) => void) {
warn_on_unused_selector(handler: (selector: Selector) => void) {
if (this.node.name !== 'media') return;
this.children.forEach(child => {
child.warnOnUnusedSelector(handler);
child.warn_on_unused_selector(handler);
});
}
}
const keys = {};
export default class Stylesheet {
source: string;
ast: Ast;
filename: string;
dev: boolean;
hasStyles: boolean;
has_styles: boolean;
id: string;
children: (Rule|Atrule)[];
keyframes: Map<string, string>;
children: (Rule|Atrule)[] = [];
keyframes: Map<string, string> = new Map();
nodesWithCssClass: Set<Node>;
nodesWithRefCssClass: Map<String, Node>;
nodes_with_css_class: Set<Node> = new Set();
constructor(source: string, ast: Ast, filename: string, dev: boolean) {
this.source = source;
@ -256,19 +264,13 @@ export default class Stylesheet {
this.filename = filename;
this.dev = dev;
this.children = [];
this.keyframes = new Map();
this.nodesWithCssClass = new Set();
this.nodesWithRefCssClass = new Map();
if (ast.css && ast.css.children.length) {
this.id = `svelte-${hash(ast.css.content.styles)}`;
this.hasStyles = true;
this.has_styles = true;
const stack: (Rule | Atrule)[] = [];
let currentAtrule: Atrule = null;
let current_atrule: Atrule = null;
walk(ast.css, {
enter: (node: Node) => {
@ -282,13 +284,13 @@ export default class Stylesheet {
// possibly other future constructs)
if (last && !(last instanceof Atrule)) return;
if (currentAtrule) {
currentAtrule.children.push(atrule);
if (current_atrule) {
current_atrule.children.push(atrule);
} else {
this.children.push(atrule);
}
if (isKeyframesNode(node)) {
if (is_keyframes_node(node)) {
node.expression.children.forEach((expression: Node) => {
if (expression.type === 'Identifier' && !expression.name.startsWith('-global-')) {
this.keyframes.set(expression.name, `${this.id}-${expression.name}`);
@ -296,15 +298,15 @@ export default class Stylesheet {
});
}
currentAtrule = atrule;
current_atrule = atrule;
}
if (node.type === 'Rule') {
const rule = new Rule(node, this, currentAtrule);
const rule = new Rule(node, this, current_atrule);
stack.push(rule);
if (currentAtrule) {
currentAtrule.children.push(rule);
if (current_atrule) {
current_atrule.children.push(rule);
} else {
this.children.push(rule);
}
@ -313,16 +315,16 @@ export default class Stylesheet {
leave: (node: Node) => {
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 {
this.hasStyles = false;
this.has_styles = false;
}
}
apply(node: Element) {
if (!this.hasStyles) return;
if (!this.has_styles) return;
const stack: Element[] = [];
let parent: Node = node;
@ -337,13 +339,13 @@ export default class Stylesheet {
}
reify() {
this.nodesWithCssClass.forEach((node: Node) => {
node.addCssClass();
this.nodes_with_css_class.forEach((node: Node) => {
node.add_css_class();
});
}
render(cssOutputFilename: string, shouldTransformSelectors: boolean) {
if (!this.hasStyles) {
render(file: string, should_transform_selectors: boolean) {
if (!this.has_styles) {
return { code: null, map: null };
}
@ -356,7 +358,7 @@ export default class Stylesheet {
}
});
if (shouldTransformSelectors) {
if (should_transform_selectors) {
this.children.forEach((child: (Atrule|Rule)) => {
child.transform(code, this.id, this.keyframes);
});
@ -364,7 +366,7 @@ export default class Stylesheet {
let c = 0;
this.children.forEach(child => {
if (child.isUsed(this.dev)) {
if (child.is_used(this.dev)) {
code.remove(c, child.node.start);
child.minify(code, this.dev);
c = child.node.end;
@ -378,7 +380,7 @@ export default class Stylesheet {
map: code.generateMap({
includeContent: true,
source: this.filename,
file: cssOutputFilename
file
})
};
}
@ -389,9 +391,9 @@ export default class Stylesheet {
});
}
warnOnUnusedSelectors(component: Component) {
warn_on_unused_selectors(component: Component) {
this.children.forEach(child => {
child.warnOnUnusedSelector((selector: Selector) => {
child.warn_on_unused_selector((selector: Selector) => {
component.warn(selector.node, {
code: `css-unused-selector`,
message: `Unused CSS selector`

@ -2,14 +2,14 @@ import { Node } from '../interfaces';
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') {
set.add(node.value);
}
else if (node.type === 'ConditionalExpression') {
gatherPossibleValues(node.consequent, set);
gatherPossibleValues(node.alternate, set);
gather_possible_values(node.consequent, set);
gather_possible_values(node.alternate, set);
}
else {

@ -1,8 +1,8 @@
import { assign } from '../internal';
import Stats from '../Stats';
import parse from '../parse/index';
import renderDOM from './render-dom/index';
import renderSSR from './render-ssr/index';
import render_dom from './render-dom/index';
import render_ssr from './render-ssr/index';
import { CompileOptions, Ast, Warning } from '../interfaces';
import Component from './Component';
import fuzzymatch from '../utils/fuzzymatch';
@ -100,8 +100,8 @@ export default function compile(source: string, options: CompileOptions = {}) {
const js = options.generate === false
? null
: options.generate === 'ssr'
? renderSSR(component, options)
: renderDOM(component, options);
? render_ssr(component, options)
: render_dom(component, options);
return component.generate(js);
}

@ -6,7 +6,7 @@ export default class Action extends Node {
type: 'Action';
name: string;
expression: Expression;
usesContext: boolean;
uses_context: boolean;
constructor(component: 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)
: 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
? new Expression(component, this, scope, info.expression)

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

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

@ -1,6 +1,6 @@
import Node from './shared/Node';
import Block from '../render-dom/Block';
import mapChildren from './shared/mapChildren';
import map_children from './shared/map_children';
import TemplateScope from './shared/TemplateScope';
export default class CatchBlock extends Node {
@ -13,8 +13,8 @@ export default class CatchBlock extends Node {
this.scope = scope.child();
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,9 +2,28 @@ import Node from './shared/Node';
import ElseBlock from './ElseBlock';
import Block from '../render-dom/Block';
import Expression from './shared/Expression';
import mapChildren from './shared/mapChildren';
import map_children from './shared/map_children';
import TemplateScope from './shared/TemplateScope';
import unpackDestructuring from '../../utils/unpackDestructuring';
import { Node as INode } from '../../interfaces';
function unpack_destructuring(contexts: Array<{ name: string, tail: string }>, node: INode, tail: string) {
if (!node) return;
if (node.type === 'Identifier') {
contexts.push({
key: node,
tail
});
} else if (node.type === 'ArrayPattern') {
node.elements.forEach((element, i) => {
unpack_destructuring(contexts, element, `${tail}[${i}]`);
});
} else if (node.type === 'ObjectPattern') {
node.properties.forEach((property) => {
unpack_destructuring(contexts, property.value, `${tail}.${property.key.name}`);
});
}
}
export default class EachBlock extends Node {
type: 'EachBlock';
@ -19,7 +38,7 @@ export default class EachBlock extends Node {
key: Expression;
scope: TemplateScope;
contexts: Array<{ name: string, tail: string }>;
hasAnimation: boolean;
has_animation: boolean;
has_binding = false;
children: Node[];
@ -36,7 +55,7 @@ export default class EachBlock extends Node {
this.scope = scope.child();
this.contexts = [];
unpackDestructuring(this.contexts, info.context, '');
unpack_destructuring(this.contexts, info.context, '');
this.contexts.forEach(context => {
this.scope.add(context.key.name, this.expression.dependencies, this);
@ -52,11 +71,11 @@ export default class EachBlock extends Node {
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) {
const child = this.children.find(child => !!child.animation);
component.error(child.animation, {
@ -66,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
? new ElseBlock(component, this, this.scope, info.else)

@ -1,4 +1,4 @@
import isVoidElementName from '../../utils/isVoidElementName';
import { is_void } from '../../utils/names';
import Node from './shared/Node';
import Attribute from './Attribute';
import Binding from './Binding';
@ -9,7 +9,7 @@ import Action from './Action';
import Class from './Class';
import Text from './Text';
import { namespaces } from '../../utils/namespaces';
import mapChildren from './shared/mapChildren';
import map_children from './shared/map_children';
import { dimensions } from '../../utils/patterns';
import fuzzymatch from '../../utils/fuzzymatch';
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 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 ariaAttributeSet = new Set(ariaAttributes);
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 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 ariaRoleSet = new Set(ariaRoles);
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 aria_role_set = new Set(aria_roles);
const a11yRequiredAttributes = {
const a11y_required_attributes = {
a: ['href'],
area: ['alt', 'aria-label', 'aria-labelledby'],
@ -37,12 +37,12 @@ const a11yRequiredAttributes = {
object: ['title', 'aria-label', 'aria-labelledby']
};
const a11yDistractingElements = new Set([
const a11y_distracting_elements = new Set([
'blink',
'marquee'
]);
const a11yRequiredContent = new Set([
const a11y_required_content = new Set([
// anchor-has-content
'a',
@ -55,9 +55,9 @@ const a11yRequiredContent = new Set([
'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',
'stopPropagation',
'capture',
@ -65,7 +65,7 @@ const validModifiers = new Set([
'passive'
]);
const passiveEvents = new Set([
const passive_events = new Set([
'wheel',
'touchstart',
'touchmove',
@ -93,10 +93,10 @@ export default class Element extends Node {
super(component, parent, scope, info);
this.name = info.name;
const parentElement = parent.findNearest(/^Element/);
const parent_element = parent.find_nearest(/^Element/);
this.namespace = this.name === 'svg' ?
namespaces.svg :
parentElement ? parentElement.namespace : this.component.namespace;
parent_element ? parent_element.namespace : this.component.namespace;
if (!this.namespace && svg.test(this.name)) {
this.component.warn(this, {
@ -107,9 +107,9 @@ export default class Element extends Node {
if (this.name === 'textarea') {
if (info.children.length > 0) {
const valueAttribute = info.attributes.find(node => node.name === 'value');
if (valueAttribute) {
component.error(valueAttribute, {
const value_attribute = info.attributes.find(node => node.name === 'value');
if (value_attribute) {
component.error(value_attribute, {
code: `textarea-duplicate-value`,
message: `A <textarea> can have either a value attribute or (equivalently) child content, but not both`
});
@ -131,9 +131,9 @@ export default class Element extends Node {
// Special case — treat these the same way:
// <option>{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({
type: 'Attribute',
name: 'value',
@ -202,7 +202,7 @@ export default class Element extends Node {
this.scope = scope;
}
this.children = mapChildren(component, this, this.scope, info.children);
this.children = map_children(component, this, this.scope, info.children);
this.validate();
@ -210,7 +210,7 @@ export default class Element extends Node {
}
validate() {
if (a11yDistractingElements.has(this.name)) {
if (a11y_distracting_elements.has(this.name)) {
// no-distracting-elements
this.component.warn(this, {
code: `a11y-distracting-elements`,
@ -244,25 +244,25 @@ export default class Element extends Node {
}
}
this.validateAttributes();
this.validateBindings();
this.validateContent();
this.validateEventHandlers();
this.validate_attributes();
this.validate_bindings();
this.validate_content();
this.validate_event_handlers();
}
validateAttributes() {
validate_attributes() {
const { component } = this;
const attributeMap = new Map();
const attribute_map = new Map();
this.attributes.forEach(attribute => {
if (attribute.isSpread) return;
if (attribute.is_spread) return;
const name = attribute.name.toLowerCase();
// aria-props
if (name.startsWith('aria-')) {
if (invisibleElements.has(this.name)) {
if (invisible_elements.has(this.name)) {
// aria-unsupported-elements
component.warn(attribute, {
code: `a11y-aria-attributes`,
@ -271,8 +271,8 @@ export default class Element extends Node {
}
const type = name.slice(5);
if (!ariaAttributeSet.has(type)) {
const match = fuzzymatch(type, ariaAttributes);
if (!aria_attribute_set.has(type)) {
const match = fuzzymatch(type, aria_attributes);
let message = `A11y: Unknown aria attribute 'aria-${type}'`;
if (match) message += ` (did you mean '${match}'?)`;
@ -292,7 +292,7 @@ export default class Element extends Node {
// aria-role
if (name === 'role') {
if (invisibleElements.has(this.name)) {
if (invisible_elements.has(this.name)) {
// aria-unsupported-elements
component.warn(attribute, {
code: `a11y-misplaced-role`,
@ -300,9 +300,9 @@ export default class Element extends Node {
});
}
const value = attribute.getStaticValue();
if (value && !ariaRoleSet.has(value)) {
const match = fuzzymatch(value, ariaRoles);
const value = attribute.get_static_value();
if (value && !aria_role_set.has(value)) {
const match = fuzzymatch(value, aria_roles);
let message = `A11y: Unknown role '${value}'`;
if (match) message += ` (did you mean '${match}'?)`;
@ -339,7 +339,7 @@ export default class Element extends Node {
// tabindex-no-positive
if (name === 'tabindex') {
const value = attribute.getStaticValue();
const value = attribute.get_static_value();
if (!isNaN(value) && +value > 0) {
component.warn(attribute, {
code: `a11y-positive-tabindex`,
@ -349,7 +349,7 @@ export default class Element extends Node {
}
if (name === 'slot') {
if (!attribute.isStatic) {
if (!attribute.is_static) {
component.error(attribute, {
code: `invalid-slot-attribute`,
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
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) {
const value = attribute.getStaticValue();
const value = attribute.get_static_value();
if (value === '' || value === '#') {
component.warn(attribute, {
@ -405,19 +405,19 @@ export default class Element extends Node {
}
else {
const requiredAttributes = a11yRequiredAttributes[this.name];
if (requiredAttributes) {
const hasAttribute = requiredAttributes.some(name => attributeMap.has(name));
const required_attributes = a11y_required_attributes[this.name];
if (required_attributes) {
const has_attribute = required_attributes.some(name => attribute_map.has(name));
if (!hasAttribute) {
shouldHaveAttribute(this, requiredAttributes);
if (!has_attribute) {
should_have_attribute(this, required_attributes);
}
}
if (this.name === 'input') {
const type = attributeMap.get('type');
if (type && type.getStaticValue() === 'image') {
shouldHaveAttribute(
const type = attribute_map.get('type');
if (type && type.get_static_value() === 'image') {
should_have_attribute(
this,
['alt', 'aria-label', 'aria-labelledby'],
'input type="image"'
@ -427,24 +427,24 @@ export default class Element extends Node {
}
}
validateBindings() {
validate_bindings() {
const { component } = this;
const checkTypeAttribute = () => {
const check_type_attribute = () => {
const attribute = this.attributes.find(
(attribute: Attribute) => attribute.name === 'type'
);
if (!attribute) return null;
if (!attribute.isStatic) {
if (!attribute.is_static) {
component.error(attribute, {
code: `invalid-type`,
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) {
component.error(attribute, {
@ -476,14 +476,14 @@ export default class Element extends Node {
(attribute: Attribute) => attribute.name === 'multiple'
);
if (attribute && !attribute.isStatic) {
if (attribute && !attribute.is_static) {
component.error(attribute, {
code: `dynamic-multiple-attribute`,
message: `'multiple' attribute cannot be dynamic if select uses two-way binding`
});
}
} else {
checkTypeAttribute();
check_type_attribute();
}
} else if (name === 'checked' || name === 'indeterminate') {
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, {
code: `invalid-binding`,
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') {
component.error(binding, {
@ -523,7 +523,7 @@ export default class Element extends Node {
});
}
const type = checkTypeAttribute();
const type = check_type_attribute();
if (type !== 'file') {
component.error(binding, {
@ -557,7 +557,7 @@ export default class Element extends Node {
code: 'invalid-binding',
message: `'${binding.name}' is not a valid binding on SVG elements`
});
} else if (isVoidElementName(this.name)) {
} else if (is_void(this.name)) {
component.error(binding, {
code: 'invalid-binding',
message: `'${binding.name}' is not a valid binding on void elements like <${this.name}>. Use a wrapper element instead`
@ -572,8 +572,8 @@ export default class Element extends Node {
});
}
validateContent() {
if (!a11yRequiredContent.has(this.name)) return;
validate_content() {
if (!a11y_required_content.has(this.name)) return;
if (this.children.length === 0) {
this.component.warn(this, {
@ -583,7 +583,7 @@ export default class Element extends Node {
}
}
validateEventHandlers() {
validate_event_handlers() {
const { component } = this;
this.handlers.forEach(handler => {
@ -595,16 +595,16 @@ export default class Element extends Node {
}
handler.modifiers.forEach(modifier => {
if (!validModifiers.has(modifier)) {
if (!valid_modifiers.has(modifier)) {
component.error(handler, {
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 (passiveEvents.has(handler.name)) {
if (handler.canMakePassive) {
if (passive_events.has(handler.name)) {
if (handler.can_make_passive) {
component.warn(handler, {
code: 'redundant-event-modifier',
message: `Touch event handlers that don't use the 'event' object are passive by default`
@ -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
// how event listeners work
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
handler.modifiers.add('passive');
}
});
}
getStaticAttributeValue(name: string) {
get_static_attribute_value(name: string) {
const attribute = this.attributes.find(
(attr: Attribute) => attr.type === 'Attribute' && attr.name.toLowerCase() === name
);
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 === 1 && attribute.chunks[0].type === 'Text') {
@ -652,20 +652,20 @@ export default class Element extends Node {
return null;
}
isMediaNode() {
is_media_node() {
return this.name === 'audio' || this.name === 'video';
}
addCssClass(className = this.component.stylesheet.id) {
const classAttribute = this.attributes.find(a => a.name === 'class');
if (classAttribute && !classAttribute.isTrue) {
if (classAttribute.chunks.length === 1 && classAttribute.chunks[0].type === 'Text') {
(classAttribute.chunks[0] as Text).data += ` ${className}`;
add_css_class(class_name = this.component.stylesheet.id) {
const class_attribute = this.attributes.find(a => a.name === 'class');
if (class_attribute && !class_attribute.is_true) {
if (class_attribute.chunks.length === 1 && class_attribute.chunks[0].type === 'Text') {
(class_attribute.chunks[0] as Text).data += ` ${class_name}`;
} else {
(<Node[]>classAttribute.chunks).push(
(<Node[]>class_attribute.chunks).push(
new Text(this.component, this, this.scope, {
type: 'Text',
data: ` ${className}`
data: ` ${class_name}`
})
);
}
@ -674,14 +674,14 @@ export default class Element extends Node {
new Attribute(this.component, this, this.scope, {
type: 'Attribute',
name: 'class',
value: [{ type: 'Text', data: className }]
value: [{ type: 'Text', data: class_name }]
})
);
}
}
}
function shouldHaveAttribute(
function should_have_attribute(
node,
attributes: string[],
name = node.name

@ -1,6 +1,6 @@
import Node from './shared/Node';
import Block from '../render-dom/Block';
import mapChildren from './shared/mapChildren';
import map_children from './shared/map_children';
export default class ElseBlock extends Node {
type: 'ElseBlock';
@ -9,8 +9,8 @@ export default class ElseBlock extends Node {
constructor(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();
}
}

@ -1,7 +1,7 @@
import Node from './shared/Node';
import Expression from './shared/Expression';
import Component from '../Component';
import deindent from '../../utils/deindent';
import deindent from '../utils/deindent';
import Block from '../render-dom/Block';
export default class EventHandler extends Node {
@ -9,8 +9,8 @@ export default class EventHandler extends Node {
modifiers: Set<string>;
expression: Expression;
handler_name: string;
usesContext = false;
canMakePassive = false;
uses_context = false;
can_make_passive = false;
constructor(component: Component, parent, template_scope, info) {
super(component, parent, template_scope, info);
@ -20,12 +20,12 @@ export default class EventHandler extends Node {
if (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) {
// TODO make this detection more accurate — if `event.preventDefault` isn't called, and
// `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') {
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) {
this.canMakePassive = true;
this.can_make_passive = true;
}
}
} else {
const name = component.getUniqueName(`${this.name}_handler`);
const name = component.get_unique_name(`${this.name}_handler`);
component.add_var({
name,

@ -1,6 +1,6 @@
import Node from './shared/Node';
import Component from '../Component';
import mapChildren from './shared/mapChildren';
import map_children from './shared/map_children';
import Block from '../render-dom/Block';
import TemplateScope from './shared/TemplateScope';
@ -14,6 +14,6 @@ export default class Fragment extends Node {
super(component, null, scope, info);
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 Block from '../render-dom/Block';
import mapChildren from './shared/mapChildren';
import map_children from './shared/map_children';
export default class Head extends Node {
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));
}));
}

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

@ -1,6 +1,6 @@
import Node from './shared/Node';
import Attribute from './Attribute';
import mapChildren from './shared/mapChildren';
import map_children from './shared/map_children';
import Binding from './Binding';
import EventHandler from './EventHandler';
import Expression from './shared/Expression';
@ -96,6 +96,6 @@ export default class InlineComponent extends Node {
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 Block from '../render-dom/Block';
import mapChildren from './shared/mapChildren';
import map_children from './shared/map_children';
export default class PendingBlock extends Node {
block: Block;
@ -8,8 +8,8 @@ export default class PendingBlock extends Node {
constructor(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.value.length !== 1 || attr.value[0].type !== 'Text') {
component.error(attr, {
@ -34,8 +27,8 @@ export default class Slot extends Element {
});
}
const slotName = attr.value[0].data;
if (slotName === 'default') {
const slot_name = attr.value[0].data;
if (slot_name === 'default') {
component.error(attr, {
code: `invalid-slot-name`,
message: `default is a reserved word — it cannot be used as a slot name`
@ -46,11 +39,11 @@ export default class Slot extends Element {
// TODO should duplicate slots be disallowed? Feels like it's more likely to be a
// bug than anything. Perhaps it should be a warning
// if (validator.slots.has(slotName)) {
// validator.error(`duplicate '${slotName}' <slot> element`, nameAttribute.start);
// if (validator.slots.has(slot_name)) {
// validator.error(`duplicate '${slot_name}' <slot> element`, nameAttribute.start);
// }
// validator.slots.add(slotName);
// validator.slots.add(slot_name);
});
// if (node.attributes.length === 0) && validator.slots.has('default')) {
@ -61,14 +54,14 @@ export default class Slot extends Element {
// }
}
getStaticAttributeValue(name: string) {
get_static_attribute_value(name: string) {
const attribute = this.attributes.find(
attr => attr.name.toLowerCase() === name
);
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 === 1 && attribute.chunks[0].type === 'Text') {

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

@ -1,6 +1,6 @@
import Node from './shared/Node';
import Block from '../render-dom/Block';
import mapChildren from './shared/mapChildren';
import map_children from './shared/map_children';
import TemplateScope from './shared/TemplateScope';
export default class ThenBlock extends Node {
@ -13,8 +13,8 @@ export default class ThenBlock extends Node {
this.scope = scope.child();
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 mapChildren from './shared/mapChildren';
import map_children from './shared/map_children';
export default class Title extends Node {
type: 'Title';
children: any[]; // TODO
shouldCache: boolean;
should_cache: boolean;
constructor(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) {
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' ||
scope.names.has(info.children[0].name)

@ -21,11 +21,11 @@ export default class Transition extends Node {
this.is_local = info.modifiers.includes('local');
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 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, {
code: `duplicate-transition`,

@ -1,12 +1,12 @@
import Node from './shared/Node';
import Binding from './Binding';
import EventHandler from './EventHandler';
import flattenReference from '../../utils/flattenReference';
import flatten_reference from '../utils/flatten_reference';
import fuzzymatch from '../../utils/fuzzymatch';
import list from '../../utils/list';
import Action from './Action';
const validBindings = [
const valid_bindings = [
'innerWidth',
'innerHeight',
'outerWidth',
@ -32,7 +32,7 @@ export default class Window extends Node {
else if (node.type === 'Binding') {
if (node.expression.type !== 'Identifier') {
const { parts } = flattenReference(node.expression);
const { parts } = flatten_reference(node.expression);
// TODO is this constraint necessary?
component.error(node.expression, {
@ -41,11 +41,11 @@ export default class Window extends Node {
});
}
if (!~validBindings.indexOf(node.name)) {
if (!~valid_bindings.indexOf(node.name)) {
const match = (
node.name === 'width' ? 'innerWidth' :
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>`;
@ -58,7 +58,7 @@ export default class Window extends Node {
} else {
component.error(node, {
code: `invalid-binding`,
message: `${message} — valid bindings are ${list(validBindings)}`
message: `${message} — valid bindings are ${list(valid_bindings)}`
});
}
}

@ -1,19 +1,19 @@
import Component from '../../Component';
import { walk } from 'estree-walker';
import isReference from 'is-reference';
import flattenReference from '../../../utils/flattenReference';
import { createScopes, Scope, extractNames } from '../../../utils/annotateWithScopes';
import is_reference from 'is-reference';
import flatten_reference from '../../utils/flatten_reference';
import { create_scopes, Scope, extract_names } from '../../utils/scope';
import { Node } from '../../../interfaces';
import globalWhitelist from '../../../utils/globalWhitelist';
import deindent from '../../../utils/deindent';
import { globals } from '../../../utils/names';
import deindent from '../../utils/deindent';
import Wrapper from '../../render-dom/wrappers/shared/Wrapper';
import sanitize from '../../../utils/sanitize';
import { sanitize } from '../../../utils/names';
import TemplateScope from './TemplateScope';
import getObject from '../../../utils/getObject';
import get_object from '../../utils/get_object';
import { nodes_match } from '../../../utils/nodes_match';
import Block from '../../render-dom/Block';
const binaryOperators: Record<string, number> = {
const binary_operators: Record<string, number> = {
'**': 15,
'*': 14,
'/': 14,
@ -38,7 +38,7 @@ const binaryOperators: Record<string, number> = {
'|': 7
};
const logicalOperators: Record<string, number> = {
const logical_operators: Record<string, number> = {
'&&': 6,
'||': 5
};
@ -52,8 +52,8 @@ const precedence: Record<string, (node?: Node) => number> = {
CallExpression: () => 19,
UpdateExpression: () => 17,
UnaryExpression: () => 16,
BinaryExpression: (node: Node) => binaryOperators[node.operator],
LogicalExpression: (node: Node) => logicalOperators[node.operator],
BinaryExpression: (node: Node) => binary_operators[node.operator],
LogicalExpression: (node: Node) => logical_operators[node.operator],
ConditionalExpression: () => 4,
AssignmentExpression: () => 3,
YieldExpression: () => 2,
@ -77,8 +77,7 @@ export default class Expression {
is_synthetic: boolean;
declarations: string[] = [];
usesContext = false;
usesEvent = false;
uses_context = false;
rendered: string;
@ -93,11 +92,11 @@ export default class Expression {
this.node = info;
this.template_scope = template_scope;
this.owner = owner;
this.is_synthetic = owner.isSynthetic;
this.is_synthetic = owner.is_synthetic;
const { dependencies, contextual_dependencies } = this;
let { map, scope } = createScopes(info);
let { map, scope } = create_scopes(info);
this.scope = scope;
this.scope_map = map;
@ -118,12 +117,12 @@ export default class Expression {
function_expression = node;
}
if (isReference(node, parent)) {
const { name, nodes } = flattenReference(node);
if (is_reference(node, parent)) {
const { name, nodes } = flatten_reference(node);
if (scope.has(name)) return;
if (globalWhitelist.has(name) && !component.var_lookup.has(name)) return;
if (globals.has(name) && !component.var_lookup.has(name)) return;
if (name[0] === '$' && template_scope.names.has(name.slice(1))) {
component.error(node, {
@ -137,12 +136,12 @@ export default class Expression {
dependencies.add(name);
}
} else if (template_scope.names.has(name)) {
expression.usesContext = true;
expression.uses_context = true;
contextual_dependencies.add(name);
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 {
if (!function_expression) {
@ -164,10 +163,10 @@ export default class Expression {
if (node.type === 'AssignmentExpression') {
deep = node.left.type === 'MemberExpression';
names = deep
? [getObject(node.left).name]
: extractNames(node.left);
? [get_object(node.left).name]
: extract_names(node.left);
} else if (node.type === 'UpdateExpression') {
const { name } = getObject(node.argument);
const { name } = get_object(node.argument);
names = [name];
}
}
@ -175,7 +174,7 @@ export default class Expression {
if (names) {
names.forEach(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);
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;
}
@ -253,24 +252,24 @@ export default class Expression {
scope = map.get(node);
}
if (isReference(node, parent)) {
const { name, nodes } = flattenReference(node);
if (is_reference(node, parent)) {
const { name, nodes } = flatten_reference(node);
if (scope.has(name)) return;
if (globalWhitelist.has(name) && !component.var_lookup.has(name)) return;
if (globals.has(name) && !component.var_lookup.has(name)) return;
if (function_expression) {
if (template_scope.names.has(name)) {
contextual_dependencies.add(name);
template_scope.dependenciesForName.get(name).forEach(dependency => {
template_scope.dependencies_for_name.get(name).forEach(dependency => {
dependencies.add(dependency);
});
} else {
dependencies.add(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
? `${name}: ctx.`
: 'ctx.');
@ -289,8 +288,8 @@ export default class Expression {
if (function_expression) {
if (node.type === 'AssignmentExpression') {
const names = node.left.type === 'MemberExpression'
? [getObject(node.left).name]
: extractNames(node.left);
? [get_object(node.left).name]
: extract_names(node.left);
if (node.operator === '=' && nodes_match(node.left, node.right)) {
const dirty = names.filter(name => {
@ -311,7 +310,7 @@ export default class Expression {
});
}
} else if (node.type === 'UpdateExpression') {
const { name } = getObject(node.argument);
const { name } = get_object(node.argument);
if (scope.declarations.has(name)) return;
@ -347,7 +346,7 @@ export default class Expression {
// the return value doesn't matter
}
const name = component.getUniqueName(
const name = component.get_unique_name(
sanitize(get_function_name(node, owner))
);
@ -465,9 +464,9 @@ export default class Expression {
});
if (declarations.length > 0) {
block.maintainContext = true;
block.maintain_context = true;
declarations.forEach(declaration => {
block.builders.init.addBlock(declaration);
block.builders.init.add_block(declaration);
});
}
@ -487,11 +486,11 @@ function get_function_name(node, parent) {
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 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);

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

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

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

@ -16,7 +16,7 @@ import Title from '../Title';
import Window from '../Window';
import Node from './Node';
function getConstructor(type): typeof Node {
function get_constructor(type): typeof Node {
switch (type) {
case 'AwaitBlock': return AwaitBlock;
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;
return children.map(child => {
const constructor = getConstructor(child.type);
const constructor = get_constructor(child.type);
const node = new constructor(component, parent, scope, child);
if (last) last.next = node;

@ -1,10 +1,7 @@
import CodeBuilder from '../../utils/CodeBuilder';
import deindent from '../../utils/deindent';
import CodeBuilder from '../utils/CodeBuilder';
import deindent from '../utils/deindent';
import Renderer from './Renderer';
import Wrapper from './wrappers/shared/Wrapper';
import EachBlockWrapper from './wrappers/EachBlock';
import InlineComponentWrapper from './wrappers/InlineComponent';
import ElementWrapper from './wrappers/Element';
export interface BlockOptions {
parent?: Block;
@ -48,19 +45,19 @@ export default class Block {
event_listeners: string[] = [];
maintainContext: boolean;
hasAnimation: boolean;
hasIntros: boolean;
hasOutros: boolean;
hasIntroMethod: boolean; // could have the method without the transition, due to siblings
hasOutroMethod: boolean;
maintain_context: boolean;
has_animation: boolean;
has_intros: boolean;
has_outros: boolean;
has_intro_method: boolean; // could have the method without the transition, due to siblings
has_outro_method: boolean;
outros: number;
aliases: 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;
constructor(options: BlockOptions) {
@ -94,19 +91,19 @@ export default class Block {
destroy: new CodeBuilder(),
};
this.hasAnimation = false;
this.hasIntroMethod = 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_animation = false;
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.has_outro_method = false;
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.aliases = new Map().set('ctx', this.getUniqueName('ctx'));
if (this.key) this.aliases.set('key', this.getUniqueName('key'));
this.aliases = new Map().set('ctx', this.get_unique_name('ctx'));
if (this.key) this.aliases.set('key', this.get_unique_name('key'));
}
assignVariableNames() {
assign_variable_names() {
const seen = new Set();
const dupes = new Set();
@ -116,7 +113,7 @@ export default class Block {
const wrapper = this.wrappers[i];
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)) {
dupes.add(wrapper.var);
@ -136,60 +133,60 @@ export default class Block {
if (dupes.has(wrapper.var)) {
const i = counts.get(wrapper.var) || 0;
counts.set(wrapper.var, i + 1);
wrapper.var = this.getUniqueName(wrapper.var + i);
wrapper.var = this.get_unique_name(wrapper.var + i);
} 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 => {
this.dependencies.add(dependency);
});
this.hasUpdateMethod = true;
this.has_update_method = true;
}
addElement(
add_element(
name: string,
renderStatement: string,
claimStatement: string,
parentNode: string,
noDetach?: boolean
render_statement: string,
claim_statement: string,
parent_node: string,
no_detach?: boolean
) {
this.addVariable(name);
this.builders.create.addLine(`${name} = ${renderStatement};`);
this.add_variable(name);
this.builders.create.add_line(`${name} = ${render_statement};`);
if (this.renderer.options.hydratable) {
this.builders.claim.addLine(`${name} = ${claimStatement || renderStatement};`);
this.builders.claim.add_line(`${name} = ${claim_statement || render_statement};`);
}
if (parentNode) {
this.builders.mount.addLine(`@append(${parentNode}, ${name});`);
if (parentNode === 'document.head') this.builders.destroy.addLine(`@detachNode(${name});`);
if (parent_node) {
this.builders.mount.add_line(`@append(${parent_node}, ${name});`);
if (parent_node === 'document.head') this.builders.destroy.add_line(`@detach(${name});`);
} else {
this.builders.mount.addLine(`@insert(#target, ${name}, anchor);`);
if (!noDetach) this.builders.destroy.addConditional('detach', `@detachNode(${name});`);
this.builders.mount.add_line(`@insert(#target, ${name}, anchor);`);
if (!no_detach) this.builders.destroy.add_conditional('detaching', `@detach(${name});`);
}
}
addIntro(local?: boolean) {
this.hasIntros = this.hasIntroMethod = this.renderer.hasIntroTransitions = true;
if (!local && this.parent) this.parent.addIntro();
add_intro(local?: boolean) {
this.has_intros = this.has_intro_method = true;
if (!local && this.parent) this.parent.add_intro();
}
addOutro(local?: boolean) {
this.hasOutros = this.hasOutroMethod = this.renderer.hasOutroTransitions = true;
add_outro(local?: boolean) {
this.has_outros = this.has_outro_method = true;
this.outros += 1;
if (!local && this.parent) this.parent.addOutro();
if (!local && this.parent) this.parent.add_outro();
}
addAnimation() {
this.hasAnimation = true;
add_animation() {
this.has_animation = true;
}
addVariable(name: string, init?: string) {
add_variable(name: string, init?: string) {
if (name[0] === '#') {
name = this.alias(name.slice(1));
}
@ -205,7 +202,7 @@ export default class Block {
alias(name: string) {
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);
@ -215,146 +212,146 @@ export default class Block {
return new Block(Object.assign({}, this, { key: null }, options, { parent: this }));
}
getContents(localKey?: string) {
get_contents(local_key?: string) {
const { dev } = this.renderer.options;
if (this.hasOutros) {
this.addVariable('#current');
if (this.has_outros) {
this.add_variable('#current');
if (!this.builders.intro.isEmpty()) {
this.builders.intro.addLine(`#current = true;`);
this.builders.mount.addLine(`#current = true;`);
if (!this.builders.intro.is_empty()) {
this.builders.intro.add_line(`#current = true;`);
this.builders.mount.add_line(`#current = true;`);
}
if (!this.builders.outro.isEmpty()) {
this.builders.outro.addLine(`#current = false;`);
if (!this.builders.outro.is_empty()) {
this.builders.outro.add_line(`#current = false;`);
}
}
if (this.autofocus) {
this.builders.mount.addLine(`${this.autofocus}.focus();`);
this.builders.mount.add_line(`${this.autofocus}.focus();`);
}
this.renderListeners();
this.render_listeners();
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) {
properties.addBlock(`key: ${localKey},`);
if (local_key) {
properties.add_block(`key: ${local_key},`);
}
if (this.first) {
properties.addBlock(`first: null,`);
this.builders.hydrate.addLine(`this.first = ${this.first};`);
properties.add_block(`first: null,`);
this.builders.hydrate.add_line(`this.first = ${this.first};`);
}
if (this.builders.create.isEmpty() && this.builders.hydrate.isEmpty()) {
properties.addLine(`c: @noop,`);
if (this.builders.create.is_empty() && this.builders.hydrate.is_empty()) {
properties.add_line(`c: @noop,`);
} else {
const hydrate = !this.builders.hydrate.isEmpty() && (
const hydrate = !this.builders.hydrate.is_empty() && (
this.renderer.options.hydratable
? `this.h()`
: this.builders.hydrate
);
properties.addBlock(deindent`
${methodName('c', 'create')}() {
properties.add_block(deindent`
${method_name('c', 'create')}() {
${this.builders.create}
${hydrate}
},
`);
}
if (this.renderer.options.hydratable || !this.builders.claim.isEmpty()) {
if (this.builders.claim.isEmpty() && this.builders.hydrate.isEmpty()) {
properties.addLine(`l: @noop,`);
if (this.renderer.options.hydratable || !this.builders.claim.is_empty()) {
if (this.builders.claim.is_empty() && this.builders.hydrate.is_empty()) {
properties.add_line(`l: @noop,`);
} else {
properties.addBlock(deindent`
${methodName('l', 'claim')}(nodes) {
properties.add_block(deindent`
${method_name('l', 'claim')}(nodes) {
${this.builders.claim}
${this.renderer.options.hydratable && !this.builders.hydrate.isEmpty() && `this.h();`}
${this.renderer.options.hydratable && !this.builders.hydrate.is_empty() && `this.h();`}
},
`);
}
}
if (this.renderer.options.hydratable && !this.builders.hydrate.isEmpty()) {
properties.addBlock(deindent`
${methodName('h', 'hydrate')}() {
if (this.renderer.options.hydratable && !this.builders.hydrate.is_empty()) {
properties.add_block(deindent`
${method_name('h', 'hydrate')}() {
${this.builders.hydrate}
},
`);
}
if (this.builders.mount.isEmpty()) {
properties.addLine(`m: @noop,`);
if (this.builders.mount.is_empty()) {
properties.add_line(`m: @noop,`);
} else {
properties.addBlock(deindent`
${methodName('m', 'mount')}(#target, anchor) {
properties.add_block(deindent`
${method_name('m', 'mount')}(#target, anchor) {
${this.builders.mount}
},
`);
}
if (this.hasUpdateMethod || this.maintainContext) {
if (this.builders.update.isEmpty() && !this.maintainContext) {
properties.addLine(`p: @noop,`);
if (this.has_update_method || this.maintain_context) {
if (this.builders.update.is_empty() && !this.maintain_context) {
properties.add_line(`p: @noop,`);
} else {
properties.addBlock(deindent`
${methodName('p', 'update')}(changed, ${this.maintainContext ? 'new_ctx' : 'ctx'}) {
${this.maintainContext && `ctx = new_ctx;`}
properties.add_block(deindent`
${method_name('p', 'update')}(changed, ${this.maintain_context ? 'new_ctx' : 'ctx'}) {
${this.maintain_context && `ctx = new_ctx;`}
${this.builders.update}
},
`);
}
}
if (this.hasAnimation) {
properties.addBlock(deindent`
${methodName('r', 'measure')}() {
if (this.has_animation) {
properties.add_block(deindent`
${method_name('r', 'measure')}() {
${this.builders.measure}
},
${methodName('f', 'fix')}() {
${method_name('f', 'fix')}() {
${this.builders.fix}
},
${methodName('a', 'animate')}() {
${method_name('a', 'animate')}() {
${this.builders.animate}
},
`);
}
if (this.hasIntroMethod || this.hasOutroMethod) {
if (this.builders.intro.isEmpty()) {
properties.addLine(`i: @noop,`);
if (this.has_intro_method || this.has_outro_method) {
if (this.builders.intro.is_empty()) {
properties.add_line(`i: @noop,`);
} else {
properties.addBlock(deindent`
${methodName('i', 'intro')}(#local) {
${this.hasOutros && `if (#current) return;`}
properties.add_block(deindent`
${method_name('i', 'intro')}(#local) {
${this.has_outros && `if (#current) return;`}
${this.builders.intro}
},
`);
}
if (this.builders.outro.isEmpty()) {
properties.addLine(`o: @noop,`);
if (this.builders.outro.is_empty()) {
properties.add_line(`o: @noop,`);
} else {
properties.addBlock(deindent`
${methodName('o', 'outro')}(#local) {
properties.add_block(deindent`
${method_name('o', 'outro')}(#local) {
${this.builders.outro}
},
`);
}
}
if (this.builders.destroy.isEmpty()) {
properties.addLine(`d: @noop`);
if (this.builders.destroy.is_empty()) {
properties.add_line(`d: @noop`);
} else {
properties.addBlock(deindent`
${methodName('d', 'destroy')}(detach) {
properties.add_block(deindent`
${method_name('d', 'destroy')}(detaching) {
${this.builders.destroy}
}
`);
@ -369,7 +366,7 @@ export default class Block {
})
.join(', ')};`}
${!this.builders.init.isEmpty() && this.builders.init}
${!this.builders.init.is_empty() && this.builders.init}
return {
${properties}
@ -379,26 +376,26 @@ export default class Block {
});
}
renderListeners(chunk: string = '') {
render_listeners(chunk: string = '') {
if (this.event_listeners.length > 0) {
this.addVariable(`#dispose${chunk}`);
this.add_variable(`#dispose${chunk}`);
if (this.event_listeners.length === 1) {
this.builders.hydrate.addLine(
this.builders.hydrate.add_line(
`#dispose${chunk} = ${this.event_listeners[0]};`
);
this.builders.destroy.addLine(
this.builders.destroy.add_line(
`#dispose${chunk}();`
)
} else {
this.builders.hydrate.addBlock(deindent`
this.builders.hydrate.add_block(deindent`
#dispose${chunk} = [
${this.event_listeners.join(',\n')}
];
`);
this.builders.destroy.addLine(
this.builders.destroy.add_line(
`@run_all(#dispose${chunk});`
);
}
@ -406,12 +403,12 @@ export default class Block {
}
toString() {
const localKey = this.key && this.getUniqueName('key');
const local_key = this.key && this.get_unique_name('key');
return deindent`
${this.comment && `// ${this.comment}`}
function ${this.name}(${this.key ? `${localKey}, ` : ''}ctx) {
${this.getContents(localKey)}
function ${this.name}(${this.key ? `${local_key}, ` : ''}ctx) {
${this.get_contents(local_key)}
}
`;
}

@ -2,7 +2,7 @@ import Block from './Block';
import { CompileOptions } from '../../interfaces';
import Component from '../Component';
import FragmentWrapper from './wrappers/Fragment';
import CodeBuilder from '../../utils/CodeBuilder';
import CodeBuilder from '../utils/CodeBuilder';
export default class Renderer {
component: Component; // TODO Maybe Renderer shouldn't know about Component?
@ -11,16 +11,13 @@ export default class Renderer {
blocks: (Block | string)[];
readonly: Set<string>;
slots: Set<string>;
metaBindings: CodeBuilder;
bindingGroups: string[];
meta_bindings: CodeBuilder;
binding_groups: string[];
block: Block;
fragment: FragmentWrapper;
fileVar: string;
hasIntroTransitions: boolean;
hasOutroTransitions: boolean;
file_var: string;
constructor(component: Component, options: CompileOptions) {
this.component = component;
@ -30,12 +27,12 @@ export default class Renderer {
this.readonly = 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
this.metaBindings = new CodeBuilder();
this.meta_bindings = new CodeBuilder();
this.bindingGroups = [];
this.binding_groups = [];
// main block
this.block = new Block({
@ -48,7 +45,7 @@ export default class Renderer {
dependencies: new Set(),
});
this.block.hasUpdateMethod = true;
this.block.has_update_method = true;
this.blocks = [];
this.fragment = new FragmentWrapper(
@ -62,11 +59,11 @@ export default class Renderer {
this.blocks.forEach(block => {
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');
}

@ -1,16 +1,16 @@
import deindent from '../../utils/deindent';
import { stringify, escape } from '../../utils/stringify';
import CodeBuilder from '../../utils/CodeBuilder';
import deindent from '../utils/deindent';
import { stringify, escape } from '../utils/stringify';
import CodeBuilder from '../utils/CodeBuilder';
import Component from '../Component';
import Renderer from './Renderer';
import { CompileOptions } from '../../interfaces';
import { walk } from 'estree-walker';
import stringifyProps from '../../utils/stringifyProps';
import addToSet from '../../utils/addToSet';
import getObject from '../../utils/getObject';
import { extractNames } from '../../utils/annotateWithScopes';
import { stringify_props } from '../utils/stringify_props';
import add_to_set from '../utils/add_to_set';
import get_object from '../utils/get_object';
import { extract_names } from '../utils/scope';
import { nodes_match } from '../../utils/nodes_match';
import sanitize from '../../utils/sanitize';
import { sanitize } from '../../utils/names';
export default function dom(
component: Component,
@ -21,26 +21,26 @@ export default function dom(
const renderer = new Renderer(component, options);
const { block } = renderer;
block.hasOutroMethod = true;
block.has_outro_method = true;
// prevent fragment being created twice (#1063)
if (options.customElement) block.builders.create.addLine(`this.c = @noop;`);
if (options.customElement) block.builders.create.add_line(`this.c = @noop;`);
const builder = new CodeBuilder();
if (component.compileOptions.dev) {
builder.addLine(`const ${renderer.fileVar} = ${JSON.stringify(component.file)};`);
if (component.compile_options.dev) {
builder.add_line(`const ${renderer.file_var} = ${JSON.stringify(component.file)};`);
}
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, { onlyEscapeAtSymbol: true });
css.code, { only_escape_at_symbol: true });
if (styles && component.compileOptions.css !== false && !options.customElement) {
builder.addBlock(deindent`
if (styles && component.compile_options.css !== false && !options.customElement) {
builder.add_block(deindent`
function @add_css() {
var style = @createElement("style");
var style = @element("style");
style.id = '${component.stylesheet.id}-style';
style.textContent = ${styles};
@append(document.head, style);
@ -53,11 +53,11 @@ export default function dom(
const blocks = renderer.blocks.slice().reverse();
blocks.forEach(block => {
builder.addBlock(block.toString());
builder.add_block(block.toString());
});
if (options.dev && !options.hydratable) {
block.builders.claim.addLine(
block.builders.claim.add_line(
'throw new Error("options.hydrate only works if the component was compiled with the `hydratable: true` option");'
);
}
@ -66,7 +66,7 @@ export default function dom(
// explicit opt-in, or something?
const should_add_css = (
!options.customElement &&
component.stylesheet.hasStyles &&
component.stylesheet.has_styles &&
options.css !== false
);
@ -90,7 +90,7 @@ export default function dom(
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;
props.forEach(x => {
@ -102,7 +102,7 @@ export default function dom(
return ${x.name};
}
`);
} else if (component.componentOptions.accessors) {
} else if (component.component_options.accessors) {
body.push(deindent`
get ${x.export_name}() {
return this.$$.ctx.${x.name};
@ -111,7 +111,7 @@ export default function dom(
}
if (variable.writable && !renderer.readonly.has(x.export_name)) {
if (component.componentOptions.accessors) {
if (component.component_options.accessors) {
body.push(deindent`
set ${x.export_name}(${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`
set ${x.export_name}(value) {
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
// checking that expected ones were passed
const expected = props.filter(prop => !prop.initialised);
@ -168,15 +168,15 @@ export default function dom(
let names = [];
if (node.left.type === 'MemberExpression') {
const left_object_name = getObject(node.left).name;
const left_object_name = get_object(node.left).name;
left_object_name && (names = [left_object_name]);
} else {
names = extractNames(node.left);
names = extract_names(node.left);
}
if (node.operator === '=' && nodes_match(node.left, node.right)) {
const dirty = names.filter(name => {
return scope.findOwner(name) === component.instance_scope;
return scope.find_owner(name) === component.instance_scope;
});
if (dirty.length) component.has_reactive_assignments = true;
@ -184,7 +184,7 @@ export default function dom(
code.overwrite(node.start, node.end, dirty.map(n => component.invalidate(n)).join('; '));
} else {
names.forEach(name => {
const owner = scope.findOwner(name);
const owner = scope.find_owner(name);
if (owner && owner !== component.instance_scope) return;
const variable = component.var_lookup.get(name);
@ -197,9 +197,9 @@ export default function dom(
}
else if (node.type === 'UpdateExpression') {
const { name } = getObject(node.argument);
const { name } = get_object(node.argument);
if (scope.findOwner(name) !== component.instance_scope) return;
if (scope.find_owner(name) !== component.instance_scope) return;
const variable = component.var_lookup.get(name);
if (variable && variable.hoistable) return;
@ -258,7 +258,7 @@ export default function dom(
const subscribe = component.helper('subscribe');
let insert = `${subscribe}($$self, ${name}, $${callback})`;
if (component.compileOptions.dev) {
if (component.compile_options.dev) {
const validate_store = component.helper('validate_store');
insert = `${validate_store}(${name}, '${name}'); ${insert}`;
}
@ -272,9 +272,9 @@ export default function dom(
args.push('$$props', '$$invalidate');
}
builder.addBlock(deindent`
builder.add_block(deindent`
function create_fragment(ctx) {
${block.getContents()}
${block.get_contents()}
}
${component.module_javascript}
@ -303,7 +303,7 @@ export default function dom(
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`);
}
@ -322,7 +322,7 @@ export default function dom(
const all_reactive_dependencies = new Set();
component.reactive_declarations.forEach(d => {
addToSet(all_reactive_dependencies, d.dependencies);
add_to_set(all_reactive_dependencies, d.dependencies);
});
const reactive_store_subscriptions = reactive_stores
@ -331,7 +331,7 @@ export default function dom(
return !variable || variable.hoistable;
})
.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}); });
`);
@ -379,7 +379,7 @@ export default function dom(
return $name;
});
builder.addBlock(deindent`
builder.add_block(deindent`
function ${definition}(${args.join(', ')}) {
${reactive_store_declarations.length > 0 && `let ${reactive_store_declarations.join(', ')};`}
@ -391,7 +391,7 @@ export default function dom(
${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')}
@ -404,7 +404,7 @@ export default function dom(
};
`}
return ${stringifyProps(filtered_declarations)};
return ${stringify_props(filtered_declarations)};
}
`);
}
@ -412,12 +412,12 @@ export default function dom(
const prop_names = `[${props.map(v => JSON.stringify(v.export_name)).join(', ')}]`;
if (options.customElement) {
builder.addBlock(deindent`
builder.add_block(deindent`
class ${name} extends @SvelteElement {
constructor(options) {
super();
${css.code && `this.shadowRoot.innerHTML = \`<style>${escape(css.code, { onlyEscapeAtSymbol: true }).replace(/\\/g, '\\\\')}${options.dev ? `\n/*# sourceMappingURL=${css.map.toUrl()} */` : ''}</style>\`;`}
${css.code && `this.shadowRoot.innerHTML = \`<style>${escape(css.code, { only_escape_at_symbol: true }).replace(/\\/g, '\\\\')}${options.dev ? `\n/*# sourceMappingURL=${css.map.toUrl()} */` : ''}</style>\`;`}
@init(this, { target: this.shadowRoot }, ${definition}, create_fragment, ${not_equal}, ${prop_names});
@ -448,7 +448,7 @@ export default function dom(
} else {
const superclass = options.dev ? 'SvelteComponentDev' : 'SvelteComponent';
builder.addBlock(deindent`
builder.add_block(deindent`
class ${name} extends @${superclass} {
constructor(options) {
super(${options.dev && `options`});

@ -2,8 +2,8 @@ import Wrapper from './shared/Wrapper';
import Renderer from '../Renderer';
import Block from '../Block';
import AwaitBlock from '../../nodes/AwaitBlock';
import createDebuggingComment from '../../../utils/createDebuggingComment';
import deindent from '../../../utils/deindent';
import create_debugging_comment from './shared/create_debugging_comment';
import deindent from '../../utils/deindent';
import FragmentWrapper from './Fragment';
import PendingBlock from '../../nodes/PendingBlock';
import ThenBlock from '../../nodes/ThenBlock';
@ -13,7 +13,7 @@ class AwaitBlockBranch extends Wrapper {
node: PendingBlock | ThenBlock | CatchBlock;
block: Block;
fragment: FragmentWrapper;
isDynamic: boolean;
is_dynamic: boolean;
var = null;
@ -23,14 +23,14 @@ class AwaitBlockBranch extends Wrapper {
block: Block,
parent: Wrapper,
node: AwaitBlock,
stripWhitespace: boolean,
nextSibling: Wrapper
strip_whitespace: boolean,
next_sibling: Wrapper
) {
super(renderer, block, parent, node);
this.block = block.child({
comment: createDebuggingComment(node, this.renderer.component),
name: this.renderer.component.getUniqueName(`create_${status}_block`)
comment: create_debugging_comment(node, this.renderer.component),
name: this.renderer.component.get_unique_name(`create_${status}_block`)
});
this.fragment = new FragmentWrapper(
@ -38,11 +38,11 @@ class AwaitBlockBranch extends Wrapper {
this.block,
this.node.children,
parent,
stripWhitespace,
nextSibling
strip_whitespace,
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,
parent: Wrapper,
node: AwaitBlock,
stripWhitespace: boolean,
nextSibling: Wrapper
strip_whitespace: boolean,
next_sibling: Wrapper
) {
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 hasIntros = false;
let hasOutros = false;
let is_dynamic = false;
let has_intros = false;
let has_outros = false;
['pending', 'then', 'catch'].forEach(status => {
const child = this.node[status];
@ -82,59 +82,59 @@ export default class AwaitBlockWrapper extends Wrapper {
block,
this,
child,
stripWhitespace,
nextSibling
strip_whitespace,
next_sibling
);
renderer.blocks.push(branch.block);
if (branch.isDynamic) {
isDynamic = true;
if (branch.is_dynamic) {
is_dynamic = true;
// 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.hasOutros) hasOutros = true;
if (branch.block.has_intros) has_intros = true;
if (branch.block.has_outros) has_outros = true;
this[status] = branch;
});
this.pending.block.hasUpdateMethod = isDynamic;
this.then.block.hasUpdateMethod = isDynamic;
this.catch.block.hasUpdateMethod = isDynamic;
this.pending.block.has_update_method = is_dynamic;
this.then.block.has_update_method = is_dynamic;
this.catch.block.has_update_method = is_dynamic;
this.pending.block.hasIntroMethod = hasIntros;
this.then.block.hasIntroMethod = hasIntros;
this.catch.block.hasIntroMethod = hasIntros;
this.pending.block.has_intro_method = has_intros;
this.then.block.has_intro_method = has_intros;
this.catch.block.has_intro_method = has_intros;
this.pending.block.hasOutroMethod = hasOutros;
this.then.block.hasOutroMethod = hasOutros;
this.catch.block.hasOutroMethod = hasOutros;
this.pending.block.has_outro_method = has_outros;
this.then.block.has_outro_method = has_outros;
this.catch.block.has_outro_method = has_outros;
if (hasOutros) {
block.addOutro();
if (has_outros) {
block.add_outro();
}
}
render(
block: Block,
parentNode: string,
parentNodes: string
parent_node: string,
parent_nodes: string
) {
const anchor = this.getOrCreateAnchor(block, parentNode, parentNodes);
const updateMountNode = this.getUpdateMountNode(anchor);
const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes);
const update_mount_node = this.get_update_mount_node(anchor);
const snippet = this.node.expression.render(block);
const info = block.getUniqueName(`info`);
const promise = block.getUniqueName(`promise`);
const info = block.get_unique_name(`info`);
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',
'current: null',
this.pending.block.name && `pending: ${this.pending.block.name}`,
@ -142,42 +142,42 @@ export default class AwaitBlockWrapper extends Wrapper {
this.catch.block.name && `catch: ${this.catch.block.name}`,
this.then.block.name && `value: '${this.node.value}'`,
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);
block.builders.init.addBlock(deindent`
block.builders.init.add_block(deindent`
let ${info} = {
${infoProps.join(',\n')}
${info_props.join(',\n')}
};
`);
block.builders.init.addBlock(deindent`
@handlePromise(${promise} = ${snippet}, ${info});
block.builders.init.add_block(deindent`
@handle_promise(${promise} = ${snippet}, ${info});
`);
block.builders.create.addBlock(deindent`
block.builders.create.add_block(deindent`
${info}.block.c();
`);
if (parentNodes && this.renderer.options.hydratable) {
block.builders.claim.addBlock(deindent`
${info}.block.l(${parentNodes});
if (parent_nodes && this.renderer.options.hydratable) {
block.builders.claim.add_block(deindent`
${info}.block.l(${parent_nodes});
`);
}
const initialMountNode = parentNode || '#target';
const anchorNode = parentNode ? 'null' : 'anchor';
const initial_mount_node = parent_node || '#target';
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.addBlock(deindent`
${info}.block.m(${initialMountNode}, ${info}.anchor = ${anchorNode});
${info}.mount = () => ${updateMountNode};
block.builders.mount.add_block(deindent`
${info}.block.m(${initial_mount_node}, ${info}.anchor = ${anchor_node});
${info}.mount = () => ${update_mount_node};
${info}.anchor = ${anchor};
`);
if (hasTransitions) {
block.builders.intro.addLine(`${info}.block.i();`);
if (has_transitions) {
block.builders.intro.add_line(`${info}.block.i();`);
}
const conditions = [];
@ -191,15 +191,15 @@ export default class AwaitBlockWrapper extends Wrapper {
conditions.push(
`${promise} !== (${promise} = ${snippet})`,
`@handlePromise(${promise}, ${info})`
`@handle_promise(${promise}, ${info})`
);
block.builders.update.addLine(
block.builders.update.add_line(
`${info}.ctx = ctx;`
);
if (this.pending.block.hasUpdateMethod) {
block.builders.update.addBlock(deindent`
if (this.pending.block.has_update_method) {
block.builders.update.add_block(deindent`
if (${conditions.join(' && ')}) {
// nothing
} else {
@ -207,13 +207,13 @@ export default class AwaitBlockWrapper extends Wrapper {
}
`);
} else {
block.builders.update.addBlock(deindent`
block.builders.update.add_block(deindent`
${conditions.join(' && ')}
`);
}
if (this.pending.block.hasOutroMethod) {
block.builders.outro.addBlock(deindent`
if (this.pending.block.has_outro_method) {
block.builders.outro.add_block(deindent`
for (let #i = 0; #i < 3; #i += 1) {
const block = ${info}.blocks[#i];
if (block) block.o();
@ -221,8 +221,8 @@ export default class AwaitBlockWrapper extends Wrapper {
`);
}
block.builders.destroy.addBlock(deindent`
${info}.block.d(${parentNode ? '' : 'detach'});
block.builders.destroy.add_block(deindent`
${info}.block.d(${parent_node ? '' : 'detaching'});
${info} = null;
`);

@ -1,20 +1,20 @@
import Block from '../Block';
import Wrapper from './shared/Wrapper';
import deindent from '../../../utils/deindent';
import deindent from '../../utils/deindent';
import Body from '../../nodes/Body';
export default class BodyWrapper extends Wrapper {
node: Body;
render(block: Block, parentNode: string, parentNodes: string) {
render(block: Block, parent_node: string, parent_nodes: string) {
this.node.handlers.forEach(handler => {
const snippet = handler.render(block);
block.builders.init.addBlock(deindent`
block.builders.init.add_block(deindent`
document.body.addEventListener("${handler.name}", ${snippet});
`);
block.builders.destroy.addBlock(deindent`
block.builders.destroy.add_block(deindent`
document.body.removeEventListener("${handler.name}", ${snippet});
`);
});

@ -2,8 +2,8 @@ import Renderer from '../Renderer';
import Wrapper from './shared/Wrapper';
import Block from '../Block';
import DebugTag from '../../nodes/DebugTag';
import addToSet from '../../../utils/addToSet';
import deindent from '../../../utils/deindent';
import add_to_set from '../../utils/add_to_set';
import deindent from '../../utils/deindent';
export default class DebugTagWrapper extends Wrapper {
node: DebugTag;
@ -13,13 +13,13 @@ export default class DebugTagWrapper extends Wrapper {
block: Block,
parent: Wrapper,
node: DebugTag,
stripWhitespace: boolean,
nextSibling: Wrapper
strip_whitespace: boolean,
next_sibling: Wrapper
) {
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 { component } = renderer;
@ -34,8 +34,8 @@ export default class DebugTagWrapper extends Wrapper {
});
const statement = `[✂${this.node.start + 1}-${this.node.start + 7}✂];`;
block.builders.create.addLine(statement);
block.builders.update.addLine(statement);
block.builders.create.add_line(statement);
block.builders.update.add_line(statement);
} else {
const { code } = component;
code.overwrite(this.node.start + 1, this.node.start + 7, 'log', {
@ -45,14 +45,14 @@ export default class DebugTagWrapper extends Wrapper {
const dependencies = new Set();
this.node.expressions.forEach(expression => {
addToSet(dependencies, expression.dependencies);
add_to_set(dependencies, expression.dependencies);
});
const condition = Array.from(dependencies).map(d => `changed.${d}`).join(' || ');
const identifiers = this.node.expressions.map(e => e.node.name).join(', ');
block.builders.update.addBlock(deindent`
block.builders.update.add_block(deindent`
if (${condition}) {
const { ${identifiers} } = ctx;
console.${log}({ ${identifiers} });
@ -60,7 +60,7 @@ export default class DebugTagWrapper extends Wrapper {
}
`);
block.builders.create.addBlock(deindent`
block.builders.create.add_block(deindent`
{
const { ${identifiers} } = ctx;
console.${log}({ ${identifiers} });

@ -1,17 +1,17 @@
import Renderer from '../Renderer';
import Block from '../Block';
import Wrapper from './shared/Wrapper';
import createDebuggingComment from '../../../utils/createDebuggingComment';
import create_debugging_comment from './shared/create_debugging_comment';
import EachBlock from '../../nodes/EachBlock';
import FragmentWrapper from './Fragment';
import deindent from '../../../utils/deindent';
import deindent from '../../utils/deindent';
import ElseBlock from '../../nodes/ElseBlock';
class ElseBlockWrapper extends Wrapper {
node: ElseBlock;
block: Block;
fragment: FragmentWrapper;
isDynamic: boolean;
is_dynamic: boolean;
var = null;
@ -20,14 +20,14 @@ class ElseBlockWrapper extends Wrapper {
block: Block,
parent: Wrapper,
node: ElseBlock,
stripWhitespace: boolean,
nextSibling: Wrapper
strip_whitespace: boolean,
next_sibling: Wrapper
) {
super(renderer, block, parent, node);
this.block = block.child({
comment: createDebuggingComment(node, this.renderer.component),
name: this.renderer.component.getUniqueName(`create_else_block`)
comment: create_debugging_comment(node, this.renderer.component),
name: this.renderer.component.get_unique_name(`create_else_block`)
});
this.fragment = new FragmentWrapper(
@ -35,11 +35,11 @@ class ElseBlockWrapper extends Wrapper {
this.block,
this.node.children,
parent,
stripWhitespace,
nextSibling
strip_whitespace,
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;
}
contextProps: string[];
indexName: string;
context_props: string[];
index_name: string;
var = 'each';
@ -70,27 +70,27 @@ export default class EachBlockWrapper extends Wrapper {
block: Block,
parent: Wrapper,
node: EachBlock,
stripWhitespace: boolean,
nextSibling: Wrapper
strip_whitespace: boolean,
next_sibling: Wrapper
) {
super(renderer, block, parent, node);
this.cannotUseInnerHTML();
this.cannot_use_innerhtml();
const { dependencies } = node.expression;
block.addDependencies(dependencies);
block.add_dependencies(dependencies);
this.block = block.child({
comment: createDebuggingComment(this.node, this.renderer.component),
name: renderer.component.getUniqueName('create_each_block'),
comment: create_debugging_comment(this.node, this.renderer.component),
name: renderer.component.get_unique_name('create_each_block'),
key: node.key as string,
bindings: new Map(block.bindings)
});
// 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'
? node.expression.node.elements.length
@ -102,13 +102,13 @@ export default class EachBlockWrapper extends Wrapper {
while (renderer.component.source[c] !== 'e') c += 1;
renderer.component.code.overwrite(c, c + 4, 'length');
const each_block_value = renderer.component.getUniqueName(`${this.var}_value`);
const iterations = block.getUniqueName(`${this.var}_blocks`);
const each_block_value = renderer.component.get_unique_name(`${this.var}_value`);
const iterations = block.get_unique_name(`${this.var}_blocks`);
this.vars = {
create_each_block: this.block.name,
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,
length: `[✂${c}-${c+4}✂]`,
@ -124,18 +124,18 @@ export default class EachBlockWrapper extends Wrapper {
node.contexts.forEach(prop => {
this.block.bindings.set(prop.key.name, {
object: this.vars.each_block_value,
property: this.indexName,
snippet: `${this.vars.each_block_value}[${this.indexName}]${prop.tail}`
property: this.index_name,
snippet: `${this.vars.each_block_value}[${this.index_name}]${prop.tail}`
});
});
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);
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) {
this.else = new ElseBlockWrapper(
@ -143,112 +143,112 @@ export default class EachBlockWrapper extends Wrapper {
block,
this,
this.node.else,
stripWhitespace,
nextSibling
strip_whitespace,
next_sibling
);
renderer.blocks.push(this.else.block);
if (this.else.isDynamic) {
this.block.addDependencies(this.else.block.dependencies);
if (this.else.is_dynamic) {
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)) {
block.addOutro();
if (this.block.has_outros || (this.else && this.else.block.has_outros)) {
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;
const { renderer } = this;
const { component } = renderer;
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();
this.vars.anchor = needsAnchor
? block.getUniqueName(`${this.var}_anchor`)
this.vars.anchor = needs_anchor
? block.get_unique_name(`${this.var}_anchor`)
: (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.node.index) this.contextProps.push(`child_ctx.${this.indexName} = i;`);
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.context_props.push(`child_ctx.${this.index_name} = i;`);
const snippet = this.node.expression.render(block);
block.builders.init.addLine(`var ${this.vars.each_block_value} = ${snippet};`);
block.builders.init.add_line(`var ${this.vars.each_block_value} = ${snippet};`);
renderer.blocks.push(deindent`
function ${this.vars.get_each_context}(ctx, list, i) {
const child_ctx = Object.create(ctx);
${this.contextProps}
${this.context_props}
return child_ctx;
}
`);
if (this.node.key) {
this.renderKeyed(block, parentNode, parentNodes, snippet);
this.render_keyed(block, parent_node, parent_nodes, snippet);
} else {
this.renderUnkeyed(block, parentNode, parentNodes, snippet);
this.render_unkeyed(block, parent_node, parent_nodes, snippet);
}
if (this.block.hasIntroMethod || this.block.hasOutroMethod) {
block.builders.intro.addBlock(deindent`
if (this.block.has_intro_method || this.block.has_outro_method) {
block.builders.intro.add_block(deindent`
for (var #i = 0; #i < ${this.vars.data_length}; #i += 1) ${this.vars.iterations}[#i].i();
`);
}
if (needsAnchor) {
block.addElement(
if (needs_anchor) {
block.add_element(
this.vars.anchor,
`@createComment()`,
parentNodes && `@createComment()`,
parentNode
`@comment()`,
parent_nodes && `@comment()`,
parent_node
);
}
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.addLine(`var ${each_block_else} = null;`);
block.builders.init.add_line(`var ${each_block_else} = null;`);
// TODO neaten this up... will end up with an empty line in the block
block.builders.init.addBlock(deindent`
block.builders.init.add_block(deindent`
if (!${this.vars.data_length}) {
${each_block_else} = ${this.else.block.name}(ctx);
${each_block_else}.c();
}
`);
block.builders.mount.addBlock(deindent`
block.builders.mount.add_block(deindent`
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) {
block.builders.update.addBlock(deindent`
if (this.else.block.has_update_method) {
block.builders.update.add_block(deindent`
if (!${this.vars.data_length} && ${each_block_else}) {
${each_block_else}.p(changed, ctx);
} else if (!${this.vars.data_length}) {
${each_block_else} = ${this.else.block.name}(ctx);
${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}) {
${each_block_else}.d(1);
${each_block_else} = null;
}
`);
} else {
block.builders.update.addBlock(deindent`
block.builders.update.add_block(deindent`
if (${this.vars.data_length}) {
if (${each_block_else}) {
${each_block_else}.d(1);
@ -257,13 +257,13 @@ export default class EachBlockWrapper extends Wrapper {
} else if (!${each_block_else}) {
${each_block_else} = ${this.else.block.name}(ctx);
${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.addBlock(deindent`
if (${each_block_else}) ${each_block_else}.d(${parentNode ? '' : 'detach'});
block.builders.destroy.add_block(deindent`
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,
parentNode: string,
parentNodes: string,
parent_node: string,
parent_nodes: string,
snippet: string
) {
const {
@ -288,25 +288,25 @@ export default class EachBlockWrapper extends Wrapper {
view_length
} = this.vars;
const get_key = block.getUniqueName('get_key');
const lookup = block.getUniqueName(`${this.var}_lookup`);
const get_key = block.get_unique_name('get_key');
const lookup = block.get_unique_name(`${this.var}_lookup`);
block.addVariable(iterations, '[]');
block.addVariable(lookup, `@blankObject()`);
block.add_variable(iterations, '[]');
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;
} else {
this.block.first = this.block.getUniqueName('first');
this.block.addElement(
this.block.first = this.block.get_unique_name('first');
this.block.add_element(
this.block.first,
`@createComment()`,
parentNodes && `@createComment()`,
`@comment()`,
parent_nodes && `@comment()`,
null
);
}
block.builders.init.addBlock(deindent`
block.builders.init.add_block(deindent`
const ${get_key} = ctx => ${this.node.key.render()};
for (var #i = 0; #i < ${this.vars.each_block_value}.${length}; #i += 1) {
@ -316,58 +316,57 @@ export default class EachBlockWrapper extends Wrapper {
}
`);
const initialMountNode = parentNode || '#target';
const updateMountNode = this.getUpdateMountNode(anchor);
const anchorNode = parentNode ? 'null' : 'anchor';
const initial_mount_node = parent_node || '#target';
const update_mount_node = this.get_update_mount_node(anchor);
const anchor_node = parent_node ? 'null' : 'anchor';
block.builders.create.addBlock(deindent`
block.builders.create.add_block(deindent`
for (#i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].c();
`);
if (parentNodes && this.renderer.options.hydratable) {
block.builders.claim.addBlock(deindent`
for (#i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].l(${parentNodes});
if (parent_nodes && this.renderer.options.hydratable) {
block.builders.claim.add_block(deindent`
for (#i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].l(${parent_nodes});
`);
}
block.builders.mount.addBlock(deindent`
for (#i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].m(${initialMountNode}, ${anchorNode});
block.builders.mount.add_block(deindent`
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.hasAnimation
? `@fixAndOutroAndDestroyBlock`
: this.block.hasOutros
? `@outroAndDestroyBlock`
: `@destroyBlock`;
const destroy = this.node.has_animation
? `@fix_and_outro_and_destroy_block`
: this.block.has_outros
? `@outro_and_destroy_block`
: `@destroy_block`;
block.builders.update.addBlock(deindent`
block.builders.update.add_block(deindent`
const ${this.vars.each_block_value} = ${snippet};
${this.block.hasOutros && `@group_outros();`}
${this.node.hasAnimation && `for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].r();`}
${iterations} = @updateKeyedEach(${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});
${this.node.hasAnimation && `for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].a();`}
${this.block.hasOutros && `@check_outros();`}
${this.block.has_outros && `@group_outros();`}
${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}, ${update_mount_node}, ${destroy}, ${create_each_block}, ${anchor}, ${this.vars.get_each_context});
${this.node.has_animation && `for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].a();`}
${this.block.has_outros && `@check_outros();`}
`);
if (this.block.hasOutros) {
block.builders.outro.addBlock(deindent`
if (this.block.has_outros) {
block.builders.outro.add_block(deindent`
for (#i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].o();
`);
}
block.builders.destroy.addBlock(deindent`
for (#i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].d(${parentNode ? '' : 'detach'});
block.builders.destroy.add_block(deindent`
for (#i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].d(${parent_node ? '' : 'detaching'});
`);
}
renderUnkeyed(
render_unkeyed(
block: Block,
parentNode: string,
parentNodes: string,
parent_node: string,
parent_nodes: string,
snippet: string
) {
const {
@ -380,7 +379,7 @@ export default class EachBlockWrapper extends Wrapper {
anchor
} = this.vars;
block.builders.init.addBlock(deindent`
block.builders.init.add_block(deindent`
var ${iterations} = [];
for (var #i = 0; #i < ${data_length}; #i += 1) {
@ -388,44 +387,44 @@ export default class EachBlockWrapper extends Wrapper {
}
`);
const initialMountNode = parentNode || '#target';
const updateMountNode = this.getUpdateMountNode(anchor);
const anchorNode = parentNode ? 'null' : 'anchor';
const initial_mount_node = parent_node || '#target';
const update_mount_node = this.get_update_mount_node(anchor);
const anchor_node = parent_node ? 'null' : 'anchor';
block.builders.create.addBlock(deindent`
block.builders.create.add_block(deindent`
for (var #i = 0; #i < ${view_length}; #i += 1) {
${iterations}[#i].c();
}
`);
if (parentNodes && this.renderer.options.hydratable) {
block.builders.claim.addBlock(deindent`
if (parent_nodes && this.renderer.options.hydratable) {
block.builders.claim.add_block(deindent`
for (var #i = 0; #i < ${view_length}; #i += 1) {
${iterations}[#i].l(${parentNodes});
${iterations}[#i].l(${parent_nodes});
}
`);
}
block.builders.mount.addBlock(deindent`
block.builders.mount.add_block(deindent`
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;
dependencies.forEach((dependency: string) => {
allDependencies.add(dependency);
all_dependencies.add(dependency);
});
const outroBlock = this.block.hasOutros && block.getUniqueName('outroBlock')
if (outroBlock) {
block.builders.init.addBlock(deindent`
function ${outroBlock}(i, detach, local) {
const outro_block = this.block.has_outros && block.get_unique_name('outro_block')
if (outro_block) {
block.builders.init.add_block(deindent`
function ${outro_block}(i, detaching, local) {
if (${iterations}[i]) {
if (detach) {
if (detaching) {
@on_outro(() => {
${iterations}[i].d(detach);
${iterations}[i].d(detaching);
${iterations}[i] = null;
});
}
@ -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}`)
.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 !== '') {
const forLoopBody = this.block.hasUpdateMethod
const for_loop_body = this.block.has_update_method
? deindent`
if (${iterations}[#i]) {
${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].c();
${has_transitions && `${iterations}[#i].i(1);`}
${iterations}[#i].m(${updateMountNode}, ${anchor});
${iterations}[#i].m(${update_mount_node}, ${anchor});
}
`
: deindent`
${iterations}[#i] = ${create_each_block}(child_ctx);
${iterations}[#i].c();
${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;
if (this.block.hasOutros) {
if (this.block.has_outros) {
remove_old_blocks = deindent`
@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();
`;
} else {
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);
}
${!fixed_length && `${view_length} = ${this.vars.each_block_value}.${length};`}
@ -487,26 +486,26 @@ export default class EachBlockWrapper extends Wrapper {
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);
${forLoopBody}
${for_loop_body}
}
${remove_old_blocks}
`;
block.builders.update.addBlock(deindent`
block.builders.update.add_block(deindent`
if (${condition}) {
${update}
}
`);
}
if (outroBlock) {
block.builders.outro.addBlock(deindent`
if (outro_block) {
block.builders.outro.add_block(deindent`
${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);`
);
}
block.builders.destroy.addBlock(`@destroyEach(${iterations}, detach);`);
block.builders.destroy.add_block(`@destroy_each(${iterations}, detaching);`);
}
}

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

@ -1,37 +1,42 @@
import Binding from '../../../nodes/Binding';
import ElementWrapper from '.';
import { dimensions } from '../../../../utils/patterns';
import getObject from '../../../../utils/getObject';
import get_object from '../../../utils/get_object';
import Block from '../../Block';
import Node from '../../../nodes/shared/Node';
import Renderer from '../../Renderer';
import flattenReference from '../../../../utils/flattenReference';
import { get_tail } from '../../../../utils/get_tail_snippet';
import flatten_reference from '../../../utils/flatten_reference';
import EachBlock from '../../../nodes/EachBlock';
import { Node as INode } from '../../../../interfaces';
// TODO this should live in a specific binding
const readOnlyMediaAttributes = new Set([
const read_only_media_attributes = new Set([
'duration',
'buffered',
'seekable',
'played'
]);
function get_tail(node: INode) {
const end = node.end;
while (node.type === 'MemberExpression') node = node.object;
return { start: node.end, end };
}
export default class BindingWrapper {
node: Binding;
parent: ElementWrapper;
object: string;
handler: {
usesContext: boolean;
uses_context: boolean;
mutation: string;
contextual_dependencies: Set<string>,
snippet?: string
};
snippet: string;
initialUpdate: string;
isReadOnly: boolean;
needsLock: boolean;
is_readonly: boolean;
needs_lock: boolean;
constructor(block: Block, node: Binding, parent: ElementWrapper) {
this.node = node;
@ -39,55 +44,55 @@ export default class BindingWrapper {
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'>`?
if (parent.node.name === 'select') {
parent.selectBindingDependencies = dependencies;
parent.select_binding_dependencies = dependencies;
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
// the list and the index, if they're not otherwise referenced
const { name } = getObject(this.node.expression.node);
const eachBlock = this.parent.node.scope.getOwner(name);
const { name } = get_object(this.node.expression.node);
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 = getObject(this.node.expression.node).name;
this.object = get_object(this.node.expression.node).name;
// TODO unfortunate code is necessary because we need to use `ctx`
// inside the fragment, but not inside the <script>
const contextless_snippet = this.parent.renderer.component.source.slice(this.node.expression.node.start, this.node.expression.node.end);
// 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);
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) ||
(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?
);
this.needsLock = this.node.name === 'currentTime'; // TODO others?
this.needs_lock = this.node.name === 'currentTime'; // TODO others?
}
get_dependencies() {
const dependencies = new Set(this.node.expression.dependencies);
this.node.expression.dependencies.forEach((prop: string) => {
const indirectDependencies = this.parent.renderer.component.indirectDependencies.get(prop);
if (indirectDependencies) {
indirectDependencies.forEach(indirectDependency => {
dependencies.add(indirectDependency);
const indirect_dependencies = this.parent.renderer.component.indirect_dependencies.get(prop);
if (indirect_dependencies) {
indirect_dependencies.forEach(indirect_dependency => {
dependencies.add(indirect_dependency);
});
}
});
@ -95,83 +100,83 @@ export default class BindingWrapper {
return dependencies;
}
isReadOnlyMediaAttribute() {
return readOnlyMediaAttributes.has(this.node.name);
is_readonly_media_attribute() {
return read_only_media_attributes.has(this.node.name);
}
render(block: Block, lock: string) {
if (this.isReadOnly) return;
if (this.is_readonly) return;
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) {
updateConditions.push(`changed.${dependencyArray[0]}`)
} else if (dependencyArray.length > 1) {
updateConditions.push(
`(${dependencyArray.map(prop => `changed.${prop}`).join(' || ')})`
if (dependency_array.length === 1) {
update_conditions.push(`changed.${dependency_array[0]}`)
} else if (dependency_array.length > 1) {
update_conditions.push(
`(${dependency_array.map(prop => `changed.${prop}`).join(' || ')})`
)
}
// model to view
let updateDom = getDomUpdater(parent, this);
let update_dom = get_dom_updater(parent, this);
// special cases
switch (this.node.name) {
case 'group':
const bindingGroup = getBindingGroup(parent.renderer, this.node.expression.node);
const binding_group = get_binding_group(parent.renderer, this.node.expression.node);
block.builders.hydrate.addLine(
`ctx.$$binding_groups[${bindingGroup}].push(${parent.var});`
block.builders.hydrate.add_line(
`ctx.$$binding_groups[${binding_group}].push(${parent.var});`
);
block.builders.destroy.addLine(
`ctx.$$binding_groups[${bindingGroup}].splice(ctx.$$binding_groups[${bindingGroup}].indexOf(${parent.var}), 1);`
block.builders.destroy.add_line(
`ctx.$$binding_groups[${binding_group}].splice(ctx.$$binding_groups[${binding_group}].indexOf(${parent.var}), 1);`
);
break;
case 'currentTime':
case 'volume':
updateConditions.push(`!isNaN(${this.snippet})`);
update_conditions.push(`!isNaN(${this.snippet})`);
break;
case 'paused':
// this is necessary to prevent audio restarting by itself
const last = block.getUniqueName(`${parent.var}_is_paused`);
block.addVariable(last, 'true');
const last = block.get_unique_name(`${parent.var}_is_paused`);
block.add_variable(last, 'true');
updateConditions.push(`${last} !== (${last} = ${this.snippet})`);
updateDom = `${parent.var}[${last} ? "pause" : "play"]();`;
update_conditions.push(`${last} !== (${last} = ${this.snippet})`);
update_dom = `${parent.var}[${last} ? "pause" : "play"]();`;
break;
case 'value':
if (parent.getStaticAttributeValue('type') === 'file') {
updateDom = null;
if (parent.get_static_attribute_value('type') === 'file') {
update_dom = null;
}
}
if (updateDom) {
block.builders.update.addLine(
updateConditions.length ? `if (${updateConditions.join(' && ')}) ${updateDom}` : updateDom
if (update_dom) {
block.builders.update.add_line(
update_conditions.length ? `if (${update_conditions.join(' && ')}) ${update_dom}` : update_dom
);
}
if (!/(currentTime|paused)/.test(this.node.name)) {
block.builders.mount.addBlock(updateDom);
block.builders.mount.add_block(update_dom);
}
}
}
function getDomUpdater(
function get_dom_updater(
element: ElementWrapper,
binding: BindingWrapper
) {
const { node } = element;
if (binding.isReadOnlyMediaAttribute()) {
if (binding.is_readonly_media_attribute()) {
return null;
}
@ -180,13 +185,13 @@ function getDomUpdater(
}
if (node.name === 'select') {
return node.getStaticAttributeValue('multiple') === true ?
`@selectOptions(${element.var}, ${binding.snippet})` :
`@selectOption(${element.var}, ${binding.snippet})`;
return node.get_static_attribute_value('multiple') === true ?
`@select_options(${element.var}, ${binding.snippet})` :
`@select_option(${element.var}, ${binding.snippet})`;
}
if (binding.node.name === 'group') {
const type = node.getStaticAttributeValue('type');
const type = node.get_static_attribute_value('type');
const condition = type === 'checkbox'
? `~${binding.snippet}.indexOf(${element.var}.__value)`
@ -198,16 +203,16 @@ function getDomUpdater(
return `${element.var}.${binding.node.name} = ${binding.snippet};`;
}
function getBindingGroup(renderer: Renderer, value: Node) {
const { parts } = flattenReference(value); // TODO handle cases involving computed member expressions
function get_binding_group(renderer: Renderer, value: Node) {
const { parts } = flatten_reference(value); // TODO handle cases involving computed member expressions
const keypath = parts.join('.');
// TODO handle contextual bindings — `keypath` should include unique ID of
// each block that provides context
let index = renderer.bindingGroups.indexOf(keypath);
let index = renderer.binding_groups.indexOf(keypath);
if (index === -1) {
index = renderer.bindingGroups.length;
renderer.bindingGroups.push(keypath);
index = renderer.binding_groups.length;
renderer.binding_groups.push(keypath);
}
return index;
@ -219,14 +224,14 @@ function mutate_store(store, value, tail) {
: `${store}.set(${value});`;
}
function getEventHandler(
function get_event_handler(
binding: BindingWrapper,
renderer: Renderer,
block: Block,
name: 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;
let tail = '';
@ -235,11 +240,11 @@ function getEventHandler(
tail = renderer.component.source.slice(start, end);
}
if (binding.node.isContextual) {
if (binding.node.is_contextual) {
const { object, property, snippet } = block.bindings.get(name);
return {
usesContext: true,
uses_context: true,
mutation: store
? mutate_store(store, value, tail)
: `${snippet}${tail} = ${value};`,
@ -253,7 +258,7 @@ function getEventHandler(
if (binding.node.expression.node.type === 'MemberExpression') {
return {
usesContext: binding.node.expression.usesContext,
uses_context: binding.node.expression.uses_context,
mutation,
contextual_dependencies: binding.node.expression.contextual_dependencies,
snippet
@ -261,13 +266,13 @@ function getEventHandler(
}
return {
usesContext: false,
uses_context: false,
mutation,
contextual_dependencies: new Set()
};
}
function getValueFromDom(
function get_value_from_dom(
renderer: Renderer,
element: ElementWrapper,
binding: BindingWrapper
@ -281,18 +286,18 @@ function getValueFromDom(
// <select bind:value='selected>
if (node.name === 'select') {
return node.getStaticAttributeValue('multiple') === true ?
`@selectMultipleValue(this)` :
`@selectValue(this)`;
return node.get_static_attribute_value('multiple') === true ?
`@select_multiple_value(this)` :
`@select_value(this)`;
}
const type = node.getStaticAttributeValue('type');
const type = node.get_static_attribute_value('type');
// <input type='checkbox' bind:group='foo'>
if (name === 'group') {
const bindingGroup = getBindingGroup(renderer, binding.node.expression.node);
const binding_group = get_binding_group(renderer, binding.node.expression.node);
if (type === 'checkbox') {
return `@getBindingGroupValue($$binding_groups[${bindingGroup}])`;
return `@get_binding_group_value($$binding_groups[${binding_group}])`;
}
return `this.__value`;
@ -300,11 +305,11 @@ function getValueFromDom(
// <input type='range|number' bind:value>
if (type === 'range' || type === 'number') {
return `@toNumber(this.${name})`;
return `@to_number(this.${name})`;
}
if ((name === 'buffered' || name === 'seekable' || name === 'played')) {
return `@timeRangesToArray(this.${name})`
return `@time_ranges_to_array(this.${name})`
}
// everything else

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

@ -0,0 +1,12 @@
const svg_attributes = 'accent-height accumulate additive alignment-baseline allowReorder alphabetic amplitude arabic-form ascent attributeName attributeType autoReverse azimuth baseFrequency baseline-shift baseProfile bbox begin bias by calcMode cap-height class clip clipPathUnits clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering contentScriptType contentStyleType cursor cx cy d decelerate descent diffuseConstant direction display divisor dominant-baseline dur dx dy edgeMode elevation enable-background end exponent externalResourcesRequired fill fill-opacity fill-rule filter filterRes filterUnits flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight format from fr fx fy g1 g2 glyph-name glyph-orientation-horizontal glyph-orientation-vertical glyphRef gradientTransform gradientUnits hanging height href horiz-adv-x horiz-origin-x id ideographic image-rendering in in2 intercept k k1 k2 k3 k4 kernelMatrix kernelUnitLength kerning keyPoints keySplines keyTimes lang lengthAdjust letter-spacing lighting-color limitingConeAngle local marker-end marker-mid marker-start markerHeight markerUnits markerWidth mask maskContentUnits maskUnits mathematical max media method min mode name numOctaves offset onabort onactivate onbegin onclick onend onerror onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup onrepeat onresize onscroll onunload opacity operator order orient orientation origin overflow overline-position overline-thickness panose-1 paint-order pathLength patternContentUnits patternTransform patternUnits pointer-events points pointsAtX pointsAtY pointsAtZ preserveAlpha preserveAspectRatio primitiveUnits r radius refX refY rendering-intent repeatCount repeatDur requiredExtensions requiredFeatures restart result rotate rx ry scale seed shape-rendering slope spacing specularConstant specularExponent speed spreadMethod startOffset stdDeviation stemh stemv stitchTiles stop-color stop-opacity strikethrough-position strikethrough-thickness string stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style surfaceScale systemLanguage tabindex tableValues target targetX targetY text-anchor text-decoration text-rendering textLength to transform type u1 u2 underline-position underline-thickness unicode unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical values version vert-adv-y vert-origin-x vert-origin-y viewBox viewTarget visibility width widths word-spacing writing-mode x x-height x1 x2 xChannelSelector xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space y y1 y2 yChannelSelector z zoomAndPan'.split(' ');
const svg_attribute_lookup = new Map();
svg_attributes.forEach(name => {
svg_attribute_lookup.set(name.toLowerCase(), name);
});
export default function fix_attribute_casing(name) {
name = name.toLowerCase();
return svg_attribute_lookup.get(name) || name;
}

@ -3,86 +3,84 @@ import Element from '../../../nodes/Element';
import Wrapper from '../shared/Wrapper';
import Block from '../../Block';
import Node from '../../../nodes/shared/Node';
import { quotePropIfNecessary, quoteNameIfNecessary } from '../../../../utils/quoteIfNecessary';
import isVoidElementName from '../../../../utils/isVoidElementName';
import { is_void, quote_prop_if_necessary, quote_name_if_necessary, sanitize } from '../../../../utils/names';
import FragmentWrapper from '../Fragment';
import { stringify, escapeHTML, escape } from '../../../../utils/stringify';
import { stringify, escape_html, escape } from '../../../utils/stringify';
import TextWrapper from '../Text';
import fixAttributeCasing from '../../../../utils/fixAttributeCasing';
import deindent from '../../../../utils/deindent';
import fix_attribute_casing from './fix_attribute_casing';
import deindent from '../../../utils/deindent';
import { namespaces } from '../../../../utils/namespaces';
import AttributeWrapper from './Attribute';
import StyleAttributeWrapper from './StyleAttribute';
import { dimensions } from '../../../../utils/patterns';
import Binding from './Binding';
import InlineComponentWrapper from '../InlineComponent';
import addToSet from '../../../../utils/addToSet';
import addEventHandlers from '../shared/addEventHandlers';
import addActions from '../shared/addActions';
import createDebuggingComment from '../../../../utils/createDebuggingComment';
import sanitize from '../../../../utils/sanitize';
import add_to_set from '../../../utils/add_to_set';
import add_event_handlers from '../shared/add_event_handlers';
import add_actions from '../shared/add_actions';
import create_debugging_comment from '../shared/create_debugging_comment';
import { get_context_merger } from '../shared/get_context_merger';
const events = [
{
eventNames: ['input'],
event_names: ['input'],
filter: (node: Element, name: string) =>
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) =>
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) =>
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) =>
dimensions.test(name)
},
// media events
{
eventNames: ['timeupdate'],
event_names: ['timeupdate'],
filter: (node: Element, name: string) =>
node.isMediaNode() &&
node.is_media_node() &&
(name === 'currentTime' || name === 'played')
},
{
eventNames: ['durationchange'],
event_names: ['durationchange'],
filter: (node: Element, name: string) =>
node.isMediaNode() &&
node.is_media_node() &&
name === 'duration'
},
{
eventNames: ['play', 'pause'],
event_names: ['play', 'pause'],
filter: (node: Element, name: string) =>
node.isMediaNode() &&
node.is_media_node() &&
name === 'paused'
},
{
eventNames: ['progress'],
event_names: ['progress'],
filter: (node: Element, name: string) =>
node.isMediaNode() &&
node.is_media_node() &&
name === 'buffered'
},
{
eventNames: ['loadedmetadata'],
event_names: ['loadedmetadata'],
filter: (node: Element, name: string) =>
node.isMediaNode() &&
node.is_media_node() &&
(name === 'buffered' || name === 'seekable')
},
{
eventNames: ['volumechange'],
event_names: ['volumechange'],
filter: (node: Element, name: string) =>
node.isMediaNode() &&
node.is_media_node() &&
name === 'volume'
}
];
@ -92,10 +90,10 @@ export default class ElementWrapper extends Wrapper {
fragment: FragmentWrapper;
attributes: AttributeWrapper[];
bindings: Binding[];
classDependencies: string[];
class_dependencies: string[];
slot_block: Block;
selectBindingDependencies?: Set<string>;
select_binding_dependencies?: Set<string>;
var: string;
@ -104,13 +102,13 @@ export default class ElementWrapper extends Wrapper {
block: Block,
parent: Wrapper,
node: Element,
stripWhitespace: boolean,
nextSibling: Wrapper
strip_whitespace: boolean,
next_sibling: Wrapper
) {
super(renderer, block, parent, node);
this.var = node.name.replace(/[^a-zA-Z0-9_$]/g, '_')
this.classDependencies = [];
this.class_dependencies = [];
this.attributes = this.node.attributes.map(attribute => {
if (attribute.name === 'slot') {
@ -129,12 +127,12 @@ export default class ElementWrapper extends Wrapper {
}
if (owner && owner.node.type === 'InlineComponent') {
const name = attribute.getStaticValue();
const name = attribute.get_static_value();
if (!(owner as InlineComponentWrapper).slots.has(name)) {
const child_block = block.child({
comment: createDebuggingComment(node, this.renderer.component),
name: this.renderer.component.getUniqueName(`create_${sanitize(name)}_slot`)
comment: create_debugging_comment(node, this.renderer.component),
name: this.renderer.component.get_unique_name(`create_${sanitize(name)}_slot`)
});
const fn = get_context_merger(this.node.lets);
@ -163,46 +161,46 @@ export default class ElementWrapper extends Wrapper {
this.bindings = this.node.bindings.map(binding => new Binding(block, binding, this));
if (node.intro || node.outro) {
if (node.intro) block.addIntro(node.intro.is_local);
if (node.outro) block.addOutro(node.outro.is_local);
if (node.intro) block.add_intro(node.intro.is_local);
if (node.outro) block.add_outro(node.outro.is_local);
}
if (node.animation) {
block.addAnimation();
block.add_animation();
}
// add directive and handler dependencies
[node.animation, node.outro, ...node.actions, ...node.classes].forEach(directive => {
if (directive && directive.expression) {
block.addDependencies(directive.expression.dependencies);
block.add_dependencies(directive.expression.dependencies);
}
});
node.handlers.forEach(handler => {
if (handler.expression) {
block.addDependencies(handler.expression.dependencies);
block.add_dependencies(handler.expression.dependencies);
}
});
if (this.parent) {
if (node.actions.length > 0) this.parent.cannotUseInnerHTML();
if (node.animation) this.parent.cannotUseInnerHTML();
if (node.bindings.length > 0) this.parent.cannotUseInnerHTML();
if (node.classes.length > 0) this.parent.cannotUseInnerHTML();
if (node.intro || node.outro) this.parent.cannotUseInnerHTML();
if (node.handlers.length > 0) this.parent.cannotUseInnerHTML();
if (node.actions.length > 0) this.parent.cannot_use_innerhtml();
if (node.animation) this.parent.cannot_use_innerhtml();
if (node.bindings.length > 0) this.parent.cannot_use_innerhtml();
if (node.classes.length > 0) this.parent.cannot_use_innerhtml();
if (node.intro || node.outro) this.parent.cannot_use_innerhtml();
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) {
this.parent.cannotUseInnerHTML(); // need to use addLoc
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) {
block.parent.addDependencies(block.dependencies);
block.parent.add_dependencies(block.dependencies);
// appalling hack
const index = block.parent.wrappers.indexOf(this);
@ -211,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;
if (this.node.name === 'slot') {
const slotName = this.getStaticAttributeValue('name') || 'default';
const slotName = this.get_static_attribute_value('name') || 'default';
renderer.slots.add(slotName);
}
@ -226,58 +224,58 @@ export default class ElementWrapper extends Wrapper {
}
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);
const renderStatement = this.getRenderStatement();
block.builders.create.addLine(
`${node} = ${renderStatement};`
block.add_variable(node);
const render_statement = this.get_render_statement();
block.builders.create.add_line(
`${node} = ${render_statement};`
);
if (renderer.options.hydratable) {
if (parentNodes) {
block.builders.claim.addBlock(deindent`
${node} = ${this.getClaimStatement(parentNodes)};
if (parent_nodes) {
block.builders.claim.add_block(deindent`
${node} = ${this.get_claim_statement(parent_nodes)};
var ${nodes} = @children(${this.node.name === 'template' ? `${node}.content` : node});
`);
} else {
block.builders.claim.addLine(
`${node} = ${renderStatement};`
block.builders.claim.add_line(
`${node} = ${render_statement};`
);
}
}
if (parentNode) {
block.builders.mount.addLine(
`@append(${parentNode}, ${node});`
if (parent_node) {
block.builders.mount.add_line(
`@append(${parent_node}, ${node});`
);
if (parentNode === 'document.head') {
block.builders.destroy.addLine(`@detachNode(${node});`);
if (parent_node === 'document.head') {
block.builders.destroy.add_line(`@detach(${node});`);
}
} else {
block.builders.mount.addLine(`@insert(#target, ${node}, anchor);`);
block.builders.mount.add_line(`@insert(#target, ${node}, anchor);`);
// TODO we eventually need to consider what happens to elements
// that belong to the same outgroup as an outroing element...
block.builders.destroy.addConditional('detach', `@detachNode(${node});`);
block.builders.destroy.add_conditional('detaching', `@detach(${node});`);
}
// 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') {
block.builders.create.addLine(
block.builders.create.add_line(
`${node}.textContent = ${stringify(this.fragment.nodes[0].data)};`
);
} else {
const innerHTML = escape(
const inner_html = escape(
this.fragment.nodes
.map(toHTML)
.map(to_html)
.join('')
);
block.builders.create.addLine(
`${node}.innerHTML = \`${innerHTML}\`;`
block.builders.create.add_line(
`${node}.innerHTML = \`${inner_html}\`;`
);
}
} else {
@ -290,31 +288,31 @@ export default class ElementWrapper extends Wrapper {
});
}
const eventHandlerOrBindingUsesContext = (
this.bindings.some(binding => binding.handler.usesContext) ||
this.node.handlers.some(handler => handler.usesContext) ||
this.node.actions.some(action => action.usesContext)
const event_handler_or_binding_uses_context = (
this.bindings.some(binding => binding.handler.uses_context) ||
this.node.handlers.some(handler => handler.uses_context) ||
this.node.actions.some(action => action.uses_context)
);
if (eventHandlerOrBindingUsesContext) {
block.maintainContext = true;
if (event_handler_or_binding_uses_context) {
block.maintain_context = true;
}
this.addBindings(block);
this.addEventHandlers(block);
this.addAttributes(block);
this.addTransitions(block);
this.addAnimation(block);
this.addActions(block);
this.addClasses(block);
this.add_bindings(block);
this.add_event_handlers(block);
this.add_attributes(block);
this.add_transitions(block);
this.add_animation(block);
this.add_actions(block);
this.add_classes(block);
if (nodes && this.renderer.options.hydratable) {
block.builders.claim.addLine(
`${nodes}.forEach(@detachNode);`
block.builders.claim.add_line(
`${nodes}.forEach(@detach);`
);
}
function toHTML(wrapper: ElementWrapper | TextWrapper) {
function to_html(wrapper: ElementWrapper | TextWrapper) {
if (wrapper.node.type === 'Text') {
const { parent } = wrapper.node;
@ -325,7 +323,7 @@ export default class ElementWrapper extends Wrapper {
return raw
? wrapper.node.data
: escapeHTML(wrapper.node.data)
: escape_html(wrapper.node.data)
.replace(/\\/g, '\\\\')
.replace(/`/g, '\\`')
.replace(/\$/g, '\\$');
@ -336,67 +334,67 @@ export default class ElementWrapper extends Wrapper {
let open = `<${wrapper.node.name}`;
(wrapper as ElementWrapper).attributes.forEach((attr: AttributeWrapper) => {
open += ` ${fixAttributeCasing(attr.node.name)}${attr.stringify()}`
open += ` ${fix_attribute_casing(attr.node.name)}${attr.stringify()}`
});
if (isVoidElementName(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) {
const loc = renderer.locate(this.node.start);
block.builders.hydrate.addLine(
`@addLoc(${this.var}, ${renderer.fileVar}, ${loc.line}, ${loc.column}, ${this.node.start});`
block.builders.hydrate.add_line(
`@add_location(${this.var}, ${renderer.file_var}, ${loc.line}, ${loc.column}, ${this.node.start});`
);
}
}
getRenderStatement() {
get_render_statement() {
const { name, namespace } = this.node;
if (namespace === 'http://www.w3.org/2000/svg') {
return `@createSvgElement("${name}")`;
return `@svg_element("${name}")`;
}
if (namespace) {
return `document.createElementNS("${namespace}", "${name}")`;
}
return `@createElement("${name}")`;
return `@element("${name}")`;
}
getClaimStatement(nodes: string) {
get_claim_statement(nodes: string) {
const attributes = this.node.attributes
.filter((attr: Node) => attr.type === 'Attribute')
.map((attr: Node) => `${quoteNameIfNecessary(attr.name)}: true`)
.map((attr: Node) => `${quote_name_if_necessary(attr.name)}: true`)
.join(', ');
const name = this.node.namespace
? this.node.name
: this.node.name.toUpperCase();
return `@claimElement(${nodes}, "${name}", ${attributes
return `@claim_element(${nodes}, "${name}", ${attributes
? `{ ${attributes} }`
: `{}`}, ${this.node.namespace === namespaces.svg ? true : false})`;
}
addBindings(block: Block) {
add_bindings(block: Block) {
const { renderer } = this;
if (this.bindings.length === 0) return;
renderer.component.has_reactive_assignments = true;
const lock = this.bindings.some(binding => binding.needsLock) ?
block.getUniqueName(`${this.var}_updating`) :
const lock = this.bindings.some(binding => binding.needs_lock) ?
block.get_unique_name(`${this.var}_updating`) :
null;
if (lock) block.addVariable(lock, 'false');
if (lock) block.add_variable(lock, 'false');
const groups = events
.map(event => ({
events: event.eventNames,
events: event.event_names,
bindings: this.bindings
.filter(binding => binding.node.name !== 'this')
.filter(binding => event.filter(this.node, binding.node.name))
@ -404,7 +402,7 @@ export default class ElementWrapper extends Wrapper {
.filter(group => group.bindings.length);
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({
name: handler,
@ -413,16 +411,16 @@ export default class ElementWrapper extends Wrapper {
});
// 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 contextual_dependencies = new Set();
group.bindings.forEach(binding => {
// TODO this is a mess
addToSet(dependencies, binding.get_dependencies());
addToSet(contextual_dependencies, binding.node.expression.contextual_dependencies);
addToSet(contextual_dependencies, binding.handler.contextual_dependencies);
add_to_set(dependencies, binding.get_dependencies());
add_to_set(contextual_dependencies, binding.node.expression.contextual_dependencies);
add_to_set(contextual_dependencies, binding.handler.contextual_dependencies);
binding.render(block, lock);
});
@ -432,23 +430,23 @@ export default class ElementWrapper extends Wrapper {
// own hands
let animation_frame;
if (group.events[0] === 'timeupdate') {
animation_frame = block.getUniqueName(`${this.var}_animationframe`);
block.addVariable(animation_frame);
animation_frame = block.get_unique_name(`${this.var}_animationframe`);
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;
// TODO dry this out — similar code for event handlers and component bindings
if (has_local_function) {
// need to create a block-local function that calls an instance-level function
block.builders.init.addBlock(deindent`
block.builders.init.add_block(deindent`
function ${handler}() {
${animation_frame && deindent`
cancelAnimationFrame(${animation_frame});
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' : ''});
}
`);
@ -468,48 +466,48 @@ export default class ElementWrapper extends Wrapper {
group.events.forEach(name => {
if (name === 'resize') {
// special case
const resize_listener = block.getUniqueName(`${this.var}_resize_listener`);
block.addVariable(resize_listener);
const resize_listener = block.get_unique_name(`${this.var}_resize_listener`);
block.add_variable(resize_listener);
block.builders.mount.addLine(
`${resize_listener} = @addResizeListener(${this.var}, ${callee}.bind(${this.var}));`
block.builders.mount.add_line(
`${resize_listener} = @add_resize_listener(${this.var}, ${callee}.bind(${this.var}));`
);
block.builders.destroy.addLine(
block.builders.destroy.add_line(
`${resize_listener}.cancel();`
);
} else {
block.event_listeners.push(
`@addListener(${this.var}, "${name}", ${callee})`
`@listen(${this.var}, "${name}", ${callee})`
);
}
});
const someInitialStateIsUndefined = group.bindings
const some_initial_state_is_undefined = group.bindings
.map(binding => `${binding.snippet} === void 0`)
.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})`;
block.builders.hydrate.addLine(
`if (${someInitialStateIsUndefined}) @add_render_callback(${callback});`
block.builders.hydrate.add_line(
`if (${some_initial_state_is_undefined}) @add_render_callback(${callback});`
);
}
if (group.events[0] === 'resize') {
block.builders.hydrate.addLine(
block.builders.hydrate.add_line(
`@add_render_callback(() => ${callee}.call(${this.var}));`
);
}
});
if (lock) {
block.builders.update.addLine(`${lock} = false;`);
block.builders.update.add_line(`${lock} = false;`);
}
const this_binding = this.bindings.find(b => b.node.name === 'this');
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({
name,
@ -522,7 +520,7 @@ export default class ElementWrapper extends Wrapper {
const args = [];
for (const arg of handler.contextual_dependencies) {
args.push(arg);
block.addVariable(arg, `ctx.${arg}`);
block.add_variable(arg, `ctx.${arg}`);
}
renderer.component.partly_hoisted.push(deindent`
@ -532,9 +530,9 @@ export default class ElementWrapper extends Wrapper {
}
`);
block.builders.mount.addLine(`@add_binding_callback(() => ctx.${name}(${[this.var, 'null'].concat(args).join(', ')}));`);
block.builders.destroy.addLine(`ctx.${name}(${['null', this.var].concat(args).join(', ')});`);
block.builders.update.addLine(deindent`
block.builders.mount.add_line(`@add_binding_callback(() => ctx.${name}(${[this.var, 'null'].concat(args).join(', ')}));`);
block.builders.destroy.add_line(`ctx.${name}(${['null', this.var].concat(args).join(', ')});`);
block.builders.update.add_line(deindent`
if (changed.items) {
ctx.${name}(${['null', this.var].concat(args).join(', ')});
${args.map(a => `${a} = ctx.${a}`).join(', ')};
@ -544,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')) {
this.addSpreadAttributes(block);
this.add_spread_attributes(block);
return;
}
this.attributes.forEach((attribute: Attribute) => {
if (attribute.node.name === 'class' && attribute.node.isDynamic) {
this.classDependencies.push(...attribute.node.dependencies);
if (attribute.node.name === 'class' && attribute.node.is_dynamic) {
this.class_dependencies.push(...attribute.node.dependencies);
}
attribute.render(block);
});
}
addSpreadAttributes(block: Block) {
const levels = block.getUniqueName(`${this.var}_levels`);
const data = block.getUniqueName(`${this.var}_data`);
add_spread_attributes(block: Block) {
const levels = block.get_unique_name(`${this.var}_levels`);
const data = block.get_unique_name(`${this.var}_data`);
const initialProps = [];
const initial_props = [];
const updates = [];
this.node.attributes
@ -572,23 +570,23 @@ export default class ElementWrapper extends Wrapper {
? `(${[...attr.dependencies].map(d => `changed.${d}`).join(' || ')})`
: null;
if (attr.isSpread) {
if (attr.is_spread) {
const snippet = attr.expression.render(block);
initialProps.push(snippet);
initial_props.push(snippet);
updates.push(condition ? `${condition} && ${snippet}` : snippet);
} else {
const snippet = `{ ${quoteNameIfNecessary(attr.name)}: ${attr.getValue(block)} }`;
initialProps.push(snippet);
const snippet = `{ ${quote_name_if_necessary(attr.name)}: ${attr.get_value(block)} }`;
initial_props.push(snippet);
updates.push(condition ? `${condition} && ${snippet}` : snippet);
}
});
block.builders.init.addBlock(deindent`
block.builders.init.add_block(deindent`
var ${levels} = [
${initialProps.join(',\n')}
${initial_props.join(',\n')}
];
var ${data} = {};
@ -597,22 +595,22 @@ export default class ElementWrapper extends Wrapper {
}
`);
block.builders.hydrate.addLine(
`@setAttributes(${this.var}, ${data});`
block.builders.hydrate.add_line(
`@set_attributes(${this.var}, ${data});`
);
block.builders.update.addBlock(deindent`
@setAttributes(${this.var}, @getSpreadUpdate(${levels}, [
block.builders.update.add_block(deindent`
@set_attributes(${this.var}, @get_spread_update(${levels}, [
${updates.join(',\n')}
]));
`);
}
addEventHandlers(block: Block) {
addEventHandlers(block, this.var, this.node.handlers);
add_event_handlers(block: Block) {
add_event_handlers(block, this.var, this.node.handlers);
}
addTransitions(
add_transitions(
block: Block
) {
const { intro, outro } = this.node;
@ -622,12 +620,12 @@ export default class ElementWrapper extends Wrapper {
if (intro === outro) {
// bidirectional transition
const name = block.getUniqueName(`${this.var}_transition`);
const name = block.get_unique_name(`${this.var}_transition`);
const snippet = intro.expression
? intro.expression.render(block)
: '{}';
block.addVariable(name);
block.add_variable(name);
const fn = component.qualify(intro.name);
@ -644,31 +642,31 @@ export default class ElementWrapper extends Wrapper {
`;
if (intro.is_local) {
block.builders.intro.addBlock(deindent`
block.builders.intro.add_block(deindent`
if (#local) {
${intro_block}
}
`);
block.builders.outro.addBlock(deindent`
block.builders.outro.add_block(deindent`
if (#local) {
${outro_block}
}
`);
} else {
block.builders.intro.addBlock(intro_block);
block.builders.outro.addBlock(outro_block);
block.builders.intro.add_block(intro_block);
block.builders.outro.add_block(outro_block);
}
block.builders.destroy.addConditional('detach', `if (${name}) ${name}.end();`);
block.builders.destroy.add_conditional('detaching', `if (${name}) ${name}.end();`);
}
else {
const introName = intro && block.getUniqueName(`${this.var}_intro`);
const outroName = outro && block.getUniqueName(`${this.var}_outro`);
const intro_name = intro && block.get_unique_name(`${this.var}_intro`);
const outro_name = outro && block.get_unique_name(`${this.var}_outro`);
if (intro) {
block.addVariable(introName);
block.add_variable(intro_name);
const snippet = intro.expression
? intro.expression.render(block)
: '{}';
@ -680,19 +678,19 @@ export default class ElementWrapper extends Wrapper {
if (outro) {
intro_block = deindent`
@add_render_callback(() => {
if (${outroName}) ${outroName}.end(1);
if (!${introName}) ${introName} = @create_in_transition(${this.var}, ${fn}, ${snippet});
${introName}.start();
if (${outro_name}) ${outro_name}.end(1);
if (!${intro_name}) ${intro_name} = @create_in_transition(${this.var}, ${fn}, ${snippet});
${intro_name}.start();
});
`;
block.builders.outro.addLine(`if (${introName}) ${introName}.invalidate();`);
block.builders.outro.add_line(`if (${intro_name}) ${intro_name}.invalidate();`);
} else {
intro_block = deindent`
if (!${introName}) {
if (!${intro_name}) {
@add_render_callback(() => {
${introName} = @create_in_transition(${this.var}, ${fn}, ${snippet});
${introName}.start();
${intro_name} = @create_in_transition(${this.var}, ${fn}, ${snippet});
${intro_name}.start();
});
}
`;
@ -706,11 +704,11 @@ export default class ElementWrapper extends Wrapper {
`;
}
block.builders.intro.addBlock(intro_block);
block.builders.intro.add_block(intro_block);
}
if (outro) {
block.addVariable(outroName);
block.add_variable(outro_name);
const snippet = outro.expression
? outro.expression.render(block)
: '{}';
@ -718,15 +716,15 @@ export default class ElementWrapper extends Wrapper {
const fn = component.qualify(outro.name);
if (!intro) {
block.builders.intro.addBlock(deindent`
if (${outroName}) ${outroName}.end(1);
block.builders.intro.add_block(deindent`
if (${outro_name}) ${outro_name}.end(1);
`);
}
// TODO hide elements that have outro'd (unless they belong to a still-outroing
// group) prior to their removal from the DOM
let outro_block = deindent`
${outroName} = @create_out_transition(${this.var}, ${fn}, ${snippet});
${outro_name} = @create_out_transition(${this.var}, ${fn}, ${snippet});
`;
if (outro_block) {
@ -737,29 +735,29 @@ export default class ElementWrapper extends Wrapper {
`;
}
block.builders.outro.addBlock(outro_block);
block.builders.outro.add_block(outro_block);
block.builders.destroy.addConditional('detach', `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;
const { component } = this.renderer;
const rect = block.getUniqueName('rect');
const stop_animation = block.getUniqueName('stop_animation');
const rect = block.get_unique_name('rect');
const stop_animation = block.get_unique_name('stop_animation');
block.addVariable(rect);
block.addVariable(stop_animation, '@noop');
block.add_variable(rect);
block.add_variable(stop_animation, '@noop');
block.builders.measure.addBlock(deindent`
block.builders.measure.add_block(deindent`
${rect} = ${this.var}.getBoundingClientRect();
`);
block.builders.fix.addBlock(deindent`
block.builders.fix.add_block(deindent`
@fix_position(${this.var});
${stop_animation}();
`);
@ -768,37 +766,37 @@ export default class ElementWrapper extends Wrapper {
const name = component.qualify(this.node.animation.name);
block.builders.animate.addBlock(deindent`
block.builders.animate.add_block(deindent`
${stop_animation}();
${stop_animation} = @create_animation(${this.var}, ${rect}, ${name}, ${params});
`);
}
addActions(block: Block) {
addActions(this.renderer.component, block, this.var, this.node.actions);
add_actions(block: Block) {
add_actions(this.renderer.component, block, this.var, this.node.actions);
}
addClasses(block: Block) {
this.node.classes.forEach(classDir => {
const { expression, name } = classDir;
add_classes(block: Block) {
this.node.classes.forEach(class_directive => {
const { expression, name } = class_directive;
let snippet, dependencies;
if (expression) {
snippet = expression.render(block);
dependencies = expression.dependencies;
} else {
snippet = `${quotePropIfNecessary(name)}`;
snippet = `${quote_prop_if_necessary(name)}`;
dependencies = new Set([name]);
}
const updater = `@toggleClass(${this.var}, "${name}", ${snippet});`;
const updater = `@toggle_class(${this.var}, "${name}", ${snippet});`;
block.builders.hydrate.addLine(updater);
block.builders.hydrate.add_line(updater);
if ((dependencies && dependencies.size > 0) || this.classDependencies.length) {
const allDeps = this.classDependencies.concat(...dependencies);
const deps = allDeps.map(dependency => `changed${quotePropIfNecessary(dependency)}`).join(' || ');
const condition = allDeps.length > 1 ? `(${deps})` : deps;
if ((dependencies && dependencies.size > 0) || this.class_dependencies.length) {
const all_dependencies = this.class_dependencies.concat(...dependencies);
const deps = all_dependencies.map(dependency => `changed${quote_prop_if_necessary(dependency)}`).join(' || ');
const condition = all_dependencies.length > 1 ? `(${deps})` : deps;
block.builders.update.addConditional(
block.builders.update.add_conditional(
condition,
updater
);
@ -806,14 +804,14 @@ export default class ElementWrapper extends Wrapper {
});
}
getStaticAttributeValue(name: string) {
get_static_attribute_value(name: string) {
const attribute = this.node.attributes.find(
(attr: Attribute) => attr.type === 'Attribute' && attr.name.toLowerCase() === name
);
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 === 1 && attribute.chunks[0].type === 'Text') {
@ -823,16 +821,16 @@ export default class ElementWrapper extends Wrapper {
return null;
}
addCssClass(className = this.component.stylesheet.id) {
const classAttribute = this.attributes.find(a => a.name === 'class');
if (classAttribute && !classAttribute.isTrue) {
if (classAttribute.chunks.length === 1 && classAttribute.chunks[0].type === 'Text') {
(classAttribute.chunks[0] as Text).data += ` ${className}`;
add_css_class(class_name = this.component.stylesheet.id) {
const class_attribute = this.attributes.find(a => a.name === 'class');
if (class_attribute && !class_attribute.is_true) {
if (class_attribute.chunks.length === 1 && class_attribute.chunks[0].type === 'Text') {
(class_attribute.chunks[0] as Text).data += ` ${class_name}`;
} else {
(classAttribute.chunks as Node[]).push(
(class_attribute.chunks as Node[]).push(
new Text(this.component, this, this.scope, {
type: 'Text',
data: ` ${className}`
data: ` ${class_name}`
})
);
}
@ -841,7 +839,7 @@ export default class ElementWrapper extends Wrapper {
new Attribute(this.component, this, this.scope, {
type: 'Attribute',
name: 'class',
value: [{ type: 'Text', data: className }]
value: [{ type: 'Text', data: class_name }]
})
);
}

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

@ -12,24 +12,24 @@ export default class HeadWrapper extends Wrapper {
block: Block,
parent: Wrapper,
node: Head,
stripWhitespace: boolean,
nextSibling: Wrapper
strip_whitespace: boolean,
next_sibling: Wrapper
) {
super(renderer, block, parent, node);
this.canUseInnerHTML = false;
this.can_use_innerhtml = false;
this.fragment = new FragmentWrapper(
renderer,
block,
node.children,
this,
stripWhitespace,
nextSibling
strip_whitespace,
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);
}
}

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

@ -3,15 +3,14 @@ import Renderer from '../../Renderer';
import Block from '../../Block';
import InlineComponent from '../../../nodes/InlineComponent';
import FragmentWrapper from '../Fragment';
import { quoteNameIfNecessary, quotePropIfNecessary } from '../../../../utils/quoteIfNecessary';
import stringifyProps from '../../../../utils/stringifyProps';
import addToSet from '../../../../utils/addToSet';
import deindent from '../../../../utils/deindent';
import { quote_name_if_necessary, quote_prop_if_necessary, sanitize } from '../../../../utils/names';
import { stringify_props } from '../../../utils/stringify_props';
import add_to_set from '../../../utils/add_to_set';
import deindent from '../../../utils/deindent';
import Attribute from '../../../nodes/Attribute';
import getObject from '../../../../utils/getObject';
import flattenReference from '../../../../utils/flattenReference';
import createDebuggingComment from '../../../../utils/createDebuggingComment';
import sanitize from '../../../../utils/sanitize';
import get_object from '../../../utils/get_object';
import flatten_reference from '../../../utils/flatten_reference';
import create_debugging_comment from '../shared/create_debugging_comment';
import { get_context_merger } from '../shared/get_context_merger';
import EachBlock from '../../../nodes/EachBlock';
import TemplateScope from '../../../nodes/shared/TemplateScope';
@ -27,37 +26,37 @@ export default class InlineComponentWrapper extends Wrapper {
block: Block,
parent: Wrapper,
node: InlineComponent,
stripWhitespace: boolean,
nextSibling: Wrapper
strip_whitespace: boolean,
next_sibling: Wrapper
) {
super(renderer, block, parent, node);
this.cannotUseInnerHTML();
this.cannot_use_innerhtml();
if (this.node.expression) {
block.addDependencies(this.node.expression.dependencies);
block.add_dependencies(this.node.expression.dependencies);
}
this.node.attributes.forEach(attr => {
block.addDependencies(attr.dependencies);
block.add_dependencies(attr.dependencies);
});
this.node.bindings.forEach(binding => {
if (binding.isContextual) {
if (binding.is_contextual) {
// we need to ensure that the each block creates a context including
// the list and the index, if they're not otherwise referenced
const { name } = getObject(binding.expression.node);
const eachBlock = this.node.scope.getOwner(name);
const { name } = get_object(binding.expression.node);
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 => {
if (handler.expression) {
block.addDependencies(handler.expression.dependencies);
block.add_dependencies(handler.expression.dependencies);
}
});
@ -69,8 +68,8 @@ export default class InlineComponentWrapper extends Wrapper {
if (this.node.children.length) {
const default_slot = block.child({
comment: createDebuggingComment(node, renderer.component),
name: renderer.component.getUniqueName(`create_default_slot`)
comment: create_debugging_comment(node, renderer.component),
name: renderer.component.get_unique_name(`create_default_slot`)
});
this.renderer.blocks.push(default_slot);
@ -82,7 +81,7 @@ export default class InlineComponentWrapper extends Wrapper {
scope: this.node.scope,
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();
@ -93,16 +92,16 @@ export default class InlineComponentWrapper extends Wrapper {
}
});
block.addDependencies(dependencies);
block.add_dependencies(dependencies);
}
block.addOutro();
block.add_outro();
}
render(
block: Block,
parentNode: string,
parentNodes: string
parent_node: string,
parent_nodes: string
) {
const { renderer } = this;
const { component } = renderer;
@ -116,24 +115,24 @@ export default class InlineComponentWrapper extends Wrapper {
const postupdates: string[] = [];
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}` : ''}]`);
if (slot_props.length > 0) slot_props.push(`$$scope: { ctx }`);
const attributeObject = usesSpread
? stringifyProps(slot_props)
: stringifyProps(
this.node.attributes.map(attr => `${quoteNameIfNecessary(attr.name)}: ${attr.getValue(block)}`).concat(slot_props)
const attribute_object = uses_spread
? stringify_props(slot_props)
: stringify_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 (!usesSpread && this.node.bindings.length === 0) {
component_opts.push(`props: ${attributeObject}`);
if (!uses_spread && this.node.bindings.length === 0) {
component_opts.push(`props: ${attribute_object}`);
} else {
props = block.getUniqueName(`${name}_props`);
props = block.get_unique_name(`${name}_props`);
component_opts.push(`props: ${props}`);
}
}
@ -146,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
// will complain that options.target is missing. This would
// work better if components had separate public and private
@ -170,46 +169,46 @@ export default class InlineComponentWrapper extends Wrapper {
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} = {};`);
}
if (this.node.attributes.length) {
if (usesSpread) {
const levels = block.getUniqueName(`${this.var}_spread_levels`);
if (uses_spread) {
const levels = block.get_unique_name(`${this.var}_spread_levels`);
const initialProps = [];
const initial_props = [];
const changes = [];
const allDependencies = new Set();
const all_dependencies = new Set();
this.node.attributes.forEach(attr => {
addToSet(allDependencies, attr.dependencies);
add_to_set(all_dependencies, attr.dependencies);
});
this.node.attributes.forEach(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(' || ')})`
: null;
if (attr.isSpread) {
if (attr.is_spread) {
const value = attr.expression.render(block);
initialProps.push(value);
initial_props.push(value);
changes.push(condition ? `${condition} && ${value}` : value);
} else {
const obj = `{ ${quoteNameIfNecessary(name)}: ${attr.getValue(block)} }`;
initialProps.push(obj);
const obj = `{ ${quote_name_if_necessary(name)}: ${attr.get_value(block)} }`;
initial_props.push(obj);
changes.push(condition ? `${condition} && ${obj}` : obj);
}
});
block.builders.init.addBlock(deindent`
block.builders.init.add_block(deindent`
var ${levels} = [
${initialProps.join(',\n')}
${initial_props.join(',\n')}
];
`);
@ -219,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`
var ${name_changes} = ${allDependencies.size === 1 ? `${conditions}` : `(${conditions})`} ? @getSpreadUpdate(${levels}, [
var ${name_changes} = ${all_dependencies.size === 1 ? `${conditions}` : `(${conditions})`} ? @get_spread_update(${levels}, [
${changes.join(',\n')}
]) : {};
`);
} else {
this.node.attributes
.filter((attribute: Attribute) => attribute.isDynamic)
.filter((attribute: Attribute) => attribute.is_dynamic)
.forEach((attribute: Attribute) => {
if (attribute.dependencies.size > 0) {
updates.push(deindent`
if (${[...attribute.dependencies]
.map(dependency => `changed.${dependency}`)
.join(' || ')}) ${name_changes}${quotePropIfNecessary(attribute.name)} = ${attribute.getValue(block)};
.join(' || ')}) ${name_changes}${quote_prop_if_necessary(attribute.name)} = ${attribute.get_value(block)};
`);
}
});
@ -249,7 +248,7 @@ export default class InlineComponentWrapper extends Wrapper {
component.has_reactive_assignments = true;
if (binding.name === 'this') {
const fn = component.getUniqueName(`${this.var}_binding`);
const fn = component.get_unique_name(`${this.var}_binding`);
component.add_var({
name: fn,
@ -260,7 +259,7 @@ export default class InlineComponentWrapper extends Wrapper {
let lhs;
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
// to `array[index] = x;
const { name } = binding.expression.node;
@ -269,7 +268,7 @@ export default class InlineComponentWrapper extends Wrapper {
// TODO we need to invalidate... something
} else {
object = flattenReference(binding.expression.node).name;
object = flatten_reference(binding.expression.node).name;
lhs = component.source.slice(binding.expression.node.start, binding.expression.node.end).trim();
}
@ -280,11 +279,11 @@ export default class InlineComponentWrapper extends Wrapper {
}
`);
block.builders.destroy.addLine(`ctx.${fn}(null);`);
block.builders.destroy.add_line(`ctx.${fn}(null);`);
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({
name,
@ -292,20 +291,20 @@ export default class InlineComponentWrapper extends Wrapper {
referenced: true
});
const updating = block.getUniqueName(`updating_${binding.name}`);
block.addVariable(updating);
const updating = block.get_unique_name(`updating_${binding.name}`);
block.add_variable(updating);
const snippet = binding.expression.render(block);
statements.push(deindent`
if (${snippet} !== void 0) {
${props}${quotePropIfNecessary(binding.name)} = ${snippet};
${props}${quote_prop_if_necessary(binding.name)} = ${snippet};
}`
);
updates.push(deindent`
if (!${updating} && ${[...binding.expression.dependencies].map((dependency: string) => `changed.${dependency}`).join(' || ')}) {
${name_changes}${quotePropIfNecessary(binding.name)} = ${snippet};
${name_changes}${quote_prop_if_necessary(binding.name)} = ${snippet};
}
`);
@ -316,7 +315,7 @@ export default class InlineComponentWrapper extends Wrapper {
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
// to `array[index] = x;
const { name } = binding.expression.node;
@ -329,7 +328,7 @@ export default class InlineComponentWrapper extends Wrapper {
if (contextual_dependencies.length > 0) {
args.push(`{ ${contextual_dependencies.join(', ')} }`);
block.builders.init.addBlock(deindent`
block.builders.init.add_block(deindent`
function ${name}(value) {
if (ctx.${name}.call(null, value, ctx)) {
${updating} = true;
@ -337,9 +336,9 @@ 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 {
block.builders.init.addBlock(deindent`
block.builders.init.add_block(deindent`
function ${name}(value) {
if (ctx.${name}.call(null, value)) {
${updating} = true;
@ -366,19 +365,19 @@ export default class InlineComponentWrapper extends Wrapper {
});
if (this.node.name === 'svelte:component') {
const switch_value = block.getUniqueName('switch_value');
const switch_props = block.getUniqueName('switch_props');
const switch_value = block.get_unique_name('switch_value');
const switch_props = block.get_unique_name('switch_props');
const snippet = this.node.expression.render(block);
block.builders.init.addBlock(deindent`
block.builders.init.add_block(deindent`
var ${switch_value} = ${snippet};
function ${switch_props}(ctx) {
${(this.node.attributes.length || this.node.bindings.length) && deindent`
${props && `let ${props} = ${attributeObject};`}`}
${props && `let ${props} = ${attribute_object};`}`}
${statements}
return ${stringifyProps(component_opts)};
return ${stringify_props(component_opts)};
}
if (${switch_value}) {
@ -389,32 +388,32 @@ export default class InlineComponentWrapper extends Wrapper {
}
`);
block.builders.create.addLine(
block.builders.create.add_line(
`if (${name}) ${name}.$$.fragment.c();`
);
if (parentNodes && this.renderer.options.hydratable) {
block.builders.claim.addLine(
`if (${name}) ${name}.$$.fragment.l(${parentNodes});`
if (parent_nodes && this.renderer.options.hydratable) {
block.builders.claim.add_line(
`if (${name}) ${name}.$$.fragment.l(${parent_nodes});`
);
}
block.builders.mount.addBlock(deindent`
block.builders.mount.add_block(deindent`
if (${name}) {
@mount_component(${name}, ${parentNode || '#target'}, ${parentNode ? 'null' : 'anchor'});
@mount_component(${name}, ${parent_node || '#target'}, ${parent_node ? 'null' : 'anchor'});
}
`);
const anchor = this.getOrCreateAnchor(block, parentNode, parentNodes);
const updateMountNode = this.getUpdateMountNode(anchor);
const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes);
const update_mount_node = this.get_update_mount_node(anchor);
if (updates.length) {
block.builders.update.addBlock(deindent`
block.builders.update.add_block(deindent`
${updates}
`);
}
block.builders.update.addBlock(deindent`
block.builders.update.add_block(deindent`
if (${switch_value} !== (${switch_value} = ${snippet})) {
if (${name}) {
@group_outros();
@ -434,19 +433,19 @@ export default class InlineComponentWrapper extends Wrapper {
${name}.$$.fragment.c();
${name}.$$.fragment.i(1);
@mount_component(${name}, ${updateMountNode}, ${anchor});
@mount_component(${name}, ${update_mount_node}, ${anchor});
} else {
${name} = null;
}
}
`);
block.builders.intro.addBlock(deindent`
block.builders.intro.add_block(deindent`
if (${name}) ${name}.$$.fragment.i(#local);
`);
if (updates.length) {
block.builders.update.addBlock(deindent`
block.builders.update.add_block(deindent`
else if (${switch_value}) {
${name}.$set(${name_changes});
}
@ -455,55 +454,55 @@ export default class InlineComponentWrapper extends Wrapper {
`);
}
block.builders.outro.addLine(
block.builders.outro.add_line(
`if (${name}) ${name}.$$.fragment.o(#local);`
);
block.builders.destroy.addLine(`if (${name}) ${name}.$destroy(${parentNode ? '' : 'detach'});`);
block.builders.destroy.add_line(`if (${name}) ${name}.$destroy(${parent_node ? '' : 'detaching'});`);
} else {
const expression = this.node.name === 'svelte:self'
? '__svelte:self__' // TODO conflict-proof this
: component.qualify(this.node.name);
block.builders.init.addBlock(deindent`
block.builders.init.add_block(deindent`
${(this.node.attributes.length || this.node.bindings.length) && deindent`
${props && `let ${props} = ${attributeObject};`}`}
${props && `let ${props} = ${attribute_object};`}`}
${statements}
var ${name} = new ${expression}(${stringifyProps(component_opts)});
var ${name} = new ${expression}(${stringify_props(component_opts)});
${munged_bindings}
${munged_handlers}
`);
block.builders.create.addLine(`${name}.$$.fragment.c();`);
block.builders.create.add_line(`${name}.$$.fragment.c();`);
if (parentNodes && this.renderer.options.hydratable) {
block.builders.claim.addLine(
`${name}.$$.fragment.l(${parentNodes});`
if (parent_nodes && this.renderer.options.hydratable) {
block.builders.claim.add_line(
`${name}.$$.fragment.l(${parent_nodes});`
);
}
block.builders.mount.addLine(
`@mount_component(${name}, ${parentNode || '#target'}, ${parentNode ? 'null' : 'anchor'});`
block.builders.mount.add_line(
`@mount_component(${name}, ${parent_node || '#target'}, ${parent_node ? 'null' : 'anchor'});`
);
block.builders.intro.addBlock(deindent`
block.builders.intro.add_block(deindent`
${name}.$$.fragment.i(#local);
`);
if (updates.length) {
block.builders.update.addBlock(deindent`
block.builders.update.add_block(deindent`
${updates}
${name}.$set(${name_changes});
${postupdates.length > 0 && `${postupdates.join(' = ')} = false;`}
`);
}
block.builders.destroy.addBlock(deindent`
${name}.$destroy(${parentNode ? '' : 'detach'});
block.builders.destroy.add_block(deindent`
${name}.$destroy(${parent_node ? '' : 'detaching'});
`);
block.builders.outro.addLine(
block.builders.outro.add_line(
`${name}.$$.fragment.o(#local);`
);
}

@ -5,24 +5,24 @@ import Tag from './shared/Tag';
import Wrapper from './shared/Wrapper';
export default class MustacheTagWrapper extends Tag {
var = 'text';
var = 't';
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: Node) {
super(renderer, block, parent, node);
this.cannotUseInnerHTML();
this.cannot_use_innerhtml();
}
render(block: Block, parentNode: string, parentNodes: string) {
const { init } = this.renameThisMethod(
render(block: Block, parent_node: string, parent_nodes: string) {
const { init } = this.rename_this_method(
block,
value => `@setData(${this.var}, ${value});`
value => `@set_data(${this.var}, ${value});`
);
block.addElement(
block.add_element(
this.var,
`@createText(${init})`,
parentNodes && `@claimText(${parentNodes}, ${init})`,
parentNode
`@text(${init})`,
parent_nodes && `@claim_text(${parent_nodes}, ${init})`,
parent_node
);
}
}

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

@ -3,11 +3,11 @@ import Renderer from '../Renderer';
import Block from '../Block';
import Slot from '../../nodes/Slot';
import FragmentWrapper from './Fragment';
import deindent from '../../../utils/deindent';
import sanitize from '../../../utils/sanitize';
import addToSet from '../../../utils/addToSet';
import get_slot_data from '../../../utils/get_slot_data';
import stringifyProps from '../../../utils/stringifyProps';
import deindent from '../../utils/deindent';
import { sanitize } from '../../../utils/names';
import add_to_set from '../../utils/add_to_set';
import get_slot_data from '../../utils/get_slot_data';
import { stringify_props } from '../../utils/stringify_props';
import Expression from '../../nodes/shared/Expression';
export default class SlotWrapper extends Wrapper {
@ -22,36 +22,36 @@ export default class SlotWrapper extends Wrapper {
block: Block,
parent: Wrapper,
node: Slot,
stripWhitespace: boolean,
nextSibling: Wrapper
strip_whitespace: boolean,
next_sibling: Wrapper
) {
super(renderer, block, parent, node);
this.cannotUseInnerHTML();
this.cannot_use_innerhtml();
this.fragment = new FragmentWrapper(
renderer,
block,
node.children,
parent,
stripWhitespace,
nextSibling
strip_whitespace,
next_sibling
);
this.node.attributes.forEach(attribute => {
addToSet(this.dependencies, attribute.dependencies);
add_to_set(this.dependencies, attribute.dependencies);
});
block.addDependencies(this.dependencies);
block.add_dependencies(this.dependencies);
}
render(
block: Block,
parentNode: string,
parentNodes: string
parent_node: string,
parent_nodes: string
) {
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);
let get_slot_changes;
@ -60,8 +60,8 @@ export default class SlotWrapper extends Wrapper {
const attributes = this.node.attributes.filter(attribute => attribute.name !== 'name');
if (attributes.length > 0) {
get_slot_changes = renderer.component.getUniqueName(`get_${slot_name}_slot_changes`);
get_slot_context = renderer.component.getUniqueName(`get_${slot_name}_slot_context`);
get_slot_changes = renderer.component.get_unique_name(`get_${slot_name}_slot_changes`);
get_slot_context = renderer.component.get_unique_name(`get_${slot_name}_slot_context`);
const context_props = get_slot_data(attributes, false);
const changes_props = [];
@ -71,8 +71,8 @@ export default class SlotWrapper extends Wrapper {
attributes.forEach(attribute => {
attribute.chunks.forEach(chunk => {
if ((chunk as Expression).dependencies) {
addToSet(dependencies, (chunk as Expression).dependencies);
addToSet(dependencies, (chunk as Expression).contextual_dependencies);
add_to_set(dependencies, (chunk as Expression).dependencies);
add_to_set(dependencies, (chunk as Expression).contextual_dependencies);
}
});
@ -84,70 +84,70 @@ export default class SlotWrapper extends Wrapper {
const arg = dependencies.size > 0 ? `{ ${Array.from(dependencies).join(', ')} }` : '{}';
renderer.blocks.push(deindent`
const ${get_slot_changes} = (${arg}) => (${stringifyProps(changes_props)});
const ${get_slot_context} = (${arg}) => (${stringifyProps(context_props)});
const ${get_slot_changes} = (${arg}) => (${stringify_props(changes_props)});
const ${get_slot_context} = (${arg}) => (${stringify_props(context_props)});
`);
} else {
get_slot_context = 'null';
}
const slot = block.getUniqueName(`${sanitize(slot_name)}_slot`);
const slot_definition = block.getUniqueName(`${sanitize(slot_name)}_slot`);
const slot = block.get_unique_name(`${sanitize(slot_name)}_slot`);
const slot_definition = block.get_unique_name(`${sanitize(slot_name)}_slot`);
block.builders.init.addBlock(deindent`
block.builders.init.add_block(deindent`
const ${slot_definition} = ctx.$$slot_${sanitize(slot_name)};
const ${slot} = @create_slot(${slot_definition}, ctx, ${get_slot_context});
`);
let mountBefore = block.builders.mount.toString();
let mount_before = block.builders.mount.toString();
block.builders.create.pushCondition(`!${slot}`);
block.builders.hydrate.pushCondition(`!${slot}`);
block.builders.mount.pushCondition(`!${slot}`);
block.builders.update.pushCondition(`!${slot}`);
block.builders.destroy.pushCondition(`!${slot}`);
block.builders.create.push_condition(`!${slot}`);
block.builders.hydrate.push_condition(`!${slot}`);
block.builders.mount.push_condition(`!${slot}`);
block.builders.update.push_condition(`!${slot}`);
block.builders.destroy.push_condition(`!${slot}`);
const listeners = block.event_listeners;
block.event_listeners = [];
this.fragment.render(block, parentNode, parentNodes);
block.renderListeners(`_${slot}`);
this.fragment.render(block, parent_node, parent_nodes);
block.render_listeners(`_${slot}`);
block.event_listeners = listeners;
block.builders.create.popCondition();
block.builders.hydrate.popCondition();
block.builders.mount.popCondition();
block.builders.update.popCondition();
block.builders.destroy.popCondition();
block.builders.create.pop_condition();
block.builders.hydrate.pop_condition();
block.builders.mount.pop_condition();
block.builders.update.pop_condition();
block.builders.destroy.pop_condition();
block.builders.create.addLine(
block.builders.create.add_line(
`if (${slot}) ${slot}.c();`
);
block.builders.claim.addLine(
`if (${slot}) ${slot}.l(${parentNodes});`
block.builders.claim.add_line(
`if (${slot}) ${slot}.l(${parent_nodes});`
);
const mountLeadin = block.builders.mount.toString() !== mountBefore
const mount_leadin = block.builders.mount.toString() !== mount_before
? `else`
: `if (${slot})`;
block.builders.mount.addBlock(deindent`
${mountLeadin} {
${slot}.m(${parentNode || '#target'}, ${parentNode ? 'null' : 'anchor'});
block.builders.mount.add_block(deindent`
${mount_leadin} {
${slot}.m(${parent_node || '#target'}, ${parent_node ? 'null' : 'anchor'});
}
`);
let update_conditions = [...this.dependencies].map(name => `changed.${name}`).join(' || ');
if (this.dependencies.size > 1) update_conditions = `(${update_conditions})`;
block.builders.update.addBlock(deindent`
block.builders.update.add_block(deindent`
if (${slot} && ${slot}.p && ${update_conditions}) {
${slot}.p(@get_slot_changes(${slot_definition}, ctx, changed, ${get_slot_changes}), @get_slot_context(${slot_definition}, ctx, ${get_slot_context}));
}
`);
block.builders.destroy.addLine(
`if (${slot}) ${slot}.d(detach);`
block.builders.destroy.add_line(
`if (${slot}) ${slot}.d(detaching);`
);
}
}

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

@ -2,9 +2,8 @@ import Wrapper from './shared/Wrapper';
import Renderer from '../Renderer';
import Block from '../Block';
import Title from '../../nodes/Title';
import FragmentWrapper from './Fragment';
import { stringify } from '../../../utils/stringify';
import addToSet from '../../../utils/addToSet';
import { stringify } from '../../utils/stringify';
import add_to_set from '../../utils/add_to_set';
export default class TitleWrapper extends Wrapper {
node: Title;
@ -14,19 +13,19 @@ export default class TitleWrapper extends Wrapper {
block: Block,
parent: Wrapper,
node: Title,
stripWhitespace: boolean,
nextSibling: Wrapper
strip_whitespace: boolean,
next_sibling: Wrapper
) {
super(renderer, block, parent, node);
}
render(block: Block, parentNode: string, parentNodes: string) {
const isDynamic = !!this.node.children.find(node => node.type !== 'Text');
render(block: Block, parent_node: string, parent_nodes: string) {
const is_dynamic = !!this.node.children.find(node => node.type !== 'Text');
if (isDynamic) {
if (is_dynamic) {
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
// DRY it out if that's possible without introducing crazy indirection
@ -34,7 +33,7 @@ export default class TitleWrapper extends Wrapper {
// single {tag} — may be a non-string
const { expression } = this.node.children[0];
value = expression.render(block);
addToSet(allDependencies, expression.dependencies);
add_to_set(all_dependencies, expression.dependencies);
} else {
// '{foo} {bar}' — treat as string concatenation
value =
@ -47,50 +46,50 @@ export default class TitleWrapper extends Wrapper {
const snippet = chunk.expression.render(block);
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(' + ');
}
const last = this.node.shouldCache && block.getUniqueName(
const last = this.node.should_cache && block.get_unique_name(
`title_value`
);
if (this.node.shouldCache) block.addVariable(last);
if (this.node.should_cache) block.add_variable(last);
let updater;
const init = this.node.shouldCache ? `${last} = ${value}` : value;
const init = this.node.should_cache ? `${last} = ${value}` : value;
block.builders.init.addLine(
block.builders.init.add_line(
`document.title = ${init};`
);
updater = `document.title = ${this.node.shouldCache ? last : value};`;
updater = `document.title = ${this.node.should_cache ? last : value};`;
if (allDependencies.size) {
const dependencies = Array.from(allDependencies);
const changedCheck = (
( block.hasOutros ? `!#current || ` : '' ) +
if (all_dependencies.size) {
const dependencies = Array.from(all_dependencies);
const changed_check = (
(block.has_outros ? `!#current || ` : '') +
dependencies.map(dependency => `changed.${dependency}`).join(' || ')
);
const updateCachedValue = `${last} !== (${last} = ${value})`;
const update_cached_value = `${last} !== (${last} = ${value})`;
const condition = this.node.shouldCache ?
( dependencies.length ? `(${changedCheck}) && ${updateCachedValue}` : updateCachedValue ) :
changedCheck;
const condition = this.node.should_cache ?
(dependencies.length ? `(${changed_check}) && ${update_cached_value}` : update_cached_value) :
changed_check;
block.builders.update.addConditional(
block.builders.update.add_conditional(
condition,
updater
);
}
} else {
const value = stringify(this.node.children[0].data);
block.builders.hydrate.addLine(`document.title = ${value};`);
block.builders.hydrate.add_line(`document.title = ${value};`);
}
}
}

@ -2,12 +2,12 @@ import Renderer from '../Renderer';
import Block from '../Block';
import Node from '../../nodes/shared/Node';
import Wrapper from './shared/Wrapper';
import deindent from '../../../utils/deindent';
import addEventHandlers from './shared/addEventHandlers';
import deindent from '../../utils/deindent';
import add_event_handlers from './shared/add_event_handlers';
import Window from '../../nodes/Window';
import addActions from './shared/addActions';
import add_actions from './shared/add_actions';
const associatedEvents = {
const associated_events = {
innerWidth: 'resize',
innerHeight: 'resize',
outerWidth: 'resize',
@ -37,15 +37,15 @@ export default class WindowWrapper extends Wrapper {
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 { component } = renderer;
const events = {};
const bindings: Record<string, string> = {};
addActions(component, block, 'window', this.node.actions);
addEventHandlers(block, 'window', this.node.handlers);
add_actions(component, block, 'window', this.node.actions);
add_event_handlers(block, 'window', this.node.handlers);
this.node.bindings.forEach(binding => {
// 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
if (binding.name === 'online') return;
const associatedEvent = associatedEvents[binding.name];
const associated_event = associated_events[binding.name];
const property = properties[binding.name] || binding.name;
if (!events[associatedEvent]) events[associatedEvent] = [];
events[associatedEvent].push({
if (!events[associated_event]) events[associated_event] = [];
events[associated_event].push({
name: binding.expression.node.name,
value: property
});
});
const scrolling = block.getUniqueName(`scrolling`);
const clear_scrolling = block.getUniqueName(`clear_scrolling`);
const scrolling_timeout = block.getUniqueName(`scrolling_timeout`);
const scrolling = block.get_unique_name(`scrolling`);
const clear_scrolling = block.get_unique_name(`clear_scrolling`);
const scrolling_timeout = block.get_unique_name(`scrolling_timeout`);
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];
if (event === 'scroll') {
// TODO other bidirectional bindings...
block.addVariable(scrolling, 'false');
block.addVariable(clear_scrolling, `() => { ${scrolling} = false }`);
block.addVariable(scrolling_timeout);
block.add_variable(scrolling, 'false');
block.add_variable(clear_scrolling, `() => { ${scrolling} = false }`);
block.add_variable(scrolling_timeout);
const condition = [
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 y = bindings.scrollY && `this._state.${bindings.scrollY}`;
renderer.metaBindings.addBlock(deindent`
renderer.meta_bindings.add_block(deindent`
if (${condition}) {
window.scrollTo(${x || 'window.pageXOffset'}, ${y || 'window.pageYOffset'});
}
@ -99,7 +99,7 @@ export default class WindowWrapper extends Wrapper {
`);
block.event_listeners.push(deindent`
@addListener(window, "${event}", () => {
@listen(window, "${event}", () => {
${scrolling} = true;
clearTimeout(${scrolling_timeout});
${scrolling_timeout} = setTimeout(${clear_scrolling}, 100);
@ -108,13 +108,13 @@ export default class WindowWrapper extends Wrapper {
`);
} else {
props.forEach(prop => {
renderer.metaBindings.addLine(
renderer.meta_bindings.add_line(
`this._state.${prop.name} = window.${prop.value};`
);
});
block.event_listeners.push(deindent`
@addListener(window, "${event}", ctx.${handler_name})
@listen(window, "${event}", ctx.${handler_name})
`);
}
@ -130,7 +130,7 @@ export default class WindowWrapper extends Wrapper {
}
`);
block.builders.init.addBlock(deindent`
block.builders.init.add_block(deindent`
@add_render_callback(ctx.${handler_name});
`);
@ -139,7 +139,7 @@ export default class WindowWrapper extends Wrapper {
// special case... might need to abstract this out if we add more special cases
if (bindings.scrollX || bindings.scrollY) {
block.builders.update.addBlock(deindent`
block.builders.update.add_block(deindent`
if (${
[bindings.scrollX, bindings.scrollY].filter(Boolean).map(
b => `changed.${b}`
@ -159,23 +159,23 @@ export default class WindowWrapper extends Wrapper {
// another special case. (I'm starting to think these are all special cases.)
if (bindings.online) {
const handler_name = block.getUniqueName(`onlinestatuschanged`);
block.builders.init.addBlock(deindent`
const handler_name = block.get_unique_name(`onlinestatuschanged`);
block.builders.init.add_block(deindent`
function ${handler_name}(event) {
${component.compileOptions.dev && `component._updatingReadonlyProperty = true;`}
${component.compile_options.dev && `component._updatingReadonlyProperty = true;`}
#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("offline", ${handler_name});
`);
// add initial value
renderer.metaBindings.addLine(
renderer.meta_bindings.add_line(
`this._state.${bindings.online} = navigator.onLine;`
);
block.builders.destroy.addBlock(deindent`
block.builders.destroy.add_block(deindent`
window.removeEventListener("online", ${handler_name});
window.removeEventListener("offline", ${handler_name});
`);

@ -9,36 +9,36 @@ export default class Tag extends Wrapper {
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: MustacheTag | RawMustacheTag) {
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,
update: ((value: string) => string)
) {
const dependencies = this.node.expression.dynamic_dependencies();
const snippet = this.node.expression.render(block);
const value = this.node.shouldCache && block.getUniqueName(`${this.var}_value`);
const content = this.node.shouldCache ? value : snippet;
const value = this.node.should_cache && block.get_unique_name(`${this.var}_value`);
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) {
const changedCheck = (
(block.hasOutros ? `!#current || ` : '') +
const changed_check = (
(block.has_outros ? `!#current || ` : '') +
dependencies.map((dependency: string) => `changed.${dependency}`).join(' || ')
);
const updateCachedValue = `${value} !== (${value} = ${snippet})`;
const update_cached_value = `${value} !== (${value} = ${snippet})`;
const condition =this.node.shouldCache
? `(${changedCheck}) && ${updateCachedValue}`
: changedCheck;
const condition =this.node.should_cache
? `(${changed_check}) && ${update_cached_value}`
: changed_check;
block.builders.update.addConditional(
block.builders.update.add_conditional(
condition,
update(content)
);

@ -11,7 +11,7 @@ export default class Wrapper {
next: Wrapper | null;
var: string;
canUseInnerHTML: boolean;
can_use_innerhtml: boolean;
constructor(
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);
}
cannotUseInnerHTML() {
this.canUseInnerHTML = false;
if (this.parent) this.parent.cannotUseInnerHTML();
cannot_use_innerhtml() {
this.can_use_innerhtml = false;
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
// children need to be created first
const needsAnchor = this.next ? !this.next.isDomNode() : !parentNode || !this.parent.isDomNode();
const anchor = needsAnchor
? block.getUniqueName(`${this.var}_anchor`)
const needs_anchor = this.next ? !this.next.is_dom_node() : !parent_node || !this.parent.is_dom_node();
const anchor = needs_anchor
? block.get_unique_name(`${this.var}_anchor`)
: (this.next && this.next.var) || 'null';
if (needsAnchor) {
block.addElement(
if (needs_anchor) {
block.add_element(
anchor,
`@createComment()`,
parentNodes && `@createComment()`,
parentNode
`@comment()`,
parent_nodes && `@comment()`,
parent_node
);
}
return anchor;
}
getUpdateMountNode(anchor: string) {
return (this.parent && this.parent.isDomNode())
get_update_mount_node(anchor: string) {
return (this.parent && this.parent.is_dom_node())
? this.parent.var
: `${anchor}.parentNode`;
}
isDomNode() {
is_dom_node() {
return (
this.node.type === 'Element' ||
this.node.type === 'Text' ||

@ -2,7 +2,7 @@ import Block from '../../Block';
import Action from '../../../nodes/Action';
import Component from '../../../Component';
export default function addActions(
export default function add_actions(
component: Component,
block: Block,
target: string,
@ -17,15 +17,15 @@ export default function addActions(
dependencies = expression.dynamic_dependencies();
}
const name = block.getUniqueName(
const name = block.get_unique_name(
`${action.name.replace(/[^a-zA-Z0-9_$]/g, '_')}_action`
);
block.addVariable(name);
block.add_variable(name);
const fn = component.qualify(action.name);
block.builders.mount.addLine(
block.builders.mount.add_line(
`${name} = ${fn}.call(null, ${target}${snippet ? `, ${snippet}` : ''}) || {};`
);
@ -34,13 +34,13 @@ export default function addActions(
const deps = dependencies.map(dependency => `changed.${dependency}`).join(' || ');
conditional += dependencies.length > 1 ? `(${deps})` : deps;
block.builders.update.addConditional(
block.builders.update.add_conditional(
conditional,
`${name}.update.call(null, ${snippet});`
);
}
block.builders.destroy.addLine(
block.builders.destroy.add_line(
`if (${name} && typeof ${name}.destroy === 'function') ${name}.destroy();`
);
});

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

@ -1,7 +1,7 @@
import Component from '../compile/Component';
import { Node } from '../interfaces';
import Component from '../../../Component';
import { Node } from '../../../../interfaces';
export default function createDebuggingComment(
export default function create_debugging_comment(
node: Node,
component: Component
) {

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

@ -1,9 +1,9 @@
import Renderer from '../Renderer';
import { CompileOptions } from '../../../interfaces';
import { snip } from '../../../utils/snip';
import { snip } from '../../utils/snip';
export default function(node, renderer: Renderer, options: CompileOptions) {
renderer.append('${(function(__value) { if(@isPromise(__value)) return `');
renderer.append('${(function(__value) { if(@is_promise(__value)) return `');
renderer.render(node.pending.children, options);

@ -1,4 +1,4 @@
import { stringify } from '../../../utils/stringify';
import { stringify } from '../../utils/stringify';
export default function(node, renderer, options) {
if (!options.dev) return;

@ -1,4 +1,4 @@
import { snip } from '../../../utils/snip';
import { snip } from '../../utils/snip';
export default function(node, renderer, options) {
const snippet = snip(node.expression);

@ -1,9 +1,8 @@
import { quotePropIfNecessary, quoteNameIfNecessary } from '../../../utils/quoteIfNecessary';
import isVoidElementName from '../../../utils/isVoidElementName';
import { is_void, quote_prop_if_necessary, quote_name_if_necessary } from '../../../utils/names';
import Attribute from '../../nodes/Attribute';
import Node from '../../nodes/shared/Node';
import { snip } from '../../../utils/snip';
import { stringify_attribute } from '../../../utils/stringify_attribute';
import { snip } from '../../utils/snip';
import { stringify_attribute } from '../../utils/stringify_attribute';
import { get_slot_scope } from './shared/get_slot_scope';
// source: https://gist.github.com/ArjanSchouten/0b8574a6ad7f5065a5e7
@ -48,78 +47,78 @@ const boolean_attributes = new Set([
]);
export default function(node, renderer, options) {
let openingTag = `<${node.name}`;
let textareaContents; // awkward special case
let opening_tag = `<${node.name}`;
let textarea_contents; // awkward special case
const slot = node.getStaticAttributeValue('slot');
if (slot && node.hasAncestor('InlineComponent')) {
const slot = node.get_static_attribute_value('slot');
if (slot && node.has_ancestor('InlineComponent')) {
const slot = node.attributes.find((attribute: Node) => attribute.name === 'slot');
const slotName = slot.chunks[0].data;
const slot_name = slot.chunks[0].data;
const target = renderer.targets[renderer.targets.length - 1];
target.slotStack.push(slotName);
target.slots[slotName] = '';
target.slot_stack.push(slot_name);
target.slots[slot_name] = '';
options.slot_scopes.set(slotName, get_slot_scope(node.lets));
options.slot_scopes.set(slot_name, get_slot_scope(node.lets));
}
const classExpr = node.classes.map((classDir: Class) => {
const { expression, name } = classDir;
const snippet = expression ? snip(expression) : `ctx${quotePropIfNecessary(name)}`;
const class_expression = node.classes.map((class_directive: Class) => {
const { expression, name } = class_directive;
const snippet = expression ? snip(expression) : `ctx${quote_prop_if_necessary(name)}`;
return `${snippet} ? "${name}" : ""`;
}).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
const args = [];
node.attributes.forEach(attribute => {
if (attribute.isSpread) {
if (attribute.is_spread) {
args.push(snip(attribute.expression));
} else {
if (attribute.name === 'value' && node.name === 'textarea') {
textareaContents = stringify_attribute(attribute, true);
} else if (attribute.isTrue) {
args.push(`{ ${quoteNameIfNecessary(attribute.name)}: true }`);
textarea_contents = stringify_attribute(attribute, true);
} else if (attribute.is_true) {
args.push(`{ ${quote_name_if_necessary(attribute.name)}: true }`);
} else if (
boolean_attributes.has(attribute.name) &&
attribute.chunks.length === 1 &&
attribute.chunks[0].type !== 'Text'
) {
// a boolean attribute with one non-Text chunk
args.push(`{ ${quoteNameIfNecessary(attribute.name)}: ${snip(attribute.chunks[0])} }`);
args.push(`{ ${quote_name_if_necessary(attribute.name)}: ${snip(attribute.chunks[0])} }`);
} else {
args.push(`{ ${quoteNameIfNecessary(attribute.name)}: \`${stringify_attribute(attribute, true)}\` }`);
args.push(`{ ${quote_name_if_necessary(attribute.name)}: \`${stringify_attribute(attribute, true)}\` }`);
}
}
});
openingTag += "${@spread([" + args.join(', ') + "])}";
opening_tag += "${@spread([" + args.join(', ') + "])}";
} else {
node.attributes.forEach((attribute: Attribute) => {
if (attribute.type !== 'Attribute') return;
if (attribute.name === 'value' && node.name === 'textarea') {
textareaContents = stringify_attribute(attribute, true);
} else if (attribute.isTrue) {
openingTag += ` ${attribute.name}`;
textarea_contents = stringify_attribute(attribute, true);
} else if (attribute.is_true) {
opening_tag += ` ${attribute.name}`;
} else if (
boolean_attributes.has(attribute.name) &&
attribute.chunks.length === 1 &&
attribute.chunks[0].type !== 'Text'
) {
// a boolean attribute with one non-Text chunk
openingTag += '${' + snip(attribute.chunks[0]) + ' ? " ' + attribute.name + '" : "" }';
} else if (attribute.name === 'class' && classExpr) {
addClassAttribute = false;
openingTag += ` class="\${[\`${stringify_attribute(attribute, true)}\`, ${classExpr}].join(' ').trim() }"`;
opening_tag += '${' + snip(attribute.chunks[0]) + ' ? " ' + attribute.name + '" : "" }';
} else if (attribute.name === 'class' && class_expression) {
add_class_attribute = false;
opening_tag += ` class="\${[\`${stringify_attribute(attribute, true)}\`, ${class_expression}].join(' ').trim() }"`;
} else if (attribute.chunks.length === 1 && attribute.chunks[0].type !== 'Text') {
const { name } = attribute;
const snippet = snip(attribute.chunks[0]);
openingTag += '${(v => v == null ? "" : ` ' + name + '="${@escape(' + snippet + ')}"`)(' + snippet + ')}';
opening_tag += '${(v => v == null ? "" : ` ' + name + '="${@escape(' + snippet + ')}"`)(' + snippet + ')}';
} else {
openingTag += ` ${attribute.name}="${stringify_attribute(attribute, true)}"`;
opening_tag += ` ${attribute.name}="${stringify_attribute(attribute, true)}"`;
}
});
}
@ -131,25 +130,25 @@ export default function(node, renderer, options) {
// TODO server-render group bindings
} else {
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) {
openingTag += `\${((v) => v ? ' class="' + v + '"' : '')([${classExpr}].join(' ').trim())}`;
if (add_class_attribute) {
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) {
renderer.append(textareaContents);
if (node.name === 'textarea' && textarea_contents !== undefined) {
renderer.append(textarea_contents);
} else {
renderer.render(node.children, options);
}
if (!isVoidElementName(node.name)) {
if (!is_void(node.name)) {
renderer.append(`</${node.name}>`);
}
}

@ -1,4 +1,4 @@
import { snip } from '../../../utils/snip';
import { snip } from '../../utils/snip';
export default function(node, renderer, options) {
renderer.append('${' + snip(node.expression) + '}');

@ -1,4 +1,4 @@
import { snip } from '../../../utils/snip';
import { snip } from '../../utils/snip';
export default function(node, renderer, options) {
const snippet = snip(node.expression);

@ -1,22 +1,22 @@
import { escape, escapeTemplate, stringify } from '../../../utils/stringify';
import { quoteNameIfNecessary } from '../../../utils/quoteIfNecessary';
import { snip } from '../../../utils/snip';
import { escape, escape_template, stringify } from '../../utils/stringify';
import { quote_name_if_necessary } from '../../../utils/names';
import { snip } from '../../utils/snip';
import Renderer from '../Renderer';
import stringifyProps from '../../../utils/stringifyProps';
import { stringify_props } from '../../utils/stringify_props';
import { get_slot_scope } from './shared/get_slot_scope';
type AppendTarget = any; // TODO
function stringifyAttribute(chunk: Node) {
function stringify_attribute(chunk: Node) {
if (chunk.type === 'Text') {
return escapeTemplate(escape(chunk.data));
return escape_template(escape(chunk.data));
}
return '${@escape( ' + snip(chunk) + ')}';
return '${@escape(' + snip(chunk) + ')}';
}
function getAttributeValue(attribute) {
if (attribute.isTrue) return `true`;
function get_attribute_value(attribute) {
if (attribute.is_true) return `true`;
if (attribute.chunks.length === 0) return `''`;
if (attribute.chunks.length === 1) {
@ -28,7 +28,7 @@ function getAttributeValue(attribute) {
return snip(chunk);
}
return '`' + attribute.chunks.map(stringifyAttribute).join('') + '`';
return '`' + attribute.chunks.map(stringify_attribute).join('') + '`';
}
export default function(node, renderer: Renderer, options) {
@ -45,38 +45,38 @@ export default function(node, renderer: Renderer, options) {
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;
if (usesSpread) {
if (uses_spread) {
props = `Object.assign(${
node.attributes
.map(attribute => {
if (attribute.isSpread) {
if (attribute.is_spread) {
return snip(attribute.expression);
} else {
return `{ ${attribute.name}: ${getAttributeValue(attribute)} }`;
return `{ ${attribute.name}: ${get_attribute_value(attribute)} }`;
}
})
.concat(binding_props.map(p => `{ ${p} }`))
.join(', ')
})`;
} else {
props = stringifyProps(
props = stringify_props(
node.attributes
.map(attribute => `${attribute.name}: ${getAttributeValue(attribute)}`)
.map(attribute => `${attribute.name}: ${get_attribute_value(attribute)}`)
.concat(binding_props)
);
}
const bindings = stringifyProps(binding_fns);
const bindings = stringify_props(binding_fns);
const expression = (
node.name === 'svelte:self'
? '__svelte:self__' // TODO conflict-proof this
: node.name === 'svelte:component'
? `((${snip(node.expression)}) || @missingComponent)`
? `((${snip(node.expression)}) || @missing_component)`
: node.name
);
@ -85,7 +85,7 @@ export default function(node, renderer: Renderer, options) {
if (node.children.length) {
const target: AppendTarget = {
slots: { default: '' },
slotStack: ['default']
slot_stack: ['default']
};
renderer.targets.push(target);
@ -101,14 +101,14 @@ export default function(node, renderer: Renderer, options) {
const slot_scope = slot_scopes.get(name);
slot_fns.push(
`${quoteNameIfNecessary(name)}: (${slot_scope}) => \`${target.slots[name]}\``
`${quote_name_if_necessary(name)}: (${slot_scope}) => \`${target.slots[name]}\``
);
});
renderer.targets.pop();
}
const slots = stringifyProps(slot_fns);
const slots = stringify_props(slot_fns);
renderer.append(`\${@validate_component(${expression}, '${node.name}').$$render($$result, ${props}, ${bindings}, ${slots})}`);
}

@ -1,11 +1,11 @@
import { quotePropIfNecessary } from '../../../utils/quoteIfNecessary';
import get_slot_data from '../../../utils/get_slot_data';
import { quote_prop_if_necessary } from '../../../utils/names';
import get_slot_data from '../../utils/get_slot_data';
export default function(node, renderer, options) {
const name = node.attributes.find(attribute => attribute.name === 'name');
const slot_name = name && name.chunks[0].data || 'default';
const prop = quotePropIfNecessary(slot_name);
const prop = quote_prop_if_necessary(slot_name);
const slot_data = get_slot_data(node.attributes, true);

@ -1,4 +1,4 @@
import { snip } from '../../../utils/snip';
import { snip } from '../../utils/snip';
export default function(node, renderer, options) {
const snippet = snip(node.expression);

@ -1,4 +1,4 @@
import { escapeHTML, escapeTemplate, escape } from '../../../utils/stringify';
import { escape_html, escape_template, escape } from '../../utils/stringify';
export default function(node, renderer, options) {
let text = node.data;
@ -8,7 +8,7 @@ export default function(node, renderer, options) {
(node.parent.name !== 'script' && node.parent.name !== 'style')
) {
// unless this Text node is inside a <script> or <style> element, escape &,<,>
text = escapeHTML(text);
text = escape_html(text);
}
renderer.append(escape(escapeTemplate(text)));
renderer.append(escape(escape_template(text)));
}

@ -1,10 +1,8 @@
import deindent from '../../utils/deindent';
import deindent from '../utils/deindent';
import Component from '../Component';
import { CompileOptions } from '../../interfaces';
import { stringify } from '../../utils/stringify';
import { stringify } from '../utils/stringify';
import Renderer from './Renderer';
import { walk } from 'estree-walker';
import { extractNames } from '../../utils/annotateWithScopes';
export default function ssr(
component: Component,
@ -33,7 +31,7 @@ export default function ssr(
const assignment = `${name} = @get_store_value(${store_name});`;
return component.compileOptions.dev
return component.compile_options.dev
? `@validate_store(${store_name}, '${store_name}'); ${assignment}`
: assignment;
});
@ -48,7 +46,7 @@ export default function ssr(
const get_store_value = component.helper('get_store_value');
let insert = `${value} = ${get_store_value}(${name})`;
if (component.compileOptions.dev) {
if (component.compile_options.dev) {
const validate_store = component.helper('validate_store');
insert = `${validate_store}(${name}, '${name}'); ${insert}`;
}

@ -1,4 +1,4 @@
import repeat from './repeat';
import repeat from '../../utils/repeat';
const whitespace = /^\s+$/;
@ -24,10 +24,10 @@ export default class CodeBuilder {
constructor(str = '') {
this.current = this.last = this.root;
this.addLine(str);
this.add_line(str);
}
addConditional(condition: string, body: string) {
add_conditional(condition: string, body: string) {
if (this.last.type === 'condition' && this.last.condition === condition) {
if (body && !whitespace.test(body)) this.last.children.push({ type: 'line', line: body });
} else {
@ -37,17 +37,17 @@ export default class CodeBuilder {
}
}
addLine(line: string) {
add_line(line: string) {
if (line && !whitespace.test(line)) this.current.children.push(this.last = { type: 'line', line });
}
addBlock(block: string) {
add_block(block: string) {
if (block && !whitespace.test(block)) this.current.children.push(this.last = { type: 'line', line: block, block: true });
}
isEmpty() { return !findLine(this.root); }
is_empty() { return !find_line(this.root); }
pushCondition(condition: string) {
push_condition(condition: string) {
if (this.last.type === 'condition' && this.last.condition === condition) {
this.current = this.last as BlockChunk;
} else {
@ -57,41 +57,41 @@ export default class CodeBuilder {
}
}
popCondition() {
pop_condition() {
if (!this.current.parent) throw new Error(`Popping a condition that maybe wasn't pushed.`);
this.current = this.current.parent;
}
toString() {
return chunkToString(this.root);
return chunk_to_string(this.root);
}
}
function findLine(chunk: BlockChunk) {
function find_line(chunk: BlockChunk) {
for (const c of chunk.children) {
if (c.type === 'line' || findLine(c as BlockChunk)) return true;
if (c.type === 'line' || find_line(c as BlockChunk)) return true;
}
return false;
}
function chunkToString(chunk: Chunk, level: number = 0, lastBlock?: boolean, first?: boolean): string {
function chunk_to_string(chunk: Chunk, level: number = 0, last_block?: boolean, first?: boolean): string {
if (chunk.type === 'line') {
return `${lastBlock || (!first && chunk.block) ? '\n' : ''}${chunk.line.replace(/^/gm, repeat('\t', level))}`;
return `${last_block || (!first && chunk.block) ? '\n' : ''}${chunk.line.replace(/^/gm, repeat('\t', level))}`;
} else if (chunk.type === 'condition') {
let t = false;
const lines = chunk.children.map((c, i) => {
const str = chunkToString(c, level + 1, t, i === 0);
const str = chunk_to_string(c, level + 1, t, i === 0);
t = c.type !== 'line' || c.block;
return str;
}).filter(l => !!l);
if (!lines.length) return '';
return `${lastBlock || (!first) ? '\n' : ''}${repeat('\t', level)}if (${chunk.condition}) {\n${lines.join('\n')}\n${repeat('\t', level)}}`;
return `${last_block || (!first) ? '\n' : ''}${repeat('\t', level)}if (${chunk.condition}) {\n${lines.join('\n')}\n${repeat('\t', level)}}`;
} else if (chunk.type === 'root') {
let t = false;
const lines = chunk.children.map((c, i) => {
const str = chunkToString(c, 0, t, i === 0);
const str = chunk_to_string(c, 0, t, i === 0);
t = c.type !== 'line' || c.block;
return str;
}).filter(l => !!l);

@ -84,30 +84,30 @@ describe('CodeBuilder', () => {
it('creates a block with a line', () => {
const builder = new CodeBuilder();
builder.addLine('var answer = 42;');
builder.add_line('var answer = 42;');
assert.equal(builder.toString(), 'var answer = 42;');
});
it('creates a block with two lines', () => {
const builder = new CodeBuilder();
builder.addLine('var problems = 99;');
builder.addLine('var answer = 42;');
builder.add_line('var problems = 99;');
builder.add_line('var answer = 42;');
assert.equal(builder.toString(), 'var problems = 99;\nvar answer = 42;');
});
it('adds newlines around blocks', () => {
const builder = new CodeBuilder();
builder.addLine('// line 1');
builder.addLine('// line 2');
builder.addBlock(deindent`
if ( foo ) {
builder.add_line('// line 1');
builder.add_line('// line 2');
builder.add_block(deindent`
if (foo) {
bar();
}
`);
builder.addLine('// line 3');
builder.addLine('// line 4');
builder.add_line('// line 3');
builder.add_line('// line 4');
assert.equal(
builder.toString(),
@ -115,7 +115,7 @@ describe('CodeBuilder', () => {
// line 1
// line 2
if ( foo ) {
if (foo) {
bar();
}
@ -128,7 +128,7 @@ describe('CodeBuilder', () => {
it('nests codebuilders with correct indentation', () => {
const child = new CodeBuilder();
child.addBlock(deindent`
child.add_block(deindent`
var obj = {
answer: 42
};
@ -136,15 +136,15 @@ describe('CodeBuilder', () => {
const builder = new CodeBuilder();
builder.addLine('// line 1');
builder.addLine('// line 2');
builder.addBlock(deindent`
if ( foo ) {
builder.add_line('// line 1');
builder.add_line('// line 2');
builder.add_block(deindent`
if (foo) {
${child}
}
`);
builder.addLine('// line 3');
builder.addLine('// line 4');
builder.add_line('// line 3');
builder.add_line('// line 4');
assert.equal(
builder.toString(),
@ -152,7 +152,7 @@ describe('CodeBuilder', () => {
// line 1
// line 2
if ( foo ) {
if (foo) {
var obj = {
answer: 42
};

@ -0,0 +1,5 @@
export default function add_to_set(a: Set<any>, b: Set<any>) {
b.forEach(item => {
a.add(item);
});
}

@ -20,7 +20,7 @@ export default function deindent(
}
// discard empty codebuilders
if (expression && expression.isEmpty && expression.isEmpty()) {
if (expression && expression.is_empty && expression.is_empty()) {
expression = null;
}

@ -1,10 +1,10 @@
import { Node } from '../interfaces';
import { Node } from '../../interfaces';
export default function flattenReference(node: Node) {
export default function flatten_reference(node: Node) {
if (node.type === 'Expression') throw new Error('bad');
const nodes = [];
const parts = [];
const propEnd = node.end;
const prop_end = node.end;
while (node.type === 'MemberExpression') {
if (node.computed) return null;
@ -15,7 +15,7 @@ export default function flattenReference(node: Node) {
node = node.object;
}
const propStart = node.end;
const prop_start = node.end;
const name = node.type === 'Identifier'
? node.name
: node.type === 'ThisExpression' ? 'this' : null;
@ -25,5 +25,5 @@ export default function flattenReference(node: Node) {
parts.unshift(name);
nodes.unshift(node);
return { name, nodes, parts, keypath: `${name}[✂${propStart}-${propEnd}✂]` };
return { name, nodes, parts, keypath: `${name}[✂${prop_start}-${prop_end}✂]` };
}

@ -1,6 +1,6 @@
import { Node } from '../interfaces';
import { Node } from '../../interfaces';
export default function getObject(node: Node) {
export default function get_object(node: Node) {
while (node.type === 'ParenthesizedExpression') node = node.expression;
while (node.type === 'MemberExpression') node = node.object;
return node;

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

@ -1,8 +1,8 @@
import { walk } from 'estree-walker';
import isReference from 'is-reference';
import { Node } from '../interfaces';
import is_reference from 'is-reference';
import { Node } from '../../interfaces';
export function createScopes(expression: Node) {
export function create_scopes(expression: Node) {
const map = new WeakMap();
const globals: Map<string, Node> = new Map();
@ -26,7 +26,7 @@ export function createScopes(expression: Node) {
}
node.params.forEach((param: Node) => {
extractNames(param).forEach(name => {
extract_names(param).forEach(name => {
scope.declarations.set(name, node);
});
});
@ -37,8 +37,8 @@ export function createScopes(expression: Node) {
scope = new Scope(scope, true);
map.set(node, scope);
} else if (/(Class|Variable)Declaration/.test(node.type)) {
scope.addDeclaration(node);
} else if (node.type === 'Identifier' && isReference(node, parent)) {
scope.add_declaration(node);
} else if (node.type === 'Identifier' && is_reference(node, parent)) {
if (!scope.has(node.name) && !globals.has(node.name)) {
globals.set(node.name, node);
}
@ -71,14 +71,14 @@ export class Scope {
this.block = block;
}
addDeclaration(node: Node) {
add_declaration(node: Node) {
if (node.kind === 'var' && this.block && this.parent) {
this.parent.addDeclaration(node);
this.parent.add_declaration(node);
} else if (node.type === 'VariableDeclaration') {
const initialised = !!node.init;
node.declarations.forEach((declarator: Node) => {
extractNames(declarator.id).forEach(name => {
extract_names(declarator.id).forEach(name => {
this.declarations.set(name, node);
if (initialised) this.initialised_declarations.add(name);
});
@ -88,9 +88,9 @@ export class Scope {
}
}
findOwner(name: string): Scope {
find_owner(name: string): Scope {
if (this.declarations.has(name)) return this;
return this.parent && this.parent.findOwner(name);
return this.parent && this.parent.find_owner(name);
}
has(name: string): boolean {
@ -100,7 +100,7 @@ export class Scope {
}
}
export function extractNames(param: Node) {
export function extract_names(param: Node) {
const names: string[] = [];
extractors[param.type](names, param);
return names;
@ -133,5 +133,5 @@ const extractors = {
AssignmentPattern(names: string[], param: Node) {
extractors[param.left.type](names, param.left);
},
}
};

@ -2,8 +2,8 @@ export function stringify(data: string, options = {}) {
return JSON.stringify(escape(data, options));
}
export function escape(data: string, { onlyEscapeAtSymbol = false } = {}) {
return data.replace(onlyEscapeAtSymbol ? /@+/g : /(@+|#+)/g, (match: string) => {
export function escape(data: string, { only_escape_at_symbol = false } = {}) {
return data.replace(only_escape_at_symbol ? /@+/g : /(@+|#+)/g, (match: string) => {
return match + match[0];
});
}
@ -14,10 +14,10 @@ const escaped = {
'>': '&gt;',
};
export function escapeHTML(html) {
export function escape_html(html) {
return String(html).replace(/[&<>]/g, match => escaped[match]);
}
export function escapeTemplate(str) {
export function escape_template(str) {
return str.replace(/(\${|`|\\)/g, '\\$1');
}

@ -1,13 +1,13 @@
import Attribute from '../compile/nodes/Attribute';
import Node from '../compile/nodes/shared/Node';
import { escapeTemplate, escape } from './stringify';
import Attribute from '../nodes/Attribute';
import Node from '../nodes/shared/Node';
import { escape_template, escape } from './stringify';
import { snip } from './snip';
export function stringify_attribute(attribute: Attribute, is_ssr: boolean) {
return attribute.chunks
.map((chunk: Node) => {
if (chunk.type === 'Text') {
return escapeTemplate(escape(chunk.data).replace(/"/g, '&quot;'));
return escape_template(escape(chunk.data).replace(/"/g, '&quot;'));
}
return is_ssr

@ -1,4 +1,4 @@
export default function stringifyProps(props: string[]) {
export function stringify_props(props: string[]) {
if (!props.length) return '{}';
const joined = props.join(', ');

@ -2,7 +2,7 @@ export interface Node {
start: number;
end: number;
type: string;
[propName: string]: any;
[prop_name: string]: any;
}
export interface Parser {
@ -15,7 +15,7 @@ export interface Parser {
html: Node;
css: Node;
js: Node;
metaTags: {};
meta_tags: {};
}
export interface Ast {
@ -67,7 +67,7 @@ export interface Visitor {
export interface AppendTarget {
slots: Record<string, string>;
slotStack: string[]
slot_stack: string[]
}
export interface Var {

@ -1,7 +1,7 @@
import { add_render_callback, flush, intros, schedule_update, dirty_components } from './scheduler.js';
import { current_component, set_current_component } from './lifecycle.js'
import { is_function, run, run_all, noop } from './utils.js';
import { blankObject } from './utils.js';
import { blank_object } from './utils.js';
import { children } from './dom.js';
export function bind(component, name, callback) {
@ -33,10 +33,10 @@ export function mount_component(component, target, anchor) {
after_render.forEach(add_render_callback);
}
function destroy(component, detach) {
function destroy(component, detaching) {
if (component.$$) {
run_all(component.$$.on_destroy);
component.$$.fragment.d(detach);
component.$$.fragment.d(detaching);
// TODO null out other refs, including component.$$ (but need to
// preserve final state?)
@ -68,7 +68,7 @@ export function init(component, options, instance, create_fragment, not_equal, p
props: prop_names,
update: noop,
not_equal,
bound: blankObject(),
bound: blank_object(),
// lifecycle
on_mount: [],
@ -78,7 +78,7 @@ export function init(component, options, instance, create_fragment, not_equal, p
context: new Map(parent_component ? parent_component.$$.context : []),
// everything else
callbacks: blankObject(),
callbacks: blank_object(),
dirty: null
};

@ -22,11 +22,11 @@ export function create_animation(node, from, fn, params) {
let started = false;
let name;
const cssText = node.style.cssText;
const css_text = node.style.cssText;
function start() {
if (css) {
if (delay) node.style.cssText = cssText; // TODO create delayed animation instead?
if (delay) node.style.cssText = css_text; // TODO create delayed animation instead?
name = create_rule(node, 0, 1, duration, 0, easing, css);
}

@ -1,8 +1,8 @@
import { assign, isPromise } from './utils.js';
import { assign, is_promise } from './utils.js';
import { check_outros, group_outros, on_outro } from './transitions.js';
import { flush } from '../internal/scheduler.js';
export function handlePromise(promise, info) {
export function handle_promise(promise, info) {
var token = info.token = {};
function update(type, index, key, value) {
@ -41,7 +41,7 @@ export function handlePromise(promise, info) {
if (info.blocks) info.blocks[index] = block;
}
if (isPromise(promise)) {
if (is_promise(promise)) {
promise.then(value => {
update(info.then, 1, info.value, value);
}, error => {

@ -6,122 +6,99 @@ export function insert(target, node, anchor) {
target.insertBefore(node, anchor);
}
export function detachNode(node) {
export function detach(node) {
node.parentNode.removeChild(node);
}
export function detachBetween(before, after) {
export function detach_between(before, after) {
while (before.nextSibling && before.nextSibling !== after) {
before.parentNode.removeChild(before.nextSibling);
}
}
export function detachBefore(after) {
export function detach_before(after) {
while (after.previousSibling) {
after.parentNode.removeChild(after.previousSibling);
}
}
export function detachAfter(before) {
export function detach_after(before) {
while (before.nextSibling) {
before.parentNode.removeChild(before.nextSibling);
}
}
export function reinsertBetween(before, after, target) {
while (before.nextSibling && before.nextSibling !== after) {
target.appendChild(before.parentNode.removeChild(before.nextSibling));
}
}
export function reinsertChildren(parent, target) {
while (parent.firstChild) target.appendChild(parent.firstChild);
}
export function reinsertAfter(before, target) {
while (before.nextSibling) target.appendChild(before.nextSibling);
}
export function reinsertBefore(after, target) {
var parent = after.parentNode;
while (parent.firstChild !== after) target.appendChild(parent.firstChild);
}
export function destroyEach(iterations, detach) {
export function destroy_each(iterations, detaching) {
for (var i = 0; i < iterations.length; i += 1) {
if (iterations[i]) iterations[i].d(detach);
if (iterations[i]) iterations[i].d(detaching);
}
}
export function createFragment() {
return document.createDocumentFragment();
}
export function createElement(name) {
export function element(name) {
return document.createElement(name);
}
export function createSvgElement(name) {
export function svg_element(name) {
return document.createElementNS('http://www.w3.org/2000/svg', name);
}
export function createText(data) {
export function text(data) {
return document.createTextNode(data);
}
export function createComment() {
export function comment() {
return document.createComment('');
}
export function addListener(node, event, handler, options) {
export function listen(node, event, handler, options) {
node.addEventListener(event, handler, options);
return () => node.removeEventListener(event, handler, options);
}
export function preventDefault(fn) {
export function prevent_default(fn) {
return function(event) {
event.preventDefault();
return fn.call(this, event);
};
}
export function stopPropagation(fn) {
export function stop_propagation(fn) {
return function(event) {
event.stopPropagation();
return fn.call(this, event);
};
}
export function setAttribute(node, attribute, value) {
export function attr(node, attribute, value) {
if (value == null) node.removeAttribute(attribute);
else node.setAttribute(attribute, value);
}
export function setAttributes(node, attributes) {
export function set_attributes(node, attributes) {
for (var key in attributes) {
if (key === 'style') {
node.style.cssText = attributes[key];
} else if (key in node) {
node[key] = attributes[key];
} else {
setAttribute(node, key, attributes[key]);
attr(node, key, attributes[key]);
}
}
}
export function setCustomElementData(node, prop, value) {
export function set_custom_element_data(node, prop, value) {
if (prop in node) {
node[prop] = value;
} else {
setAttribute(node, prop, value);
attr(node, prop, value);
}
}
export function setXlinkAttribute(node, attribute, value) {
export function xlink_attr(node, attribute, value) {
node.setAttributeNS('http://www.w3.org/1999/xlink', attribute, value);
}
export function getBindingGroupValue(group) {
export function get_binding_group_value(group) {
var value = [];
for (var i = 0; i < group.length; i += 1) {
if (group[i].checked) value.push(group[i].__value);
@ -129,11 +106,11 @@ export function getBindingGroupValue(group) {
return value;
}
export function toNumber(value) {
export function to_number(value) {
return value === '' ? undefined : +value;
}
export function timeRangesToArray(ranges) {
export function time_ranges_to_array(ranges) {
var array = [];
for (var i = 0; i < ranges.length; i += 1) {
array.push({ start: ranges.start(i), end: ranges.end(i) });
@ -141,11 +118,11 @@ export function timeRangesToArray(ranges) {
return array;
}
export function children (element) {
export function children(element) {
return Array.from(element.childNodes);
}
export function claimElement (nodes, name, attributes, svg) {
export function claim_element(nodes, name, attributes, svg) {
for (var i = 0; i < nodes.length; i += 1) {
var node = nodes[i];
if (node.nodeName === name) {
@ -157,10 +134,10 @@ export function claimElement (nodes, name, attributes, svg) {
}
}
return svg ? createSvgElement(name) : createElement(name);
return svg ? svg_element(name) : element(name);
}
export function claimText (nodes, data) {
export function claim_text(nodes, data) {
for (var i = 0; i < nodes.length; i += 1) {
var node = nodes[i];
if (node.nodeType === 3) {
@ -169,24 +146,24 @@ export function claimText (nodes, data) {
}
}
return createText(data);
return text(data);
}
export function setData(text, data) {
export function set_data(text, data) {
text.data = '' + data;
}
export function setInputType(input, type) {
export function set_input_type(input, type) {
try {
input.type = type;
} catch (e) {}
}
export function setStyle(node, key, value) {
export function set_style(node, key, value) {
node.style.setProperty(key, value);
}
export function selectOption(select, value) {
export function select_option(select, value) {
for (var i = 0; i < select.options.length; i += 1) {
var option = select.options[i];
@ -197,25 +174,25 @@ export function selectOption(select, value) {
}
}
export function selectOptions(select, value) {
export function select_options(select, value) {
for (var i = 0; i < select.options.length; i += 1) {
var option = select.options[i];
option.selected = ~value.indexOf(option.__value);
}
}
export function selectValue(select) {
export function select_value(select) {
var selectedOption = select.querySelector(':checked') || select.options[0];
return selectedOption && selectedOption.__value;
}
export function selectMultipleValue(select) {
export function select_multiple_value(select) {
return [].map.call(select.querySelectorAll(':checked'), function(option) {
return option.__value;
});
}
export function addResizeListener(element, fn) {
export function add_resize_listener(element, fn) {
if (getComputedStyle(element).position === 'static') {
element.style.position = 'relative';
}
@ -247,7 +224,7 @@ export function addResizeListener(element, fn) {
};
}
export function toggleClass(element, name, toggle) {
export function toggle_class(element, name, toggle) {
element.classList[toggle ? 'add' : 'remove'](name);
}

@ -1,24 +1,24 @@
import { on_outro } from './transitions.js';
export function destroyBlock(block, lookup) {
export function destroy_block(block, lookup) {
block.d(1);
lookup[block.key] = null;
}
export function outroAndDestroyBlock(block, lookup) {
export function outro_and_destroy_block(block, lookup) {
on_outro(() => {
destroyBlock(block, lookup);
destroy_block(block, lookup);
});
block.o(1);
}
export function fixAndOutroAndDestroyBlock(block, lookup) {
export function fix_and_outro_and_destroy_block(block, lookup) {
block.f();
outroAndDestroyBlock(block, lookup);
outro_and_destroy_block(block, lookup);
}
export function updateKeyedEach(old_blocks, changed, get_key, dynamic, ctx, list, lookup, node, destroy, create_each_block, next, get_context) {
export function update_keyed_each(old_blocks, changed, get_key, dynamic, ctx, list, lookup, node, destroy, create_each_block, next, get_context) {
var o = old_blocks.length;
var n = list.length;

@ -1,4 +1,4 @@
export function getSpreadUpdate(levels, updates) {
export function get_spread_update(levels, updates) {
var update = {};
var to_null_out = {};

@ -1,7 +1,7 @@
import { set_current_component, current_component } from './lifecycle.js';
import { run_all, blankObject } from './utils.js';
import { run_all, blank_object } from './utils.js';
export const invalidAttributeNameCharacter = /[\s'">\/=\u{FDD0}-\u{FDEF}\u{FFFE}\u{FFFF}\u{1FFFE}\u{1FFFF}\u{2FFFE}\u{2FFFF}\u{3FFFE}\u{3FFFF}\u{4FFFE}\u{4FFFF}\u{5FFFE}\u{5FFFF}\u{6FFFE}\u{6FFFF}\u{7FFFE}\u{7FFFF}\u{8FFFE}\u{8FFFF}\u{9FFFE}\u{9FFFF}\u{AFFFE}\u{AFFFF}\u{BFFFE}\u{BFFFF}\u{CFFFE}\u{CFFFF}\u{DFFFE}\u{DFFFF}\u{EFFFE}\u{EFFFF}\u{FFFFE}\u{FFFFF}\u{10FFFE}\u{10FFFF}]/u;
export const invalid_attribute_name_character = /[\s'">\/=\u{FDD0}-\u{FDEF}\u{FFFE}\u{FFFF}\u{1FFFE}\u{1FFFF}\u{2FFFE}\u{2FFFF}\u{3FFFE}\u{3FFFF}\u{4FFFE}\u{4FFFF}\u{5FFFE}\u{5FFFF}\u{6FFFE}\u{6FFFF}\u{7FFFE}\u{7FFFF}\u{8FFFE}\u{8FFFF}\u{9FFFE}\u{9FFFF}\u{AFFFE}\u{AFFFF}\u{BFFFE}\u{BFFFF}\u{CFFFE}\u{CFFFF}\u{DFFFE}\u{DFFFF}\u{EFFFE}\u{EFFFF}\u{FFFFE}\u{FFFFF}\u{10FFFE}\u{10FFFF}]/u;
// https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
// https://infra.spec.whatwg.org/#noncharacter
@ -10,7 +10,7 @@ export function spread(args) {
let str = '';
Object.keys(attributes).forEach(name => {
if (invalidAttributeNameCharacter.test(name)) return;
if (invalid_attribute_name_character.test(name)) return;
const value = attributes[name];
if (value === undefined) return;
@ -46,7 +46,7 @@ export function each(items, fn) {
return str;
}
export const missingComponent = {
export const missing_component = {
$$render: () => ''
};
@ -79,7 +79,7 @@ export function create_ssr_component(fn) {
on_mount: [],
before_render: [],
after_render: [],
callbacks: blankObject()
callbacks: blank_object()
};
set_current_component({ $$ });

@ -1,4 +1,4 @@
import { createElement } from './dom.js';
import { element } from './dom.js';
let stylesheet;
let active = 0;
@ -27,7 +27,7 @@ export function create_rule(node, a, b, duration, delay, ease, fn, uid = 0) {
if (!current_rules[name]) {
if (!stylesheet) {
const style = createElement('style');
const style = element('style');
document.head.appendChild(style);
stylesheet = style.sheet;
}

@ -7,11 +7,11 @@ export function assign(tar, src) {
return tar;
}
export function isPromise(value) {
export function is_promise(value) {
return value && typeof value.then === 'function';
}
export function addLoc(element, file, line, column, char) {
export function add_location(element, file, line, column, char) {
element.__svelte_meta = {
loc: { file, line, column, char }
};
@ -21,7 +21,7 @@ export function run(fn) {
return fn();
}
export function blankObject() {
export function blank_object() {
return Object.create(null);
}

@ -9,7 +9,7 @@ export const parse = (source: string) => Parser.parse(source, {
preserveParens: true
});
export const parseExpressionAt = (source: string, index: number) => Parser.parseExpressionAt(source, index, {
export const parse_expression_at = (source: string, index: number) => Parser.parseExpressionAt(source, index, {
ecmaVersion: 9,
preserveParens: true
});

@ -1,14 +1,13 @@
import { isIdentifierStart, isIdentifierChar } from 'acorn';
import fragment from './state/fragment';
import { whitespace } from '../utils/patterns';
import reservedNames from '../utils/reservedNames';
import fullCharCodeAt from '../utils/fullCharCodeAt';
import { reserved } from '../utils/names';
import full_char_code_at from '../utils/full_char_code_at';
import { Node, Ast } from '../interfaces';
import error from '../utils/error';
interface ParserOptions {
filename?: string;
bind?: boolean;
customElement?: boolean;
}
@ -25,9 +24,7 @@ export class Parser {
html: Node;
css: Node[] = [];
js: Node[] = [];
metaTags = {};
allowBindings: boolean;
meta_tags = {};
constructor(template: string, options: ParserOptions) {
if (typeof template !== 'string') {
@ -38,8 +35,6 @@ export class Parser {
this.filename = options.filename;
this.customElement = options.customElement;
this.allowBindings = options.bind !== false;
this.html = {
start: null,
end: null,
@ -92,7 +87,7 @@ export class Parser {
return this.stack[this.stack.length - 1];
}
acornError(err: any) {
acorn_error(err: any) {
this.error({
code: `parse-error`,
message: err.message.replace(/ \(\d+:\d+\)$/, '')
@ -129,14 +124,14 @@ export class Parser {
return this.template.slice(this.index, this.index + str.length) === str;
}
matchRegex(pattern: RegExp) {
match_regex(pattern: RegExp) {
const match = pattern.exec(this.template.slice(this.index));
if (!match || match.index !== 0) return null;
return match[0];
}
allowWhitespace() {
allow_whitespace() {
while (
this.index < this.template.length &&
whitespace.test(this.template[this.index])
@ -146,23 +141,23 @@ export class Parser {
}
read(pattern: RegExp) {
const result = this.matchRegex(pattern);
const result = this.match_regex(pattern);
if (result) this.index += result.length;
return result;
}
readIdentifier() {
read_identifier() {
const start = this.index;
let i = this.index;
const code = fullCharCodeAt(this.template, i);
const code = full_char_code_at(this.template, i);
if (!isIdentifierStart(code, true)) return null;
i += code <= 0xffff ? 1 : 2;
while (i < this.template.length) {
const code = fullCharCodeAt(this.template, i);
const code = full_char_code_at(this.template, i);
if (!isIdentifierChar(code, true)) break;
i += code <= 0xffff ? 1 : 2;
@ -170,7 +165,7 @@ export class Parser {
const identifier = this.template.slice(this.index, this.index = i);
if (reservedNames.has(identifier)) {
if (reserved.has(identifier)) {
this.error({
code: `unexpected-reserved-word`,
message: `'${identifier}' is a reserved word in JavaScript and cannot be used here`
@ -180,7 +175,7 @@ export class Parser {
return identifier;
}
readUntil(pattern: RegExp) {
read_until(pattern: RegExp) {
if (this.index >= this.template.length)
this.error({
code: `unexpected-eof`,
@ -199,7 +194,7 @@ export class Parser {
return this.template.slice(start);
}
requireWhitespace() {
require_whitespace() {
if (!whitespace.test(this.template[this.index])) {
this.error({
code: `missing-whitespace`,
@ -207,7 +202,7 @@ export class Parser {
});
}
this.allowWhitespace();
this.allow_whitespace();
}
}

@ -26,7 +26,7 @@ type Context = {
properties?: Property[];
}
function errorOnAssignmentPattern(parser: Parser) {
function error_on_assignment_pattern(parser: Parser) {
if (parser.eat('=')) {
parser.error({
code: 'invalid-assignment-pattern',
@ -35,7 +35,7 @@ function errorOnAssignmentPattern(parser: Parser) {
}
}
export default function readContext(parser: Parser) {
export default function read_context(parser: Parser) {
const context: Context = {
start: parser.index,
end: null,
@ -47,17 +47,17 @@ export default function readContext(parser: Parser) {
context.elements = [];
do {
parser.allowWhitespace();
parser.allow_whitespace();
if (parser.template[parser.index] === ',') {
context.elements.push(null);
} else {
context.elements.push(readContext(parser));
parser.allowWhitespace();
context.elements.push(read_context(parser));
parser.allow_whitespace();
}
} while (parser.eat(','));
errorOnAssignmentPattern(parser);
error_on_assignment_pattern(parser);
parser.eat(']', true);
context.end = parser.index;
}
@ -67,20 +67,20 @@ export default function readContext(parser: Parser) {
context.properties = [];
do {
parser.allowWhitespace();
parser.allow_whitespace();
const start = parser.index;
const name = parser.readIdentifier();
const name = parser.read_identifier();
const key: Identifier = {
start,
end: parser.index,
type: 'Identifier',
name
};
parser.allowWhitespace();
parser.allow_whitespace();
const value = parser.eat(':')
? (parser.allowWhitespace(), readContext(parser))
? (parser.allow_whitespace(), read_context(parser))
: key;
const property: Property = {
@ -95,16 +95,16 @@ export default function readContext(parser: Parser) {
context.properties.push(property);
parser.allowWhitespace();
parser.allow_whitespace();
} while (parser.eat(','));
errorOnAssignmentPattern(parser);
error_on_assignment_pattern(parser);
parser.eat('}', true);
context.end = parser.index;
}
else {
const name = parser.readIdentifier();
const name = parser.read_identifier();
if (name) {
context.type = 'Identifier';
context.end = parser.index;
@ -118,7 +118,7 @@ export default function readContext(parser: Parser) {
});
}
errorOnAssignmentPattern(parser);
error_on_assignment_pattern(parser);
}
return context;

@ -1,12 +1,12 @@
import { parseExpressionAt } from '../acorn';
import { parse_expression_at } from '../acorn';
import { Parser } from '../index';
const literals = new Map([['true', true], ['false', false], ['null', null]]);
export default function readExpression(parser: Parser) {
export default function read_expression(parser: Parser) {
const start = parser.index;
const name = parser.readUntil(/\s*}/);
const name = parser.read_until(/\s*}/);
if (name && /^[a-z]+$/.test(name)) {
const end = start + name.length;
@ -31,11 +31,11 @@ export default function readExpression(parser: Parser) {
parser.index = start;
try {
const node = parseExpressionAt(parser.template, parser.index);
const node = parse_expression_at(parser.template, parser.index);
parser.index = node.end;
return node;
} catch (err) {
parser.acornError(err);
parser.acorn_error(err);
}
}

@ -3,7 +3,7 @@ import repeat from '../../utils/repeat';
import { Parser } from '../index';
import { Node } from '../../interfaces';
const scriptClosingTag = '</script>';
const script_closing_tag = '</script>';
function get_context(parser: Parser, attributes: Node[], start: number) {
const context = attributes.find(attribute => attribute.name === 'context');
@ -28,28 +28,28 @@ function get_context(parser: Parser, attributes: Node[], start: number) {
return value;
}
export default function readScript(parser: Parser, start: number, attributes: Node[]) {
const scriptStart = parser.index;
const scriptEnd = parser.template.indexOf(scriptClosingTag, scriptStart);
export default function read_script(parser: Parser, start: number, attributes: Node[]) {
const script_start = parser.index;
const script_end = parser.template.indexOf(script_closing_tag, script_start);
if (scriptEnd === -1) parser.error({
if (script_end === -1) parser.error({
code: `unclosed-script`,
message: `<script> must have a closing tag`
});
const source =
repeat(' ', scriptStart) + parser.template.slice(scriptStart, scriptEnd);
parser.index = scriptEnd + scriptClosingTag.length;
repeat(' ', script_start) + parser.template.slice(script_start, script_end);
parser.index = script_end + script_closing_tag.length;
let ast;
try {
ast = acorn.parse(source);
} catch (err) {
parser.acornError(err);
parser.acorn_error(err);
}
ast.start = scriptStart;
ast.start = script_start;
return {
start,
end: parser.index,

@ -3,17 +3,17 @@ import { walk } from 'estree-walker';
import { Parser } from '../index';
import { Node } from '../../interfaces';
export default function readStyle(parser: Parser, start: number, attributes: Node[]) {
const contentStart = parser.index;
const styles = parser.readUntil(/<\/style>/);
const contentEnd = parser.index;
export default function read_style(parser: Parser, start: number, attributes: Node[]) {
const content_start = parser.index;
const styles = parser.read_until(/<\/style>/);
const content_end = parser.index;
let ast;
try {
ast = parse(styles, {
positions: true,
offset: contentStart,
offset: content_start,
});
} catch (err) {
if (err.name === 'CssSyntaxError') {
@ -37,7 +37,7 @@ export default function readStyle(parser: Parser, start: number, attributes: Nod
const a = node.children[i];
const b = node.children[i + 1];
if (isRefSelector(a, b)) {
if (is_ref_selector(a, b)) {
parser.error({
code: `invalid-ref-selector`,
message: 'ref selectors are no longer supported'
@ -63,14 +63,14 @@ export default function readStyle(parser: Parser, start: number, attributes: Nod
attributes,
children: ast.children,
content: {
start: contentStart,
end: contentEnd,
start: content_start,
end: content_end,
styles,
},
};
}
function isRefSelector(a: Node, b: Node) {
function is_ref_selector(a: Node, b: Node) {
if (!b) return false;
return (

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

Loading…
Cancel
Save