move render logic into separate phase (#1678)

pull/1759/head
Rich Harris 6 years ago
parent 8cb649024f
commit e0fe31327a

@ -5,7 +5,6 @@ import { walk, childKeys } from 'estree-walker';
import { getLocator } from 'locate-character';
import Stats from '../Stats';
import deindent from '../utils/deindent';
import CodeBuilder from '../utils/CodeBuilder';
import reservedNames from '../utils/reservedNames';
import namespaces from '../utils/namespaces';
import { removeNode } from '../utils/removeNode';
@ -13,13 +12,11 @@ import nodeToString from '../utils/nodeToString';
import wrapModule from './wrapModule';
import annotateWithScopes from '../utils/annotateWithScopes';
import getName from '../utils/getName';
import Stylesheet from '../css/Stylesheet';
import Stylesheet from './css/Stylesheet';
import { test } from '../config';
import Fragment from './nodes/Fragment';
import shared from './shared';
import { DomTarget } from './dom';
import { SsrTarget } from './ssr';
import { Node, GenerateOptions, ShorthandImport, Ast, CompileOptions, CustomElementOptions } from '../interfaces';
import { Node, ShorthandImport, Ast, CompileOptions, CustomElementOptions } from '../interfaces';
import error from '../utils/error';
import getCodeFrame from '../utils/getCodeFrame';
import checkForComputedKeys from './validate/js/utils/checkForComputedKeys';
@ -101,7 +98,6 @@ export default class Component {
name: string;
options: CompileOptions;
fragment: Fragment;
target: DomTarget | SsrTarget;
customElement: CustomElementOptions;
tag: string;
@ -124,7 +120,6 @@ export default class Component {
hasComponents: boolean;
computations: Computation[];
templateProperties: Record<string, Node>;
slots: Set<string>;
javascript: [string, string];
used: {
@ -142,13 +137,11 @@ export default class Component {
code: MagicString;
bindingGroups: string[];
indirectDependencies: Map<string, Set<string>>;
expectedProperties: Set<string>;
refs: Set<string>;
file: string;
fileVar: string;
locate: (c: number) => { line: number, column: number };
stylesheet: Stylesheet;
@ -168,15 +161,13 @@ export default class Component {
source: string,
name: string,
options: CompileOptions,
stats: Stats,
target: DomTarget | SsrTarget
stats: Stats
) {
this.stats = stats;
this.ast = ast;
this.source = source;
this.options = options;
this.target = target;
this.imports = [];
this.shorthandImports = [];
@ -188,7 +179,6 @@ export default class Component {
this.transitions = new Set();
this.actions = new Set();
this.importedComponents = new Map();
this.slots = new Set();
this.used = {
components: new Set(),
@ -204,7 +194,6 @@ export default class Component {
this.refs = new Set();
this.refCallees = [];
this.bindingGroups = [];
this.indirectDependencies = new Map();
this.file = options.filename && (
@ -229,8 +218,6 @@ export default class Component {
this.aliases = new Map();
this.usedNames = new Set();
this.fileVar = options.dev && this.getUniqueName('file');
this.computations = [];
this.templateProperties = {};
this.properties = new Map();
@ -319,7 +306,11 @@ export default class Component {
return this.aliases.get(name);
}
generate(result: string, options: CompileOptions, { banner = '', name, format }: GenerateOptions ) {
generate(result: string, options: CompileOptions, {
banner = '',
name,
format
}) {
const pattern = /\[✂(\d+)-(\d+)$/;
const helpers = new Set();

@ -2,12 +2,12 @@ import MagicString from 'magic-string';
import { walk } from 'estree-walker';
import { getLocator } from 'locate-character';
import Selector from './Selector';
import getCodeFrame from '../utils/getCodeFrame';
import hash from '../utils/hash';
import removeCSSPrefix from '../utils/removeCSSPrefix';
import Element from '../compile/nodes/Element';
import { Node, Ast, Warning } from '../interfaces';
import Component from '../compile/Component';
import getCodeFrame from '../../utils/getCodeFrame';
import hash from '../../utils/hash';
import removeCSSPrefix from '../../utils/removeCSSPrefix';
import Element from '../nodes/Element';
import { Node, Ast, Warning } from '../../interfaces';
import Component from '../Component';
const isKeyframesNode = (node: Node) => removeCSSPrefix(node.name) === 'keyframes'

@ -1,13 +1,13 @@
import { assign } from '../shared';
import Stats from '../Stats';
import parse from '../parse/index';
import generate, { DomTarget } from './dom/index';
import generateSSR, { SsrTarget } from './ssr/index';
import renderDOM from './render-dom/index';
import renderSSR from './render-ssr/index';
import { CompileOptions, Warning, Ast } from '../interfaces';
import Component from './Component';
function normalize_options(options: CompileOptions): CompileOptions {
let normalized = assign({ generate: 'dom' }, options);
let normalized = assign({ generate: 'dom', dev: false }, options);
const { onwarn, onerror } = normalized;
normalized.onwarn = onwarn
@ -74,10 +74,7 @@ export default function compile(source: string, options: CompileOptions) {
source,
options.name || 'SvelteComponent',
options,
stats,
// TODO make component generator-agnostic, to allow e.g. WebGL generator
options.generate === 'ssr' ? new SsrTarget() : new DomTarget()
stats
);
stats.stop('create component');
@ -85,9 +82,11 @@ export default function compile(source: string, options: CompileOptions) {
return { ast, stats: stats.render(null), js: null, css: null };
}
const compiler = options.generate === 'ssr' ? generateSSR : generate;
if (options.generate === 'ssr') {
return renderSSR(component, options);
}
return compiler(component, options);
return renderDOM(component, options);
} catch (err) {
options.onerror(err);
return;

@ -1,19 +1,11 @@
import deindent from '../../utils/deindent';
import { escape, escapeTemplate, stringify } from '../../utils/stringify';
import fixAttributeCasing from '../../utils/fixAttributeCasing';
import addToSet from '../../utils/addToSet';
import Component from '../Component';
import Node from './shared/Node';
import Element from './Element';
import Text from './Text';
import Block from '../dom/Block';
import Expression from './shared/Expression';
export interface StyleProp {
key: string;
value: Node[];
}
export default class Attribute extends Node {
type: 'Attribute';
start: number;
@ -108,237 +100,6 @@ export default class Attribute extends Node {
: '';
}
render(block: Block) {
const node = this.parent;
const name = fixAttributeCasing(this.name);
if (name === 'style') {
const styleProps = optimizeStyle(this.chunks);
if (styleProps) {
this.renderStyle(block, styleProps);
return;
}
}
let metadata = node.namespace ? null : attributeLookup[name];
if (metadata && metadata.appliesTo && !~metadata.appliesTo.indexOf(node.name))
metadata = null;
const isIndirectlyBoundValue =
name === 'value' &&
(node.name === 'option' || // TODO check it's actually bound
(node.name === 'input' &&
node.bindings.find(
(binding: Binding) =>
/checked|group/.test(binding.name)
)));
const propertyName = isIndirectlyBoundValue
? '__value'
: metadata && metadata.propertyName;
// 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(node.name)
? '@setCustomElementData'
: name.slice(0, 6) === 'xlink:'
? '@setXlinkAttribute'
: '@setAttribute';
const isLegacyInputType = this.component.options.legacy && name === 'type' && this.parent.name === 'input';
const isDataSet = /^data-/.test(name) && !this.component.options.legacy && !node.namespace;
const camelCaseName = isDataSet ? name.replace('data-', '').replace(/(-\w)/g, function (m) {
return m[1].toUpperCase();
}) : name;
if (this.isDynamic) {
let value;
// 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
if (this.chunks.length === 1) {
// single {tag} — may be a non-string
value = this.chunks[0].snippet;
} else {
// '{foo} {bar}' — treat as string concatenation
value =
(this.chunks[0].type === 'Text' ? '' : `"" + `) +
this.chunks
.map((chunk: Node) => {
if (chunk.type === 'Text') {
return stringify(chunk.data);
} else {
return chunk.getPrecedence() <= 13
? `(${chunk.snippet})`
: chunk.snippet;
}
})
.join(' + ');
}
const isSelectValueAttribute =
name === 'value' && node.name === 'select';
const shouldCache = this.shouldCache || isSelectValueAttribute;
const last = shouldCache && block.getUniqueName(
`${node.var}_${name.replace(/[^a-zA-Z_$]/g, '_')}_value`
);
if (shouldCache) block.addVariable(last);
let updater;
const init = shouldCache ? `${last} = ${value}` : value;
if (isLegacyInputType) {
block.builders.hydrate.addLine(
`@setInputType(${node.var}, ${init});`
);
updater = `@setInputType(${node.var}, ${shouldCache ? last : value});`;
} else if (isSelectValueAttribute) {
// annoying special case
const isMultipleSelect = node.getStaticAttributeValue('multiple');
const i = block.getUniqueName('i');
const option = block.getUniqueName('option');
const ifStatement = isMultipleSelect
? deindent`
${option}.selected = ~${last}.indexOf(${option}.__value);`
: deindent`
if (${option}.__value === ${last}) {
${option}.selected = true;
break;
}`;
updater = deindent`
for (var ${i} = 0; ${i} < ${node.var}.options.length; ${i} += 1) {
var ${option} = ${node.var}.options[${i}];
${ifStatement}
}
`;
block.builders.mount.addBlock(deindent`
${last} = ${value};
${updater}
`);
} else if (propertyName) {
block.builders.hydrate.addLine(
`${node.var}.${propertyName} = ${init};`
);
updater = `${node.var}.${propertyName} = ${shouldCache ? last : value};`;
} else if (isDataSet) {
block.builders.hydrate.addLine(
`${node.var}.dataset.${camelCaseName} = ${init};`
);
updater = `${node.var}.dataset.${camelCaseName} = ${shouldCache ? last : value};`;
} else {
block.builders.hydrate.addLine(
`${method}(${node.var}, "${name}", ${init});`
);
updater = `${method}(${node.var}, "${name}", ${shouldCache ? last : value});`;
}
if (this.dependencies.size || isSelectValueAttribute) {
const dependencies = Array.from(this.dependencies);
const changedCheck = (
(block.hasOutros ? `!#current || ` : '') +
dependencies.map(dependency => `changed.${dependency}`).join(' || ')
);
const updateCachedValue = `${last} !== (${last} = ${value})`;
const condition = shouldCache ?
( dependencies.length ? `(${changedCheck}) && ${updateCachedValue}` : updateCachedValue ) :
changedCheck;
block.builders.update.addConditional(
condition,
updater
);
}
} else {
const value = this.getValue();
const statement = (
isLegacyInputType
? `@setInputType(${node.var}, ${value});`
: propertyName
? `${node.var}.${propertyName} = ${value};`
: isDataSet
? `${node.var}.dataset.${camelCaseName} = ${value};`
: `${method}(${node.var}, "${name}", ${value});`
);
block.builders.hydrate.addLine(statement);
// special case autofocus. has to be handled in a bit of a weird way
if (this.isTrue && name === 'autofocus') {
block.autofocus = node.var;
}
}
if (isIndirectlyBoundValue) {
const updateValue = `${node.var}.value = ${node.var}.__value;`;
block.builders.hydrate.addLine(updateValue);
if (this.isDynamic) block.builders.update.addLine(updateValue);
}
}
renderStyle(
block: Block,
styleProps: StyleProp[]
) {
styleProps.forEach((prop: StyleProp) => {
let value;
if (isDynamic(prop.value)) {
const propDependencies = new Set();
let shouldCache;
value =
((prop.value.length === 1 || prop.value[0].type === 'Text') ? '' : `"" + `) +
prop.value
.map((chunk: Node) => {
if (chunk.type === 'Text') {
return stringify(chunk.data);
} else {
const { dependencies, snippet } = chunk;
dependencies.forEach(d => {
propDependencies.add(d);
});
return chunk.getPrecedence() <= 13 ? `(${snippet})` : snippet;
}
})
.join(' + ');
if (propDependencies.size) {
const dependencies = Array.from(propDependencies);
const condition = (
(block.hasOutros ? `!#current || ` : '') +
dependencies.map(dependency => `changed.${dependency}`).join(' || ')
);
block.builders.update.addConditional(
condition,
`@setStyle(${this.parent.var}, "${prop.key}", ${value});`
);
}
} else {
value = stringify(prop.value[0].data);
}
block.builders.hydrate.addLine(
`@setStyle(${this.parent.var}, "${prop.key}", ${value});`
);
});
}
stringifyForSsr() {
return this.chunks
.map((chunk: Node) => {
@ -350,353 +111,4 @@ export default class Attribute extends Node {
})
.join('');
}
}
// 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'] },
align: {
appliesTo: [
'applet',
'caption',
'col',
'colgroup',
'hr',
'iframe',
'img',
'table',
'tbody',
'td',
'tfoot',
'th',
'thead',
'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'] },
bgcolor: {
propertyName: 'bgColor',
appliesTo: [
'body',
'col',
'colgroup',
'marquee',
'table',
'tbody',
'tfoot',
'td',
'th',
'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' },
contextmenu: {},
controls: { appliesTo: ['audio', 'video'] },
coords: { appliesTo: ['area'] },
data: { appliesTo: ['object'] },
datetime: { propertyName: 'dateTime', appliesTo: ['del', 'ins', 'time'] },
default: { appliesTo: ['track'] },
defer: { appliesTo: ['script'] },
dir: {},
dirname: { propertyName: 'dirName', appliesTo: ['input', 'textarea'] },
disabled: {
appliesTo: [
'button',
'command',
'fieldset',
'input',
'keygen',
'optgroup',
'option',
'select',
'textarea',
],
},
download: { appliesTo: ['a', 'area'] },
draggable: {},
dropzone: {},
enctype: { appliesTo: ['form'] },
for: { propertyName: 'htmlFor', appliesTo: ['label', 'output'] },
form: {
appliesTo: [
'button',
'fieldset',
'input',
'keygen',
'label',
'meter',
'object',
'output',
'progress',
'select',
'textarea',
],
},
formaction: { appliesTo: ['input', 'button'] },
headers: { appliesTo: ['td', 'th'] },
height: {
appliesTo: ['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'] },
id: {},
indeterminate: { appliesTo: ['input'] },
ismap: { propertyName: 'isMap', appliesTo: ['img'] },
itemprop: {},
keytype: { appliesTo: ['keygen'] },
kind: { appliesTo: ['track'] },
label: { appliesTo: ['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'] },
name: {
appliesTo: [
'button',
'form',
'fieldset',
'iframe',
'input',
'keygen',
'object',
'output',
'select',
'textarea',
'map',
'meta',
'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'] },
spellcheck: {},
src: {
appliesTo: [
'audio',
'embed',
'iframe',
'img',
'input',
'script',
'source',
'track',
'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'] },
title: {},
type: {
appliesTo: [
'button',
'command',
'embed',
'object',
'script',
'source',
'style',
'menu',
],
},
usemap: { propertyName: 'useMap', appliesTo: ['img', 'input', 'object'] },
value: {
appliesTo: [
'button',
'option',
'input',
'li',
'meter',
'progress',
'param',
'select',
'textarea',
],
},
volume: { appliesTo: ['audio', 'video'] },
width: {
appliesTo: ['canvas', 'embed', 'iframe', 'img', 'input', 'object', 'video'],
},
wrap: { appliesTo: ['textarea'] },
};
Object.keys(attributeLookup).forEach(name => {
const metadata = attributeLookup[name];
if (!metadata.propertyName) metadata.propertyName = name;
});
function optimizeStyle(value: Node[]) {
let expectingKey = true;
let i = 0;
const props: { key: string, value: Node[] }[] = [];
let chunks = value.slice();
while (chunks.length) {
const chunk = chunks[0];
if (chunk.type !== 'Text') return null;
const keyMatch = /^\s*([\w-]+):\s*/.exec(chunk.data);
if (!keyMatch) return null;
const key = keyMatch[1];
const offset = keyMatch.index + keyMatch[0].length;
const remainingData = chunk.data.slice(offset);
if (remainingData) {
chunks[0] = {
start: chunk.start + offset,
end: chunk.end,
type: 'Text',
data: remainingData
};
} else {
chunks.shift();
}
const result = getStyleValue(chunks);
if (!result) return null;
props.push({ key, value: result.value });
chunks = result.chunks;
}
return props;
}
function getStyleValue(chunks: Node[]) {
const value: Node[] = [];
let inUrl = false;
let quoteMark = null;
let escaped = false;
while (chunks.length) {
const chunk = chunks.shift();
if (chunk.type === 'Text') {
let c = 0;
while (c < chunk.data.length) {
const char = chunk.data[c];
if (escaped) {
escaped = false;
} else if (char === '\\') {
escaped = true;
} else if (char === quoteMark) {
quoteMark === null;
} else if (char === '"' || char === "'") {
quoteMark = char;
} else if (char === ')' && inUrl) {
inUrl = false;
} else if (char === 'u' && chunk.data.slice(c, c + 4) === 'url(') {
inUrl = true;
} else if (char === ';' && !inUrl && !quoteMark) {
break;
}
c += 1;
}
if (c > 0) {
value.push({
type: 'Text',
start: chunk.start,
end: chunk.start + c,
data: chunk.data.slice(0, c)
});
}
while (/[;\s]/.test(chunk.data[c])) c += 1;
const remainingData = chunk.data.slice(c);
if (remainingData) {
chunks.unshift({
start: chunk.start + c,
end: chunk.end,
type: 'Text',
data: remainingData
});
break;
}
}
else {
value.push(chunk);
}
}
return {
chunks,
value
};
}
function isDynamic(value: Node[]) {
return value.length > 1 || value[0].type !== 'Text';
}
}

@ -1,12 +1,8 @@
import deindent from '../../utils/deindent';
import Node from './shared/Node';
import Block from '../dom/Block';
import PendingBlock from './PendingBlock';
import ThenBlock from './ThenBlock';
import CatchBlock from './CatchBlock';
import createDebuggingComment from '../../utils/createDebuggingComment';
import Expression from './shared/Expression';
import { SsrTarget } from '../ssr';
export default class AwaitBlock extends Node {
expression: Expression;
@ -30,187 +26,4 @@ export default class AwaitBlock extends Node {
this.then = new ThenBlock(component, this, scope.add(this.value, deps), info.then);
this.catch = new CatchBlock(component, this, scope.add(this.error, deps), info.catch);
}
init(
block: Block,
stripWhitespace: boolean,
nextSibling: Node
) {
this.cannotUseInnerHTML();
this.var = block.getUniqueName('await_block');
block.addDependencies(this.expression.dependencies);
let isDynamic = false;
let hasIntros = false;
let hasOutros = false;
['pending', 'then', 'catch'].forEach(status => {
const child = this[status];
child.block = block.child({
comment: createDebuggingComment(child, this.component),
name: this.component.getUniqueName(`create_${status}_block`)
});
child.initChildren(child.block, stripWhitespace, nextSibling);
this.component.target.blocks.push(child.block);
if (child.block.dependencies.size > 0) {
isDynamic = true;
block.addDependencies(child.block.dependencies);
}
if (child.block.hasIntros) hasIntros = true;
if (child.block.hasOutros) hasOutros = true;
});
this.pending.block.hasUpdateMethod = isDynamic;
this.then.block.hasUpdateMethod = isDynamic;
this.catch.block.hasUpdateMethod = isDynamic;
this.pending.block.hasIntroMethod = hasIntros;
this.then.block.hasIntroMethod = hasIntros;
this.catch.block.hasIntroMethod = hasIntros;
this.pending.block.hasOutroMethod = hasOutros;
this.then.block.hasOutroMethod = hasOutros;
this.catch.block.hasOutroMethod = hasOutros;
if (hasOutros && this.component.options.nestedTransitions) block.addOutro();
}
build(
block: Block,
parentNode: string,
parentNodes: string
) {
const name = this.var;
const anchor = this.getOrCreateAnchor(block, parentNode, parentNodes);
const updateMountNode = this.getUpdateMountNode(anchor);
const { snippet } = this.expression;
const info = block.getUniqueName(`info`);
const promise = block.getUniqueName(`promise`);
block.addVariable(promise);
block.maintainContext = true;
const infoProps = [
block.alias('component') === 'component' ? 'component' : `component: #component`,
'ctx',
'current: null',
this.pending.block.name && `pending: ${this.pending.block.name}`,
this.then.block.name && `then: ${this.then.block.name}`,
this.catch.block.name && `catch: ${this.catch.block.name}`,
this.then.block.name && `value: '${this.value}'`,
this.catch.block.name && `error: '${this.error}'`,
this.pending.block.hasOutroMethod && `blocks: Array(3)`
].filter(Boolean);
block.builders.init.addBlock(deindent`
let ${info} = {
${infoProps.join(',\n')}
};
`);
block.builders.init.addBlock(deindent`
@handlePromise(${promise} = ${snippet}, ${info});
`);
block.builders.create.addBlock(deindent`
${info}.block.c();
`);
if (parentNodes) {
block.builders.claim.addBlock(deindent`
${info}.block.l(${parentNodes});
`);
}
const initialMountNode = parentNode || '#target';
const anchorNode = parentNode ? 'null' : 'anchor';
const hasTransitions = this.pending.block.hasIntroMethod || this.pending.block.hasOutroMethod;
block.builders.mount.addBlock(deindent`
${info}.block.${hasTransitions ? 'i' : 'm'}(${initialMountNode}, ${info}.anchor = ${anchorNode});
${info}.mount = () => ${updateMountNode};
`);
const conditions = [];
if (this.expression.dependencies.size > 0) {
conditions.push(
`(${[...this.expression.dependencies].map(dep => `'${dep}' in changed`).join(' || ')})`
);
}
conditions.push(
`${promise} !== (${promise} = ${snippet})`,
`@handlePromise(${promise}, ${info})`
);
block.builders.update.addLine(
`${info}.ctx = ctx;`
);
if (this.pending.block.hasUpdateMethod) {
block.builders.update.addBlock(deindent`
if (${conditions.join(' && ')}) {
// nothing
} else {
${info}.block.p(changed, @assign(@assign({}, ctx), ${info}.resolved));
}
`);
} else {
block.builders.update.addBlock(deindent`
${conditions.join(' && ')}
`);
}
if (this.pending.block.hasOutroMethod && this.component.options.nestedTransitions) {
const countdown = block.getUniqueName('countdown');
block.builders.outro.addBlock(deindent`
const ${countdown} = @callAfter(#outrocallback, 3);
for (let #i = 0; #i < 3; #i += 1) {
const block = ${info}.blocks[#i];
if (block) block.o(${countdown});
else ${countdown}();
}
`);
}
block.builders.destroy.addBlock(deindent`
${info}.block.d(${parentNode ? '' : 'detach'});
${info} = null;
`);
[this.pending, this.then, this.catch].forEach(status => {
status.children.forEach(child => {
child.build(status.block, null, 'nodes');
});
});
}
ssr() {
const target: SsrTarget = <SsrTarget>this.component.target;
const { snippet } = this.expression;
target.append('${(function(__value) { if(@isPromise(__value)) return `');
this.pending.children.forEach((child: Node) => {
child.ssr();
});
target.append('`; return function(ctx) { return `');
this.then.children.forEach((child: Node) => {
child.ssr();
});
target.append(`\`;}(Object.assign({}, ctx, { ${this.value}: __value }));}(${snippet})) }`);
}
}

@ -1,22 +1,6 @@
import Node from './shared/Node';
import Element from './Element';
import getObject from '../../utils/getObject';
import getTailSnippet from '../../utils/getTailSnippet';
import flattenReference from '../../utils/flattenReference';
import Component from '../Component';
import Block from '../dom/Block';
import Expression from './shared/Expression';
import { dimensions } from '../../utils/patterns';
const readOnlyMediaAttributes = new Set([
'duration',
'buffered',
'seekable',
'played'
]);
// TODO a lot of this element-specific stuff should live in Element —
// Binding should ideally be agnostic between Element and InlineComponent
export default class Binding extends Node {
name: string;
@ -54,263 +38,4 @@ export default class Binding extends Node {
this.obj = obj;
this.prop = prop;
}
munge(
block: Block
) {
const node: Element = this.parent;
const needsLock = node.name !== 'input' || !/radio|checkbox|range|color/.test(node.getStaticAttributeValue('type'));
const isReadOnly = (
(node.isMediaNode() && readOnlyMediaAttributes.has(this.name)) ||
dimensions.test(this.name)
);
let updateConditions: string[] = [];
const { name } = getObject(this.value.node);
const { snippet } = this.value;
// special case: if you have e.g. `<input type=checkbox bind:checked=selected.done>`
// and `selected` is an object chosen with a <select>, then when `checked` changes,
// we need to tell the component to update all the values `selected` might be
// pointing to
// TODO should this happen in preprocess?
const dependencies = new Set(this.value.dependencies);
this.value.dependencies.forEach((prop: string) => {
const indirectDependencies = this.component.indirectDependencies.get(prop);
if (indirectDependencies) {
indirectDependencies.forEach(indirectDependency => {
dependencies.add(indirectDependency);
});
}
});
// view to model
const valueFromDom = getValueFromDom(this.component, node, this);
const handler = getEventHandler(this, this.component, block, name, snippet, dependencies, valueFromDom);
// model to view
let updateDom = getDomUpdater(node, this, snippet);
let initialUpdate = updateDom;
// special cases
if (this.name === 'group') {
const bindingGroup = getBindingGroup(this.component, this.value.node);
block.builders.hydrate.addLine(
`#component._bindingGroups[${bindingGroup}].push(${node.var});`
);
block.builders.destroy.addLine(
`#component._bindingGroups[${bindingGroup}].splice(#component._bindingGroups[${bindingGroup}].indexOf(${node.var}), 1);`
);
}
if (this.name === 'currentTime' || this.name === 'volume') {
updateConditions.push(`!isNaN(${snippet})`);
if (this.name === 'currentTime') initialUpdate = null;
}
if (this.name === 'paused') {
// this is necessary to prevent audio restarting by itself
const last = block.getUniqueName(`${node.var}_is_paused`);
block.addVariable(last, 'true');
updateConditions.push(`${last} !== (${last} = ${snippet})`);
updateDom = `${node.var}[${last} ? "pause" : "play"]();`;
initialUpdate = null;
}
// bind:offsetWidth and bind:offsetHeight
if (dimensions.test(this.name)) {
initialUpdate = null;
updateDom = null;
}
const dependencyArray = [...this.value.dependencies]
if (dependencyArray.length === 1) {
updateConditions.push(`changed.${dependencyArray[0]}`)
} else if (dependencyArray.length > 1) {
updateConditions.push(
`(${dependencyArray.map(prop => `changed.${prop}`).join(' || ')})`
)
}
return {
name: this.name,
object: name,
handler,
updateDom,
initialUpdate,
needsLock: !isReadOnly && needsLock,
updateCondition: updateConditions.length ? updateConditions.join(' && ') : undefined,
isReadOnlyMediaAttribute: this.isReadOnlyMediaAttribute()
};
}
isReadOnlyMediaAttribute() {
return readOnlyMediaAttributes.has(this.name);
}
}
function getDomUpdater(
node: Element,
binding: Binding,
snippet: string
) {
if (binding.isReadOnlyMediaAttribute()) {
return null;
}
if (node.name === 'select') {
return node.getStaticAttributeValue('multiple') === true ?
`@selectOptions(${node.var}, ${snippet})` :
`@selectOption(${node.var}, ${snippet})`;
}
if (binding.name === 'group') {
const type = node.getStaticAttributeValue('type');
const condition = type === 'checkbox'
? `~${snippet}.indexOf(${node.var}.__value)`
: `${node.var}.__value === ${snippet}`;
return `${node.var}.checked = ${condition};`
}
return `${node.var}.${binding.name} = ${snippet};`;
}
function getBindingGroup(component: Component, value: Node) {
const { parts } = flattenReference(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 = component.bindingGroups.indexOf(keypath);
if (index === -1) {
index = component.bindingGroups.length;
component.bindingGroups.push(keypath);
}
return index;
}
function getEventHandler(
binding: Binding,
component: Component,
block: Block,
name: string,
snippet: string,
dependencies: Set<string>,
value: string
) {
const storeDependencies = [...dependencies].filter(prop => prop[0] === '$').map(prop => prop.slice(1));
let dependenciesArray = [...dependencies].filter(prop => prop[0] !== '$');
if (binding.isContextual) {
const tail = binding.value.node.type === 'MemberExpression'
? getTailSnippet(binding.value.node)
: '';
const head = block.bindings.get(name);
return {
usesContext: true,
usesState: true,
usesStore: storeDependencies.length > 0,
mutation: `${head}${tail} = ${value};`,
props: dependenciesArray.map(prop => `${prop}: ctx.${prop}`),
storeProps: storeDependencies.map(prop => `${prop}: $.${prop}`)
};
}
if (binding.value.node.type === 'MemberExpression') {
// This is a little confusing, and should probably be tidied up
// at some point. It addresses a tricky bug (#893), wherein
// Svelte tries to `set()` a computed property, which throws an
// error in dev mode. a) it's possible that we should be
// replacing computations with *their* dependencies, and b)
// we should probably populate `component.target.readonly` sooner so
// that we don't have to do the `.some()` here
dependenciesArray = dependenciesArray.filter(prop => !component.computations.some(computation => computation.key === prop));
return {
usesContext: false,
usesState: true,
usesStore: storeDependencies.length > 0,
mutation: `${snippet} = ${value}`,
props: dependenciesArray.map((prop: string) => `${prop}: ctx.${prop}`),
storeProps: storeDependencies.map(prop => `${prop}: $.${prop}`)
};
}
let props;
let storeProps;
if (name[0] === '$') {
props = [];
storeProps = [`${name.slice(1)}: ${value}`];
} else {
props = [`${name}: ${value}`];
storeProps = [];
}
return {
usesContext: false,
usesState: false,
usesStore: false,
mutation: null,
props,
storeProps
};
}
function getValueFromDom(
component: Component,
node: Element,
binding: Node
) {
// <select bind:value='selected>
if (node.name === 'select') {
return node.getStaticAttributeValue('multiple') === true ?
`@selectMultipleValue(${node.var})` :
`@selectValue(${node.var})`;
}
const type = node.getStaticAttributeValue('type');
// <input type='checkbox' bind:group='foo'>
if (binding.name === 'group') {
const bindingGroup = getBindingGroup(component, binding.value.node);
if (type === 'checkbox') {
return `@getBindingGroupValue(#component._bindingGroups[${bindingGroup}])`;
}
return `${node.var}.__value`;
}
// <input type='range|number' bind:value>
if (type === 'range' || type === 'number') {
return `@toNumber(${node.var}.${binding.name})`;
}
if ((binding.name === 'buffered' || binding.name === 'seekable' || binding.name === 'played')) {
return `@timeRangesToArray(${node.var}.${binding.name})`
}
// everything else
return `${node.var}.${binding.name}`;
}
function isComputed(node: Node) {
while (node.type === 'MemberExpression') {
if (node.computed) return true;
node = node.object;
}
return false;
}

@ -1,5 +1,5 @@
import Node from './shared/Node';
import Block from '../dom/Block';
import Block from '../render-dom/Block';
import mapChildren from './shared/mapChildren';
export default class CatchBlock extends Node {

@ -8,11 +8,4 @@ export default class Comment extends Node {
super(component, parent, scope, info);
this.data = info.data;
}
ssr() {
// Allow option to preserve comments, otherwise ignore
if (this.component.options.preserveComments) {
this.component.target.append(`<!--${this.data}-->`);
}
}
}

@ -1,10 +1,5 @@
import Node from './shared/Node';
import Tag from './shared/Tag';
import Block from '../dom/Block';
import Expression from './shared/Expression';
import deindent from '../../utils/deindent';
import addToSet from '../../utils/addToSet';
import { stringify } from '../../utils/stringify';
export default class DebugTag extends Node {
expressions: Expression[];
@ -16,74 +11,4 @@ export default class DebugTag extends Node {
return new Expression(component, parent, scope, node);
});
}
build(
block: Block,
parentNode: string,
parentNodes: string,
) {
if (!this.component.options.dev) return;
const { code } = this.component;
if (this.expressions.length === 0) {
// Debug all
code.overwrite(this.start + 1, this.start + 7, 'debugger', {
storeName: true
});
const statement = `[✂${this.start + 1}-${this.start + 7}✂];`;
block.builders.create.addLine(statement);
block.builders.update.addLine(statement);
} else {
const { code } = this.component;
code.overwrite(this.start + 1, this.start + 7, 'log', {
storeName: true
});
const log = `[✂${this.start + 1}-${this.start + 7}✂]`;
const dependencies = new Set();
this.expressions.forEach(expression => {
addToSet(dependencies, expression.dependencies);
});
const condition = [...dependencies].map(d => `changed.${d}`).join(' || ');
const identifiers = this.expressions.map(e => e.node.name).join(', ');
block.builders.update.addBlock(deindent`
if (${condition}) {
const { ${identifiers} } = ctx;
console.${log}({ ${identifiers} });
debugger;
}
`);
block.builders.create.addBlock(deindent`
{
const { ${identifiers} } = ctx;
console.${log}({ ${identifiers} });
debugger;
}
`);
}
}
ssr() {
if (!this.component.options.dev) return;
const filename = this.component.file || null;
const { line, column } = this.component.locate(this.start + 1);
const obj = this.expressions.length === 0
? `ctx`
: `{ ${this.expressions
.map(e => e.node.name)
.map(name => `${name}: ctx.${name}`)
.join(', ')} }`;
const str = '${@debug(' + `${filename && stringify(filename)}, ${line}, ${column}, ${obj})}`;
this.component.target.append(str);
}
}

@ -1,8 +1,6 @@
import deindent from '../../utils/deindent';
import Node from './shared/Node';
import ElseBlock from './ElseBlock';
import Block from '../dom/Block';
import createDebuggingComment from '../../utils/createDebuggingComment';
import Block from '../render-dom/Block';
import Expression from './shared/Expression';
import mapChildren from './shared/mapChildren';
import TemplateScope from './shared/TemplateScope';
@ -78,472 +76,4 @@ export default class EachBlock extends Node {
? new ElseBlock(component, this, this.scope, info.else)
: null;
}
init(
block: Block,
stripWhitespace: boolean,
nextSibling: Node
) {
this.cannotUseInnerHTML();
this.var = block.getUniqueName(`each`);
this.iterations = block.getUniqueName(`${this.var}_blocks`);
this.get_each_context = this.component.getUniqueName(`get_${this.var}_context`);
const { dependencies } = this.expression;
block.addDependencies(dependencies);
this.block = block.child({
comment: createDebuggingComment(this, this.component),
name: this.component.getUniqueName('create_each_block'),
key: this.key,
bindings: new Map(block.bindings)
});
this.each_block_value = this.component.getUniqueName('each_value');
const indexName = this.index || this.component.getUniqueName(`${this.context}_index`);
this.contexts.forEach(prop => {
this.block.bindings.set(prop.key.name, `ctx.${this.each_block_value}[ctx.${indexName}]${prop.tail}`);
});
if (this.index) {
this.block.getUniqueName(this.index); // this prevents name collisions (#1254)
}
this.contextProps = this.contexts.map(prop => `child_ctx.${prop.key.name} = list[i]${prop.tail};`);
// TODO only add these if necessary
this.contextProps.push(
`child_ctx.${this.each_block_value} = list;`,
`child_ctx.${indexName} = i;`
);
this.component.target.blocks.push(this.block);
this.initChildren(this.block, stripWhitespace, nextSibling);
block.addDependencies(this.block.dependencies);
this.block.hasUpdateMethod = this.block.dependencies.size > 0;
if (this.else) {
this.else.block = block.child({
comment: createDebuggingComment(this.else, this.component),
name: this.component.getUniqueName(`${this.block.name}_else`),
});
this.component.target.blocks.push(this.else.block);
this.else.initChildren(
this.else.block,
stripWhitespace,
nextSibling
);
this.else.block.hasUpdateMethod = this.else.block.dependencies.size > 0;
}
if (this.block.hasOutros || (this.else && this.else.block.hasOutros)) {
block.addOutro();
}
}
build(
block: Block,
parentNode: string,
parentNodes: string
) {
if (this.children.length === 0) return;
const { component } = this;
const each = this.var;
const create_each_block = this.block.name;
const iterations = this.iterations;
const needsAnchor = this.next ? !this.next.isDomNode() : !parentNode || !this.parent.isDomNode();
const anchor = needsAnchor
? block.getUniqueName(`${each}_anchor`)
: (this.next && this.next.var) || 'null';
// hack the sourcemap, so that if data is missing the bug
// is easy to find
let c = this.start + 2;
while (component.source[c] !== 'e') c += 1;
component.code.overwrite(c, c + 4, 'length');
const length = `[✂${c}-${c+4}✂]`;
const mountOrIntro = (this.block.hasIntroMethod || this.block.hasOutroMethod) ? 'i' : 'm';
const vars = {
each,
create_each_block,
length,
iterations,
anchor,
mountOrIntro,
};
const { snippet } = this.expression;
block.builders.init.addLine(`var ${this.each_block_value} = ${snippet};`);
this.component.target.blocks.push(deindent`
function ${this.get_each_context}(ctx, list, i) {
const child_ctx = Object.create(ctx);
${this.contextProps}
return child_ctx;
}
`);
if (this.key) {
this.buildKeyed(block, parentNode, parentNodes, snippet, vars);
} else {
this.buildUnkeyed(block, parentNode, parentNodes, snippet, vars);
}
if (needsAnchor) {
block.addElement(
anchor,
`@createComment()`,
parentNodes && `@createComment()`,
parentNode
);
}
if (this.else) {
const each_block_else = component.getUniqueName(`${each}_else`);
const mountOrIntro = (this.else.block.hasIntroMethod || this.else.block.hasOutroMethod) ? 'i' : 'm';
block.builders.init.addLine(`var ${each_block_else} = null;`);
// TODO neaten this up... will end up with an empty line in the block
block.builders.init.addBlock(deindent`
if (!${this.each_block_value}.${length}) {
${each_block_else} = ${this.else.block.name}(#component, ctx);
${each_block_else}.c();
}
`);
block.builders.mount.addBlock(deindent`
if (${each_block_else}) {
${each_block_else}.${mountOrIntro}(${parentNode || '#target'}, null);
}
`);
const initialMountNode = parentNode || `${anchor}.parentNode`;
if (this.else.block.hasUpdateMethod) {
block.builders.update.addBlock(deindent`
if (!${this.each_block_value}.${length} && ${each_block_else}) {
${each_block_else}.p(changed, ctx);
} else if (!${this.each_block_value}.${length}) {
${each_block_else} = ${this.else.block.name}(#component, ctx);
${each_block_else}.c();
${each_block_else}.${mountOrIntro}(${initialMountNode}, ${anchor});
} else if (${each_block_else}) {
${each_block_else}.d(1);
${each_block_else} = null;
}
`);
} else {
block.builders.update.addBlock(deindent`
if (${this.each_block_value}.${length}) {
if (${each_block_else}) {
${each_block_else}.d(1);
${each_block_else} = null;
}
} else if (!${each_block_else}) {
${each_block_else} = ${this.else.block.name}(#component, ctx);
${each_block_else}.c();
${each_block_else}.${mountOrIntro}(${initialMountNode}, ${anchor});
}
`);
}
block.builders.destroy.addBlock(deindent`
if (${each_block_else}) ${each_block_else}.d(${parentNode ? '' : 'detach'});
`);
}
this.children.forEach((child: Node) => {
child.build(this.block, null, 'nodes');
});
if (this.else) {
this.else.children.forEach((child: Node) => {
child.build(this.else.block, null, 'nodes');
});
}
}
buildKeyed(
block: Block,
parentNode: string,
parentNodes: string,
snippet: string,
{
each,
create_each_block,
length,
anchor,
mountOrIntro,
}
) {
const get_key = block.getUniqueName('get_key');
const blocks = block.getUniqueName(`${each}_blocks`);
const lookup = block.getUniqueName(`${each}_lookup`);
block.addVariable(blocks, '[]');
block.addVariable(lookup, `@blankObject()`);
if (this.children[0].isDomNode()) {
this.block.first = this.children[0].var;
} else {
this.block.first = this.block.getUniqueName('first');
this.block.addElement(
this.block.first,
`@createComment()`,
parentNodes && `@createComment()`,
null
);
}
block.builders.init.addBlock(deindent`
const ${get_key} = ctx => ${this.key.snippet};
for (var #i = 0; #i < ${this.each_block_value}.${length}; #i += 1) {
let child_ctx = ${this.get_each_context}(ctx, ${this.each_block_value}, #i);
let key = ${get_key}(child_ctx);
${blocks}[#i] = ${lookup}[key] = ${create_each_block}(#component, key, child_ctx);
}
`);
const initialMountNode = parentNode || '#target';
const updateMountNode = this.getUpdateMountNode(anchor);
const anchorNode = parentNode ? 'null' : 'anchor';
block.builders.create.addBlock(deindent`
for (#i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].c();
`);
if (parentNodes) {
block.builders.claim.addBlock(deindent`
for (#i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].l(${parentNodes});
`);
}
block.builders.mount.addBlock(deindent`
for (#i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].${mountOrIntro}(${initialMountNode}, ${anchorNode});
`);
const dynamic = this.block.hasUpdateMethod;
const rects = block.getUniqueName('rects');
const destroy = this.block.hasAnimation
? `@fixAndOutroAndDestroyBlock`
: this.block.hasOutros
? `@outroAndDestroyBlock`
: `@destroyBlock`;
block.builders.update.addBlock(deindent`
const ${this.each_block_value} = ${snippet};
${this.block.hasOutros && `@groupOutros();`}
${this.block.hasAnimation && `for (let #i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].r();`}
${blocks} = @updateKeyedEach(${blocks}, #component, changed, ${get_key}, ${dynamic ? '1' : '0'}, ctx, ${this.each_block_value}, ${lookup}, ${updateMountNode}, ${destroy}, ${create_each_block}, "${mountOrIntro}", ${anchor}, ${this.get_each_context});
${this.block.hasAnimation && `for (let #i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].a();`}
`);
if (this.block.hasOutros && this.component.options.nestedTransitions) {
const countdown = block.getUniqueName('countdown');
block.builders.outro.addBlock(deindent`
const ${countdown} = @callAfter(#outrocallback, ${blocks}.length);
for (#i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].o(${countdown});
`);
}
block.builders.destroy.addBlock(deindent`
for (#i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].d(${parentNode ? '' : 'detach'});
`);
}
buildUnkeyed(
block: Block,
parentNode: string,
parentNodes: string,
snippet: string,
{
create_each_block,
length,
iterations,
anchor,
mountOrIntro,
}
) {
block.builders.init.addBlock(deindent`
var ${iterations} = [];
for (var #i = 0; #i < ${this.each_block_value}.${length}; #i += 1) {
${iterations}[#i] = ${create_each_block}(#component, ${this.get_each_context}(ctx, ${this.each_block_value}, #i));
}
`);
const initialMountNode = parentNode || '#target';
const updateMountNode = this.getUpdateMountNode(anchor);
const anchorNode = parentNode ? 'null' : 'anchor';
block.builders.create.addBlock(deindent`
for (var #i = 0; #i < ${iterations}.length; #i += 1) {
${iterations}[#i].c();
}
`);
if (parentNodes) {
block.builders.claim.addBlock(deindent`
for (var #i = 0; #i < ${iterations}.length; #i += 1) {
${iterations}[#i].l(${parentNodes});
}
`);
}
block.builders.mount.addBlock(deindent`
for (var #i = 0; #i < ${iterations}.length; #i += 1) {
${iterations}[#i].${mountOrIntro}(${initialMountNode}, ${anchorNode});
}
`);
const allDependencies = new Set(this.block.dependencies);
const { dependencies } = this.expression;
dependencies.forEach((dependency: string) => {
allDependencies.add(dependency);
});
const outroBlock = this.block.hasOutros && block.getUniqueName('outroBlock')
if (outroBlock) {
block.builders.init.addBlock(deindent`
function ${outroBlock}(i, detach, fn) {
if (${iterations}[i]) {
${iterations}[i].o(() => {
if (detach) {
${iterations}[i].d(detach);
${iterations}[i] = null;
}
if (fn) fn();
});
}
}
`);
}
// TODO do this for keyed blocks as well
const condition = Array.from(allDependencies)
.map(dependency => `changed.${dependency}`)
.join(' || ');
if (condition !== '') {
const forLoopBody = this.block.hasUpdateMethod
? (this.block.hasIntros || this.block.hasOutros)
? deindent`
if (${iterations}[#i]) {
${iterations}[#i].p(changed, child_ctx);
} else {
${iterations}[#i] = ${create_each_block}(#component, child_ctx);
${iterations}[#i].c();
}
${iterations}[#i].i(${updateMountNode}, ${anchor});
`
: deindent`
if (${iterations}[#i]) {
${iterations}[#i].p(changed, child_ctx);
} else {
${iterations}[#i] = ${create_each_block}(#component, child_ctx);
${iterations}[#i].c();
${iterations}[#i].m(${updateMountNode}, ${anchor});
}
`
: deindent`
${iterations}[#i] = ${create_each_block}(#component, child_ctx);
${iterations}[#i].c();
${iterations}[#i].${mountOrIntro}(${updateMountNode}, ${anchor});
`;
const start = this.block.hasUpdateMethod ? '0' : `${iterations}.length`;
let destroy;
if (this.block.hasOutros) {
destroy = deindent`
@groupOutros();
for (; #i < ${iterations}.length; #i += 1) ${outroBlock}(#i, 1);
`;
} else {
destroy = deindent`
for (; #i < ${iterations}.length; #i += 1) {
${iterations}[#i].d(1);
}
${iterations}.length = ${this.each_block_value}.${length};
`;
}
block.builders.update.addBlock(deindent`
if (${condition}) {
${this.each_block_value} = ${snippet};
for (var #i = ${start}; #i < ${this.each_block_value}.${length}; #i += 1) {
const child_ctx = ${this.get_each_context}(ctx, ${this.each_block_value}, #i);
${forLoopBody}
}
${destroy}
}
`);
}
if (outroBlock && this.component.options.nestedTransitions) {
const countdown = block.getUniqueName('countdown');
block.builders.outro.addBlock(deindent`
${iterations} = ${iterations}.filter(Boolean);
const ${countdown} = @callAfter(#outrocallback, ${iterations}.length);
for (let #i = 0; #i < ${iterations}.length; #i += 1) ${outroBlock}(#i, 0, ${countdown});`
);
}
block.builders.destroy.addBlock(`@destroyEach(${iterations}, detach);`);
}
remount(name: string) {
// TODO consider keyed blocks
return `for (var #i = 0; #i < ${this.iterations}.length; #i += 1) ${this.iterations}[#i].m(${name}._slotted.default, null);`;
}
ssr() {
const { component } = this;
const { snippet } = this.expression;
const props = this.contexts.map(prop => `${prop.key.name}: item${prop.tail}`);
const getContext = this.index
? `(item, i) => Object.assign({}, ctx, { ${props.join(', ')}, ${this.index}: i })`
: `item => Object.assign({}, ctx, { ${props.join(', ')} })`;
const open = `\${ ${this.else ? `${snippet}.length ? ` : ''}@each(${snippet}, ${getContext}, ctx => \``;
component.target.append(open);
this.children.forEach((child: Node) => {
child.ssr();
});
const close = `\`)`;
component.target.append(close);
if (this.else) {
component.target.append(` : \``);
this.else.children.forEach((child: Node) => {
child.ssr();
});
component.target.append(`\``);
}
component.target.append('}');
}
}

File diff suppressed because it is too large Load Diff

@ -1,5 +1,5 @@
import Node from './shared/Node';
import Block from '../dom/Block';
import Block from '../render-dom/Block';
import mapChildren from './shared/mapChildren';
export default class ElseBlock extends Node {

@ -63,7 +63,7 @@ export default class EventHandler extends Node {
this.shouldHoist = !this.isCustomEvent && parent.hasAncestor('EachBlock');
}
render(component, block, hoisted) { // TODO hoist more event handlers
render(component, block, context, hoisted) { // TODO hoist more event handlers
if (this.insertionPoint === null) return; // TODO handle shorthand events here?
if (!validCalleeObjects.has(this.callee.name)) {
@ -87,12 +87,12 @@ export default class EventHandler extends Node {
if (this.isCustomEvent) {
this.args.forEach(arg => {
arg.overwriteThis(this.parent.var);
arg.overwriteThis(context);
});
if (this.callee && this.callee.name === 'this') {
const node = this.callee.nodes[0];
component.code.overwrite(node.start, node.end, this.parent.var, {
component.code.overwrite(node.start, node.end, context, {
storeName: true,
contentOnly: true
});

@ -1,7 +1,7 @@
import Node from './shared/Node';
import Component from '../Component';
import mapChildren from './shared/mapChildren';
import Block from '../dom/Block';
import Block from '../render-dom/Block';
import TemplateScope from './shared/TemplateScope';
export default class Fragment extends Node {
@ -16,29 +16,4 @@ export default class Fragment extends Node {
this.scope = scope;
this.children = mapChildren(component, this, scope, info.children);
}
init() {
this.block = new Block({
component: this.component,
name: '@create_main_fragment',
key: null,
bindings: new Map(),
dependencies: new Set(),
});
this.component.target.blocks.push(this.block);
this.initChildren(this.block, true, null);
this.block.hasUpdateMethod = true;
}
build() {
this.init();
this.children.forEach(child => {
child.build(this.block, null, 'nodes');
});
}
}

@ -1,5 +1,5 @@
import Node from './shared/Node';
import Block from '../dom/Block';
import Block from '../render-dom/Block';
import mapChildren from './shared/mapChildren';
export default class Head extends Node {
@ -20,34 +20,4 @@ export default class Head extends Node {
return (child.type !== 'Text' || /\S/.test(child.data));
}));
}
init(
block: Block,
stripWhitespace: boolean,
nextSibling: Node
) {
this.initChildren(block, true, null);
}
build(
block: Block,
parentNode: string,
parentNodes: string
) {
this.var = 'document.head';
this.children.forEach((child: Node) => {
child.build(block, 'document.head', null);
});
}
ssr() {
this.component.target.append('${(__result.head += `');
this.children.forEach((child: Node) => {
child.ssr();
});
this.component.target.append('`, "")}');
}
}

@ -1,22 +1,9 @@
import deindent from '../../utils/deindent';
import Node from './shared/Node';
import ElseBlock from './ElseBlock';
import Component from '../Component';
import Block from '../dom/Block';
import createDebuggingComment from '../../utils/createDebuggingComment';
import Block from '../render-dom/Block';
import Expression from './shared/Expression';
import mapChildren from './shared/mapChildren';
function isElseIf(node: ElseBlock) {
return (
node && node.children.length === 1 && node.children[0].type === 'IfBlock'
);
}
function isElseBranch(branch) {
return branch.block && !branch.condition;
}
export default class IfBlock extends Node {
type: 'IfBlock';
expression: Expression;
@ -37,476 +24,4 @@ export default class IfBlock extends Node {
this.warnIfEmptyBlock();
}
init(
block: Block,
stripWhitespace: boolean,
nextSibling: Node
) {
const { component } = this;
this.cannotUseInnerHTML();
const blocks: Block[] = [];
let dynamic = false;
let hasIntros = false;
let hasOutros = false;
function attachBlocks(node: IfBlock) {
node.var = block.getUniqueName(`if_block`);
block.addDependencies(node.expression.dependencies);
node.block = block.child({
comment: createDebuggingComment(node, component),
name: component.getUniqueName(`create_if_block`),
});
blocks.push(node.block);
node.initChildren(node.block, stripWhitespace, nextSibling);
if (node.block.dependencies.size > 0) {
dynamic = true;
block.addDependencies(node.block.dependencies);
}
if (node.block.hasIntros) hasIntros = true;
if (node.block.hasOutros) hasOutros = true;
if (isElseIf(node.else)) {
attachBlocks(node.else.children[0]);
} else if (node.else) {
node.else.block = block.child({
comment: createDebuggingComment(node.else, component),
name: component.getUniqueName(`create_if_block`),
});
blocks.push(node.else.block);
node.else.initChildren(
node.else.block,
stripWhitespace,
nextSibling
);
if (node.else.block.dependencies.size > 0) {
dynamic = true;
block.addDependencies(node.else.block.dependencies);
}
if (node.else.block.hasIntros) hasIntros = true;
if (node.else.block.hasOutros) hasOutros = true;
}
}
attachBlocks(this);
if (component.options.nestedTransitions) {
if (hasIntros) block.addIntro();
if (hasOutros) block.addOutro();
}
blocks.forEach(block => {
block.hasUpdateMethod = dynamic;
block.hasIntroMethod = hasIntros;
block.hasOutroMethod = hasOutros;
});
component.target.blocks.push(...blocks);
}
build(
block: Block,
parentNode: string,
parentNodes: string
) {
const name = this.var;
const needsAnchor = this.next ? !this.next.isDomNode() : !parentNode || !this.parent.isDomNode();
const anchor = needsAnchor
? block.getUniqueName(`${name}_anchor`)
: (this.next && this.next.var) || 'null';
const branches = this.getBranches(block, parentNode, parentNodes, this);
const hasElse = isElseBranch(branches[branches.length - 1]);
const if_name = hasElse ? '' : `if (${name}) `;
const dynamic = branches[0].hasUpdateMethod; // can use [0] as proxy for all, since they necessarily have the same value
const hasOutros = branches[0].hasOutroMethod;
const vars = { name, anchor, if_name, hasElse };
if (this.else) {
if (hasOutros) {
this.buildCompoundWithOutros(block, parentNode, parentNodes, branches, dynamic, vars);
if (this.component.options.nestedTransitions) {
block.builders.outro.addBlock(deindent`
if (${name}) ${name}.o(#outrocallback);
else #outrocallback();
`);
}
} else {
this.buildCompound(block, parentNode, parentNodes, branches, dynamic, vars);
}
} else {
this.buildSimple(block, parentNode, parentNodes, branches[0], dynamic, vars);
if (hasOutros && this.component.options.nestedTransitions) {
block.builders.outro.addBlock(deindent`
if (${name}) ${name}.o(#outrocallback);
else #outrocallback();
`);
}
}
block.builders.create.addLine(`${if_name}${name}.c();`);
if (parentNodes) {
block.builders.claim.addLine(
`${if_name}${name}.l(${parentNodes});`
);
}
if (needsAnchor) {
block.addElement(
anchor,
`@createComment()`,
parentNodes && `@createComment()`,
parentNode
);
}
}
buildCompound(
block: Block,
parentNode: string,
parentNodes: string,
branches,
dynamic,
{ name, anchor, hasElse, if_name }
) {
const select_block_type = this.component.getUniqueName(`select_block_type`);
const current_block_type = block.getUniqueName(`current_block_type`);
const current_block_type_and = hasElse ? '' : `${current_block_type} && `;
block.builders.init.addBlock(deindent`
function ${select_block_type}(ctx) {
${branches
.map(({ condition, block }) => `${condition ? `if (${condition}) ` : ''}return ${block};`)
.join('\n')}
}
`);
block.builders.init.addBlock(deindent`
var ${current_block_type} = ${select_block_type}(ctx);
var ${name} = ${current_block_type_and}${current_block_type}(#component, ctx);
`);
const mountOrIntro = branches[0].hasIntroMethod ? 'i' : 'm';
const initialMountNode = parentNode || '#target';
const anchorNode = parentNode ? 'null' : 'anchor';
block.builders.mount.addLine(
`${if_name}${name}.${mountOrIntro}(${initialMountNode}, ${anchorNode});`
);
const updateMountNode = this.getUpdateMountNode(anchor);
const changeBlock = deindent`
${if_name}${name}.d(1);
${name} = ${current_block_type_and}${current_block_type}(#component, ctx);
${if_name}${name}.c();
${if_name}${name}.${mountOrIntro}(${updateMountNode}, ${anchor});
`;
if (dynamic) {
block.builders.update.addBlock(deindent`
if (${current_block_type} === (${current_block_type} = ${select_block_type}(ctx)) && ${name}) {
${name}.p(changed, ctx);
} else {
${changeBlock}
}
`);
} else {
block.builders.update.addBlock(deindent`
if (${current_block_type} !== (${current_block_type} = ${select_block_type}(ctx))) {
${changeBlock}
}
`);
}
block.builders.destroy.addLine(`${if_name}${name}.d(${parentNode ? '' : 'detach'});`);
}
// if any of the siblings have outros, we need to keep references to the blocks
// (TODO does this only apply to bidi transitions?)
buildCompoundWithOutros(
block: Block,
parentNode: string,
parentNodes: string,
branches,
dynamic,
{ name, anchor, hasElse }
) {
const select_block_type = this.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 if_current_block_type_index = hasElse
? ''
: `if (~${current_block_type_index}) `;
block.addVariable(current_block_type_index);
block.addVariable(name);
block.builders.init.addBlock(deindent`
var ${if_block_creators} = [
${branches.map(branch => branch.block).join(',\n')}
];
var ${if_blocks} = [];
function ${select_block_type}(ctx) {
${branches
.map(({ condition, block }, i) => `${condition ? `if (${condition}) ` : ''}return ${block ? i : -1};`)
.join('\n')}
}
`);
if (hasElse) {
block.builders.init.addBlock(deindent`
${current_block_type_index} = ${select_block_type}(ctx);
${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](#component, ctx);
`);
} else {
block.builders.init.addBlock(deindent`
if (~(${current_block_type_index} = ${select_block_type}(ctx))) {
${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](#component, ctx);
}
`);
}
const mountOrIntro = branches[0].hasIntroMethod ? 'i' : 'm';
const initialMountNode = parentNode || '#target';
const anchorNode = parentNode ? 'null' : 'anchor';
block.builders.mount.addLine(
`${if_current_block_type_index}${if_blocks}[${current_block_type_index}].${mountOrIntro}(${initialMountNode}, ${anchorNode});`
);
const updateMountNode = this.getUpdateMountNode(anchor);
const destroyOldBlock = deindent`
@groupOutros();
${name}.o(function() {
${if_blocks}[${previous_block_index}].d(1);
${if_blocks}[${previous_block_index}] = null;
});
`;
const createNewBlock = deindent`
${name} = ${if_blocks}[${current_block_type_index}];
if (!${name}) {
${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](#component, ctx);
${name}.c();
}
${name}.${mountOrIntro}(${updateMountNode}, ${anchor});
`;
const changeBlock = hasElse
? deindent`
${destroyOldBlock}
${createNewBlock}
`
: deindent`
if (${name}) {
${destroyOldBlock}
}
if (~${current_block_type_index}) {
${createNewBlock}
} else {
${name} = null;
}
`;
if (dynamic) {
block.builders.update.addBlock(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}
}
`);
} else {
block.builders.update.addBlock(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}
}
`);
}
block.builders.destroy.addLine(deindent`
${if_current_block_type_index}${if_blocks}[${current_block_type_index}].d(${parentNode ? '' : 'detach'});
`);
}
buildSimple(
block: Block,
parentNode: string,
parentNodes: string,
branch,
dynamic,
{ name, anchor, if_name }
) {
block.builders.init.addBlock(deindent`
var ${name} = (${branch.condition}) && ${branch.block}(#component, ctx);
`);
const mountOrIntro = branch.hasIntroMethod ? 'i' : 'm';
const initialMountNode = parentNode || '#target';
const anchorNode = parentNode ? 'null' : 'anchor';
block.builders.mount.addLine(
`if (${name}) ${name}.${mountOrIntro}(${initialMountNode}, ${anchorNode});`
);
const updateMountNode = this.getUpdateMountNode(anchor);
const enter = dynamic
? (branch.hasIntroMethod || branch.hasOutroMethod)
? deindent`
if (${name}) {
${name}.p(changed, ctx);
} else {
${name} = ${branch.block}(#component, ctx);
if (${name}) ${name}.c();
}
${name}.i(${updateMountNode}, ${anchor});
`
: deindent`
if (${name}) {
${name}.p(changed, ctx);
} else {
${name} = ${branch.block}(#component, ctx);
${name}.c();
${name}.m(${updateMountNode}, ${anchor});
}
`
: (branch.hasIntroMethod || branch.hasOutroMethod)
? deindent`
if (!${name}) {
${name} = ${branch.block}(#component, ctx);
${name}.c();
}
${name}.i(${updateMountNode}, ${anchor});
`
: deindent`
if (!${name}) {
${name} = ${branch.block}(#component, ctx);
${name}.c();
${name}.m(${updateMountNode}, ${anchor});
}
`;
// no `p()` here — we don't want to update outroing nodes,
// as that will typically result in glitching
const exit = branch.hasOutroMethod
? deindent`
@groupOutros();
${name}.o(function() {
${name}.d(1);
${name} = null;
});
`
: deindent`
${name}.d(1);
${name} = null;
`;
block.builders.update.addBlock(deindent`
if (${branch.condition}) {
${enter}
} else if (${name}) {
${exit}
}
`);
block.builders.destroy.addLine(`${if_name}${name}.d(${parentNode ? '' : 'detach'});`);
}
getBranches(
block: Block,
parentNode: string,
parentNodes: string,
node: IfBlock
) {
const branches = [
{
condition: node.expression.snippet,
block: node.block.name,
hasUpdateMethod: node.block.hasUpdateMethod,
hasIntroMethod: node.block.hasIntroMethod,
hasOutroMethod: node.block.hasOutroMethod,
},
];
this.visitChildren(block, node);
if (isElseIf(node.else)) {
branches.push(
...this.getBranches(block, parentNode, parentNodes, node.else.children[0])
);
} else {
branches.push({
condition: null,
block: node.else ? node.else.block.name : null,
hasUpdateMethod: node.else ? node.else.block.hasUpdateMethod : false,
hasIntroMethod: node.else ? node.else.block.hasIntroMethod : false,
hasOutroMethod: node.else ? node.else.block.hasOutroMethod : false,
});
if (node.else) {
this.visitChildren(block, node.else);
}
}
return branches;
}
ssr() {
const { component } = this;
const { snippet } = this.expression;
component.target.append('${ ' + snippet + ' ? `');
this.children.forEach((child: Node) => {
child.ssr();
});
component.target.append('` : `');
if (this.else) {
this.else.children.forEach((child: Node) => {
child.ssr();
});
}
component.target.append('` }');
}
visitChildren(block: Block, node: Node) {
node.children.forEach((child: Node) => {
child.build(node.block, null, 'nodes');
});
}
}

@ -1,21 +1,10 @@
import deindent from '../../utils/deindent';
import stringifyProps from '../../utils/stringifyProps';
import CodeBuilder from '../../utils/CodeBuilder';
import getTailSnippet from '../../utils/getTailSnippet';
import getObject from '../../utils/getObject';
import { quoteNameIfNecessary, quotePropIfNecessary } from '../../utils/quoteIfNecessary';
import { escape, escapeTemplate, stringify } from '../../utils/stringify';
import Node from './shared/Node';
import Block from '../dom/Block';
import Attribute from './Attribute';
import mapChildren from './shared/mapChildren';
import Binding from './Binding';
import EventHandler from './EventHandler';
import Expression from './shared/Expression';
import { AppendTarget } from '../../interfaces';
import addToSet from '../../utils/addToSet';
import Component from '../Component';
import isValidIdentifier from '../../utils/isValidIdentifier';
import Ref from './Ref';
export default class InlineComponent extends Node {
@ -98,581 +87,4 @@ export default class InlineComponent extends Node {
this.children = mapChildren(component, this, scope, info.children);
}
init(
block: Block,
stripWhitespace: boolean,
nextSibling: Node
) {
this.cannotUseInnerHTML();
if (this.expression) {
block.addDependencies(this.expression.dependencies);
}
this.attributes.forEach(attr => {
block.addDependencies(attr.dependencies);
});
this.bindings.forEach(binding => {
block.addDependencies(binding.value.dependencies);
});
this.handlers.forEach(handler => {
block.addDependencies(handler.dependencies);
});
this.var = block.getUniqueName(
(
this.name === 'svelte:self' ? this.component.name :
this.name === 'svelte:component' ? 'switch_instance' :
this.name
).toLowerCase()
);
if (this.children.length) {
this._slots = new Set(['default']);
this.children.forEach(child => {
child.init(block, stripWhitespace, nextSibling);
});
}
if (this.component.options.nestedTransitions) {
block.addOutro();
}
}
build(
block: Block,
parentNode: string,
parentNodes: string
) {
const { component } = this;
const name = this.var;
const componentInitProperties = [`root: #component.root`, `store: #component.store`];
if (this.children.length > 0) {
const slots = Array.from(this._slots).map(name => `${quoteNameIfNecessary(name)}: @createFragment()`);
componentInitProperties.push(`slots: { ${slots.join(', ')} }`);
this.children.forEach((child: Node) => {
child.build(block, `${this.var}._slotted.default`, 'nodes');
});
}
const statements: string[] = [];
const name_initial_data = block.getUniqueName(`${name}_initial_data`);
const name_changes = block.getUniqueName(`${name}_changes`);
let name_updating: string;
let beforecreate: string = null;
const updates: string[] = [];
const usesSpread = !!this.attributes.find(a => a.isSpread);
const attributeObject = usesSpread
? '{}'
: stringifyProps(
this.attributes.map(attr => `${quoteNameIfNecessary(attr.name)}: ${attr.getValue()}`)
);
if (this.attributes.length || this.bindings.length) {
componentInitProperties.push(`data: ${name_initial_data}`);
}
if (!usesSpread && (this.attributes.filter(a => a.isDynamic).length || this.bindings.length)) {
updates.push(`var ${name_changes} = {};`);
}
if (this.attributes.length) {
if (usesSpread) {
const levels = block.getUniqueName(`${this.var}_spread_levels`);
const initialProps = [];
const changes = [];
const allDependencies = new Set();
this.attributes.forEach(attr => {
addToSet(allDependencies, attr.dependencies);
});
this.attributes.forEach(attr => {
const { name, dependencies } = attr;
const condition = dependencies.size > 0 && (dependencies.size !== allDependencies.size)
? `(${[...dependencies].map(d => `changed.${d}`).join(' || ')})`
: null;
if (attr.isSpread) {
const value = attr.expression.snippet;
initialProps.push(value);
changes.push(condition ? `${condition} && ${value}` : value);
} else {
const obj = `{ ${quoteNameIfNecessary(name)}: ${attr.getValue()} }`;
initialProps.push(obj);
changes.push(condition ? `${condition} && ${obj}` : obj);
}
});
block.builders.init.addBlock(deindent`
var ${levels} = [
${initialProps.join(',\n')}
];
`);
statements.push(deindent`
for (var #i = 0; #i < ${levels}.length; #i += 1) {
${name_initial_data} = @assign(${name_initial_data}, ${levels}[#i]);
}
`);
const conditions = [...allDependencies].map(dep => `changed.${dep}`).join(' || ');
updates.push(deindent`
var ${name_changes} = ${allDependencies.size === 1 ? `${conditions}` : `(${conditions})`} ? @getSpreadUpdate(${levels}, [
${changes.join(',\n')}
]) : {};
`);
} else {
this.attributes
.filter((attribute: Attribute) => attribute.isDynamic)
.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()};
`);
}
});
}
}
if (this.bindings.length) {
component.target.hasComplexBindings = true;
name_updating = block.alias(`${name}_updating`);
block.addVariable(name_updating, '{}');
let hasLocalBindings = false;
let hasStoreBindings = false;
const builder = new CodeBuilder();
this.bindings.forEach((binding: Binding) => {
let { name: key } = getObject(binding.value.node);
let setFromChild;
if (binding.isContextual) {
const computed = isComputed(binding.value.node);
const tail = binding.value.node.type === 'MemberExpression' ? getTailSnippet(binding.value.node) : '';
const head = block.bindings.get(key);
const lhs = binding.value.node.type === 'MemberExpression'
? binding.value.snippet
: `${head}${tail} = childState${quotePropIfNecessary(binding.name)}`;
setFromChild = deindent`
${lhs} = childState${quotePropIfNecessary(binding.name)};
${[...binding.value.dependencies]
.map((name: string) => {
const isStoreProp = name[0] === '$';
const prop = isStoreProp ? name.slice(1) : name;
const newState = isStoreProp ? 'newStoreState' : 'newState';
if (isStoreProp) hasStoreBindings = true;
else hasLocalBindings = true;
return `${newState}${quotePropIfNecessary(prop)} = ctx${quotePropIfNecessary(name)};`;
})}
`;
}
else {
const isStoreProp = key[0] === '$';
const prop = isStoreProp ? key.slice(1) : key;
const newState = isStoreProp ? 'newStoreState' : 'newState';
if (isStoreProp) hasStoreBindings = true;
else hasLocalBindings = true;
if (binding.value.node.type === 'MemberExpression') {
setFromChild = deindent`
${binding.value.snippet} = childState${quotePropIfNecessary(binding.name)};
${newState}${quotePropIfNecessary(prop)} = ctx${quotePropIfNecessary(key)};
`;
}
else {
setFromChild = `${newState}${quotePropIfNecessary(prop)} = childState${quotePropIfNecessary(binding.name)};`;
}
}
statements.push(deindent`
if (${binding.value.snippet} !== void 0) {
${name_initial_data}${quotePropIfNecessary(binding.name)} = ${binding.value.snippet};
${name_updating}${quotePropIfNecessary(binding.name)} = true;
}`
);
builder.addConditional(
`!${name_updating}${quotePropIfNecessary(binding.name)} && changed${quotePropIfNecessary(binding.name)}`,
setFromChild
);
updates.push(deindent`
if (!${name_updating}${quotePropIfNecessary(binding.name)} && ${[...binding.value.dependencies].map((dependency: string) => `changed.${dependency}`).join(' || ')}) {
${name_changes}${quotePropIfNecessary(binding.name)} = ${binding.value.snippet};
${name_updating}${quotePropIfNecessary(binding.name)} = ${binding.value.snippet} !== void 0;
}
`);
});
block.maintainContext = true; // TODO put this somewhere more logical
const initialisers = [
hasLocalBindings && 'newState = {}',
hasStoreBindings && 'newStoreState = {}',
].filter(Boolean).join(', ');
// TODO use component.on('state', ...) instead of _bind
componentInitProperties.push(deindent`
_bind(changed, childState) {
var ${initialisers};
${builder}
${hasStoreBindings && `#component.store.set(newStoreState);`}
${hasLocalBindings && `#component._set(newState);`}
${name_updating} = {};
}
`);
beforecreate = deindent`
#component.root._beforecreate.push(() => {
${name}._bind({ ${this.bindings.map(b => `${quoteNameIfNecessary(b.name)}: 1`).join(', ')} }, ${name}.get());
});
`;
}
this.handlers.forEach(handler => {
handler.var = block.getUniqueName(`${this.var}_${handler.name}`); // TODO this is hacky
handler.render(component, block, false); // TODO hoist when possible
if (handler.usesContext) block.maintainContext = true; // TODO is there a better place to put this?
});
if (this.name === 'svelte:component') {
const switch_value = block.getUniqueName('switch_value');
const switch_props = block.getUniqueName('switch_props');
const { snippet } = this.expression;
block.builders.init.addBlock(deindent`
var ${switch_value} = ${snippet};
function ${switch_props}(ctx) {
${(this.attributes.length || this.bindings.length) && deindent`
var ${name_initial_data} = ${attributeObject};`}
${statements}
return {
${componentInitProperties.join(',\n')}
};
}
if (${switch_value}) {
var ${name} = new ${switch_value}(${switch_props}(ctx));
${beforecreate}
}
${this.handlers.map(handler => deindent`
function ${handler.var}(event) {
${handler.snippet || `#component.fire("${handler.name}", event);`}
}
if (${name}) ${name}.on("${handler.name}", ${handler.var});
`)}
`);
block.builders.create.addLine(
`if (${name}) ${name}._fragment.c();`
);
if (parentNodes) {
block.builders.claim.addLine(
`if (${name}) ${name}._fragment.l(${parentNodes});`
);
}
block.builders.mount.addBlock(deindent`
if (${name}) {
${name}._mount(${parentNode || '#target'}, ${parentNode ? 'null' : 'anchor'});
${this.ref && `#component.refs.${this.ref.name} = ${name};`}
}
`);
const anchor = this.getOrCreateAnchor(block, parentNode, parentNodes);
const updateMountNode = this.getUpdateMountNode(anchor);
if (updates.length) {
block.builders.update.addBlock(deindent`
${updates}
`);
}
block.builders.update.addBlock(deindent`
if (${switch_value} !== (${switch_value} = ${snippet})) {
if (${name}) {
${this.component.options.nestedTransitions
? deindent`
@groupOutros();
const old_component = ${name};
old_component._fragment.o(() => {
old_component.destroy();
});`
: `${name}.destroy();`}
}
if (${switch_value}) {
${name} = new ${switch_value}(${switch_props}(ctx));
${this.bindings.length > 0 && deindent`
#component.root._beforecreate.push(() => {
const changed = {};
${this.bindings.map(binding => deindent`
if (${binding.value.snippet} === void 0) changed.${binding.name} = 1;`)}
${name}._bind(changed, ${name}.get());
});`}
${name}._fragment.c();
${this.children.map(child => child.remount(name))}
${name}._mount(${updateMountNode}, ${anchor});
${this.handlers.map(handler => deindent`
${name}.on("${handler.name}", ${handler.var});
`)}
${this.ref && `#component.refs.${this.ref.name} = ${name};`}
} else {
${name} = null;
${this.ref && deindent`
if (#component.refs.${this.ref.name} === ${name}) {
#component.refs.${this.ref.name} = null;
}`}
}
}
`);
if (updates.length) {
block.builders.update.addBlock(deindent`
else if (${switch_value}) {
${name}._set(${name_changes});
${this.bindings.length && `${name_updating} = {};`}
}
`);
}
block.builders.destroy.addLine(`if (${name}) ${name}.destroy(${parentNode ? '' : 'detach'});`);
} else {
const expression = this.name === 'svelte:self'
? component.name
: `%components-${this.name}`;
block.builders.init.addBlock(deindent`
${(this.attributes.length || this.bindings.length) && deindent`
var ${name_initial_data} = ${attributeObject};`}
${statements}
var ${name} = new ${expression}({
${componentInitProperties.join(',\n')}
});
${beforecreate}
${this.handlers.map(handler => deindent`
${name}.on("${handler.name}", function(event) {
${handler.snippet || `#component.fire("${handler.name}", event);`}
});
`)}
${this.ref && `#component.refs.${this.ref.name} = ${name};`}
`);
block.builders.create.addLine(`${name}._fragment.c();`);
if (parentNodes) {
block.builders.claim.addLine(
`${name}._fragment.l(${parentNodes});`
);
}
block.builders.mount.addLine(
`${name}._mount(${parentNode || '#target'}, ${parentNode ? 'null' : 'anchor'});`
);
if (updates.length) {
block.builders.update.addBlock(deindent`
${updates}
${name}._set(${name_changes});
${this.bindings.length && `${name_updating} = {};`}
`);
}
block.builders.destroy.addLine(deindent`
${name}.destroy(${parentNode ? '' : 'detach'});
${this.ref && `if (#component.refs.${this.ref.name} === ${name}) #component.refs.${this.ref.name} = null;`}
`);
}
if (this.component.options.nestedTransitions) {
block.builders.outro.addLine(
`if (${name}) ${name}._fragment.o(#outrocallback);`
);
}
}
remount(name: string) {
return `${this.var}._mount(${name}._slotted.default, null);`;
}
ssr() {
function stringifyAttribute(chunk: Node) {
if (chunk.type === 'Text') {
return escapeTemplate(escape(chunk.data));
}
return '${@escape( ' + chunk.snippet + ')}';
}
const bindingProps = this.bindings.map(binding => {
const { name } = getObject(binding.value.node);
const tail = binding.value.node.type === 'MemberExpression'
? getTailSnippet(binding.value.node)
: '';
return `${quoteNameIfNecessary(binding.name)}: ctx${quotePropIfNecessary(name)}${tail}`;
});
function getAttributeValue(attribute) {
if (attribute.isTrue) return `true`;
if (attribute.chunks.length === 0) return `''`;
if (attribute.chunks.length === 1) {
const chunk = attribute.chunks[0];
if (chunk.type === 'Text') {
return stringify(chunk.data);
}
return chunk.snippet;
}
return '`' + attribute.chunks.map(stringifyAttribute).join('') + '`';
}
const usesSpread = this.attributes.find(attr => attr.isSpread);
const props = usesSpread
? `Object.assign(${
this.attributes
.map(attribute => {
if (attribute.isSpread) {
return attribute.expression.snippet;
} else {
return `{ ${quoteNameIfNecessary(attribute.name)}: ${getAttributeValue(attribute)} }`;
}
})
.concat(bindingProps.map(p => `{ ${p} }`))
.join(', ')
})`
: `{ ${this.attributes
.map(attribute => `${quoteNameIfNecessary(attribute.name)}: ${getAttributeValue(attribute)}`)
.concat(bindingProps)
.join(', ')} }`;
const expression = (
this.name === 'svelte:self'
? this.component.name
: this.name === 'svelte:component'
? `((${this.expression.snippet}) || @missingComponent)`
: `%components-${this.name}`
);
this.bindings.forEach(binding => {
const conditions = [];
let node = this;
while (node = node.parent) {
if (node.type === 'IfBlock') {
// TODO handle contextual bindings...
conditions.push(`(${node.expression.snippet})`);
}
}
conditions.push(
`!('${binding.name}' in ctx)`,
`${expression}.data`
);
const { name } = getObject(binding.value.node);
this.component.target.bindings.push(deindent`
if (${conditions.reverse().join('&&')}) {
tmp = ${expression}.data();
if ('${name}' in tmp) {
ctx${quotePropIfNecessary(binding.name)} = tmp.${name};
settled = false;
}
}
`);
});
let open = `\${@validateSsrComponent(${expression}, '${this.name}')._render(__result, ${props}`;
const options = [];
options.push(`store: options.store`);
if (this.children.length) {
const appendTarget: AppendTarget = {
slots: { default: '' },
slotStack: ['default']
};
this.component.target.appendTargets.push(appendTarget);
this.children.forEach((child: Node) => {
child.ssr();
});
const slotted = Object.keys(appendTarget.slots)
.map(name => `${quoteNameIfNecessary(name)}: () => \`${appendTarget.slots[name]}\``)
.join(', ');
options.push(`slotted: { ${slotted} }`);
this.component.target.appendTargets.pop();
}
if (options.length) {
open += `, { ${options.join(', ')} }`;
}
this.component.target.append(open);
this.component.target.append(')}');
}
}
function isComputed(node: Node) {
while (node.type === 'MemberExpression') {
if (node.computed) return true;
node = node.object;
}
return false;
}

@ -1,37 +1,3 @@
import Node from './shared/Node';
import Tag from './shared/Tag';
import Block from '../dom/Block';
export default class MustacheTag extends Tag {
build(
block: Block,
parentNode: string,
parentNodes: string
) {
const { init } = this.renameThisMethod(
block,
value => `@setData(${this.var}, ${value});`
);
block.addElement(
this.var,
`@createText(${init})`,
parentNodes && `@claimText(${parentNodes}, ${init})`,
parentNode
);
}
remount(name: string) {
return `@append(${name}._slotted.default, ${this.var});`;
}
ssr() {
this.component.target.append(
this.parent &&
this.parent.type === 'Element' &&
this.parent.name === 'style'
? '${' + this.expression.snippet + '}'
: '${@escape(' + this.expression.snippet + ')}'
);
}
}
export default class MustacheTag extends Tag {}

@ -1,5 +1,5 @@
import Node from './shared/Node';
import Block from '../dom/Block';
import Block from '../render-dom/Block';
import mapChildren from './shared/mapChildren';
export default class PendingBlock extends Node {

@ -1,100 +1,3 @@
import deindent from '../../utils/deindent';
import Node from './shared/Node';
import Tag from './shared/Tag';
import Block from '../dom/Block';
export default class RawMustacheTag extends Tag {
build(
block: Block,
parentNode: string,
parentNodes: string
) {
const name = this.var;
const needsAnchorBefore = this.prev ? this.prev.type !== 'Element' : !parentNode;
const needsAnchorAfter = this.next ? this.next.type !== 'Element' : !parentNode;
const anchorBefore = needsAnchorBefore
? block.getUniqueName(`${name}_before`)
: (this.prev && this.prev.var) || 'null';
const anchorAfter = needsAnchorAfter
? block.getUniqueName(`${name}_after`)
: (this.next && this.next.var) || 'null';
let detach: string;
let insert: (content: string) => string;
let useInnerHTML = 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});`;
} else {
detach = `@detachBetween(${anchorBefore}, ${anchorAfter});`;
insert = content => `${anchorBefore}.insertAdjacentHTML("afterend", ${content});`;
}
const { init } = this.renameThisMethod(
block,
content => deindent`
${!useInnerHTML && 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,
true
);
}
function addAnchorAfter() {
block.addElement(
anchorAfter,
`@createElement('noscript')`,
parentNodes && `@createElement('noscript')`,
parentNode
);
}
if (needsAnchorAfter && anchorBefore === 'null') {
// anchorAfter needs to be in the DOM before we
// insert the HTML...
addAnchorAfter();
}
block.builders.mount.addLine(insert(init));
if (!parentNode) {
block.builders.destroy.addConditional('detach', needsAnchorBefore
? `${detach}\n@detachNode(${anchorBefore});`
: detach);
}
if (needsAnchorAfter && anchorBefore !== 'null') {
// ...otherwise it should go afterwards
addAnchorAfter();
}
}
remount(name: string) {
return `@append(${name}._slotted.default, ${this.var});`;
}
ssr() {
this.component.target.append('${' + this.expression.snippet + '}');
}
}
export default class RawMustacheTag extends Tag {}

@ -1,15 +1,6 @@
import deindent from '../../utils/deindent';
import isValidIdentifier from '../../utils/isValidIdentifier';
import reservedNames from '../../utils/reservedNames';
import Node from './shared/Node';
import Element from './Element';
import Attribute from './Attribute';
import Block from '../dom/Block';
import { quotePropIfNecessary } from '../../utils/quoteIfNecessary';
function sanitize(name) {
return name.replace(/[^a-zA-Z]+/g, '_').replace(/^_/, '').replace(/_$/, '');
}
export default class Slot extends Element {
type: 'Element';
@ -68,126 +59,6 @@ export default class Slot extends Element {
// }
}
init(
block: Block,
stripWhitespace: boolean,
nextSibling: Node
) {
this.cannotUseInnerHTML();
this.var = block.getUniqueName('slot');
if (this.children.length) {
this.initChildren(block, stripWhitespace, nextSibling);
}
}
build(
block: Block,
parentNode: string,
parentNodes: string
) {
const { component } = this;
const slotName = this.getStaticAttributeValue('name') || 'default';
component.slots.add(slotName);
const content_name = block.getUniqueName(`slot_content_${sanitize(slotName)}`);
const prop = quotePropIfNecessary(slotName);
block.addVariable(content_name, `#component._slotted${prop}`);
const needsAnchorBefore = this.prev ? this.prev.type !== 'Element' : !parentNode;
const needsAnchorAfter = this.next ? this.next.type !== 'Element' : !parentNode;
const anchorBefore = needsAnchorBefore
? block.getUniqueName(`${content_name}_before`)
: (this.prev && this.prev.var) || 'null';
const anchorAfter = needsAnchorAfter
? block.getUniqueName(`${content_name}_after`)
: (this.next && this.next.var) || 'null';
if (needsAnchorBefore) block.addVariable(anchorBefore);
if (needsAnchorAfter) block.addVariable(anchorAfter);
let mountBefore = block.builders.mount.toString();
let destroyBefore = block.builders.destroy.toString();
block.builders.create.pushCondition(`!${content_name}`);
block.builders.hydrate.pushCondition(`!${content_name}`);
block.builders.mount.pushCondition(`!${content_name}`);
block.builders.update.pushCondition(`!${content_name}`);
block.builders.destroy.pushCondition(`!${content_name}`);
this.children.forEach((child: Node) => {
child.build(block, parentNode, parentNodes);
});
block.builders.create.popCondition();
block.builders.hydrate.popCondition();
block.builders.mount.popCondition();
block.builders.update.popCondition();
block.builders.destroy.popCondition();
const mountLeadin = block.builders.mount.toString() !== mountBefore
? `else`
: `if (${content_name})`;
if (parentNode) {
block.builders.mount.addBlock(deindent`
${mountLeadin} {
${needsAnchorBefore && `@append(${parentNode}, ${anchorBefore} || (${anchorBefore} = @createComment()));`}
@append(${parentNode}, ${content_name});
${needsAnchorAfter && `@append(${parentNode}, ${anchorAfter} || (${anchorAfter} = @createComment()));`}
}
`);
} else {
block.builders.mount.addBlock(deindent`
${mountLeadin} {
${needsAnchorBefore && `@insert(#target, ${anchorBefore} || (${anchorBefore} = @createComment()), anchor);`}
@insert(#target, ${content_name}, anchor);
${needsAnchorAfter && `@insert(#target, ${anchorAfter} || (${anchorAfter} = @createComment()), anchor);`}
}
`);
}
// if the slot is unmounted, move nodes back into the document fragment,
// so that it can be reinserted later
// TODO so that this can work with public API, component._slotted should
// be all fragments, derived from options.slots. Not === options.slots
const unmountLeadin = block.builders.destroy.toString() !== destroyBefore
? `else`
: `if (${content_name})`;
if (anchorBefore === 'null' && anchorAfter === 'null') {
block.builders.destroy.addBlock(deindent`
${unmountLeadin} {
@reinsertChildren(${parentNode}, ${content_name});
}
`);
} else if (anchorBefore === 'null') {
block.builders.destroy.addBlock(deindent`
${unmountLeadin} {
@reinsertBefore(${anchorAfter}, ${content_name});
}
`);
} else if (anchorAfter === 'null') {
block.builders.destroy.addBlock(deindent`
${unmountLeadin} {
@reinsertAfter(${anchorBefore}, ${content_name});
}
`);
} else {
block.builders.destroy.addBlock(deindent`
${unmountLeadin} {
@reinsertBetween(${anchorBefore}, ${anchorAfter}, ${content_name});
@detachNode(${anchorBefore});
@detachNode(${anchorAfter});
}
`);
}
}
getStaticAttributeValue(name: string) {
const attribute = this.attributes.find(
attr => attr.name.toLowerCase() === name
@ -204,19 +75,4 @@ export default class Slot extends Element {
return null;
}
ssr() {
const name = this.attributes.find(attribute => attribute.name === 'name');
const slotName = name && name.chunks[0].data || 'default';
const prop = quotePropIfNecessary(slotName);
this.component.target.append(`\${options && options.slotted && options.slotted${prop} ? options.slotted${prop}() : \``);
this.children.forEach((child: Node) => {
child.ssr();
});
this.component.target.append(`\`}`);
}
}

@ -1,30 +1,4 @@
import { escape, escapeHTML, escapeTemplate, stringify } from '../../utils/stringify';
import Node from './shared/Node';
import Block from '../dom/Block';
// 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([
'audio',
'datalist',
'dl',
'optgroup',
'select',
'video',
]);
function shouldSkip(node: Text) {
if (/\S/.test(node.data)) return false;
const parentElement = node.findNearest(/(?:Element|InlineComponent|Head)/);
if (!parentElement) return false;
if (parentElement.type === 'Head') return true;
if (parentElement.type === 'InlineComponent') return parentElement.children.length === 1 && node === parentElement.children[0];
return parentElement.namespace || elementsWithoutText.has(parentElement.name);
}
export default class Text extends Node {
type: 'Text';
@ -35,45 +9,4 @@ export default class Text extends Node {
super(component, parent, scope, info);
this.data = info.data;
}
init(block: Block) {
if (shouldSkip(this)) {
this.shouldSkip = true;
return;
}
this.var = block.getUniqueName(`text`);
}
build(
block: Block,
parentNode: string,
parentNodes: string
) {
if (this.shouldSkip) return;
block.addElement(
this.var,
`@createText(${stringify(this.data)})`,
parentNodes && `@claimText(${parentNodes}, ${stringify(this.data)})`,
parentNode
);
}
remount(name: string) {
return `@append(${name}._slotted.default, ${this.var});`;
}
ssr() {
let text = this.data;
if (
!this.parent ||
this.parent.type !== 'Element' ||
(this.parent.name !== 'script' && this.parent.name !== 'style')
) {
// unless this Text node is inside a <script> or <style> element, escape &,<,>
text = escapeHTML(text);
}
this.component.target.append(escape(escapeTemplate(text)));
}
}

@ -1,5 +1,5 @@
import Node from './shared/Node';
import Block from '../dom/Block';
import Block from '../render-dom/Block';
import mapChildren from './shared/mapChildren';
export default class ThenBlock extends Node {

@ -1,6 +1,4 @@
import { stringify } from '../../utils/stringify';
import Node from './shared/Node';
import Block from '../dom/Block';
import mapChildren from './shared/mapChildren';
export default class Title extends Node {
@ -35,96 +33,4 @@ export default class Title extends Node {
)
: true;
}
build(
block: Block,
parentNode: string,
parentNodes: string
) {
const isDynamic = !!this.children.find(node => node.type !== 'Text');
if (isDynamic) {
let value;
const allDependencies = 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
if (this.children.length === 1) {
// single {tag} — may be a non-string
const { expression } = this.children[0];
const { dependencies, snippet } = this.children[0].expression;
value = snippet;
dependencies.forEach(d => {
allDependencies.add(d);
});
} else {
// '{foo} {bar}' — treat as string concatenation
value =
(this.children[0].type === 'Text' ? '' : `"" + `) +
this.children
.map((chunk: Node) => {
if (chunk.type === 'Text') {
return stringify(chunk.data);
} else {
const { dependencies, snippet } = chunk.expression;
dependencies.forEach(d => {
allDependencies.add(d);
});
return chunk.expression.getPrecedence() <= 13 ? `(${snippet})` : snippet;
}
})
.join(' + ');
}
const last = this.shouldCache && block.getUniqueName(
`title_value`
);
if (this.shouldCache) block.addVariable(last);
let updater;
const init = this.shouldCache ? `${last} = ${value}` : value;
block.builders.init.addLine(
`document.title = ${init};`
);
updater = `document.title = ${this.shouldCache ? last : value};`;
if (allDependencies.size) {
const dependencies = Array.from(allDependencies);
const changedCheck = (
( block.hasOutros ? `!#current || ` : '' ) +
dependencies.map(dependency => `changed.${dependency}`).join(' || ')
);
const updateCachedValue = `${last} !== (${last} = ${value})`;
const condition = this.shouldCache ?
( dependencies.length ? `(${changedCheck}) && ${updateCachedValue}` : updateCachedValue ) :
changedCheck;
block.builders.update.addConditional(
condition,
updater
);
}
} else {
const value = stringify(this.children[0].data);
block.builders.hydrate.addLine(`document.title = ${value};`);
}
}
ssr() {
this.component.target.append(`<title>`);
this.children.forEach((child: Node) => {
child.ssr();
});
this.component.target.append(`</title>`);
}
}

@ -1,35 +1,10 @@
import deindent from '../../utils/deindent';
import Node from './shared/Node';
import Block from '../dom/Block';
import Binding from './Binding';
import EventHandler from './EventHandler';
import flattenReference from '../../utils/flattenReference';
import fuzzymatch from '../validate/utils/fuzzymatch';
import list from '../../utils/list';
const associatedEvents = {
innerWidth: 'resize',
innerHeight: 'resize',
outerWidth: 'resize',
outerHeight: 'resize',
scrollX: 'scroll',
scrollY: 'scroll',
};
const properties = {
scrollX: 'pageXOffset',
scrollY: 'pageYOffset'
};
const readonly = new Set([
'innerWidth',
'innerHeight',
'outerWidth',
'outerHeight',
'online',
]);
const validBindings = [
'innerWidth',
'innerHeight',
@ -96,175 +71,4 @@ export default class Window extends Node {
}
});
}
build(
block: Block,
parentNode: string,
parentNodes: string
) {
const { component } = this;
const events = {};
const bindings: Record<string, string> = {};
this.handlers.forEach(handler => {
// TODO verify that it's a valid callee (i.e. built-in or declared method)
component.addSourcemapLocations(handler.expression);
const isCustomEvent = component.events.has(handler.name);
let usesState = handler.dependencies.size > 0;
handler.render(component, block, false); // TODO hoist?
const handlerName = block.getUniqueName(`onwindow${handler.name}`);
const handlerBody = deindent`
${usesState && `var ctx = #component.get();`}
${handler.snippet};
`;
if (isCustomEvent) {
// TODO dry this out
block.addVariable(handlerName);
block.builders.hydrate.addBlock(deindent`
${handlerName} = %events-${handler.name}.call(#component, window, function(event) {
${handlerBody}
});
`);
block.builders.destroy.addLine(deindent`
${handlerName}.destroy();
`);
} else {
block.builders.init.addBlock(deindent`
function ${handlerName}(event) {
${handlerBody}
}
window.addEventListener("${handler.name}", ${handlerName});
`);
block.builders.destroy.addBlock(deindent`
window.removeEventListener("${handler.name}", ${handlerName});
`);
}
});
this.bindings.forEach(binding => {
// in dev mode, throw if read-only values are written to
if (readonly.has(binding.name)) {
component.target.readonly.add(binding.value.node.name);
}
bindings[binding.name] = binding.value.node.name;
// 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 property = properties[binding.name] || binding.name;
if (!events[associatedEvent]) events[associatedEvent] = [];
events[associatedEvent].push(
`${binding.value.node.name}: this.${property}`
);
// add initial value
component.target.metaBindings.push(
`this._state.${binding.value.node.name} = window.${property};`
);
});
const lock = block.getUniqueName(`window_updating`);
const clear = block.getUniqueName(`clear_window_updating`);
const timeout = block.getUniqueName(`window_updating_timeout`);
Object.keys(events).forEach(event => {
const handlerName = block.getUniqueName(`onwindow${event}`);
const props = events[event].join(',\n');
if (event === 'scroll') {
// TODO other bidirectional bindings...
block.addVariable(lock, 'false');
block.addVariable(clear, `function() { ${lock} = false; }`);
block.addVariable(timeout);
}
const handlerBody = deindent`
${event === 'scroll' && deindent`
if (${lock}) return;
${lock} = true;
`}
${component.options.dev && `component._updatingReadonlyProperty = true;`}
#component.set({
${props}
});
${component.options.dev && `component._updatingReadonlyProperty = false;`}
${event === 'scroll' && `${lock} = false;`}
`;
block.builders.init.addBlock(deindent`
function ${handlerName}(event) {
${handlerBody}
}
window.addEventListener("${event}", ${handlerName});
`);
block.builders.destroy.addBlock(deindent`
window.removeEventListener("${event}", ${handlerName});
`);
});
// special case... might need to abstract this out if we add more special cases
if (bindings.scrollX || bindings.scrollY) {
block.builders.init.addBlock(deindent`
#component.on("state", ({ changed, current }) => {
if (${
[bindings.scrollX, bindings.scrollY].map(
binding => binding && `changed["${binding}"]`
).filter(Boolean).join(' || ')
}) {
${lock} = true;
clearTimeout(${timeout});
window.scrollTo(${
bindings.scrollX ? `current["${bindings.scrollX}"]` : `window.pageXOffset`
}, ${
bindings.scrollY ? `current["${bindings.scrollY}"]` : `window.pageYOffset`
});
${timeout} = setTimeout(${clear}, 100);
}
});
`);
}
// another special case. (I'm starting to think these are all special cases.)
if (bindings.online) {
const handlerName = block.getUniqueName(`onlinestatuschanged`);
block.builders.init.addBlock(deindent`
function ${handlerName}(event) {
${component.options.dev && `component._updatingReadonlyProperty = true;`}
#component.set({ ${bindings.online}: navigator.onLine });
${component.options.dev && `component._updatingReadonlyProperty = false;`}
}
window.addEventListener("online", ${handlerName});
window.addEventListener("offline", ${handlerName});
`);
// add initial value
component.target.metaBindings.push(
`this._state.${bindings.online} = navigator.onLine;`
);
block.builders.destroy.addBlock(deindent`
window.removeEventListener("online", ${handlerName});
window.removeEventListener("offline", ${handlerName});
`);
}
}
ssr() {
// noop
}
}

@ -1,5 +1,5 @@
import Component from './../../Component';
import Block from '../../dom/Block';
import Block from '../../render-dom/Block';
import { trimStart, trimEnd } from '../../../utils/trim';
export default class Node {
@ -39,98 +39,6 @@ export default class Node {
}
}
init(
block: Block,
stripWhitespace: boolean,
nextSibling: Node
) {
// implemented by subclasses
}
initChildren(
block: Block,
stripWhitespace: boolean,
nextSibling: Node
) {
// glue text nodes together
const cleaned: Node[] = [];
let lastChild: Node;
let windowComponent;
this.children.forEach((child: Node) => {
if (child.type === 'Comment') return;
// special case — this is an easy way to remove whitespace surrounding
// <svelte:window/>. lil hacky but it works
if (child.type === 'Window') {
windowComponent = child;
return;
}
if (child.type === 'Text' && lastChild && lastChild.type === 'Text') {
lastChild.data += child.data;
lastChild.end = child.end;
} else {
if (child.type === 'Text' && stripWhitespace && cleaned.length === 0) {
child.data = trimStart(child.data);
if (child.data) cleaned.push(child);
} else {
cleaned.push(child);
}
}
lastChild = child;
});
lastChild = null;
cleaned.forEach((child: Node, i: number) => {
child.canUseInnerHTML = !this.component.options.hydratable;
child.init(block, stripWhitespace, cleaned[i + 1] || nextSibling);
if (child.shouldSkip) return;
if (lastChild) lastChild.next = child;
child.prev = lastChild;
lastChild = child;
});
// We want to remove trailing whitespace inside an element/component/block,
// *unless* there is no whitespace between this node and its next sibling
if (stripWhitespace && lastChild && lastChild.type === 'Text') {
const shouldTrim = (
nextSibling ? (nextSibling.type === 'Text' && /^\s/.test(nextSibling.data)) : !this.hasAncestor('EachBlock')
);
if (shouldTrim) {
lastChild.data = trimEnd(lastChild.data);
if (!lastChild.data) {
cleaned.pop();
lastChild = cleaned[cleaned.length - 1];
lastChild.next = null;
}
}
}
this.children = cleaned;
if (windowComponent) cleaned.unshift(windowComponent);
}
build(
block: Block,
parentNode: string,
parentNodes: string
) {
// implemented by subclasses
}
isDomNode() {
return this.type === 'Element' || this.type === 'Text' || this.type === 'MustacheTag';
}
hasAncestor(type: string) {
return this.parent ?
this.parent.type === type || this.parent.hasAncestor(type) :
@ -142,30 +50,6 @@ export default class Node {
if (this.parent) return this.parent.findNearest(selector);
}
getOrCreateAnchor(block: Block, parentNode: string, parentNodes: 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`)
: (this.next && this.next.var) || 'null';
if (needsAnchor) {
block.addElement(
anchor,
`@createComment()`,
parentNodes && `@createComment()`,
parentNode
);
}
return anchor;
}
getUpdateMountNode(anchor: string) {
return this.parent.isDomNode() ? this.parent.var : `${anchor}.parentNode`;
}
remount(name: string) {
return `${this.var}.m(${name}._slotted.default, null);`;
}

@ -1,6 +1,5 @@
import Node from './Node';
import Expression from './Expression';
import Block from '../../dom/Block';
export default class Tag extends Node {
expression: Expression;
@ -15,42 +14,4 @@ export default class Tag extends Node {
(this.expression.dependencies.size && scope.names.has(info.expression.name))
);
}
init(block: Block) {
this.cannotUseInnerHTML();
this.var = block.getUniqueName(this.type === 'MustacheTag' ? 'text' : 'raw');
block.addDependencies(this.expression.dependencies);
}
renameThisMethod(
block: Block,
update: ((value: string) => string)
) {
const { snippet, dependencies } = this.expression;
const value = this.shouldCache && block.getUniqueName(`${this.var}_value`);
const content = this.shouldCache ? value : snippet;
if (this.shouldCache) block.addVariable(value, snippet);
if (dependencies.size) {
const changedCheck = (
(block.hasOutros ? `!#current || ` : '') +
[...dependencies].map((dependency: string) => `changed.${dependency}`).join(' || ')
);
const updateCachedValue = `${value} !== (${value} = ${snippet})`;
const condition = this.shouldCache ?
(dependencies.size ? `(${changedCheck}) && ${updateCachedValue}` : updateCachedValue) :
changedCheck;
block.builders.update.addConditional(
condition,
update(content)
);
}
return { init: content };
}
}

@ -1,30 +1,33 @@
import CodeBuilder from '../../utils/CodeBuilder';
import deindent from '../../utils/deindent';
import { escape } from '../../utils/stringify';
import Component from '../Component';
import Renderer from './Renderer';
import Wrapper from './wrappers/shared/Wrapper';
export interface BlockOptions {
parent?: Block;
name: string;
component?: Component;
renderer?: Renderer;
comment?: string;
key?: string;
bindings?: Map<string, string>;
bindings?: Map<string, () => string>;
dependencies?: Set<string>;
}
export default class Block {
parent?: Block;
component: Component;
renderer: Renderer;
name: string;
comment?: string;
wrappers: Wrapper[];
key: string;
first: string;
dependencies: Set<string>;
bindings: Map<string, string>;
bindings: Map<string, () => string>;
builders: {
init: CodeBuilder;
@ -58,10 +61,12 @@ export default class Block {
constructor(options: BlockOptions) {
this.parent = options.parent;
this.component = options.component;
this.renderer = options.renderer;
this.name = options.name;
this.comment = options.comment;
this.wrappers = [];
// for keyed each blocks
this.key = options.key;
this.first = null;
@ -90,7 +95,7 @@ export default class Block {
this.hasOutroMethod = false;
this.outros = 0;
this.getUniqueName = this.component.getUniqueNameMaker();
this.getUniqueName = this.renderer.component.getUniqueNameMaker();
this.variables = new Map();
this.aliases = new Map()
@ -101,6 +106,43 @@ export default class Block {
this.hasUpdateMethod = false; // determined later
}
assignVariableNames() {
const seen = new Set();
const dupes = new Set();
let i = this.wrappers.length;
while (i--) {
const wrapper = this.wrappers[i];
if (!wrapper.var) continue;
if (wrapper.parent && wrapper.parent.canUseInnerHTML) continue;
if (seen.has(wrapper.var)) {
dupes.add(wrapper.var);
}
seen.add(wrapper.var);
}
const counts = new Map();
i = this.wrappers.length;
while (i--) {
const wrapper = this.wrappers[i];
if (!wrapper.var) continue;
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);
} else {
wrapper.var = this.getUniqueName(wrapper.var);
}
}
}
addDependencies(dependencies: Set<string>) {
dependencies.forEach(dependency => {
this.dependencies.add(dependency);
@ -128,11 +170,11 @@ export default class Block {
}
addIntro() {
this.hasIntros = this.hasIntroMethod = this.component.target.hasIntroTransitions = true;
this.hasIntros = this.hasIntroMethod = this.renderer.hasIntroTransitions = true;
}
addOutro() {
this.hasOutros = this.hasOutroMethod = this.component.target.hasOutroTransitions = true;
this.hasOutros = this.hasOutroMethod = this.renderer.hasOutroTransitions = true;
this.outros += 1;
}
@ -167,7 +209,7 @@ export default class Block {
}
toString() {
const { dev } = this.component.options;
const { dev } = this.renderer.options;
if (this.hasIntroMethod || this.hasOutroMethod) {
this.addVariable('#current');
@ -202,7 +244,7 @@ export default class Block {
properties.addBlock(`c: @noop,`);
} else {
const hydrate = !this.builders.hydrate.isEmpty() && (
this.component.options.hydratable
this.renderer.options.hydratable
? `this.h()`
: this.builders.hydrate
);
@ -215,7 +257,7 @@ export default class Block {
`);
}
if (this.component.options.hydratable) {
if (this.renderer.options.hydratable) {
if (this.builders.claim.isEmpty() && this.builders.hydrate.isEmpty()) {
properties.addBlock(`l: @noop,`);
} else {
@ -228,7 +270,7 @@ export default class Block {
}
}
if (this.component.options.hydratable && !this.builders.hydrate.isEmpty()) {
if (this.renderer.options.hydratable && !this.builders.hydrate.isEmpty()) {
properties.addBlock(deindent`
${dev ? 'h: function hydrate' : 'h'}() {
${this.builders.hydrate}

@ -0,0 +1,75 @@
import Block from './Block';
import { CompileOptions } from '../../interfaces';
import Component from '../Component';
import FragmentWrapper from './wrappers/Fragment';
export default class Renderer {
component: Component; // TODO Maybe Renderer shouldn't know about Component?
options: CompileOptions;
blocks: (Block | string)[];
readonly: Set<string>;
slots: Set<string>;
metaBindings: string[];
bindingGroups: string[];
block: Block;
fragment: FragmentWrapper;
usedNames: Set<string>;
fileVar: string;
hasIntroTransitions: boolean;
hasOutroTransitions: boolean;
hasComplexBindings: boolean;
constructor(component: Component, options: CompileOptions) {
this.component = component;
this.options = options;
this.locate = component.locate; // TODO messy
this.readonly = new Set();
this.slots = new Set();
this.usedNames = new Set();
this.fileVar = options.dev && this.component.getUniqueName('file');
// initial values for e.g. window.innerWidth, if there's a <svelte:window> meta tag
this.metaBindings = [];
this.bindingGroups = [];
// main block
this.block = new Block({
renderer: this,
name: '@create_main_fragment',
key: null,
bindings: new Map(),
dependencies: new Set(),
});
this.block.hasUpdateMethod = true;
this.blocks = [];
this.fragment = new FragmentWrapper(
this,
this.block,
component.fragment.children,
null,
true,
null
);
this.blocks.push(this.block);
this.blocks.forEach(block => {
if (typeof block !== 'string') {
block.assignVariableNames();
}
});
this.fragment.render(this.block, null, 'nodes');
}
}

@ -3,28 +3,8 @@ import { stringify, escape } from '../../utils/stringify';
import CodeBuilder from '../../utils/CodeBuilder';
import globalWhitelist from '../../utils/globalWhitelist';
import Component from '../Component';
import Stylesheet from '../../css/Stylesheet';
import Stats from '../../Stats';
import Block from './Block';
import { Ast, CompileOptions } from '../../interfaces';
export class DomTarget {
blocks: (Block|string)[];
readonly: Set<string>;
metaBindings: string[];
hasIntroTransitions: boolean;
hasOutroTransitions: boolean;
hasComplexBindings: boolean;
constructor() {
this.blocks = [];
this.readonly = new Set();
// initial values for e.g. window.innerWidth, if there's a <svelte:window> meta tag
this.metaBindings = [];
}
}
import Renderer from './Renderer';
import { CompileOptions } from '../../interfaces';
export default function dom(
component: Component,
@ -38,8 +18,9 @@ export default function dom(
templateProperties
} = component;
component.fragment.build();
const { block } = component.fragment;
const renderer = new Renderer(component, options);
const { block } = renderer;
if (component.options.nestedTransitions) {
block.hasOutroMethod = true;
@ -54,14 +35,14 @@ export default function dom(
if (computations.length) {
computations.forEach(({ key, deps, hasRestParam }) => {
if (component.target.readonly.has(key)) {
if (renderer.readonly.has(key)) {
// <svelte:window> bindings
throw new Error(
`Cannot have a computed value '${key}' that clashes with a read-only property`
);
}
component.target.readonly.add(key);
renderer.readonly.add(key);
if (deps) {
deps.forEach(dep => {
@ -98,7 +79,7 @@ export default function dom(
}
if (component.options.dev) {
builder.addLine(`const ${component.fileVar} = ${JSON.stringify(component.file)};`);
builder.addLine(`const ${renderer.fileVar} = ${JSON.stringify(component.file)};`);
}
const css = component.stylesheet.render(options.filename, !component.customElement);
@ -117,7 +98,11 @@ export default function dom(
`);
}
component.target.blocks.forEach(block => {
// fix order
// TODO the deconflicted names of blocks are reversed... should set them here
const blocks = renderer.blocks.slice().reverse();
blocks.forEach(block => {
builder.addBlock(block.toString());
});
@ -169,7 +154,7 @@ export default function dom(
${component.refs.size > 0 && `this.refs = {};`}
this._state = ${initialState.reduce((state, piece) => `@assign(${state}, ${piece})`)};
${storeProps.length > 0 && `this.store._add(this, [${storeProps.map(prop => `"${prop.slice(1)}"`)}]);`}
${component.target.metaBindings}
${renderer.metaBindings}
${computations.length && `this._recompute({ ${Array.from(computationDeps).map(dep => `${dep}: 1`).join(', ')} }, this._state);`}
${options.dev &&
Array.from(component.expectedProperties).map(prop => {
@ -185,8 +170,8 @@ export default function dom(
return `if (${conditions.join(' && ')}) console.warn("${message}");`
})}
${component.bindingGroups.length &&
`this._bindingGroups = [${Array(component.bindingGroups.length).fill('[]').join(', ')}];`}
${renderer.bindingGroups.length &&
`this._bindingGroups = [${Array(renderer.bindingGroups.length).fill('[]').join(', ')}];`}
this._intro = ${component.options.skipIntroByDefault ? '!!options.intro' : 'true'};
${templateProperties.onstate && `this._handlers.state = [%onstate];`}
@ -198,7 +183,7 @@ export default function dom(
}];`
)}
${component.slots.size && `this._slotted = options.slots || {};`}
${renderer.slots.size && `this._slotted = options.slots || {};`}
${component.customElement ?
deindent`
@ -238,7 +223,7 @@ export default function dom(
this._fragment.c();`}
this._mount(options.target, options.anchor);
${(component.hasComponents || component.target.hasComplexBindings || hasInitHooks || component.target.hasIntroTransitions) &&
${(component.hasComponents || renderer.hasComplexBindings || hasInitHooks || renderer.hasIntroTransitions) &&
`@flush(this);`}
}
`}
@ -270,7 +255,7 @@ export default function dom(
}
`).join('\n\n')}
${component.slots.size && deindent`
${renderer.slots.size && deindent`
connectedCallback() {
Object.keys(this._slotted).forEach(key => {
this.appendChild(this._slotted[key]);
@ -281,7 +266,7 @@ export default function dom(
this.set({ [attr]: newValue });
}
${(component.hasComponents || component.target.hasComplexBindings || templateProperties.oncreate || component.target.hasIntroTransitions) && deindent`
${(component.hasComponents || renderer.hasComplexBindings || templateProperties.oncreate || renderer.hasIntroTransitions) && deindent`
connectedCallback() {
@flush(this);
}
@ -314,7 +299,7 @@ export default function dom(
builder.addBlock(deindent`
${options.dev && deindent`
${name}.prototype._checkReadOnly = function _checkReadOnly(newState) {
${Array.from(component.target.readonly).map(
${Array.from(renderer.readonly).map(
prop =>
`if ('${prop}' in newState && !this._updatingReadonlyProperty) throw new Error("${debugName}: Cannot set read-only property '${prop}'");`
)}

@ -0,0 +1,230 @@
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 FragmentWrapper from './Fragment';
import PendingBlock from '../../nodes/PendingBlock';
import ThenBlock from '../../nodes/ThenBlock';
import CatchBlock from '../../nodes/CatchBlock';
class AwaitBlockBranch extends Wrapper {
node: PendingBlock | ThenBlock | CatchBlock;
block: Block;
fragment: FragmentWrapper;
isDynamic: boolean;
var = null;
constructor(
status: string,
renderer: Renderer,
block: Block,
parent: Wrapper,
node: AwaitBlock,
stripWhitespace: boolean,
nextSibling: Wrapper
) {
super(renderer, block, parent, node);
this.block = block.child({
comment: createDebuggingComment(node, this.renderer.component),
name: this.renderer.component.getUniqueName(`create_${status}_block`)
});
this.fragment = new FragmentWrapper(
renderer,
this.block,
this.node.children,
parent,
stripWhitespace,
nextSibling
);
this.isDynamic = this.block.dependencies.size > 0;
}
}
export default class AwaitBlockWrapper extends Wrapper {
node: AwaitBlock;
pending: AwaitBlockBranch;
then: AwaitBlockBranch;
catch: AwaitBlockBranch;
var = 'await_block';
constructor(
renderer: Renderer,
block: Block,
parent: Wrapper,
node: AwaitBlock,
stripWhitespace: boolean,
nextSibling: Wrapper
) {
super(renderer, block, parent, node);
this.cannotUseInnerHTML();
block.addDependencies(this.node.expression.dependencies);
let isDynamic = false;
let hasIntros = false;
let hasOutros = false;
['pending', 'then', 'catch'].forEach(status => {
const child = this.node[status];
const branch = new AwaitBlockBranch(
status,
renderer,
block,
parent,
child,
stripWhitespace,
nextSibling
);
renderer.blocks.push(branch.block);
if (branch.isDynamic) {
isDynamic = true;
// TODO should blocks update their own parents?
block.addDependencies(branch.block.dependencies);
}
if (branch.block.hasIntros) hasIntros = true;
if (branch.block.hasOutros) hasOutros = true;
this[status] = branch;
});
this.pending.block.hasUpdateMethod = isDynamic;
this.then.block.hasUpdateMethod = isDynamic;
this.catch.block.hasUpdateMethod = isDynamic;
this.pending.block.hasIntroMethod = hasIntros;
this.then.block.hasIntroMethod = hasIntros;
this.catch.block.hasIntroMethod = hasIntros;
this.pending.block.hasOutroMethod = hasOutros;
this.then.block.hasOutroMethod = hasOutros;
this.catch.block.hasOutroMethod = hasOutros;
if (hasOutros && this.renderer.options.nestedTransitions) {
block.addOutro();
}
}
render(
block: Block,
parentNode: string,
parentNodes: string
) {
const anchor = this.getOrCreateAnchor(block, parentNode, parentNodes);
const updateMountNode = this.getUpdateMountNode(anchor);
const { snippet } = this.node.expression;
const info = block.getUniqueName(`info`);
const promise = block.getUniqueName(`promise`);
block.addVariable(promise);
block.maintainContext = true;
const infoProps = [
block.alias('component') === 'component' ? 'component' : `component: #component`,
'ctx',
'current: null',
this.pending.block.name && `pending: ${this.pending.block.name}`,
this.then.block.name && `then: ${this.then.block.name}`,
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)`
].filter(Boolean);
block.builders.init.addBlock(deindent`
let ${info} = {
${infoProps.join(',\n')}
};
`);
block.builders.init.addBlock(deindent`
@handlePromise(${promise} = ${snippet}, ${info});
`);
block.builders.create.addBlock(deindent`
${info}.block.c();
`);
if (parentNodes) {
block.builders.claim.addBlock(deindent`
${info}.block.l(${parentNodes});
`);
}
const initialMountNode = parentNode || '#target';
const anchorNode = parentNode ? 'null' : 'anchor';
const hasTransitions = this.pending.block.hasIntroMethod || this.pending.block.hasOutroMethod;
block.builders.mount.addBlock(deindent`
${info}.block.${hasTransitions ? 'i' : 'm'}(${initialMountNode}, ${info}.anchor = ${anchorNode});
${info}.mount = () => ${updateMountNode};
`);
const conditions = [];
if (this.node.expression.dependencies.size > 0) {
conditions.push(
`(${[...this.node.expression.dependencies].map(dep => `'${dep}' in changed`).join(' || ')})`
);
}
conditions.push(
`${promise} !== (${promise} = ${snippet})`,
`@handlePromise(${promise}, ${info})`
);
block.builders.update.addLine(
`${info}.ctx = ctx;`
);
if (this.pending.block.hasUpdateMethod) {
block.builders.update.addBlock(deindent`
if (${conditions.join(' && ')}) {
// nothing
} else {
${info}.block.p(changed, @assign(@assign({}, ctx), ${info}.resolved));
}
`);
} else {
block.builders.update.addBlock(deindent`
${conditions.join(' && ')}
`);
}
if (this.pending.block.hasOutroMethod && this.renderer.options.nestedTransitions) {
const countdown = block.getUniqueName('countdown');
block.builders.outro.addBlock(deindent`
const ${countdown} = @callAfter(#outrocallback, 3);
for (let #i = 0; #i < 3; #i += 1) {
const block = ${info}.blocks[#i];
if (block) block.o(${countdown});
else ${countdown}();
}
`);
}
block.builders.destroy.addBlock(deindent`
${info}.block.d(${parentNode ? '' : 'detach'});
${info} = null;
`);
[this.pending, this.then, this.catch].forEach(branch => {
branch.fragment.render(branch.block, null, 'nodes');
});
}
}

@ -0,0 +1,72 @@
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';
export default class DebugTagWrapper extends Wrapper {
node: DebugTag;
constructor(
renderer: Renderer,
block: Block,
parent: Wrapper,
node: DebugTag,
stripWhitespace: boolean,
nextSibling: Wrapper
) {
super(renderer, block, parent, node);
}
render(block: Block, parentNode: string, parentNodes: string) {
const { renderer } = this;
const { component } = renderer;
if (!renderer.options.dev) return;
const { code } = component;
if (this.node.expressions.length === 0) {
// Debug all
code.overwrite(this.node.start + 1, this.node.start + 7, 'debugger', {
storeName: true
});
const statement = `[✂${this.node.start + 1}-${this.node.start + 7}✂];`;
block.builders.create.addLine(statement);
block.builders.update.addLine(statement);
} else {
const { code } = component;
code.overwrite(this.node.start + 1, this.node.start + 7, 'log', {
storeName: true
});
const log = `[✂${this.node.start + 1}-${this.node.start + 7}✂]`;
const dependencies = new Set();
this.node.expressions.forEach(expression => {
addToSet(dependencies, expression.dependencies);
});
const condition = [...dependencies].map(d => `changed.${d}`).join(' || ');
const identifiers = this.node.expressions.map(e => e.node.name).join(', ');
block.builders.update.addBlock(deindent`
if (${condition}) {
const { ${identifiers} } = ctx;
console.${log}({ ${identifiers} });
debugger;
}
`);
block.builders.create.addBlock(deindent`
{
const { ${identifiers} } = ctx;
console.${log}({ ${identifiers} });
debugger;
}
`);
}
}
}

@ -0,0 +1,503 @@
import Renderer from '../Renderer';
import Block from '../Block';
import Node from '../../nodes/shared/Node';
import Wrapper from './shared/Wrapper';
import createDebuggingComment from '../../../utils/createDebuggingComment';
import EachBlock from '../../nodes/EachBlock';
import FragmentWrapper from './Fragment';
import deindent from '../../../utils/deindent';
import ElseBlock from '../../nodes/ElseBlock';
class ElseBlockWrapper extends Wrapper {
node: ElseBlock;
block: Block;
fragment: FragmentWrapper;
isDynamic: boolean;
var = null;
constructor(
renderer: Renderer,
block: Block,
parent: Wrapper,
node: ElseBlock,
stripWhitespace: boolean,
nextSibling: Wrapper
) {
super(renderer, block, parent, node);
this.block = block.child({
comment: createDebuggingComment(node, this.renderer.component),
name: this.renderer.component.getUniqueName(`create_else_block`)
});
this.fragment = new FragmentWrapper(
renderer,
this.block,
this.node.children,
parent,
stripWhitespace,
nextSibling
);
this.isDynamic = this.block.dependencies.size > 0;
if (this.isDynamic) {
// TODO this can't be right
this.block.hasUpdateMethod = true;
}
}
}
export default class EachBlockWrapper extends Wrapper {
block: Block;
node: EachBlock;
fragment: FragmentWrapper;
else?: ElseBlockWrapper;
var: string;
vars: {
anchor: string;
create_each_block: string;
each_block_value: string;
get_each_context: string;
iterations: string;
length: string;
mountOrIntro: string;
}
constructor(
renderer: Renderer,
block: Block,
parent: Wrapper,
node: EachBlock,
stripWhitespace: boolean,
nextSibling: Wrapper
) {
super(renderer, block, parent, node);
this.cannotUseInnerHTML();
this.var = 'each';
const { dependencies } = node.expression;
block.addDependencies(dependencies);
this.block = block.child({
comment: createDebuggingComment(this.node, this.renderer.component),
name: renderer.component.getUniqueName('create_each_block'),
key: <string>node.key, // TODO...
bindings: new Map(block.bindings)
});
// TODO this seems messy
this.block.hasAnimation = this.node.hasAnimation;
this.indexName = this.node.index || renderer.component.getUniqueName(`${this.node.context}_index`);
node.contexts.forEach(prop => {
// TODO this doesn't feel great
this.block.bindings.set(prop.key.name, () => `ctx.${this.vars.each_block_value}[ctx.${this.indexName}]${prop.tail}`);
});
if (this.node.index) {
this.block.getUniqueName(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);
if (this.node.else) {
this.else = new ElseBlockWrapper(
renderer,
block,
this,
this.node.else,
stripWhitespace,
nextSibling
);
renderer.blocks.push(this.else.block);
if (this.else.isDynamic) {
this.block.addDependencies(this.else.block.dependencies);
}
}
block.addDependencies(this.block.dependencies);
this.block.hasUpdateMethod = this.block.dependencies.size > 0; // TODO should this logic be in Block?
if (this.block.hasOutros || (this.else && this.else.block.hasOutros)) {
block.addOutro();
}
}
render(block: Block, parentNode: string, parentNodes: string) {
if (this.fragment.nodes.length === 0) return;
const { renderer } = this;
const { component } = renderer;
// hack the sourcemap, so that if data is missing the bug
// is easy to find
let c = this.node.start + 2;
while (component.source[c] !== 'e') c += 1;
component.code.overwrite(c, c + 4, 'length');
const length = `[✂${c}-${c+4}✂]`;
const needsAnchor = this.next
? !this.next.isDomNode() :
!parentNode || !this.parent.isDomNode();
this.vars = {
anchor: needsAnchor
? block.getUniqueName(`${this.var}_anchor`)
: (this.next && this.next.var) || 'null',
create_each_block: this.block.name,
each_block_value: renderer.component.getUniqueName(`${this.var}_value`),
get_each_context: renderer.component.getUniqueName(`get_${this.var}_context`),
iterations: block.getUniqueName(`${this.var}_blocks`),
length: `[✂${c}-${c+4}✂]`,
mountOrIntro: (this.block.hasIntroMethod || this.block.hasOutroMethod)
? 'i'
: 'm'
};
this.contextProps = this.node.contexts.map(prop => `child_ctx.${prop.key.name} = list[i]${prop.tail};`);
// TODO only add these if necessary
this.contextProps.push(
`child_ctx.${this.vars.each_block_value} = list;`,
`child_ctx.${this.indexName} = i;`
);
const { snippet } = this.node.expression;
block.builders.init.addLine(`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}
return child_ctx;
}
`);
if (this.node.key) {
this.renderKeyed(block, parentNode, parentNodes, snippet);
} else {
this.renderUnkeyed(block, parentNode, parentNodes, snippet);
}
if (needsAnchor) {
block.addElement(
this.vars.anchor,
`@createComment()`,
parentNodes && `@createComment()`,
parentNode
);
}
if (this.else) {
const each_block_else = component.getUniqueName(`${this.var}_else`);
const mountOrIntro = (this.else.block.hasIntroMethod || this.else.block.hasOutroMethod) ? 'i' : 'm';
block.builders.init.addLine(`var ${each_block_else} = null;`);
// TODO neaten this up... will end up with an empty line in the block
block.builders.init.addBlock(deindent`
if (!${this.vars.each_block_value}.${length}) {
${each_block_else} = ${this.else.block.name}(#component, ctx);
${each_block_else}.c();
}
`);
block.builders.mount.addBlock(deindent`
if (${each_block_else}) {
${each_block_else}.${mountOrIntro}(${parentNode || '#target'}, null);
}
`);
const initialMountNode = parentNode || `${this.vars.anchor}.parentNode`;
if (this.else.block.hasUpdateMethod) {
block.builders.update.addBlock(deindent`
if (!${this.vars.each_block_value}.${length} && ${each_block_else}) {
${each_block_else}.p(changed, ctx);
} else if (!${this.vars.each_block_value}.${length}) {
${each_block_else} = ${this.else.block.name}(#component, ctx);
${each_block_else}.c();
${each_block_else}.${mountOrIntro}(${initialMountNode}, ${this.vars.anchor});
} else if (${each_block_else}) {
${each_block_else}.d(1);
${each_block_else} = null;
}
`);
} else {
block.builders.update.addBlock(deindent`
if (${this.vars.each_block_value}.${length}) {
if (${each_block_else}) {
${each_block_else}.d(1);
${each_block_else} = null;
}
} else if (!${each_block_else}) {
${each_block_else} = ${this.else.block.name}(#component, ctx);
${each_block_else}.c();
${each_block_else}.${mountOrIntro}(${initialMountNode}, ${this.vars.anchor});
}
`);
}
block.builders.destroy.addBlock(deindent`
if (${each_block_else}) ${each_block_else}.d(${parentNode ? '' : 'detach'});
`);
}
this.fragment.render(this.block, null, 'nodes');
if (this.else) {
this.else.fragment.render(this.else.block, null, 'nodes');
}
}
renderKeyed(
block: Block,
parentNode: string,
parentNodes: string,
snippet: string
) {
const {
create_each_block,
length,
anchor,
mountOrIntro,
} = this.vars;
const get_key = block.getUniqueName('get_key');
const blocks = block.getUniqueName(`${this.var}_blocks`);
const lookup = block.getUniqueName(`${this.var}_lookup`);
block.addVariable(blocks, '[]');
block.addVariable(lookup, `@blankObject()`);
if (this.fragment.nodes[0].isDomNode()) {
this.block.first = this.fragment.nodes[0].var;
} else {
this.block.first = this.block.getUniqueName('first');
this.block.addElement(
this.block.first,
`@createComment()`,
parentNodes && `@createComment()`,
null
);
}
block.builders.init.addBlock(deindent`
const ${get_key} = ctx => ${this.node.key.snippet};
for (var #i = 0; #i < ${this.vars.each_block_value}.${length}; #i += 1) {
let child_ctx = ${this.vars.get_each_context}(ctx, ${this.vars.each_block_value}, #i);
let key = ${get_key}(child_ctx);
${blocks}[#i] = ${lookup}[key] = ${create_each_block}(#component, key, child_ctx);
}
`);
const initialMountNode = parentNode || '#target';
const updateMountNode = this.getUpdateMountNode(anchor);
const anchorNode = parentNode ? 'null' : 'anchor';
block.builders.create.addBlock(deindent`
for (#i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].c();
`);
if (parentNodes) {
block.builders.claim.addBlock(deindent`
for (#i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].l(${parentNodes});
`);
}
block.builders.mount.addBlock(deindent`
for (#i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].${mountOrIntro}(${initialMountNode}, ${anchorNode});
`);
const dynamic = this.block.hasUpdateMethod;
const rects = block.getUniqueName('rects');
const destroy = this.node.hasAnimation
? `@fixAndOutroAndDestroyBlock`
: this.block.hasOutros
? `@outroAndDestroyBlock`
: `@destroyBlock`;
block.builders.update.addBlock(deindent`
const ${this.vars.each_block_value} = ${snippet};
${this.block.hasOutros && `@groupOutros();`}
${this.node.hasAnimation && `for (let #i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].r();`}
${blocks} = @updateKeyedEach(${blocks}, #component, changed, ${get_key}, ${dynamic ? '1' : '0'}, ctx, ${this.vars.each_block_value}, ${lookup}, ${updateMountNode}, ${destroy}, ${create_each_block}, "${mountOrIntro}", ${anchor}, ${this.vars.get_each_context});
${this.node.hasAnimation && `for (let #i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].a();`}
`);
if (this.block.hasOutros && this.renderer.component.options.nestedTransitions) {
const countdown = block.getUniqueName('countdown');
block.builders.outro.addBlock(deindent`
const ${countdown} = @callAfter(#outrocallback, ${blocks}.length);
for (#i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].o(${countdown});
`);
}
block.builders.destroy.addBlock(deindent`
for (#i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].d(${parentNode ? '' : 'detach'});
`);
}
renderUnkeyed(
block: Block,
parentNode: string,
parentNodes: string,
snippet: string
) {
const {
create_each_block,
length,
iterations,
anchor,
mountOrIntro,
} = this.vars;
block.builders.init.addBlock(deindent`
var ${iterations} = [];
for (var #i = 0; #i < ${this.vars.each_block_value}.${length}; #i += 1) {
${iterations}[#i] = ${create_each_block}(#component, ${this.vars.get_each_context}(ctx, ${this.vars.each_block_value}, #i));
}
`);
const initialMountNode = parentNode || '#target';
const updateMountNode = this.getUpdateMountNode(anchor);
const anchorNode = parentNode ? 'null' : 'anchor';
block.builders.create.addBlock(deindent`
for (var #i = 0; #i < ${iterations}.length; #i += 1) {
${iterations}[#i].c();
}
`);
if (parentNodes) {
block.builders.claim.addBlock(deindent`
for (var #i = 0; #i < ${iterations}.length; #i += 1) {
${iterations}[#i].l(${parentNodes});
}
`);
}
block.builders.mount.addBlock(deindent`
for (var #i = 0; #i < ${iterations}.length; #i += 1) {
${iterations}[#i].${mountOrIntro}(${initialMountNode}, ${anchorNode});
}
`);
const allDependencies = new Set(this.block.dependencies);
const { dependencies } = this.node.expression;
dependencies.forEach((dependency: string) => {
allDependencies.add(dependency);
});
const outroBlock = this.block.hasOutros && block.getUniqueName('outroBlock')
if (outroBlock) {
block.builders.init.addBlock(deindent`
function ${outroBlock}(i, detach, fn) {
if (${iterations}[i]) {
${iterations}[i].o(() => {
if (detach) {
${iterations}[i].d(detach);
${iterations}[i] = null;
}
if (fn) fn();
});
}
}
`);
}
// TODO do this for keyed blocks as well
const condition = Array.from(allDependencies)
.map(dependency => `changed.${dependency}`)
.join(' || ');
if (condition !== '') {
const forLoopBody = this.block.hasUpdateMethod
? (this.block.hasIntros || this.block.hasOutros)
? deindent`
if (${iterations}[#i]) {
${iterations}[#i].p(changed, child_ctx);
} else {
${iterations}[#i] = ${create_each_block}(#component, child_ctx);
${iterations}[#i].c();
}
${iterations}[#i].i(${updateMountNode}, ${anchor});
`
: deindent`
if (${iterations}[#i]) {
${iterations}[#i].p(changed, child_ctx);
} else {
${iterations}[#i] = ${create_each_block}(#component, child_ctx);
${iterations}[#i].c();
${iterations}[#i].m(${updateMountNode}, ${anchor});
}
`
: deindent`
${iterations}[#i] = ${create_each_block}(#component, child_ctx);
${iterations}[#i].c();
${iterations}[#i].${mountOrIntro}(${updateMountNode}, ${anchor});
`;
const start = this.block.hasUpdateMethod ? '0' : `${iterations}.length`;
let destroy;
if (this.block.hasOutros) {
destroy = deindent`
@groupOutros();
for (; #i < ${iterations}.length; #i += 1) ${outroBlock}(#i, 1);
`;
} else {
destroy = deindent`
for (; #i < ${iterations}.length; #i += 1) {
${iterations}[#i].d(1);
}
${iterations}.length = ${this.vars.each_block_value}.${length};
`;
}
block.builders.update.addBlock(deindent`
if (${condition}) {
${this.vars.each_block_value} = ${snippet};
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}
}
${destroy}
}
`);
}
if (outroBlock && this.renderer.component.options.nestedTransitions) {
const countdown = block.getUniqueName('countdown');
block.builders.outro.addBlock(deindent`
${iterations} = ${iterations}.filter(Boolean);
const ${countdown} = @callAfter(#outrocallback, ${iterations}.length);
for (let #i = 0; #i < ${iterations}.length; #i += 1) ${outroBlock}(#i, 0, ${countdown});`
);
}
block.builders.destroy.addBlock(`@destroyEach(${iterations}, detach);`);
}
remount(name: string) {
// TODO consider keyed blocks
return `for (var #i = 0; #i < ${this.vars.iterations}.length; #i += 1) ${this.vars.iterations}[#i].m(${name}._slotted.default, null);`;
}
}

@ -0,0 +1,456 @@
import Attribute from '../../../nodes/Attribute';
import Block from '../../Block';
import fixAttributeCasing from '../../../../utils/fixAttributeCasing';
import ElementWrapper from './index';
import { stringify } from '../../../../utils/stringify';
import deindent from '../../../../utils/deindent';
export default class AttributeWrapper {
node: Attribute;
parent: ElementWrapper;
constructor(parent: ElementWrapper, block: Block, node: Attribute) {
this.node = node;
this.parent = parent;
if (node.dependencies.size > 0) {
parent.cannotUseInnerHTML();
block.addDependencies(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 => {
this.node.dependencies.forEach((dependency: string) => {
this.parent.renderer.component.indirectDependencies.get(prop).add(dependency);
});
});
}
}
}
}
render(block: Block) {
const element = this.parent;
const name = fixAttributeCasing(this.node.name);
let metadata = element.node.namespace ? null : attributeLookup[name];
if (metadata && metadata.appliesTo && !~metadata.appliesTo.indexOf(element.node.name))
metadata = null;
const isIndirectlyBoundValue =
name === 'value' &&
(element.node.name === 'option' || // TODO check it's actually bound
(element.node.name === 'input' &&
element.node.bindings.find(
(binding: Binding) =>
/checked|group/.test(binding.name)
)));
const propertyName = isIndirectlyBoundValue
? '__value'
: metadata && metadata.propertyName;
// 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'
: name.slice(0, 6) === 'xlink:'
? '@setXlinkAttribute'
: '@setAttribute';
const isLegacyInputType = element.renderer.component.options.legacy && name === 'type' && this.parent.node.name === 'input';
const isDataSet = /^data-/.test(name) && !element.renderer.component.options.legacy && !element.node.namespace;
const camelCaseName = isDataSet ? name.replace('data-', '').replace(/(-\w)/g, function (m) {
return m[1].toUpperCase();
}) : name;
if (this.node.isDynamic) {
let value;
// 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
if (this.node.chunks.length === 1) {
// single {tag} — may be a non-string
value = this.node.chunks[0].snippet;
} else {
// '{foo} {bar}' — treat as string concatenation
value =
(this.node.chunks[0].type === 'Text' ? '' : `"" + `) +
this.node.chunks
.map((chunk: Node) => {
if (chunk.type === 'Text') {
return stringify(chunk.data);
} else {
return chunk.getPrecedence() <= 13
? `(${chunk.snippet})`
: chunk.snippet;
}
})
.join(' + ');
}
const isSelectValueAttribute =
name === 'value' && element.node.name === 'select';
const shouldCache = this.node.shouldCache || isSelectValueAttribute;
const last = shouldCache && block.getUniqueName(
`${element.var}_${name.replace(/[^a-zA-Z_$]/g, '_')}_value`
);
if (shouldCache) block.addVariable(last);
let updater;
const init = shouldCache ? `${last} = ${value}` : value;
if (isLegacyInputType) {
block.builders.hydrate.addLine(
`@setInputType(${element.var}, ${init});`
);
updater = `@setInputType(${element.var}, ${shouldCache ? last : value});`;
} else if (isSelectValueAttribute) {
// annoying special case
const isMultipleSelect = element.getStaticAttributeValue('multiple');
const i = block.getUniqueName('i');
const option = block.getUniqueName('option');
const ifStatement = isMultipleSelect
? deindent`
${option}.selected = ~${last}.indexOf(${option}.__value);`
: deindent`
if (${option}.__value === ${last}) {
${option}.selected = true;
break;
}`;
updater = deindent`
for (var ${i} = 0; ${i} < ${element.var}.options.length; ${i} += 1) {
var ${option} = ${element.var}.options[${i}];
${ifStatement}
}
`;
block.builders.mount.addBlock(deindent`
${last} = ${value};
${updater}
`);
} else if (propertyName) {
block.builders.hydrate.addLine(
`${element.var}.${propertyName} = ${init};`
);
updater = `${element.var}.${propertyName} = ${shouldCache ? last : value};`;
} else if (isDataSet) {
block.builders.hydrate.addLine(
`${element.var}.dataset.${camelCaseName} = ${init};`
);
updater = `${element.var}.dataset.${camelCaseName} = ${shouldCache ? last : value};`;
} else {
block.builders.hydrate.addLine(
`${method}(${element.var}, "${name}", ${init});`
);
updater = `${method}(${element.var}, "${name}", ${shouldCache ? last : value});`;
}
if (this.node.dependencies.size || isSelectValueAttribute) {
const dependencies = Array.from(this.node.dependencies);
const changedCheck = (
(block.hasOutros ? `!#current || ` : '') +
dependencies.map(dependency => `changed.${dependency}`).join(' || ')
);
const updateCachedValue = `${last} !== (${last} = ${value})`;
const condition = shouldCache ?
( dependencies.length ? `(${changedCheck}) && ${updateCachedValue}` : updateCachedValue ) :
changedCheck;
block.builders.update.addConditional(
condition,
updater
);
}
} else {
const value = this.node.getValue();
const statement = (
isLegacyInputType
? `@setInputType(${element.var}, ${value});`
: propertyName
? `${element.var}.${propertyName} = ${value};`
: isDataSet
? `${element.var}.dataset.${camelCaseName} = ${value};`
: `${method}(${element.var}, "${name}", ${value});`
);
block.builders.hydrate.addLine(statement);
// special case autofocus. has to be handled in a bit of a weird way
if (this.node.isTrue && name === 'autofocus') {
block.autofocus = element.var;
}
}
if (isIndirectlyBoundValue) {
const updateValue = `${element.var}.value = ${element.var}.__value;`;
block.builders.hydrate.addLine(updateValue);
if (this.node.isDynamic) block.builders.update.addLine(updateValue);
}
}
stringify() {
const value = this.node.chunks;
if (value === true) return '';
if (value.length === 0) return `=""`;
return `="${value.map(chunk => {
return chunk.type === 'Text'
? chunk.data.replace(/"/g, '\\"')
: `\${${chunk.snippet}}`
})}"`;
}
}
// 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'] },
align: {
appliesTo: [
'applet',
'caption',
'col',
'colgroup',
'hr',
'iframe',
'img',
'table',
'tbody',
'td',
'tfoot',
'th',
'thead',
'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'] },
bgcolor: {
propertyName: 'bgColor',
appliesTo: [
'body',
'col',
'colgroup',
'marquee',
'table',
'tbody',
'tfoot',
'td',
'th',
'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' },
contextmenu: {},
controls: { appliesTo: ['audio', 'video'] },
coords: { appliesTo: ['area'] },
data: { appliesTo: ['object'] },
datetime: { propertyName: 'dateTime', appliesTo: ['del', 'ins', 'time'] },
default: { appliesTo: ['track'] },
defer: { appliesTo: ['script'] },
dir: {},
dirname: { propertyName: 'dirName', appliesTo: ['input', 'textarea'] },
disabled: {
appliesTo: [
'button',
'command',
'fieldset',
'input',
'keygen',
'optgroup',
'option',
'select',
'textarea',
],
},
download: { appliesTo: ['a', 'area'] },
draggable: {},
dropzone: {},
enctype: { appliesTo: ['form'] },
for: { propertyName: 'htmlFor', appliesTo: ['label', 'output'] },
form: {
appliesTo: [
'button',
'fieldset',
'input',
'keygen',
'label',
'meter',
'object',
'output',
'progress',
'select',
'textarea',
],
},
formaction: { appliesTo: ['input', 'button'] },
headers: { appliesTo: ['td', 'th'] },
height: {
appliesTo: ['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'] },
id: {},
indeterminate: { appliesTo: ['input'] },
ismap: { propertyName: 'isMap', appliesTo: ['img'] },
itemprop: {},
keytype: { appliesTo: ['keygen'] },
kind: { appliesTo: ['track'] },
label: { appliesTo: ['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'] },
name: {
appliesTo: [
'button',
'form',
'fieldset',
'iframe',
'input',
'keygen',
'object',
'output',
'select',
'textarea',
'map',
'meta',
'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'] },
spellcheck: {},
src: {
appliesTo: [
'audio',
'embed',
'iframe',
'img',
'input',
'script',
'source',
'track',
'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'] },
title: {},
type: {
appliesTo: [
'button',
'command',
'embed',
'object',
'script',
'source',
'style',
'menu',
],
},
usemap: { propertyName: 'useMap', appliesTo: ['img', 'input', 'object'] },
value: {
appliesTo: [
'button',
'option',
'input',
'li',
'meter',
'progress',
'param',
'select',
'textarea',
],
},
volume: { appliesTo: ['audio', 'video'] },
width: {
appliesTo: ['canvas', 'embed', 'iframe', 'img', 'input', 'object', 'video'],
},
wrap: { appliesTo: ['textarea'] },
};
Object.keys(attributeLookup).forEach(name => {
const metadata = attributeLookup[name];
if (!metadata.propertyName) metadata.propertyName = name;
});

@ -0,0 +1,324 @@
import Binding from '../../../nodes/Binding';
import ElementWrapper from '.';
import { dimensions } from '../../../../utils/patterns';
import getObject from '../../../../utils/getObject';
import Block from '../../Block';
import Node from '../../../nodes/shared/Node';
import Renderer from '../../Renderer';
import flattenReference from '../../../../utils/flattenReference';
import getTailSnippet from '../../../../utils/getTailSnippet';
// TODO this should live in a specific binding
const readOnlyMediaAttributes = new Set([
'duration',
'buffered',
'seekable',
'played'
]);
export default class BindingWrapper {
node: Binding;
parent: ElementWrapper;
object: string;
handler: any; // TODO
updateDom: string;
initialUpdate: string;
needsLock: boolean;
updateCondition: string;
constructor(block: Block, node: Binding, parent: ElementWrapper) {
this.node = node;
this.parent = parent;
const { dependencies } = this.node.value;
block.addDependencies(dependencies);
// TODO does this also apply to e.g. `<input type='checkbox' bind:group='foo'>`?
if (parent.node.name === 'select') {
parent.selectBindingDependencies = dependencies;
dependencies.forEach((prop: string) => {
parent.renderer.component.indirectDependencies.set(prop, new Set());
});
}
}
isReadOnlyMediaAttribute() {
return readOnlyMediaAttributes.has(this.node.name);
}
munge(block: Block) {
const { parent } = this;
const { renderer } = parent;
const needsLock = (
parent.node.name !== 'input' ||
!/radio|checkbox|range|color/.test(parent.node.getStaticAttributeValue('type'))
);
const isReadOnly = (
(parent.node.isMediaNode() && readOnlyMediaAttributes.has(this.node.name)) ||
dimensions.test(this.node.name)
);
let updateConditions: string[] = [];
const { name } = getObject(this.node.value.node);
const { snippet } = this.node.value;
// special case: if you have e.g. `<input type=checkbox bind:checked=selected.done>`
// and `selected` is an object chosen with a <select>, then when `checked` changes,
// we need to tell the component to update all the values `selected` might be
// pointing to
// TODO should this happen in preprocess?
const dependencies = new Set(this.node.value.dependencies);
this.node.value.dependencies.forEach((prop: string) => {
const indirectDependencies = renderer.component.indirectDependencies.get(prop);
if (indirectDependencies) {
indirectDependencies.forEach(indirectDependency => {
dependencies.add(indirectDependency);
});
}
});
// view to model
const valueFromDom = getValueFromDom(renderer, this.parent, this);
const handler = getEventHandler(this, renderer, block, name, snippet, dependencies, valueFromDom);
// model to view
let updateDom = getDomUpdater(parent, this, snippet);
let initialUpdate = updateDom;
// special cases
if (this.node.name === 'group') {
const bindingGroup = getBindingGroup(renderer, this.node.value.node);
block.builders.hydrate.addLine(
`#component._bindingGroups[${bindingGroup}].push(${parent.var});`
);
block.builders.destroy.addLine(
`#component._bindingGroups[${bindingGroup}].splice(#component._bindingGroups[${bindingGroup}].indexOf(${parent.var}), 1);`
);
}
if (this.node.name === 'currentTime' || this.node.name === 'volume') {
updateConditions.push(`!isNaN(${snippet})`);
if (this.node.name === 'currentTime') initialUpdate = null;
}
if (this.node.name === 'paused') {
// this is necessary to prevent audio restarting by itself
const last = block.getUniqueName(`${parent.var}_is_paused`);
block.addVariable(last, 'true');
updateConditions.push(`${last} !== (${last} = ${snippet})`);
updateDom = `${parent.var}[${last} ? "pause" : "play"]();`;
initialUpdate = null;
}
// bind:offsetWidth and bind:offsetHeight
if (dimensions.test(this.node.name)) {
initialUpdate = null;
updateDom = null;
}
const dependencyArray = [...this.node.value.dependencies]
if (dependencyArray.length === 1) {
updateConditions.push(`changed.${dependencyArray[0]}`)
} else if (dependencyArray.length > 1) {
updateConditions.push(
`(${dependencyArray.map(prop => `changed.${prop}`).join(' || ')})`
)
}
return {
name: this.node.name,
object: name,
handler: handler,
usesContext: handler.usesContext,
updateDom: updateDom,
initialUpdate: initialUpdate,
needsLock: !isReadOnly && needsLock,
updateCondition: updateConditions.length ? updateConditions.join(' && ') : undefined,
isReadOnlyMediaAttribute: this.isReadOnlyMediaAttribute()
};
}
}
function getDomUpdater(
element: ElementWrapper,
binding: BindingWrapper,
snippet: string
) {
const { node } = element;
if (binding.isReadOnlyMediaAttribute()) {
return null;
}
if (node.name === 'select') {
return node.getStaticAttributeValue('multiple') === true ?
`@selectOptions(${element.var}, ${snippet})` :
`@selectOption(${element.var}, ${snippet})`;
}
if (binding.node.name === 'group') {
const type = node.getStaticAttributeValue('type');
const condition = type === 'checkbox'
? `~${snippet}.indexOf(${element.var}.__value)`
: `${element.var}.__value === ${snippet}`;
return `${element.var}.checked = ${condition};`
}
return `${element.var}.${binding.node.name} = ${snippet};`;
}
function getBindingGroup(renderer: Renderer, value: Node) {
const { parts } = flattenReference(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);
if (index === -1) {
index = renderer.bindingGroups.length;
renderer.bindingGroups.push(keypath);
}
return index;
}
function getEventHandler(
binding: BindingWrapper,
renderer: Renderer,
block: Block,
name: string,
snippet: string,
dependencies: Set<string>,
value: string
) {
const storeDependencies = [...dependencies].filter(prop => prop[0] === '$').map(prop => prop.slice(1));
let dependenciesArray = [...dependencies].filter(prop => prop[0] !== '$');
if (binding.node.isContextual) {
const tail = binding.node.value.node.type === 'MemberExpression'
? getTailSnippet(binding.node.value.node)
: '';
const head = block.bindings.get(name);
return {
usesContext: true,
usesState: true,
usesStore: storeDependencies.length > 0,
mutation: `${head()}${tail} = ${value};`,
props: dependenciesArray.map(prop => `${prop}: ctx.${prop}`),
storeProps: storeDependencies.map(prop => `${prop}: $.${prop}`)
};
}
if (binding.node.value.node.type === 'MemberExpression') {
// This is a little confusing, and should probably be tidied up
// at some point. It addresses a tricky bug (#893), wherein
// Svelte tries to `set()` a computed property, which throws an
// error in dev mode. a) it's possible that we should be
// replacing computations with *their* dependencies, and b)
// we should probably populate `component.target.readonly` sooner so
// that we don't have to do the `.some()` here
dependenciesArray = dependenciesArray.filter(prop => !renderer.component.computations.some(computation => computation.key === prop));
return {
usesContext: false,
usesState: true,
usesStore: storeDependencies.length > 0,
mutation: `${snippet} = ${value}`,
props: dependenciesArray.map((prop: string) => `${prop}: ctx.${prop}`),
storeProps: storeDependencies.map(prop => `${prop}: $.${prop}`)
};
}
let props;
let storeProps;
if (name[0] === '$') {
props = [];
storeProps = [`${name.slice(1)}: ${value}`];
} else {
props = [`${name}: ${value}`];
storeProps = [];
}
return {
usesContext: false,
usesState: false,
usesStore: false,
mutation: null,
props,
storeProps
};
}
function isComputed(node: Node) {
while (node.type === 'MemberExpression') {
if (node.computed) return true;
node = node.object;
}
return false;
}
function getValueFromDom(
renderer: Renderer,
element: ElementWrapper,
binding: BindingWrapper
) {
const { node } = element;
const { name } = binding.node;
// <select bind:value='selected>
if (node.name === 'select') {
return node.getStaticAttributeValue('multiple') === true ?
`@selectMultipleValue(${element.var})` :
`@selectValue(${element.var})`;
}
const type = node.getStaticAttributeValue('type');
// <input type='checkbox' bind:group='foo'>
if (name === 'group') {
const bindingGroup = getBindingGroup(renderer, binding.node.value.node);
if (type === 'checkbox') {
return `@getBindingGroupValue(#component._bindingGroups[${bindingGroup}])`;
}
return `${element.var}.__value`;
}
// <input type='range|number' bind:value>
if (type === 'range' || type === 'number') {
return `@toNumber(${element.var}.${name})`;
}
if ((name === 'buffered' || name === 'seekable' || name === 'played')) {
return `@timeRangesToArray(${element.var}.${name})`
}
// everything else
return `${element.var}.${name}`;
}
function isComputed(node: Node) {
while (node.type === 'MemberExpression') {
if (node.computed) return true;
node = node.object;
}
return false;
}

@ -0,0 +1,178 @@
import Attribute from '../../../nodes/Attribute';
import Block from '../../Block';
import AttributeWrapper from './Attribute';
import Node from '../../../nodes/shared/Node';
import ElementWrapper from '.';
import { stringify } from '../../../../utils/stringify';
export interface StyleProp {
key: string;
value: Node[];
}
export default class StyleAttributeWrapper extends AttributeWrapper {
node: Attribute;
parent: ElementWrapper;
render(block: Block) {
const styleProps = optimizeStyle(this.node.chunks);
if (!styleProps) return super.render(block);
styleProps.forEach((prop: StyleProp) => {
let value;
if (isDynamic(prop.value)) {
const propDependencies = new Set();
let shouldCache;
value =
((prop.value.length === 1 || prop.value[0].type === 'Text') ? '' : `"" + `) +
prop.value
.map((chunk: Node) => {
if (chunk.type === 'Text') {
return stringify(chunk.data);
} else {
const { dependencies, snippet } = chunk;
dependencies.forEach(d => {
propDependencies.add(d);
});
return chunk.getPrecedence() <= 13 ? `(${snippet})` : snippet;
}
})
.join(' + ');
if (propDependencies.size) {
const dependencies = Array.from(propDependencies);
const condition = (
(block.hasOutros ? `!#current || ` : '') +
dependencies.map(dependency => `changed.${dependency}`).join(' || ')
);
block.builders.update.addConditional(
condition,
`@setStyle(${this.parent.var}, "${prop.key}", ${value});`
);
}
} else {
value = stringify(prop.value[0].data);
}
block.builders.hydrate.addLine(
`@setStyle(${this.parent.var}, "${prop.key}", ${value});`
);
});
}
}
function optimizeStyle(value: Node[]) {
const props: { key: string, value: Node[] }[] = [];
let chunks = value.slice();
while (chunks.length) {
const chunk = chunks[0];
if (chunk.type !== 'Text') return null;
const keyMatch = /^\s*([\w-]+):\s*/.exec(chunk.data);
if (!keyMatch) return null;
const key = keyMatch[1];
const offset = keyMatch.index + keyMatch[0].length;
const remainingData = chunk.data.slice(offset);
if (remainingData) {
chunks[0] = {
start: chunk.start + offset,
end: chunk.end,
type: 'Text',
data: remainingData
};
} else {
chunks.shift();
}
const result = getStyleValue(chunks);
if (!result) return null;
props.push({ key, value: result.value });
chunks = result.chunks;
}
return props;
}
function getStyleValue(chunks: Node[]) {
const value: Node[] = [];
let inUrl = false;
let quoteMark = null;
let escaped = false;
while (chunks.length) {
const chunk = chunks.shift();
if (chunk.type === 'Text') {
let c = 0;
while (c < chunk.data.length) {
const char = chunk.data[c];
if (escaped) {
escaped = false;
} else if (char === '\\') {
escaped = true;
} else if (char === quoteMark) {
quoteMark === null;
} else if (char === '"' || char === "'") {
quoteMark = char;
} else if (char === ')' && inUrl) {
inUrl = false;
} else if (char === 'u' && chunk.data.slice(c, c + 4) === 'url(') {
inUrl = true;
} else if (char === ';' && !inUrl && !quoteMark) {
break;
}
c += 1;
}
if (c > 0) {
value.push({
type: 'Text',
start: chunk.start,
end: chunk.start + c,
data: chunk.data.slice(0, c)
});
}
while (/[;\s]/.test(chunk.data[c])) c += 1;
const remainingData = chunk.data.slice(c);
if (remainingData) {
chunks.unshift({
start: chunk.start + c,
end: chunk.end,
type: 'Text',
data: remainingData
});
break;
}
}
else {
value.push(chunk);
}
}
return {
chunks,
value
};
}
function isDynamic(value: Node[]) {
return value.length > 1 || value[0].type !== 'Text';
}

@ -0,0 +1,921 @@
import Renderer from '../../Renderer';
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 FragmentWrapper from '../Fragment';
import { stringify, escapeHTML, escape } from '../../../../utils/stringify';
import TextWrapper from '../Text';
import fixAttributeCasing from '../../../../utils/fixAttributeCasing';
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';
const events = [
{
eventNames: ['input'],
filter: (node: Element, name: string) =>
node.name === 'textarea' ||
node.name === 'input' && !/radio|checkbox|range/.test(node.getStaticAttributeValue('type'))
},
{
eventNames: ['change'],
filter: (node: Element, name: string) =>
node.name === 'select' ||
node.name === 'input' && /radio|checkbox/.test(node.getStaticAttributeValue('type'))
},
{
eventNames: ['change', 'input'],
filter: (node: Element, name: string) =>
node.name === 'input' && node.getStaticAttributeValue('type') === 'range'
},
{
eventNames: ['resize'],
filter: (node: Element, name: string) =>
dimensions.test(name)
},
// media events
{
eventNames: ['timeupdate'],
filter: (node: Element, name: string) =>
node.isMediaNode() &&
(name === 'currentTime' || name === 'played')
},
{
eventNames: ['durationchange'],
filter: (node: Element, name: string) =>
node.isMediaNode() &&
name === 'duration'
},
{
eventNames: ['play', 'pause'],
filter: (node: Element, name: string) =>
node.isMediaNode() &&
name === 'paused'
},
{
eventNames: ['progress'],
filter: (node: Element, name: string) =>
node.isMediaNode() &&
name === 'buffered'
},
{
eventNames: ['loadedmetadata'],
filter: (node: Element, name: string) =>
node.isMediaNode() &&
(name === 'buffered' || name === 'seekable')
},
{
eventNames: ['volumechange'],
filter: (node: Element, name: string) =>
node.isMediaNode() &&
name === 'volume'
}
];
export default class ElementWrapper extends Wrapper {
node: Element;
fragment: FragmentWrapper;
attributes: AttributeWrapper[];
bindings: Binding[];
classDependencies: string[];
initialUpdate: string;
slotOwner?: InlineComponentWrapper;
selectBindingDependencies?: Set<string>;
var: string;
constructor(
renderer: Renderer,
block: Block,
parent: Wrapper,
node: Element,
stripWhitespace: boolean,
nextSibling: Wrapper
) {
super(renderer, block, parent, node);
this.var = node.name.replace(/[^a-zA-Z0-9_$]/g, '_')
this.classDependencies = [];
this.attributes = this.node.attributes.map(attribute => {
if (attribute.name === 'slot') {
// TODO make separate subclass for this?
let owner = this.parent;
while (owner) {
if (owner.node.type === 'InlineComponent') {
break;
}
if (owner.node.type === 'Element' && /-/.test(owner.node.name)) {
break;
}
owner = owner.parent;
}
if (owner && owner.node.type === 'InlineComponent') {
this.slotOwner = <InlineComponentWrapper>owner;
owner._slots.add(attribute.getStaticValue());
}
}
if (attribute.name === 'style') {
return new StyleAttributeWrapper(this, block, attribute);
}
return new AttributeWrapper(this, block, attribute);
});
let has_bindings;
const binding_lookup = {};
this.node.bindings.forEach(binding => {
binding_lookup[binding.name] = binding;
has_bindings = true;
});
const type = this.node.getStaticAttributeValue('type');
// ordinarily, there'll only be one... but we need to handle
// the rare case where an element can have multiple bindings,
// e.g. <audio bind:paused bind:currentTime>
this.bindings = this.node.bindings.map(binding => new Binding(block, binding, this));
// TODO remove this, it's just useful during refactoring
if (has_bindings && !this.bindings.length) {
throw new Error(`no binding was created`);
}
if (node.intro || node.outro) {
if (node.intro) block.addIntro();
if (node.outro) block.addOutro();
}
if (node.animation) {
block.addAnimation();
}
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.ref) this.parent.cannotUseInnerHTML();
if (this.node.name === 'option') this.parent.cannotUseInnerHTML();
if (renderer.options.dev) {
this.parent.cannotUseInnerHTML(); // need to use addLoc
}
}
this.fragment = new FragmentWrapper(renderer, block, node.children, this, stripWhitespace, nextSibling);
}
render(block: Block, parentNode: string, parentNodes: string) {
const { renderer } = this;
if (this.node.name === 'slot') {
const slotName = this.getStaticAttributeValue('name') || 'default';
renderer.slots.add(slotName);
}
if (this.node.name === 'noscript') return;
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 slot = this.node.attributes.find((attribute: Node) => attribute.name === 'slot');
const prop = slot && quotePropIfNecessary(slot.chunks[0].data);
let initialMountNode;
if (this.slotOwner) {
initialMountNode = `${this.slotOwner.var}._slotted${prop}`;
} else {
initialMountNode = parentNode;
}
block.addVariable(node);
const renderStatement = this.getRenderStatement();
block.builders.create.addLine(
`${node} = ${renderStatement};`
);
if (renderer.options.hydratable) {
if (parentNodes) {
block.builders.claim.addBlock(deindent`
${node} = ${this.getClaimStatement(parentNodes)};
var ${nodes} = @children(${this.node.name === 'template' ? `${node}.content` : node});
`);
} else {
block.builders.claim.addLine(
`${node} = ${renderStatement};`
);
}
}
if (initialMountNode) {
block.builders.mount.addLine(
`@append(${initialMountNode}, ${node});`
);
if (initialMountNode === 'document.head') {
block.builders.destroy.addLine(`@detachNode(${node});`);
}
} else {
block.builders.mount.addLine(`@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});`);
}
// insert static children with textContent or innerHTML
if (!this.node.namespace && this.canUseInnerHTML && this.fragment.nodes.length > 0) {
if (this.fragment.nodes.length === 1 && this.fragment.nodes[0].node.type === 'Text') {
block.builders.create.addLine(
`${node}.textContent = ${stringify(this.fragment.nodes[0].data)};`
);
} else {
const innerHTML = escape(
this.fragment.nodes
.map(toHTML)
.join('')
);
block.builders.create.addLine(
`${node}.innerHTML = \`${innerHTML}\`;`
);
}
} else {
this.fragment.nodes.forEach((child: Wrapper) => {
child.render(
block,
this.node.name === 'template' ? `${node}.content` : node,
nodes
);
});
}
let hasHoistedEventHandlerOrBinding = (
//(this.hasAncestor('EachBlock') && this.bindings.length > 0) ||
this.node.handlers.some(handler => handler.shouldHoist)
);
const eventHandlerOrBindingUsesComponent = (
this.bindings.length > 0 ||
this.node.handlers.some(handler => handler.usesComponent)
);
const eventHandlerOrBindingUsesContext = (
this.bindings.some(binding => binding.node.usesContext) ||
this.node.handlers.some(handler => handler.usesContext)
);
if (hasHoistedEventHandlerOrBinding) {
const initialProps: string[] = [];
const updates: string[] = [];
if (eventHandlerOrBindingUsesComponent) {
const component = block.alias('component');
initialProps.push(component === 'component' ? 'component' : `component: ${component}`);
}
if (eventHandlerOrBindingUsesContext) {
initialProps.push(`ctx`);
block.builders.update.addLine(`${node}._svelte.ctx = ctx;`);
block.maintainContext = true;
}
if (initialProps.length) {
block.builders.hydrate.addBlock(deindent`
${node}._svelte = { ${initialProps.join(', ')} };
`);
}
} else {
if (eventHandlerOrBindingUsesContext) {
block.maintainContext = true;
}
}
this.addBindings(block);
this.addEventHandlers(block);
if (this.node.ref) this.addRef(block);
this.addAttributes(block);
this.addTransitions(block);
this.addAnimation(block);
this.addActions(block);
this.addClasses(block);
if (this.initialUpdate) {
block.builders.mount.addBlock(this.initialUpdate);
}
if (nodes) {
block.builders.claim.addLine(
`${nodes}.forEach(@detachNode);`
);
}
function toHTML(wrapper: ElementWrapper | TextWrapper) {
if (wrapper.node.type === 'Text') {
const { parent } = wrapper.node;
const raw = parent && (
parent.name === 'script' ||
parent.name === 'style'
);
return raw
? wrapper.node.data
: escapeHTML(wrapper.node.data)
.replace(/\\/g, '\\\\')
.replace(/`/g, '\\`')
.replace(/\$/g, '\\$');
}
if (wrapper.node.name === 'noscript') return '';
let open = `<${wrapper.node.name}`;
(<ElementWrapper>wrapper).attributes.forEach((attr: AttributeWrapper) => {
open += ` ${fixAttributeCasing(attr.node.name)}${attr.stringify()}`
});
if (isVoidElementName(wrapper.node.name)) return open + '>';
return `${open}>${wrapper.fragment.nodes.map(toHTML).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});`
);
}
}
getRenderStatement() {
const { name, namespace } = this.node;
if (namespace === 'http://www.w3.org/2000/svg') {
return `@createSvgElement("${name}")`;
}
if (namespace) {
return `document.createElementNS("${namespace}", "${name}")`;
}
return `@createElement("${name}")`;
}
getClaimStatement(nodes: string) {
const attributes = this.node.attributes
.filter((attr: Node) => attr.type === 'Attribute')
.map((attr: Node) => `${quoteNameIfNecessary(attr.name)}: true`)
.join(', ');
const name = this.node.namespace
? this.node.name
: this.node.name.toUpperCase();
return `@claimElement(${nodes}, "${name}", ${attributes
? `{ ${attributes} }`
: `{}`}, ${this.node.namespace === namespaces.svg ? true : false})`;
}
// addBindings(block: Block) {
// if (this.bindings.length === 0) return;
// if (this.node.name === 'select' || this.isMediaNode()) {
// this.renderer.hasComplexBindings = true;
// }
// this.bindings.forEach(binding => {
// binding.render(block);
// });
// this.initialUpdate = this.bindings.map(binding => binding.initialUpdate).filter(Boolean).join('\n');
// }
addBindings(block: Block) {
const { renderer } = this;
if (this.bindings.length === 0) return;
if (this.node.name === 'select' || this.isMediaNode()) {
this.renderer.hasComplexBindings = true;
}
const needsLock = this.node.name !== 'input' || !/radio|checkbox|range|color/.test(this.getStaticAttributeValue('type'));
// TODO munge in constructor
const mungedBindings = this.bindings.map(binding => binding.munge(block));
const lock = mungedBindings.some(binding => binding.needsLock) ?
block.getUniqueName(`${this.var}_updating`) :
null;
if (lock) block.addVariable(lock, 'false');
const groups = events
.map(event => {
return {
events: event.eventNames,
bindings: mungedBindings.filter(binding => event.filter(this.node, binding.name))
};
})
.filter(group => group.bindings.length);
groups.forEach(group => {
const handler = block.getUniqueName(`${this.var}_${group.events.join('_')}_handler`);
const needsLock = group.bindings.some(binding => binding.needsLock);
group.bindings.forEach(binding => {
if (!binding.updateDom) return;
const updateConditions = needsLock ? [`!${lock}`] : [];
if (binding.updateCondition) updateConditions.push(binding.updateCondition);
block.builders.update.addLine(
updateConditions.length ? `if (${updateConditions.join(' && ')}) ${binding.updateDom}` : binding.updateDom
);
});
const usesStore = group.bindings.some(binding => binding.handler.usesStore);
const mutations = group.bindings.map(binding => binding.handler.mutation).filter(Boolean).join('\n');
const props = new Set();
const storeProps = new Set();
group.bindings.forEach(binding => {
binding.handler.props.forEach(prop => {
props.add(prop);
});
binding.handler.storeProps.forEach(prop => {
storeProps.add(prop);
});
}); // TODO use stringifyProps here, once indenting is fixed
// media bindings — awkward special case. The native timeupdate events
// fire too infrequently, so we need to take matters into our
// own hands
let animation_frame;
if (group.events[0] === 'timeupdate') {
animation_frame = block.getUniqueName(`${this.var}_animationframe`);
block.addVariable(animation_frame);
}
block.builders.init.addBlock(deindent`
function ${handler}() {
${
animation_frame && deindent`
cancelAnimationFrame(${animation_frame});
if (!${this.var}.paused) ${animation_frame} = requestAnimationFrame(${handler});`
}
${usesStore && `var $ = #component.store.get();`}
${needsLock && `${lock} = true;`}
${mutations.length > 0 && mutations}
${props.size > 0 && `#component.set({ ${Array.from(props).join(', ')} });`}
${storeProps.size > 0 && `#component.store.set({ ${Array.from(storeProps).join(', ')} });`}
${needsLock && `${lock} = false;`}
}
`);
group.events.forEach(name => {
if (name === 'resize') {
// special case
const resize_listener = block.getUniqueName(`${this.var}_resize_listener`);
block.addVariable(resize_listener);
block.builders.mount.addLine(
`${resize_listener} = @addResizeListener(${this.var}, ${handler});`
);
block.builders.destroy.addLine(
`${resize_listener}.cancel();`
);
} else {
block.builders.hydrate.addLine(
`@addListener(${this.var}, "${name}", ${handler});`
);
block.builders.destroy.addLine(
`@removeListener(${this.var}, "${name}", ${handler});`
);
}
});
const allInitialStateIsDefined = group.bindings
.map(binding => `'${binding.object}' in ctx`)
.join(' && ');
if (this.node.name === 'select' || group.bindings.find(binding => binding.name === 'indeterminate' || binding.isReadOnlyMediaAttribute)) {
renderer.hasComplexBindings = true;
block.builders.hydrate.addLine(
`if (!(${allInitialStateIsDefined})) #component.root._beforecreate.push(${handler});`
);
}
if (group.events[0] === 'resize') {
renderer.hasComplexBindings = true;
block.builders.hydrate.addLine(
`#component.root._beforecreate.push(${handler});`
);
}
});
this.initialUpdate = mungedBindings.map(binding => binding.initialUpdate).filter(Boolean).join('\n');
}
addAttributes(block: Block) {
if (this.node.attributes.find(attr => attr.type === 'Spread')) {
this.addSpreadAttributes(block);
return;
}
this.attributes.forEach((attribute: Attribute) => {
if (attribute.node.name === 'class' && attribute.node.isDynamic) {
this.classDependencies.push(...attribute.node.dependencies);
}
attribute.render(block);
});
}
addSpreadAttributes(block: Block) {
const levels = block.getUniqueName(`${this.var}_levels`);
const data = block.getUniqueName(`${this.var}_data`);
const initialProps = [];
const updates = [];
this.node.attributes
.filter(attr => attr.type === 'Attribute' || attr.type === 'Spread')
.forEach(attr => {
const condition = attr.dependencies.size > 0
? `(${[...attr.dependencies].map(d => `changed.${d}`).join(' || ')})`
: null;
if (attr.isSpread) {
const { snippet, dependencies } = attr.expression;
initialProps.push(snippet);
updates.push(condition ? `${condition} && ${snippet}` : snippet);
} else {
const snippet = `{ ${quoteNameIfNecessary(attr.name)}: ${attr.getValue()} }`;
initialProps.push(snippet);
updates.push(condition ? `${condition} && ${snippet}` : snippet);
}
});
block.builders.init.addBlock(deindent`
var ${levels} = [
${initialProps.join(',\n')}
];
var ${data} = {};
for (var #i = 0; #i < ${levels}.length; #i += 1) {
${data} = @assign(${data}, ${levels}[#i]);
}
`);
block.builders.hydrate.addLine(
`@setAttributes(${this.var}, ${data});`
);
block.builders.update.addBlock(deindent`
@setAttributes(${this.var}, @getSpreadUpdate(${levels}, [
${updates.join(',\n')}
]));
`);
}
addEventHandlers(block: Block) {
const { renderer } = this;
const { component } = renderer;
this.node.handlers.forEach(handler => {
const isCustomEvent = component.events.has(handler.name);
if (handler.callee) {
// TODO move handler render method into a wrapper
handler.render(this.renderer.component, block, this.var, handler.shouldHoist);
}
const target = handler.shouldHoist ? 'this' : this.var;
// get a name for the event handler that is globally unique
// if hoisted, locally unique otherwise
const handlerName = (handler.shouldHoist ? component : block).getUniqueName(
`${handler.name.replace(/[^a-zA-Z0-9_$]/g, '_')}_handler`
);
const component_name = block.alias('component'); // can't use #component, might be hoisted
// create the handler body
const handlerBody = deindent`
${handler.shouldHoist && (
handler.usesComponent || handler.usesContext
? `const { ${[handler.usesComponent && 'component', handler.usesContext && 'ctx'].filter(Boolean).join(', ')} } = ${target}._svelte;`
: null
)}
${handler.snippet ?
handler.snippet :
`${component_name}.fire("${handler.name}", event);`}
`;
if (isCustomEvent) {
block.addVariable(handlerName);
block.builders.hydrate.addBlock(deindent`
${handlerName} = %events-${handler.name}.call(${component_name}, ${this.var}, function(event) {
${handlerBody}
});
`);
block.builders.destroy.addLine(deindent`
${handlerName}.destroy();
`);
} else {
const handlerFunction = deindent`
function ${handlerName}(event) {
${handlerBody}
}
`;
if (handler.shouldHoist) {
renderer.blocks.push(handlerFunction);
} else {
block.builders.init.addBlock(handlerFunction);
}
block.builders.hydrate.addLine(
`@addListener(${this.var}, "${handler.name}", ${handlerName});`
);
block.builders.destroy.addLine(
`@removeListener(${this.var}, "${handler.name}", ${handlerName});`
);
}
});
}
addRef(block: Block) {
const ref = `#component.refs.${this.node.ref.name}`;
block.builders.mount.addLine(
`${ref} = ${this.var};`
);
block.builders.destroy.addLine(
`if (${ref} === ${this.var}) ${ref} = null;`
);
}
addTransitions(
block: Block
) {
const { intro, outro } = this.node;
if (!intro && !outro) return;
if (intro === outro) {
const name = block.getUniqueName(`${this.var}_transition`);
const snippet = intro.expression
? intro.expression.snippet
: '{}';
block.addVariable(name);
const fn = `%transitions-${intro.name}`;
block.builders.intro.addConditional(`#component.root._intro`, deindent`
if (${name}) ${name}.invalidate();
#component.root._aftercreate.push(() => {
if (!${name}) ${name} = @wrapTransition(#component, ${this.var}, ${fn}, ${snippet}, true);
${name}.run(1);
});
`);
block.builders.outro.addBlock(deindent`
if (!${name}) ${name} = @wrapTransition(#component, ${this.var}, ${fn}, ${snippet}, false);
${name}.run(0, () => {
#outrocallback();
${name} = null;
});
`);
block.builders.destroy.addConditional('detach', `if (${name}) ${name}.abort();`);
} else {
const introName = intro && block.getUniqueName(`${this.var}_intro`);
const outroName = outro && block.getUniqueName(`${this.var}_outro`);
if (intro) {
block.addVariable(introName);
const snippet = intro.expression
? intro.expression.snippet
: '{}';
const fn = `%transitions-${intro.name}`; // TODO add built-in transitions?
if (outro) {
block.builders.intro.addBlock(deindent`
if (${introName}) ${introName}.abort(1);
if (${outroName}) ${outroName}.abort(1);
`);
}
block.builders.intro.addConditional(`#component.root._intro`, deindent`
#component.root._aftercreate.push(() => {
${introName} = @wrapTransition(#component, ${this.var}, ${fn}, ${snippet}, true);
${introName}.run(1);
});
`);
}
if (outro) {
block.addVariable(outroName);
const snippet = outro.expression
? outro.expression.snippet
: '{}';
const fn = `%transitions-${outro.name}`;
block.builders.intro.addBlock(deindent`
if (${outroName}) ${outroName}.abort(1);
`);
// TODO hide elements that have outro'd (unless they belong to a still-outroing
// group) prior to their removal from the DOM
block.builders.outro.addBlock(deindent`
${outroName} = @wrapTransition(#component, ${this.var}, ${fn}, ${snippet}, false);
${outroName}.run(0, #outrocallback);
`);
block.builders.destroy.addConditional('detach', `if (${outroName}) ${outroName}.abort();`);
}
}
}
addAnimation(block: Block) {
if (!this.node.animation) return;
const rect = block.getUniqueName('rect');
const animation = block.getUniqueName('animation');
block.addVariable(rect);
block.addVariable(animation);
block.builders.measure.addBlock(deindent`
${rect} = ${this.var}.getBoundingClientRect();
`);
block.builders.fix.addBlock(deindent`
@fixPosition(${this.var});
if (${animation}) ${animation}.stop();
`);
const params = this.node.animation.expression ? this.node.animation.expression.snippet : '{}';
block.builders.animate.addBlock(deindent`
if (${animation}) ${animation}.stop();
${animation} = @wrapAnimation(${this.var}, ${rect}, %animations-${this.node.animation.name}, ${params});
`);
}
addActions(block: Block) {
this.node.actions.forEach(action => {
const { expression } = action;
let snippet, dependencies;
if (expression) {
snippet = expression.snippet;
dependencies = expression.dependencies;
}
const name = block.getUniqueName(
`${action.name.replace(/[^a-zA-Z0-9_$]/g, '_')}_action`
);
block.addVariable(name);
const fn = `%actions-${action.name}`;
block.builders.mount.addLine(
`${name} = ${fn}.call(#component, ${this.var}${snippet ? `, ${snippet}` : ''}) || {};`
);
if (dependencies && dependencies.size > 0) {
let conditional = `typeof ${name}.update === 'function' && `;
const deps = [...dependencies].map(dependency => `changed.${dependency}`).join(' || ');
conditional += dependencies.size > 1 ? `(${deps})` : deps;
block.builders.update.addConditional(
conditional,
`${name}.update.call(#component, ${snippet});`
);
}
block.builders.destroy.addLine(
`if (typeof ${name}.destroy === 'function') ${name}.destroy.call(#component);`
);
});
}
addClasses(block: Block) {
this.node.classes.forEach(classDir => {
const { expression, name } = classDir;
let snippet, dependencies;
if (expression) {
snippet = expression.snippet;
dependencies = expression.dependencies;
} else {
snippet = `ctx${quotePropIfNecessary(name)}`;
dependencies = new Set([name]);
}
const updater = `@toggleClass(${this.var}, "${name}", ${snippet});`;
block.builders.hydrate.addLine(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;
block.builders.update.addConditional(
condition,
updater
);
}
});
}
getStaticAttributeValue(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.chunks.length === 0) return '';
if (attribute.chunks.length === 1 && attribute.chunks[0].type === 'Text') {
return attribute.chunks[0].data;
}
return null;
}
isMediaNode() {
return this.node.name === 'audio' || this.node.name === 'video';
}
remount(name: string) {
const slot = this.attributes.find(attribute => attribute.name === 'slot');
if (slot) {
const prop = quotePropIfNecessary(slot.chunks[0].data);
return `@append(${name}._slotted${prop}, ${this.var});`;
}
return `@append(${name}._slotted.default, ${this.var});`;
}
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') {
(<Text>classAttribute.chunks[0]).data += ` ${className}`;
} else {
(<Node[]>classAttribute.chunks).push(
new Text(this.component, this, this.scope, {
type: 'Text',
data: ` ${className}`
})
);
}
} else {
this.attributes.push(
new Attribute(this.component, this, this.scope, {
type: 'Attribute',
name: 'class',
value: [{ type: 'Text', data: className }]
})
);
}
}
}

@ -0,0 +1,142 @@
import Wrapper from './shared/Wrapper';
import AwaitBlock from './AwaitBlock';
import DebugTag from './DebugTag';
import EachBlock from './EachBlock';
import Element from './Element';
import Head from './Head';
import IfBlock from './IfBlock';
import InlineComponent from './InlineComponent';
import MustacheTag from './MustacheTag';
import RawMustacheTag from './RawMustacheTag';
import Slot from './Slot';
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';
const wrappers = {
AwaitBlock,
Comment: null,
DebugTag,
EachBlock,
Element,
Head,
IfBlock,
InlineComponent,
MustacheTag,
RawMustacheTag,
Slot,
Text,
Title,
Window
};
function link(next: Wrapper, prev: Wrapper) {
prev.next = next;
if (next) next.prev = prev;
}
export default class FragmentWrapper {
nodes: Wrapper[];
constructor(
renderer: Renderer,
block: Block,
nodes: Node[],
parent: Wrapper,
stripWhitespace: boolean,
nextSibling: Wrapper
) {
this.nodes = [];
let lastChild: Wrapper;
let windowWrapper;
let i = nodes.length;
while (i--) {
const child = nodes[i];
if (!(child.type in wrappers)) {
throw new Error(`TODO implement ${child.type}`);
}
// 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);
continue;
}
if (child.type === 'Text') {
let { data } = child;
// 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')
);
if (shouldTrim) {
data = trimEnd(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;
continue;
}
const wrapper = new TextWrapper(renderer, block, parent, child, data);
if (wrapper.skip) continue;
this.nodes.unshift(wrapper);
link(lastChild, lastChild = wrapper);
}
else {
const Wrapper = wrappers[child.type];
if (!Wrapper) continue;
const wrapper = new Wrapper(renderer, block, parent, child, stripWhitespace, lastChild || nextSibling);
this.nodes.unshift(wrapper);
link(lastChild, lastChild = wrapper);
}
}
if (stripWhitespace) {
const first = <TextWrapper>this.nodes[0];
if (first && first.node.type === 'Text') {
first.data = trimStart(first.data);
if (!first.data) {
first.var = null;
this.nodes.shift();
if (this.nodes.length) {
link(null, this.nodes[0]);
}
}
}
}
if (windowWrapper) {
this.nodes.unshift(windowWrapper);
link(lastChild, windowWrapper);
}
}
render(block: Block, parentNode: string, parentNodes: string) {
for (let i = 0; i < this.nodes.length; i += 1) {
this.nodes[i].render(block, parentNode, parentNodes);
}
}
}

@ -0,0 +1,33 @@
import Wrapper from './shared/Wrapper';
import Renderer from '../Renderer';
import Block from '../Block';
import Head from '../../nodes/Head';
import FragmentWrapper from './Fragment';
export default class HeadWrapper extends Wrapper {
fragment: FragmentWrapper;
constructor(
renderer: Renderer,
block: Block,
parent: Wrapper,
node: Head,
stripWhitespace: boolean,
nextSibling: Wrapper
) {
super(renderer, block, parent, node);
this.fragment = new FragmentWrapper(
renderer,
block,
node.children,
parent,
stripWhitespace,
nextSibling
);
}
render(block: Block, parentNode: string, parentNodes: string) {
this.fragment.render(block, 'document.head', null);
}
}

@ -0,0 +1,474 @@
import Wrapper from './shared/Wrapper';
import Renderer from '../Renderer';
import Block from '../Block';
import EachBlock from '../../nodes/EachBlock';
import IfBlock from '../../nodes/IfBlock';
import createDebuggingComment from '../../../utils/createDebuggingComment';
import ElseBlock from '../../nodes/ElseBlock';
import FragmentWrapper from './Fragment';
import deindent from '../../../utils/deindent';
function isElseIf(node: ElseBlock) {
return (
node && node.children.length === 1 && node.children[0].type === 'IfBlock'
);
}
class IfBlockBranch extends Wrapper {
block: Block;
fragment: FragmentWrapper;
condition: string;
isDynamic: boolean;
var = null;
constructor(
renderer: Renderer,
block: Block,
parent: IfBlockWrapper,
node: IfBlock | ElseBlock,
stripWhitespace: boolean,
nextSibling: Wrapper
) {
super(renderer, block, parent, node);
this.condition = (<IfBlock>node).expression && (<IfBlock>node).expression.snippet;
this.block = block.child({
comment: createDebuggingComment(node, parent.renderer.component),
name: parent.renderer.component.getUniqueName(
(<IfBlock>node).expression ? `create_if_block` : `create_else_block`
)
});
this.fragment = new FragmentWrapper(renderer, this.block, node.children, parent.parent, stripWhitespace, nextSibling);
this.isDynamic = this.block.dependencies.size > 0;
}
}
export default class IfBlockWrapper extends Wrapper {
node: IfBlock;
branches: IfBlockBranch[];
var = 'if_block';
constructor(
renderer: Renderer,
block: Block,
parent: Wrapper,
node: EachBlock,
stripWhitespace: boolean,
nextSibling: Wrapper
) {
super(renderer, block, parent, node);
const { component } = renderer;
this.cannotUseInnerHTML();
this.branches = [];
const blocks: Block[] = [];
let isDynamic = false;
let hasIntros = false;
let hasOutros = false;
const createBranches = (node: IfBlock) => {
const branch = new IfBlockBranch(
renderer,
block,
this,
node,
stripWhitespace,
nextSibling
);
this.branches.push(branch);
blocks.push(branch.block);
block.addDependencies(node.expression.dependencies);
if (branch.block.dependencies.size > 0) {
isDynamic = true;
block.addDependencies(branch.block.dependencies);
}
if (branch.block.hasIntros) hasIntros = true;
if (branch.block.hasOutros) hasOutros = true;
if (isElseIf(node.else)) {
createBranches(node.else.children[0]);
} else if (node.else) {
const branch = new IfBlockBranch(
renderer,
block,
this,
node.else,
stripWhitespace,
nextSibling
);
this.branches.push(branch);
blocks.push(branch.block);
if (branch.block.dependencies.size > 0) {
isDynamic = true;
block.addDependencies(branch.block.dependencies);
}
if (branch.block.hasIntros) hasIntros = true;
if (branch.block.hasOutros) hasOutros = true;
}
};
createBranches(this.node);
if (component.options.nestedTransitions) {
if (hasIntros) block.addIntro();
if (hasOutros) block.addOutro();
}
blocks.forEach(block => {
block.hasUpdateMethod = isDynamic;
block.hasIntroMethod = hasIntros;
block.hasOutroMethod = hasOutros;
});
renderer.blocks.push(...blocks);
}
render(
block: Block,
parentNode: string,
parentNodes: string
) {
const name = this.var;
const needsAnchor = this.next ? !this.next.isDomNode() : !parentNode || !this.parent.isDomNode();
const anchor = needsAnchor
? block.getUniqueName(`${name}_anchor`)
: (this.next && this.next.var) || 'null';
const hasElse = !(this.branches[this.branches.length - 1].condition);
const if_name = hasElse ? '' : `if (${name}) `;
const dynamic = this.branches[0].block.hasUpdateMethod; // can use [0] as proxy for all, since they necessarily have the same value
const hasOutros = this.branches[0].block.hasOutroMethod;
const vars = { name, anchor, if_name, hasElse };
if (this.node.else) {
if (hasOutros) {
this.renderCompoundWithOutros(block, parentNode, parentNodes, dynamic, vars);
if (this.renderer.options.nestedTransitions) {
block.builders.outro.addBlock(deindent`
if (${name}) ${name}.o(#outrocallback);
else #outrocallback();
`);
}
} else {
this.renderCompound(block, parentNode, parentNodes, dynamic, vars);
}
} else {
this.renderSimple(block, parentNode, parentNodes, dynamic, vars);
if (hasOutros && this.renderer.options.nestedTransitions) {
block.builders.outro.addBlock(deindent`
if (${name}) ${name}.o(#outrocallback);
else #outrocallback();
`);
}
}
block.builders.create.addLine(`${if_name}${name}.c();`);
if (parentNodes) {
block.builders.claim.addLine(
`${if_name}${name}.l(${parentNodes});`
);
}
if (needsAnchor) {
block.addElement(
anchor,
`@createComment()`,
parentNodes && `@createComment()`,
parentNode
);
}
this.branches.forEach(branch => {
branch.fragment.render(branch.block, null, 'nodes');
});
}
renderCompound(
block: Block,
parentNode: string,
parentNodes: string,
dynamic,
{ name, anchor, hasElse, if_name }
) {
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} && `;
block.builders.init.addBlock(deindent`
function ${select_block_type}(ctx) {
${this.branches
.map(({ condition, block }) => `${condition ? `if (${condition}) ` : ''}return ${block.name};`)
.join('\n')}
${!hasElse && `return -1;`}
}
`);
block.builders.init.addBlock(deindent`
var ${current_block_type} = ${select_block_type}(ctx);
var ${name} = ${current_block_type_and}${current_block_type}(#component, ctx);
`);
const mountOrIntro = this.branches[0].block.hasIntroMethod ? 'i' : 'm';
const initialMountNode = parentNode || '#target';
const anchorNode = parentNode ? 'null' : 'anchor';
block.builders.mount.addLine(
`${if_name}${name}.${mountOrIntro}(${initialMountNode}, ${anchorNode});`
);
const updateMountNode = this.getUpdateMountNode(anchor);
const changeBlock = deindent`
${if_name}${name}.d(1);
${name} = ${current_block_type_and}${current_block_type}(#component, ctx);
${if_name}${name}.c();
${if_name}${name}.${mountOrIntro}(${updateMountNode}, ${anchor});
`;
if (dynamic) {
block.builders.update.addBlock(deindent`
if (${current_block_type} === (${current_block_type} = ${select_block_type}(ctx)) && ${name}) {
${name}.p(changed, ctx);
} else {
${changeBlock}
}
`);
} else {
block.builders.update.addBlock(deindent`
if (${current_block_type} !== (${current_block_type} = ${select_block_type}(ctx))) {
${changeBlock}
}
`);
}
block.builders.destroy.addLine(`${if_name}${name}.d(${parentNode ? '' : 'detach'});`);
}
// if any of the siblings have outros, we need to keep references to the blocks
// (TODO does this only apply to bidi transitions?)
renderCompoundWithOutros(
block: Block,
parentNode: string,
parentNodes: string,
dynamic,
{ name, anchor, hasElse }
) {
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 if_current_block_type_index = hasElse
? ''
: `if (~${current_block_type_index}) `;
block.addVariable(current_block_type_index);
block.addVariable(name);
block.builders.init.addBlock(deindent`
var ${if_block_creators} = [
${this.branches.map(branch => branch.block.name).join(',\n')}
];
var ${if_blocks} = [];
function ${select_block_type}(ctx) {
${this.branches
.map(({ condition, block }, i) => `${condition ? `if (${condition}) ` : ''}return ${block ? i : -1};`)
.join('\n')}
${!hasElse && `return -1;`}
}
`);
if (hasElse) {
block.builders.init.addBlock(deindent`
${current_block_type_index} = ${select_block_type}(ctx);
${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](#component, ctx);
`);
} else {
block.builders.init.addBlock(deindent`
if (~(${current_block_type_index} = ${select_block_type}(ctx))) {
${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](#component, ctx);
}
`);
}
const mountOrIntro = this.branches[0].block.hasIntroMethod ? 'i' : 'm';
const initialMountNode = parentNode || '#target';
const anchorNode = parentNode ? 'null' : 'anchor';
block.builders.mount.addLine(
`${if_current_block_type_index}${if_blocks}[${current_block_type_index}].${mountOrIntro}(${initialMountNode}, ${anchorNode});`
);
const updateMountNode = this.getUpdateMountNode(anchor);
const destroyOldBlock = deindent`
@groupOutros();
${name}.o(function() {
${if_blocks}[${previous_block_index}].d(1);
${if_blocks}[${previous_block_index}] = null;
});
`;
const createNewBlock = deindent`
${name} = ${if_blocks}[${current_block_type_index}];
if (!${name}) {
${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](#component, ctx);
${name}.c();
}
${name}.${mountOrIntro}(${updateMountNode}, ${anchor});
`;
const changeBlock = hasElse
? deindent`
${destroyOldBlock}
${createNewBlock}
`
: deindent`
if (${name}) {
${destroyOldBlock}
}
if (~${current_block_type_index}) {
${createNewBlock}
} else {
${name} = null;
}
`;
if (dynamic) {
block.builders.update.addBlock(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}
}
`);
} else {
block.builders.update.addBlock(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}
}
`);
}
block.builders.destroy.addLine(deindent`
${if_current_block_type_index}${if_blocks}[${current_block_type_index}].d(${parentNode ? '' : 'detach'});
`);
}
renderSimple(
block: Block,
parentNode: string,
parentNodes: string,
dynamic,
{ name, anchor, if_name }
) {
const branch = this.branches[0];
block.builders.init.addBlock(deindent`
var ${name} = (${branch.condition}) && ${branch.block.name}(#component, ctx);
`);
const mountOrIntro = branch.block.hasIntroMethod ? 'i' : 'm';
const initialMountNode = parentNode || '#target';
const anchorNode = parentNode ? 'null' : 'anchor';
block.builders.mount.addLine(
`if (${name}) ${name}.${mountOrIntro}(${initialMountNode}, ${anchorNode});`
);
const updateMountNode = this.getUpdateMountNode(anchor);
const enter = dynamic
? (branch.block.hasIntroMethod || branch.block.hasOutroMethod)
? deindent`
if (${name}) {
${name}.p(changed, ctx);
} else {
${name} = ${branch.block.name}(#component, ctx);
if (${name}) ${name}.c();
}
${name}.i(${updateMountNode}, ${anchor});
`
: deindent`
if (${name}) {
${name}.p(changed, ctx);
} else {
${name} = ${branch.block.name}(#component, ctx);
${name}.c();
${name}.m(${updateMountNode}, ${anchor});
}
`
: (branch.block.hasIntroMethod || branch.block.hasOutroMethod)
? deindent`
if (!${name}) {
${name} = ${branch.block.name}(#component, ctx);
${name}.c();
}
${name}.i(${updateMountNode}, ${anchor});
`
: deindent`
if (!${name}) {
${name} = ${branch.block.name}(#component, ctx);
${name}.c();
${name}.m(${updateMountNode}, ${anchor});
}
`;
// no `p()` here — we don't want to update outroing nodes,
// as that will typically result in glitching
const exit = branch.block.hasOutroMethod
? deindent`
@groupOutros();
${name}.o(function() {
${name}.d(1);
${name} = null;
});
`
: deindent`
${name}.d(1);
${name} = null;
`;
block.builders.update.addBlock(deindent`
if (${branch.condition}) {
${enter}
} else if (${name}) {
${exit}
}
`);
block.builders.destroy.addLine(`${if_name}${name}.d(${parentNode ? '' : 'detach'});`);
}
}

@ -0,0 +1,478 @@
import Wrapper from '../shared/Wrapper';
import Renderer from '../../Renderer';
import Block from '../../Block';
import Node from '../../../nodes/shared/Node';
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 Attribute from '../../../nodes/Attribute';
import CodeBuilder from '../../../../utils/CodeBuilder';
import getObject from '../../../../utils/getObject';
import Binding from '../../../nodes/Binding';
import getTailSnippet from '../../../../utils/getTailSnippet';
export default class InlineComponentWrapper extends Wrapper {
var: string;
_slots: Set<string>; // TODO lose the underscore
node: InlineComponent;
fragment: FragmentWrapper;
constructor(
renderer: Renderer,
block: Block,
parent: Wrapper,
node: InlineComponent,
stripWhitespace: boolean,
nextSibling: Wrapper
) {
super(renderer, block, parent, node);
this.cannotUseInnerHTML();
if (this.node.expression) {
block.addDependencies(this.node.expression.dependencies);
}
this.node.attributes.forEach(attr => {
block.addDependencies(attr.dependencies);
});
this.node.bindings.forEach(binding => {
block.addDependencies(binding.value.dependencies);
});
this.node.handlers.forEach(handler => {
block.addDependencies(handler.dependencies);
});
this.var = (
this.node.name === 'svelte:self' ? renderer.component.name :
this.node.name === 'svelte:component' ? 'switch_instance' :
this.node.name
).toLowerCase();
if (this.node.children.length) {
this._slots = new Set(['default']);
this.fragment = new FragmentWrapper(renderer, block, node.children, this, stripWhitespace, nextSibling);
}
if (renderer.component.options.nestedTransitions) {
block.addOutro();
}
}
render(
block: Block,
parentNode: string,
parentNodes: string
) {
const { renderer } = this;
const { component } = renderer;
const name = this.var;
const componentInitProperties = [
`root: #component.root`,
`store: #component.store`
];
if (this.fragment) {
const slots = Array.from(this._slots).map(name => `${quoteNameIfNecessary(name)}: @createFragment()`);
componentInitProperties.push(`slots: { ${slots.join(', ')} }`);
this.fragment.nodes.forEach((child: Wrapper) => {
child.render(block, `${this.var}._slotted.default`, 'nodes');
});
}
const statements: string[] = [];
const name_initial_data = block.getUniqueName(`${name}_initial_data`);
const name_changes = block.getUniqueName(`${name}_changes`);
let name_updating: string;
let beforecreate: string = null;
const updates: string[] = [];
const usesSpread = !!this.node.attributes.find(a => a.isSpread);
const attributeObject = usesSpread
? '{}'
: stringifyProps(
this.node.attributes.map(attr => `${quoteNameIfNecessary(attr.name)}: ${attr.getValue()}`)
);
if (this.node.attributes.length || this.node.bindings.length) {
componentInitProperties.push(`data: ${name_initial_data}`);
}
if (!usesSpread && (this.node.attributes.filter(a => a.isDynamic).length || this.node.bindings.length)) {
updates.push(`var ${name_changes} = {};`);
}
if (this.node.attributes.length) {
if (usesSpread) {
const levels = block.getUniqueName(`${this.var}_spread_levels`);
const initialProps = [];
const changes = [];
const allDependencies = new Set();
this.node.attributes.forEach(attr => {
addToSet(allDependencies, attr.dependencies);
});
this.node.attributes.forEach(attr => {
const { name, dependencies } = attr;
const condition = dependencies.size > 0 && (dependencies.size !== allDependencies.size)
? `(${[...dependencies].map(d => `changed.${d}`).join(' || ')})`
: null;
if (attr.isSpread) {
const value = attr.expression.snippet;
initialProps.push(value);
changes.push(condition ? `${condition} && ${value}` : value);
} else {
const obj = `{ ${quoteNameIfNecessary(name)}: ${attr.getValue()} }`;
initialProps.push(obj);
changes.push(condition ? `${condition} && ${obj}` : obj);
}
});
block.builders.init.addBlock(deindent`
var ${levels} = [
${initialProps.join(',\n')}
];
`);
statements.push(deindent`
for (var #i = 0; #i < ${levels}.length; #i += 1) {
${name_initial_data} = @assign(${name_initial_data}, ${levels}[#i]);
}
`);
const conditions = [...allDependencies].map(dep => `changed.${dep}`).join(' || ');
updates.push(deindent`
var ${name_changes} = ${allDependencies.size === 1 ? `${conditions}` : `(${conditions})`} ? @getSpreadUpdate(${levels}, [
${changes.join(',\n')}
]) : {};
`);
} else {
this.node.attributes
.filter((attribute: Attribute) => attribute.isDynamic)
.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()};
`);
}
});
}
}
if (this.node.bindings.length) {
renderer.hasComplexBindings = true;
name_updating = block.alias(`${name}_updating`);
block.addVariable(name_updating, '{}');
let hasLocalBindings = false;
let hasStoreBindings = false;
const builder = new CodeBuilder();
this.node.bindings.forEach((binding: Binding) => {
let { name: key } = getObject(binding.value.node);
let setFromChild;
if (binding.isContextual) {
const computed = isComputed(binding.value.node);
const tail = binding.value.node.type === 'MemberExpression' ? getTailSnippet(binding.value.node) : '';
const head = block.bindings.get(key);
const lhs = binding.value.node.type === 'MemberExpression'
? binding.value.snippet
: `${head()}${tail} = childState${quotePropIfNecessary(binding.name)}`;
setFromChild = deindent`
${lhs} = childState${quotePropIfNecessary(binding.name)};
${[...binding.value.dependencies]
.map((name: string) => {
const isStoreProp = name[0] === '$';
const prop = isStoreProp ? name.slice(1) : name;
const newState = isStoreProp ? 'newStoreState' : 'newState';
if (isStoreProp) hasStoreBindings = true;
else hasLocalBindings = true;
return `${newState}${quotePropIfNecessary(prop)} = ctx${quotePropIfNecessary(name)};`;
})}
`;
}
else {
const isStoreProp = key[0] === '$';
const prop = isStoreProp ? key.slice(1) : key;
const newState = isStoreProp ? 'newStoreState' : 'newState';
if (isStoreProp) hasStoreBindings = true;
else hasLocalBindings = true;
if (binding.value.node.type === 'MemberExpression') {
setFromChild = deindent`
${binding.value.snippet} = childState${quotePropIfNecessary(binding.name)};
${newState}${quotePropIfNecessary(prop)} = ctx${quotePropIfNecessary(key)};
`;
}
else {
setFromChild = `${newState}${quotePropIfNecessary(prop)} = childState${quotePropIfNecessary(binding.name)};`;
}
}
statements.push(deindent`
if (${binding.value.snippet} !== void 0) {
${name_initial_data}${quotePropIfNecessary(binding.name)} = ${binding.value.snippet};
${name_updating}${quotePropIfNecessary(binding.name)} = true;
}`
);
builder.addConditional(
`!${name_updating}${quotePropIfNecessary(binding.name)} && changed${quotePropIfNecessary(binding.name)}`,
setFromChild
);
updates.push(deindent`
if (!${name_updating}${quotePropIfNecessary(binding.name)} && ${[...binding.value.dependencies].map((dependency: string) => `changed.${dependency}`).join(' || ')}) {
${name_changes}${quotePropIfNecessary(binding.name)} = ${binding.value.snippet};
${name_updating}${quotePropIfNecessary(binding.name)} = ${binding.value.snippet} !== void 0;
}
`);
});
block.maintainContext = true; // TODO put this somewhere more logical
const initialisers = [
hasLocalBindings && 'newState = {}',
hasStoreBindings && 'newStoreState = {}',
].filter(Boolean).join(', ');
// TODO use component.on('state', ...) instead of _bind
componentInitProperties.push(deindent`
_bind(changed, childState) {
var ${initialisers};
${builder}
${hasStoreBindings && `#component.store.set(newStoreState);`}
${hasLocalBindings && `#component._set(newState);`}
${name_updating} = {};
}
`);
beforecreate = deindent`
#component.root._beforecreate.push(() => {
${name}._bind({ ${this.node.bindings.map(b => `${quoteNameIfNecessary(b.name)}: 1`).join(', ')} }, ${name}.get());
});
`;
}
this.node.handlers.forEach(handler => {
handler.var = block.getUniqueName(`${this.var}_${handler.name}`); // TODO this is hacky
handler.render(component, block, this.var, false); // TODO hoist when possible
if (handler.usesContext) block.maintainContext = true; // TODO is there a better place to put this?
});
if (this.node.name === 'svelte:component') {
const switch_value = block.getUniqueName('switch_value');
const switch_props = block.getUniqueName('switch_props');
const { snippet } = this.node.expression;
block.builders.init.addBlock(deindent`
var ${switch_value} = ${snippet};
function ${switch_props}(ctx) {
${(this.node.attributes.length || this.node.bindings.length) && deindent`
var ${name_initial_data} = ${attributeObject};`}
${statements}
return {
${componentInitProperties.join(',\n')}
};
}
if (${switch_value}) {
var ${name} = new ${switch_value}(${switch_props}(ctx));
${beforecreate}
}
${this.node.handlers.map(handler => deindent`
function ${handler.var}(event) {
${handler.snippet || `#component.fire("${handler.name}", event);`}
}
if (${name}) ${name}.on("${handler.name}", ${handler.var});
`)}
`);
block.builders.create.addLine(
`if (${name}) ${name}._fragment.c();`
);
if (parentNodes) {
block.builders.claim.addLine(
`if (${name}) ${name}._fragment.l(${parentNodes});`
);
}
block.builders.mount.addBlock(deindent`
if (${name}) {
${name}._mount(${parentNode || '#target'}, ${parentNode ? 'null' : 'anchor'});
${this.node.ref && `#component.refs.${this.node.ref.name} = ${name};`}
}
`);
const anchor = this.getOrCreateAnchor(block, parentNode, parentNodes);
const updateMountNode = this.getUpdateMountNode(anchor);
if (updates.length) {
block.builders.update.addBlock(deindent`
${updates}
`);
}
block.builders.update.addBlock(deindent`
if (${switch_value} !== (${switch_value} = ${snippet})) {
if (${name}) {
${component.options.nestedTransitions
? deindent`
@groupOutros();
const old_component = ${name};
old_component._fragment.o(() => {
old_component.destroy();
});`
: `${name}.destroy();`}
}
if (${switch_value}) {
${name} = new ${switch_value}(${switch_props}(ctx));
${this.node.bindings.length > 0 && deindent`
#component.root._beforecreate.push(() => {
const changed = {};
${this.node.bindings.map(binding => deindent`
if (${binding.value.snippet} === void 0) changed.${binding.name} = 1;`)}
${name}._bind(changed, ${name}.get());
});`}
${name}._fragment.c();
${this.fragment && this.fragment.nodes.map(child => child.remount(name))}
${name}._mount(${updateMountNode}, ${anchor});
${this.node.handlers.map(handler => deindent`
${name}.on("${handler.name}", ${handler.var});
`)}
${this.node.ref && `#component.refs.${this.node.ref.name} = ${name};`}
} else {
${name} = null;
${this.node.ref && deindent`
if (#component.refs.${this.node.ref.name} === ${name}) {
#component.refs.${this.node.ref.name} = null;
}`}
}
}
`);
if (updates.length) {
block.builders.update.addBlock(deindent`
else if (${switch_value}) {
${name}._set(${name_changes});
${this.node.bindings.length && `${name_updating} = {};`}
}
`);
}
block.builders.destroy.addLine(`if (${name}) ${name}.destroy(${parentNode ? '' : 'detach'});`);
} else {
const expression = this.node.name === 'svelte:self'
? component.name
: `%components-${this.node.name}`;
block.builders.init.addBlock(deindent`
${(this.node.attributes.length || this.node.bindings.length) && deindent`
var ${name_initial_data} = ${attributeObject};`}
${statements}
var ${name} = new ${expression}({
${componentInitProperties.join(',\n')}
});
${beforecreate}
${this.node.handlers.map(handler => deindent`
${name}.on("${handler.name}", function(event) {
${handler.snippet || `#component.fire("${handler.name}", event);`}
});
`)}
${this.node.ref && `#component.refs.${this.node.ref.name} = ${name};`}
`);
block.builders.create.addLine(`${name}._fragment.c();`);
if (parentNodes) {
block.builders.claim.addLine(
`${name}._fragment.l(${parentNodes});`
);
}
block.builders.mount.addLine(
`${name}._mount(${parentNode || '#target'}, ${parentNode ? 'null' : 'anchor'});`
);
if (updates.length) {
block.builders.update.addBlock(deindent`
${updates}
${name}._set(${name_changes});
${this.node.bindings.length && `${name_updating} = {};`}
`);
}
block.builders.destroy.addLine(deindent`
${name}.destroy(${parentNode ? '' : 'detach'});
${this.node.ref && `if (#component.refs.${this.node.ref.name} === ${name}) #component.refs.${this.node.ref.name} = null;`}
`);
}
if (component.options.nestedTransitions) {
block.builders.outro.addLine(
`if (${name}) ${name}._fragment.o(#outrocallback);`
);
}
}
remount(name: string) {
return `${this.var}._mount(${name}._slotted.default, null);`;
}
}
function isComputed(node: Node) {
while (node.type === 'MemberExpression') {
if (node.computed) return true;
node = node.object;
}
return false;
}

@ -0,0 +1,27 @@
import Renderer from '../Renderer';
import Block from '../Block';
import Node from '../../nodes/shared/Node';
import Tag from './shared/Tag';
export default class MustacheTagWrapper extends Tag {
var = 'text';
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: Node) {
super(renderer, block, parent, node);
this.cannotUseInnerHTML();
}
render(block: Block, parentNode: string, parentNodes: string) {
const { init } = this.renameThisMethod(
block,
value => `@setData(${this.var}, ${value});`
);
block.addElement(
this.var,
`@createText(${init})`,
parentNodes && `@claimText(${parentNodes}, ${init})`,
parentNode
);
}
}

@ -0,0 +1,103 @@
import Renderer from '../Renderer';
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';
export default class RawMustacheTagWrapper extends Tag {
var = 'raw';
constructor(
renderer: Renderer,
block: Block,
parent: Wrapper,
node: Node
) {
super(renderer, block, parent, node);
this.cannotUseInnerHTML();
}
render(block: Block, parentNode: string, parentNodes: 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;
const anchorBefore = needsAnchorBefore
? block.getUniqueName(`${name}_before`)
: (this.prev && this.prev.var) || 'null';
const anchorAfter = needsAnchorAfter
? block.getUniqueName(`${name}_after`)
: (this.next && this.next.var) || 'null';
let detach: string;
let insert: (content: string) => string;
let useInnerHTML = 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});`;
} else {
detach = `@detachBetween(${anchorBefore}, ${anchorAfter});`;
insert = content => `${anchorBefore}.insertAdjacentHTML("afterend", ${content});`;
}
const { init } = this.renameThisMethod(
block,
content => deindent`
${!useInnerHTML && 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,
true
);
}
function addAnchorAfter() {
block.addElement(
anchorAfter,
`@createElement('noscript')`,
parentNodes && `@createElement('noscript')`,
parentNode
);
}
if (needsAnchorAfter && anchorBefore === 'null') {
// anchorAfter needs to be in the DOM before we
// insert the HTML...
addAnchorAfter();
}
block.builders.mount.addLine(insert(init));
if (!parentNode) {
block.builders.destroy.addConditional('detach', needsAnchorBefore
? `${detach}\n@detachNode(${anchorBefore});`
: detach);
}
if (needsAnchorAfter && anchorBefore !== 'null') {
// ...otherwise it should go afterwards
addAnchorAfter();
}
}
}

@ -0,0 +1,144 @@
import Wrapper from './shared/Wrapper';
import Renderer from '../Renderer';
import Block from '../Block';
import Slot from '../../nodes/Slot';
import { quotePropIfNecessary } from '../../../utils/quoteIfNecessary';
import FragmentWrapper from './Fragment';
import deindent from '../../../utils/deindent';
function sanitize(name) {
return name.replace(/[^a-zA-Z]+/g, '_').replace(/^_/, '').replace(/_$/, '');
}
export default class SlotWrapper extends Wrapper {
node: Slot;
fragment: FragmentWrapper;
var = 'slot';
constructor(
renderer: Renderer,
block: Block,
parent: Wrapper,
node: Slot,
stripWhitespace: boolean,
nextSibling: Wrapper
) {
super(renderer, block, parent, node);
this.cannotUseInnerHTML();
this.fragment = new FragmentWrapper(
renderer,
block,
node.children,
parent,
stripWhitespace,
nextSibling
);
}
render(
block: Block,
parentNode: string,
parentNodes: string
) {
const { renderer } = this;
const slotName = this.node.getStaticAttributeValue('name') || 'default';
renderer.slots.add(slotName);
const content_name = block.getUniqueName(`slot_content_${sanitize(slotName)}`);
const prop = quotePropIfNecessary(slotName);
block.addVariable(content_name, `#component._slotted${prop}`);
// TODO can we 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;
const anchorBefore = needsAnchorBefore
? block.getUniqueName(`${content_name}_before`)
: (this.prev && this.prev.var) || 'null';
const anchorAfter = needsAnchorAfter
? block.getUniqueName(`${content_name}_after`)
: (this.next && this.next.var) || 'null';
if (needsAnchorBefore) block.addVariable(anchorBefore);
if (needsAnchorAfter) block.addVariable(anchorAfter);
let mountBefore = block.builders.mount.toString();
let destroyBefore = block.builders.destroy.toString();
block.builders.create.pushCondition(`!${content_name}`);
block.builders.hydrate.pushCondition(`!${content_name}`);
block.builders.mount.pushCondition(`!${content_name}`);
block.builders.update.pushCondition(`!${content_name}`);
block.builders.destroy.pushCondition(`!${content_name}`);
this.fragment.render(block, parentNode, parentNodes);
block.builders.create.popCondition();
block.builders.hydrate.popCondition();
block.builders.mount.popCondition();
block.builders.update.popCondition();
block.builders.destroy.popCondition();
const mountLeadin = block.builders.mount.toString() !== mountBefore
? `else`
: `if (${content_name})`;
if (parentNode) {
block.builders.mount.addBlock(deindent`
${mountLeadin} {
${needsAnchorBefore && `@append(${parentNode}, ${anchorBefore} || (${anchorBefore} = @createComment()));`}
@append(${parentNode}, ${content_name});
${needsAnchorAfter && `@append(${parentNode}, ${anchorAfter} || (${anchorAfter} = @createComment()));`}
}
`);
} else {
block.builders.mount.addBlock(deindent`
${mountLeadin} {
${needsAnchorBefore && `@insert(#target, ${anchorBefore} || (${anchorBefore} = @createComment()), anchor);`}
@insert(#target, ${content_name}, anchor);
${needsAnchorAfter && `@insert(#target, ${anchorAfter} || (${anchorAfter} = @createComment()), anchor);`}
}
`);
}
// if the slot is unmounted, move nodes back into the document fragment,
// so that it can be reinserted later
// TODO so that this can work with public API, component._slotted should
// be all fragments, derived from options.slots. Not === options.slots
const unmountLeadin = block.builders.destroy.toString() !== destroyBefore
? `else`
: `if (${content_name})`;
if (anchorBefore === 'null' && anchorAfter === 'null') {
block.builders.destroy.addBlock(deindent`
${unmountLeadin} {
@reinsertChildren(${parentNode}, ${content_name});
}
`);
} else if (anchorBefore === 'null') {
block.builders.destroy.addBlock(deindent`
${unmountLeadin} {
@reinsertBefore(${anchorAfter}, ${content_name});
}
`);
} else if (anchorAfter === 'null') {
block.builders.destroy.addBlock(deindent`
${unmountLeadin} {
@reinsertAfter(${anchorBefore}, ${content_name});
}
`);
} else {
block.builders.destroy.addBlock(deindent`
${unmountLeadin} {
@reinsertBetween(${anchorBefore}, ${anchorAfter}, ${content_name});
@detachNode(${anchorBefore});
@detachNode(${anchorAfter});
}
`);
}
}
}

@ -0,0 +1,67 @@
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';
// 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([
'audio',
'datalist',
'dl',
'optgroup',
'select',
'video',
]);
// TODO this should probably be in Fragment
function shouldSkip(node: Text) {
if (/\S/.test(node.data)) return false;
const parentElement = node.findNearest(/(?:Element|InlineComponent|Head)/);
if (!parentElement) return false;
if (parentElement.type === 'Head') return true;
if (parentElement.type === 'InlineComponent') return parentElement.children.length === 1 && node === parentElement.children[0];
return parentElement.namespace || elementsWithoutText.has(parentElement.name);
}
export default class TextWrapper extends Wrapper {
node: Text;
data: string;
skip: boolean;
var: string;
constructor(
renderer: Renderer,
block: Block,
parent: Wrapper,
node: Text,
data: string
) {
super(renderer, block, parent, node);
this.skip = shouldSkip(this.node);
this.data = data;
this.var = this.skip ? null : 'text';
}
render(block: Block, parentNode: string, parentNodes: string) {
if (this.skip) return;
block.addElement(
this.var,
`@createText(${stringify(this.data)})`,
parentNodes && `@claimText(${parentNodes}, ${stringify(this.data)})`,
parentNode
);
}
remount(name: string) {
return `@append(${name}._slotted.default, ${this.var});`;
}
}

@ -0,0 +1,99 @@
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';
export default class TitleWrapper extends Wrapper {
node: Title;
constructor(
renderer: Renderer,
block: Block,
parent: Wrapper,
node: Title,
stripWhitespace: boolean,
nextSibling: Wrapper
) {
super(renderer, block, parent, node);
}
render(block: Block, parentNode: string, parentNodes: string) {
const isDynamic = !!this.node.children.find(node => node.type !== 'Text');
if (isDynamic) {
let value;
const allDependencies = 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
if (this.node.children.length === 1) {
// single {tag} — may be a non-string
const { expression } = this.node.children[0];
const { dependencies, snippet } = this.node.children[0].expression;
value = snippet;
dependencies.forEach(d => {
allDependencies.add(d);
});
} else {
// '{foo} {bar}' — treat as string concatenation
value =
(this.node.children[0].type === 'Text' ? '' : `"" + `) +
this.node.children
.map((chunk: Node) => {
if (chunk.type === 'Text') {
return stringify(chunk.data);
} else {
const { dependencies, snippet } = chunk.expression;
dependencies.forEach(d => {
allDependencies.add(d);
});
return chunk.expression.getPrecedence() <= 13 ? `(${snippet})` : snippet;
}
})
.join(' + ');
}
const last = this.node.shouldCache && block.getUniqueName(
`title_value`
);
if (this.node.shouldCache) block.addVariable(last);
let updater;
const init = this.node.shouldCache ? `${last} = ${value}` : value;
block.builders.init.addLine(
`document.title = ${init};`
);
updater = `document.title = ${this.node.shouldCache ? last : value};`;
if (allDependencies.size) {
const dependencies = Array.from(allDependencies);
const changedCheck = (
( block.hasOutros ? `!#current || ` : '' ) +
dependencies.map(dependency => `changed.${dependency}`).join(' || ')
);
const updateCachedValue = `${last} !== (${last} = ${value})`;
const condition = this.node.shouldCache ?
( dependencies.length ? `(${changedCheck}) && ${updateCachedValue}` : updateCachedValue ) :
changedCheck;
block.builders.update.addConditional(
condition,
updater
);
}
} else {
const value = stringify(this.node.children[0].data);
block.builders.hydrate.addLine(`document.title = ${value};`);
}
}
}

@ -0,0 +1,198 @@
import Renderer from '../Renderer';
import Block from '../Block';
import Node from '../../nodes/shared/Node';
import Wrapper from './shared/Wrapper';
import deindent from '../../../utils/deindent';
const associatedEvents = {
innerWidth: 'resize',
innerHeight: 'resize',
outerWidth: 'resize',
outerHeight: 'resize',
scrollX: 'scroll',
scrollY: 'scroll',
};
const properties = {
scrollX: 'pageXOffset',
scrollY: 'pageYOffset'
};
const readonly = new Set([
'innerWidth',
'innerHeight',
'outerWidth',
'outerHeight',
'online',
]);
export default class WindowWrapper extends Wrapper {
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: Node) {
super(renderer, block, parent, node);
}
render(block: Block, parentNode: string, parentNodes: string) {
const { renderer } = this;
const { component } = renderer;
const events = {};
const bindings: Record<string, string> = {};
this.node.handlers.forEach(handler => {
// TODO verify that it's a valid callee (i.e. built-in or declared method)
component.addSourcemapLocations(handler.expression);
const isCustomEvent = component.events.has(handler.name);
let usesState = handler.dependencies.size > 0;
handler.render(component, block, 'window', false); // TODO hoist?
const handlerName = block.getUniqueName(`onwindow${handler.name}`);
const handlerBody = deindent`
${usesState && `var ctx = #component.get();`}
${handler.snippet};
`;
if (isCustomEvent) {
// TODO dry this out
block.addVariable(handlerName);
block.builders.hydrate.addBlock(deindent`
${handlerName} = %events-${handler.name}.call(#component, window, function(event) {
${handlerBody}
});
`);
block.builders.destroy.addLine(deindent`
${handlerName}.destroy();
`);
} else {
block.builders.init.addBlock(deindent`
function ${handlerName}(event) {
${handlerBody}
}
window.addEventListener("${handler.name}", ${handlerName});
`);
block.builders.destroy.addBlock(deindent`
window.removeEventListener("${handler.name}", ${handlerName});
`);
}
});
this.node.bindings.forEach(binding => {
// in dev mode, throw if read-only values are written to
if (readonly.has(binding.name)) {
renderer.readonly.add(binding.value.node.name);
}
bindings[binding.name] = binding.value.node.name;
// 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 property = properties[binding.name] || binding.name;
if (!events[associatedEvent]) events[associatedEvent] = [];
events[associatedEvent].push(
`${binding.value.node.name}: this.${property}`
);
// add initial value
renderer.metaBindings.push(
`this._state.${binding.value.node.name} = window.${property};`
);
});
const lock = block.getUniqueName(`window_updating`);
const clear = block.getUniqueName(`clear_window_updating`);
const timeout = block.getUniqueName(`window_updating_timeout`);
Object.keys(events).forEach(event => {
const handlerName = block.getUniqueName(`onwindow${event}`);
const props = events[event].join(',\n');
if (event === 'scroll') {
// TODO other bidirectional bindings...
block.addVariable(lock, 'false');
block.addVariable(clear, `function() { ${lock} = false; }`);
block.addVariable(timeout);
}
const handlerBody = deindent`
${event === 'scroll' && deindent`
if (${lock}) return;
${lock} = true;
`}
${component.options.dev && `component._updatingReadonlyProperty = true;`}
#component.set({
${props}
});
${component.options.dev && `component._updatingReadonlyProperty = false;`}
${event === 'scroll' && `${lock} = false;`}
`;
block.builders.init.addBlock(deindent`
function ${handlerName}(event) {
${handlerBody}
}
window.addEventListener("${event}", ${handlerName});
`);
block.builders.destroy.addBlock(deindent`
window.removeEventListener("${event}", ${handlerName});
`);
});
// special case... might need to abstract this out if we add more special cases
if (bindings.scrollX || bindings.scrollY) {
block.builders.init.addBlock(deindent`
#component.on("state", ({ changed, current }) => {
if (${
[bindings.scrollX, bindings.scrollY].map(
binding => binding && `changed["${binding}"]`
).filter(Boolean).join(' || ')
}) {
${lock} = true;
clearTimeout(${timeout});
window.scrollTo(${
bindings.scrollX ? `current["${bindings.scrollX}"]` : `window.pageXOffset`
}, ${
bindings.scrollY ? `current["${bindings.scrollY}"]` : `window.pageYOffset`
});
${timeout} = setTimeout(${clear}, 100);
}
});
`);
}
// another special case. (I'm starting to think these are all special cases.)
if (bindings.online) {
const handlerName = block.getUniqueName(`onlinestatuschanged`);
block.builders.init.addBlock(deindent`
function ${handlerName}(event) {
${component.options.dev && `component._updatingReadonlyProperty = true;`}
#component.set({ ${bindings.online}: navigator.onLine });
${component.options.dev && `component._updatingReadonlyProperty = false;`}
}
window.addEventListener("online", ${handlerName});
window.addEventListener("offline", ${handlerName});
`);
// add initial value
renderer.metaBindings.push(
`this._state.${bindings.online} = navigator.onLine;`
);
block.builders.destroy.addBlock(deindent`
window.removeEventListener("online", ${handlerName});
window.removeEventListener("offline", ${handlerName});
`);
}
}
}

@ -0,0 +1,62 @@
import Renderer from '../../Renderer';
import Block from '../../Block';
import Wrapper from './Wrapper';
import EventHandler from '../../../nodes/EventHandler';
import validCalleeObjects from '../../../../utils/validCalleeObjects';
export default class EventHandlerWrapper extends Wrapper {
node: EventHandler;
constructor(
renderer: Renderer,
block: Block,
parent: Wrapper,
node: EventHandler,
stripWhitespace: boolean,
nextSibling: Wrapper
) {
super(renderer, block, parent, node);
}
render(block: Block, parentNode: string, parentNodes: string) {
const { renderer } = this;
const { component } = renderer;
const hoisted = this.node.shouldHoist;
if (this.node.insertionPoint === null) return; // TODO handle shorthand events here?
if (!validCalleeObjects.has(this.node.callee.name)) {
const component_name = hoisted ? `component` : block.alias(`component`);
// allow event.stopPropagation(), this.select() etc
// TODO verify that it's a valid callee (i.e. built-in or declared method)
if (this.node.callee.name[0] === '$' && !component.methods.has(this.node.callee.name)) {
component.code.overwrite(
this.node.insertionPoint,
this.node.insertionPoint + 1,
`${component_name}.store.`
);
} else {
component.code.prependRight(
this.node.insertionPoint,
`${component_name}.`
);
}
}
if (this.node.isCustomEvent) {
this.node.args.forEach(arg => {
arg.overwriteThis(this.parent.var);
});
if (this.node.callee && this.node.callee.name === 'this') {
const node = this.node.callee.nodes[0];
component.code.overwrite(node.start, node.end, this.parent.var, {
storeName: true,
contentOnly: true
});
}
}
}
}

@ -0,0 +1,67 @@
import Wrapper from './Wrapper';
import Renderer from '../../Renderer';
import Block from '../../Block';
import Node from '../../../nodes/shared/Node';
import MustacheTag from '../../../nodes/MustacheTag';
import RawMustacheTag from '../../../nodes/RawMustacheTag';
export default class Tag extends Wrapper {
node: MustacheTag | RawMustacheTag;
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: MustacheTag | RawMustacheTag) {
super(renderer, block, parent, node);
this.cannotUseInnerHTML();
block.addDependencies(node.expression.dependencies);
}
render(block: Block, parentNode: string, parentNodes: string) {
const { init } = this.renameThisMethod(
block,
value => `@setData(${this.var}, ${value});`
);
block.addElement(
this.var,
`@createText(${init})`,
parentNodes && `@claimText(${parentNodes}, ${init})`,
parentNode
);
}
renameThisMethod(
block: Block,
update: ((value: string) => string)
) {
const { snippet, dependencies } = this.node.expression;
const value = this.node.shouldCache && block.getUniqueName(`${this.var}_value`);
const content = this.node.shouldCache ? value : snippet;
if (this.node.shouldCache) block.addVariable(value, snippet);
if (dependencies.size) {
const changedCheck = (
(block.hasOutros ? `!#current || ` : '') +
[...dependencies].map((dependency: string) => `changed.${dependency}`).join(' || ')
);
const updateCachedValue = `${value} !== (${value} = ${snippet})`;
const condition = this.node.shouldCache ?
(dependencies.size ? `(${changedCheck}) && ${updateCachedValue}` : updateCachedValue) :
changedCheck;
block.builders.update.addConditional(
condition,
update(content)
);
}
return { init: content };
}
remount(name: string) {
return `@append(${name}._slotted.default, ${this.var});`;
}
}

@ -0,0 +1,92 @@
import Renderer from '../../Renderer';
import Node from '../../../nodes/shared/Node';
import Block from '../../Block';
export default class Wrapper {
renderer: Renderer;
parent: Wrapper;
node: Node;
prev: Wrapper | null;
next: Wrapper | null;
var: string;
canUseInnerHTML: boolean;
constructor(
renderer: Renderer,
block: Block,
parent: Wrapper,
node: Node
) {
this.node = node;
// make these non-enumerable so that they can be logged sensibly
// (TODO in dev only?)
Object.defineProperties(this, {
renderer: {
value: renderer
},
parent: {
value: parent
}
});
this.canUseInnerHTML = !renderer.options.hydratable;
block.wrappers.push(this);
}
cannotUseInnerHTML() {
this.canUseInnerHTML = false;
if (this.parent) this.parent.cannotUseInnerHTML();
}
// TODO do we still need equivalent method on Node?
findNearest(pattern) {
if (pattern.test(this.node.type)) return this;
return this.parent && this.parent.findNearest(pattern);
}
getOrCreateAnchor(block: Block, parentNode: string, parentNodes: 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`)
: (this.next && this.next.var) || 'null';
if (needsAnchor) {
block.addElement(
anchor,
`@createComment()`,
parentNodes && `@createComment()`,
parentNode
);
}
return anchor;
}
getUpdateMountNode(anchor: string) {
return (this.parent && this.parent.isDomNode())
? this.parent.var
: `${anchor}.parentNode`;
}
isDomNode() {
return (
this.node.type === 'Element' ||
this.node.type === 'Text' ||
this.node.type === 'MustacheTag'
);
}
render(block: Block, parentNode: string, parentNodes: string) {
throw new Error(`render method not implemented by subclass ${this.node.type}`);
}
remount(name: string) {
return `${this.var}.m(${name}._slotted.default, null);`;
}
}

@ -0,0 +1,71 @@
import AwaitBlock from './handlers/AwaitBlock';
import Comment from './handlers/Comment';
import DebugTag from './handlers/DebugTag';
import EachBlock from './handlers/EachBlock';
import Element from './handlers/Element';
import Head from './handlers/Head';
import HtmlTag from './handlers/HtmlTag';
import IfBlock from './handlers/IfBlock';
import InlineComponent from './handlers/InlineComponent';
import Slot from './handlers/Slot';
import Tag from './handlers/Tag';
import Text from './handlers/Text';
import Title from './handlers/Title';
import { CompileOptions } from '../../interfaces';
type Handler = (node: any, renderer: Renderer, options: CompileOptions) => void;
function noop(){}
const handlers: Record<string, Handler> = {
AwaitBlock,
Comment,
DebugTag,
EachBlock,
Element,
Head,
IfBlock,
InlineComponent,
MustacheTag: Tag, // TODO MustacheTag is an anachronism
RawMustacheTag: HtmlTag,
Slot,
Text,
Title,
Window: noop
};
type AppendTarget = any; // TODO
export default class Renderer {
bindings: string[];
code: string;
targets: AppendTarget[];
constructor() {
this.bindings = [];
this.code = '';
this.targets = [];
}
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;
} else {
this.code += code;
}
}
render(nodes, options) {
nodes.forEach(node => {
const handler = handlers[node.type];
if (!handler) {
throw new Error(`No handler for '${node.type}' nodes`);
}
handler(node, this, options);
});
}
}

@ -0,0 +1,16 @@
import Renderer from '../Renderer';
import { CompileOptions } from '../../../interfaces';
export default function(node, renderer: Renderer, options: CompileOptions) {
const { snippet } = node.expression;
renderer.append('${(function(__value) { if(@isPromise(__value)) return `');
renderer.render(node.pending.children, options);
renderer.append('`; return function(ctx) { return `');
renderer.render(node.then.children, options);
renderer.append(`\`;}(Object.assign({}, ctx, { ${node.value}: __value }));}(${snippet})) }`);
}

@ -0,0 +1,8 @@
import Renderer from '../Renderer';
import { CompileOptions } from '../../../interfaces';
export default function(node, renderer: Renderer, options: CompileOptions) {
if (options.preserveComments) {
renderer.append(`<!--${node.data}-->`);
}
}

@ -0,0 +1,19 @@
import { stringify } from '../../../utils/stringify';
export default function(node, renderer, options) {
if (!options.dev) return;
const filename = options.file || null;
const { line, column } = options.locate(node.start + 1);
const obj = node.expressions.length === 0
? `ctx`
: `{ ${node.expressions
.map(e => e.node.name)
.map(name => `${name}: ctx.${name}`)
.join(', ')} }`;
const str = '${@debug(' + `${filename && stringify(filename)}, ${line}, ${column}, ${obj})}`;
renderer.append(str);
}

@ -0,0 +1,25 @@
export default function(node, renderer, options) {
const { snippet } = node.expression;
const props = node.contexts.map(prop => `${prop.key.name}: item${prop.tail}`);
const getContext = node.index
? `(item, i) => Object.assign({}, ctx, { ${props.join(', ')}, ${node.index}: i })`
: `item => Object.assign({}, ctx, { ${props.join(', ')} })`;
const open = `\${ ${node.else ? `${snippet}.length ? ` : ''}@each(${snippet}, ${getContext}, ctx => \``;
renderer.append(open);
renderer.render(node.children, options);
const close = `\`)`;
renderer.append(close);
if (node.else) {
renderer.append(` : \``);
renderer.render(node.else.children, options);
renderer.append(`\``);
}
renderer.append('}');
}

@ -0,0 +1,132 @@
import { quotePropIfNecessary, quoteNameIfNecessary } from '../../../utils/quoteIfNecessary';
import isVoidElementName from '../../../utils/isVoidElementName';
// source: https://gist.github.com/ArjanSchouten/0b8574a6ad7f5065a5e7
const boolean_attributes = new Set([
'async',
'autocomplete',
'autofocus',
'autoplay',
'border',
'challenge',
'checked',
'compact',
'contenteditable',
'controls',
'default',
'defer',
'disabled',
'formnovalidate',
'frameborder',
'hidden',
'indeterminate',
'ismap',
'loop',
'multiple',
'muted',
'nohref',
'noresize',
'noshade',
'novalidate',
'nowrap',
'open',
'readonly',
'required',
'reversed',
'scoped',
'scrolling',
'seamless',
'selected',
'sortable',
'spellcheck',
'translate'
]);
export default function(node, renderer, options) {
let openingTag = `<${node.name}`;
let textareaContents; // awkward special case
const slot = node.getStaticAttributeValue('slot');
if (slot && node.hasAncestor('InlineComponent')) {
const slot = node.attributes.find((attribute: Node) => attribute.name === 'slot');
const slotName = slot.chunks[0].data;
const target = renderer.targets[renderer.targets.length - 1];
target.slotStack.push(slotName);
target.slots[slotName] = '';
}
const classExpr = node.classes.map((classDir: Class) => {
const { expression, name } = classDir;
const snippet = expression ? expression.snippet : `ctx${quotePropIfNecessary(name)}`;
return `${snippet} ? "${name}" : ""`;
}).join(', ');
let addClassAttribute = classExpr ? true : false;
if (node.attributes.find(attr => attr.isSpread)) {
// TODO dry this out
const args = [];
node.attributes.forEach(attribute => {
if (attribute.isSpread) {
args.push(attribute.expression.snippet);
} else {
if (attribute.name === 'value' && node.name === 'textarea') {
textareaContents = attribute.stringifyForSsr();
} else if (attribute.isTrue) {
args.push(`{ ${quoteNameIfNecessary(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)}: ${attribute.chunks[0].snippet} }`);
} else {
args.push(`{ ${quoteNameIfNecessary(attribute.name)}: \`${attribute.stringifyForSsr()}\` }`);
}
}
});
openingTag += "${@spread([" + args.join(', ') + "])}";
} else {
node.attributes.forEach((attribute: Node) => {
if (attribute.type !== 'Attribute') return;
if (attribute.name === 'value' && node.name === 'textarea') {
textareaContents = attribute.stringifyForSsr();
} else if (attribute.isTrue) {
openingTag += ` ${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 += '${' + attribute.chunks[0].snippet + ' ? " ' + attribute.name + '" : "" }';
} else if (attribute.name === 'class' && classExpr) {
addClassAttribute = false;
openingTag += ` class="\${ [\`${attribute.stringifyForSsr()}\`, ${classExpr} ].join(' ').trim() }"`;
} else {
openingTag += ` ${attribute.name}="${attribute.stringifyForSsr()}"`;
}
});
}
if (addClassAttribute) {
openingTag += ` class="\${ [${classExpr}].join(' ').trim() }"`;
}
openingTag += '>';
renderer.append(openingTag);
if (node.name === 'textarea' && textareaContents !== undefined) {
renderer.append(textareaContents);
} else {
renderer.render(node.children, options);
}
if (!isVoidElementName(node.name)) {
renderer.append(`</${node.name}>`);
}
}

@ -0,0 +1,7 @@
export default function(node, renderer, options) {
renderer.append('${(__result.head += `');
renderer.render(node.children, options);
renderer.append('`, "")}');
}

@ -0,0 +1,3 @@
export default function(node, renderer, options) {
renderer.append('${' + node.expression.snippet + '}');
}

@ -0,0 +1,15 @@
export default function(node, renderer, options) {
const { snippet } = node.expression;
renderer.append('${ ' + snippet + ' ? `');
renderer.render(node.children, options);
renderer.append('` : `');
if (node.else) {
renderer.render(node.else.children, options);
}
renderer.append('` }');
}

@ -0,0 +1,130 @@
import { escape, escapeTemplate, stringify } from '../../../utils/stringify';
import getObject from '../../../utils/getObject';
import getTailSnippet from '../../../utils/getTailSnippet';
import { quoteNameIfNecessary, quotePropIfNecessary } from '../../../utils/quoteIfNecessary';
import deindent from '../../../utils/deindent';
type AppendTarget = any; // TODO
export default function(node, renderer, options) {
function stringifyAttribute(chunk: Node) {
if (chunk.type === 'Text') {
return escapeTemplate(escape(chunk.data));
}
return '${@escape( ' + chunk.snippet + ')}';
}
const bindingProps = node.bindings.map(binding => {
const { name } = getObject(binding.value.node);
const tail = binding.value.node.type === 'MemberExpression'
? getTailSnippet(binding.value.node)
: '';
return `${quoteNameIfNecessary(binding.name)}: ctx${quotePropIfNecessary(name)}${tail}`;
});
function getAttributeValue(attribute) {
if (attribute.isTrue) return `true`;
if (attribute.chunks.length === 0) return `''`;
if (attribute.chunks.length === 1) {
const chunk = attribute.chunks[0];
if (chunk.type === 'Text') {
return stringify(chunk.data);
}
return chunk.snippet;
}
return '`' + attribute.chunks.map(stringifyAttribute).join('') + '`';
}
const usesSpread = node.attributes.find(attr => attr.isSpread);
const props = usesSpread
? `Object.assign(${
node.attributes
.map(attribute => {
if (attribute.isSpread) {
return attribute.expression.snippet;
} else {
return `{ ${quoteNameIfNecessary(attribute.name)}: ${getAttributeValue(attribute)} }`;
}
})
.concat(bindingProps.map(p => `{ ${p} }`))
.join(', ')
})`
: `{ ${node.attributes
.map(attribute => `${quoteNameIfNecessary(attribute.name)}: ${getAttributeValue(attribute)}`)
.concat(bindingProps)
.join(', ')} }`;
const expression = (
node.name === 'svelte:self'
? node.component.name
: node.name === 'svelte:component'
? `((${node.expression.snippet}) || @missingComponent)`
: `%components-${node.name}`
);
node.bindings.forEach(binding => {
const conditions = [];
let parent = node;
while (parent = parent.parent) {
if (parent.type === 'IfBlock') {
// TODO handle contextual bindings...
conditions.push(`(${parent.expression.snippet})`);
}
}
conditions.push(
`!('${binding.name}' in ctx)`,
`${expression}.data`
);
const { name } = getObject(binding.value.node);
renderer.bindings.push(deindent`
if (${conditions.reverse().join('&&')}) {
tmp = ${expression}.data();
if ('${name}' in tmp) {
ctx${quotePropIfNecessary(binding.name)} = tmp.${name};
settled = false;
}
}
`);
});
let open = `\${@validateSsrComponent(${expression}, '${node.name}')._render(__result, ${props}`;
const component_options = [];
component_options.push(`store: options.store`);
if (node.children.length) {
const target: AppendTarget = {
slots: { default: '' },
slotStack: ['default']
};
renderer.targets.push(target);
renderer.render(node.children, options);
const slotted = Object.keys(target.slots)
.map(name => `${quoteNameIfNecessary(name)}: () => \`${target.slots[name]}\``)
.join(', ');
component_options.push(`slotted: { ${slotted} }`);
renderer.targets.pop();
}
if (component_options.length) {
open += `, { ${component_options.join(', ')} }`;
}
renderer.append(open);
renderer.append(')}');
}

@ -0,0 +1,14 @@
import { quotePropIfNecessary } from '../../../utils/quoteIfNecessary';
export default function(node, renderer, options) {
const name = node.attributes.find(attribute => attribute.name === 'name');
const slotName = name && name.chunks[0].data || 'default';
const prop = quotePropIfNecessary(slotName);
renderer.append(`\${options && options.slotted && options.slotted${prop} ? options.slotted${prop}() : \``);
renderer.render(node.children, options);
renderer.append(`\`}`);
}

@ -0,0 +1,9 @@
export default function(node, renderer, options) {
renderer.append(
node.parent &&
node.parent.type === 'Element' &&
node.parent.name === 'style'
? '${' + node.expression.snippet + '}'
: '${@escape(' + node.expression.snippet + ')}'
);
}

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

@ -0,0 +1,7 @@
export default function(node, renderer, options) {
renderer.append(`<title>`);
renderer.render(node.children, options);
renderer.append(`</title>`);
}

@ -1,45 +1,25 @@
import deindent from '../../utils/deindent';
import Component from '../Component';
import globalWhitelist from '../../utils/globalWhitelist';
import { Node, CompileOptions } from '../../interfaces';
import { AppendTarget } from '../../interfaces';
import { CompileOptions } from '../../interfaces';
import { stringify } from '../../utils/stringify';
import CodeBuilder from '../../utils/CodeBuilder';
export class SsrTarget {
bindings: string[];
renderCode: string;
appendTargets: AppendTarget[];
constructor() {
this.bindings = [];
this.renderCode = '';
this.appendTargets = [];
}
append(code: string) {
if (this.appendTargets.length) {
const appendTarget = this.appendTargets[this.appendTargets.length - 1];
const slotName = appendTarget.slotStack[appendTarget.slotStack.length - 1];
appendTarget.slots[slotName] += code;
} else {
this.renderCode += code;
}
}
}
import Renderer from './Renderer';
export default function ssr(
component: Component,
options: CompileOptions
) {
const renderer = new Renderer();
const format = options.format || 'cjs';
const { computations, name, templateProperties } = component;
// create main render() function
trim(component.fragment.children).forEach((node: Node) => {
node.ssr();
});
renderer.render(trim(component.fragment.children), Object.assign({
locate: component.locate
}, options));
const css = component.customElement ?
{ code: null, map: null } :
@ -139,7 +119,7 @@ export default function ssr(
({ key }) => `ctx.${key} = %computed-${key}(ctx);`
)}
${component.target.bindings.length &&
${renderer.bindings.length &&
deindent`
var settled = false;
var tmp;
@ -147,11 +127,11 @@ export default function ssr(
while (!settled) {
settled = true;
${component.target.bindings.join('\n\n')}
${renderer.bindings.join('\n\n')}
}
`}
return \`${component.target.renderCode}\`;
return \`${renderer.code}\`;
};
${name}.css = {

@ -68,13 +68,6 @@ export interface CompileOptions {
nestedTransitions?: boolean;
}
export interface GenerateOptions {
name: string;
format: ModuleFormat;
banner?: string;
sharedPath?: string;
}
export interface ShorthandImport {
name: string;
source: string;

@ -49,7 +49,7 @@ export const missingComponent = {
export function validateSsrComponent(component, name) {
if (!component || !component._render) {
if (name === 'svelte:component') name += 'this={...}';
if (name === 'svelte:component') name += ' this={...}';
throw new Error(`<${name}> is not a valid SSR component. You may need to review your build config to ensure that dependencies are compiled, rather than imported as pre-compiled modules`);
}

@ -1,20 +1,19 @@
/* src/Main.html generated by Svelte vx.y.z */
/* src/Main.html generated by Svelte v2.13.4 */
const file = "src/Main.html";
function create_main_fragment(component, ctx) {
var p, text;
var p;
return {
c: function create() {
p = createElement("p");
text = createText("Hello world!");
p.textContent = "Hello world!";
addLoc(p, file, 0, 0, 0);
},
m: function mount(target, anchor) {
insert(target, p, anchor);
append(p, text);
},
p: noop,
@ -64,10 +63,6 @@ function createElement(name) {
return document.createElement(name);
}
function createText(data) {
return document.createTextNode(data);
}
function addLoc(element, file, line, column, char) {
element.__svelte_meta = {
loc: { file, line, column, char }
@ -78,10 +73,6 @@ function insert(target, node, anchor) {
target.insertBefore(node, anchor);
}
function append(target, node) {
target.appendChild(node);
}
function noop() {}
function detachNode(node) {

@ -9,24 +9,24 @@ var Main = (function(answer) { "use strict";
};
function create_main_fragment(component, ctx) {
var p, text, text_1;
var p, text0, text1;
return {
c() {
p = createElement("p");
text = createText("The answer is ");
text_1 = createText(ctx.answer);
text0 = createText("The answer is ");
text1 = createText(ctx.answer);
},
m(target, anchor) {
insert(target, p, anchor);
append(p, text);
append(p, text_1);
append(p, text0);
append(p, text1);
},
p(changed, ctx) {
if (changed.answer) {
setData(text_1, ctx.answer);
setData(text1, ctx.answer);
}
},

@ -1,24 +1,24 @@
/* src/Main.html generated by Svelte vx.y.z */
function create_main_fragment(component, ctx) {
var h1, text, text_1;
var h1, text0, text1;
return {
c() {
h1 = createElement("h1");
text = createText("Hello ");
text_1 = createText(ctx.$name);
text0 = createText("Hello ");
text1 = createText(ctx.$name);
},
m(target, anchor) {
insert(target, h1, anchor);
append(h1, text);
append(h1, text_1);
append(h1, text0);
append(h1, text1);
},
p(changed, ctx) {
if (changed.$name) {
setData(text_1, ctx.$name);
setData(text1, ctx.$name);
}
},

@ -191,30 +191,30 @@ var protoDev = {
const file = undefined;
function create_main_fragment(component, ctx) {
var h1, text, text_1, text_2, text_3;
var h1, text0, text1, text2, text3;
return {
c: function create() {
h1 = createElement("h1");
text = createText("Hello ");
text_1 = createText(ctx.name);
text_2 = createText("!");
text_3 = createText("\n");
text0 = createText("Hello ");
text1 = createText(ctx.name);
text2 = createText("!");
text3 = createText("\n");
debugger;
addLoc(h1, file, 0, 0, 0);
},
m: function mount(target, anchor) {
insert(target, h1, anchor);
append(h1, text);
append(h1, text_1);
append(h1, text_2);
insert(target, text_3, anchor);
append(h1, text0);
append(h1, text1);
append(h1, text2);
insert(target, text3, anchor);
},
p: function update(changed, ctx) {
if (changed.name) {
setData(text_1, ctx.name);
setData(text1, ctx.name);
}
debugger;
@ -223,7 +223,7 @@ function create_main_fragment(component, ctx) {
d: function destroy$$1(detach) {
if (detach) {
detachNode(h1);
detachNode(text_3);
detachNode(text3);
}
}
};

@ -4,30 +4,30 @@ import { addLoc, append, assign, createElement, createText, detachNode, init, in
const file = undefined;
function create_main_fragment(component, ctx) {
var h1, text, text_1, text_2, text_3;
var h1, text0, text1, text2, text3;
return {
c: function create() {
h1 = createElement("h1");
text = createText("Hello ");
text_1 = createText(ctx.name);
text_2 = createText("!");
text_3 = createText("\n");
text0 = createText("Hello ");
text1 = createText(ctx.name);
text2 = createText("!");
text3 = createText("\n");
debugger;
addLoc(h1, file, 0, 0, 0);
},
m: function mount(target, anchor) {
insert(target, h1, anchor);
append(h1, text);
append(h1, text_1);
append(h1, text_2);
insert(target, text_3, anchor);
append(h1, text0);
append(h1, text1);
append(h1, text2);
insert(target, text3, anchor);
},
p: function update(changed, ctx) {
if (changed.name) {
setData(text_1, ctx.name);
setData(text1, ctx.name);
}
debugger;
@ -36,7 +36,7 @@ function create_main_fragment(component, ctx) {
d: function destroy(detach) {
if (detach) {
detachNode(h1);
detachNode(text_3);
detachNode(text3);
}
}
};

@ -196,8 +196,16 @@ var protoDev = {
const file = undefined;
function get_each_context(ctx, list, i) {
const child_ctx = Object.create(ctx);
child_ctx.thing = list[i];
child_ctx.each_value = list;
child_ctx.thing_index = i;
return child_ctx;
}
function create_main_fragment(component, ctx) {
var text, p, text_1, text_2;
var text0, p, text1, text2;
var each_value = ctx.things;
@ -213,10 +221,10 @@ function create_main_fragment(component, ctx) {
each_blocks[i].c();
}
text = createText("\n\n");
text0 = createText("\n\n");
p = createElement("p");
text_1 = createText("foo: ");
text_2 = createText(ctx.foo);
text1 = createText("foo: ");
text2 = createText(ctx.foo);
addLoc(p, file, 5, 0, 91);
},
@ -225,10 +233,10 @@ function create_main_fragment(component, ctx) {
each_blocks[i].m(target, anchor);
}
insert(target, text, anchor);
insert(target, text0, anchor);
insert(target, p, anchor);
append(p, text_1);
append(p, text_2);
append(p, text1);
append(p, text2);
},
p: function update(changed, ctx) {
@ -243,7 +251,7 @@ function create_main_fragment(component, ctx) {
} else {
each_blocks[i] = create_each_block(component, child_ctx);
each_blocks[i].c();
each_blocks[i].m(text.parentNode, text);
each_blocks[i].m(text0.parentNode, text0);
}
}
@ -254,7 +262,7 @@ function create_main_fragment(component, ctx) {
}
if (changed.foo) {
setData(text_2, ctx.foo);
setData(text2, ctx.foo);
}
},
@ -262,7 +270,7 @@ function create_main_fragment(component, ctx) {
destroyEach(each_blocks, detach);
if (detach) {
detachNode(text);
detachNode(text0);
detachNode(p);
}
}
@ -271,13 +279,13 @@ function create_main_fragment(component, ctx) {
// (1:0) {#each things as thing}
function create_each_block(component, ctx) {
var span, text_value = ctx.thing.name, text, text_1;
var span, text0_value = ctx.thing.name, text0, text1;
return {
c: function create() {
span = createElement("span");
text = createText(text_value);
text_1 = createText("\n\t");
text0 = createText(text0_value);
text1 = createText("\n\t");
{
const { foo, bar, baz, thing } = ctx;
@ -289,13 +297,13 @@ function create_each_block(component, ctx) {
m: function mount(target, anchor) {
insert(target, span, anchor);
append(span, text);
insert(target, text_1, anchor);
append(span, text0);
insert(target, text1, anchor);
},
p: function update(changed, ctx) {
if ((changed.things) && text_value !== (text_value = ctx.thing.name)) {
setData(text, text_value);
if ((changed.things) && text0_value !== (text0_value = ctx.thing.name)) {
setData(text0, text0_value);
}
if (changed.foo || changed.bar || changed.baz || changed.things) {
@ -308,20 +316,12 @@ function create_each_block(component, ctx) {
d: function destroy$$1(detach) {
if (detach) {
detachNode(span);
detachNode(text_1);
detachNode(text1);
}
}
};
}
function get_each_context(ctx, list, i) {
const child_ctx = Object.create(ctx);
child_ctx.thing = list[i];
child_ctx.each_value = list;
child_ctx.thing_index = i;
return child_ctx;
}
function SvelteComponent(options) {
this._debugName = '<SvelteComponent>';
if (!options || (!options.target && !options.root)) throw new Error("'target' is a required option");

@ -3,8 +3,16 @@ import { addLoc, append, assign, createElement, createText, destroyEach, detachN
const file = undefined;
function get_each_context(ctx, list, i) {
const child_ctx = Object.create(ctx);
child_ctx.thing = list[i];
child_ctx.each_value = list;
child_ctx.thing_index = i;
return child_ctx;
}
function create_main_fragment(component, ctx) {
var text, p, text_1, text_2;
var text0, p, text1, text2;
var each_value = ctx.things;
@ -20,10 +28,10 @@ function create_main_fragment(component, ctx) {
each_blocks[i].c();
}
text = createText("\n\n");
text0 = createText("\n\n");
p = createElement("p");
text_1 = createText("foo: ");
text_2 = createText(ctx.foo);
text1 = createText("foo: ");
text2 = createText(ctx.foo);
addLoc(p, file, 5, 0, 91);
},
@ -32,10 +40,10 @@ function create_main_fragment(component, ctx) {
each_blocks[i].m(target, anchor);
}
insert(target, text, anchor);
insert(target, text0, anchor);
insert(target, p, anchor);
append(p, text_1);
append(p, text_2);
append(p, text1);
append(p, text2);
},
p: function update(changed, ctx) {
@ -50,7 +58,7 @@ function create_main_fragment(component, ctx) {
} else {
each_blocks[i] = create_each_block(component, child_ctx);
each_blocks[i].c();
each_blocks[i].m(text.parentNode, text);
each_blocks[i].m(text0.parentNode, text0);
}
}
@ -61,7 +69,7 @@ function create_main_fragment(component, ctx) {
}
if (changed.foo) {
setData(text_2, ctx.foo);
setData(text2, ctx.foo);
}
},
@ -69,7 +77,7 @@ function create_main_fragment(component, ctx) {
destroyEach(each_blocks, detach);
if (detach) {
detachNode(text);
detachNode(text0);
detachNode(p);
}
}
@ -78,13 +86,13 @@ function create_main_fragment(component, ctx) {
// (1:0) {#each things as thing}
function create_each_block(component, ctx) {
var span, text_value = ctx.thing.name, text, text_1;
var span, text0_value = ctx.thing.name, text0, text1;
return {
c: function create() {
span = createElement("span");
text = createText(text_value);
text_1 = createText("\n\t");
text0 = createText(text0_value);
text1 = createText("\n\t");
{
const { foo, bar, baz, thing } = ctx;
@ -96,13 +104,13 @@ function create_each_block(component, ctx) {
m: function mount(target, anchor) {
insert(target, span, anchor);
append(span, text);
insert(target, text_1, anchor);
append(span, text0);
insert(target, text1, anchor);
},
p: function update(changed, ctx) {
if ((changed.things) && text_value !== (text_value = ctx.thing.name)) {
setData(text, text_value);
if ((changed.things) && text0_value !== (text0_value = ctx.thing.name)) {
setData(text0, text0_value);
}
if (changed.foo || changed.bar || changed.baz || changed.things) {
@ -115,20 +123,12 @@ function create_each_block(component, ctx) {
d: function destroy(detach) {
if (detach) {
detachNode(span);
detachNode(text_1);
detachNode(text1);
}
}
};
}
function get_each_context(ctx, list, i) {
const child_ctx = Object.create(ctx);
child_ctx.thing = list[i];
child_ctx.each_value = list;
child_ctx.thing_index = i;
return child_ctx;
}
function SvelteComponent(options) {
this._debugName = '<SvelteComponent>';
if (!options || (!options.target && !options.root)) throw new Error("'target' is a required option");

@ -196,8 +196,16 @@ var protoDev = {
const file = undefined;
function get_each_context(ctx, list, i) {
const child_ctx = Object.create(ctx);
child_ctx.thing = list[i];
child_ctx.each_value = list;
child_ctx.thing_index = i;
return child_ctx;
}
function create_main_fragment(component, ctx) {
var text, p, text_1, text_2;
var text0, p, text1, text2;
var each_value = ctx.things;
@ -213,10 +221,10 @@ function create_main_fragment(component, ctx) {
each_blocks[i].c();
}
text = createText("\n\n");
text0 = createText("\n\n");
p = createElement("p");
text_1 = createText("foo: ");
text_2 = createText(ctx.foo);
text1 = createText("foo: ");
text2 = createText(ctx.foo);
addLoc(p, file, 5, 0, 74);
},
@ -225,10 +233,10 @@ function create_main_fragment(component, ctx) {
each_blocks[i].m(target, anchor);
}
insert(target, text, anchor);
insert(target, text0, anchor);
insert(target, p, anchor);
append(p, text_1);
append(p, text_2);
append(p, text1);
append(p, text2);
},
p: function update(changed, ctx) {
@ -243,7 +251,7 @@ function create_main_fragment(component, ctx) {
} else {
each_blocks[i] = create_each_block(component, child_ctx);
each_blocks[i].c();
each_blocks[i].m(text.parentNode, text);
each_blocks[i].m(text0.parentNode, text0);
}
}
@ -254,7 +262,7 @@ function create_main_fragment(component, ctx) {
}
if (changed.foo) {
setData(text_2, ctx.foo);
setData(text2, ctx.foo);
}
},
@ -262,7 +270,7 @@ function create_main_fragment(component, ctx) {
destroyEach(each_blocks, detach);
if (detach) {
detachNode(text);
detachNode(text0);
detachNode(p);
}
}
@ -271,13 +279,13 @@ function create_main_fragment(component, ctx) {
// (1:0) {#each things as thing}
function create_each_block(component, ctx) {
var span, text_value = ctx.thing.name, text, text_1;
var span, text0_value = ctx.thing.name, text0, text1;
return {
c: function create() {
span = createElement("span");
text = createText(text_value);
text_1 = createText("\n\t");
text0 = createText(text0_value);
text1 = createText("\n\t");
{
const { foo } = ctx;
@ -289,13 +297,13 @@ function create_each_block(component, ctx) {
m: function mount(target, anchor) {
insert(target, span, anchor);
append(span, text);
insert(target, text_1, anchor);
append(span, text0);
insert(target, text1, anchor);
},
p: function update(changed, ctx) {
if ((changed.things) && text_value !== (text_value = ctx.thing.name)) {
setData(text, text_value);
if ((changed.things) && text0_value !== (text0_value = ctx.thing.name)) {
setData(text0, text0_value);
}
if (changed.foo) {
@ -308,20 +316,12 @@ function create_each_block(component, ctx) {
d: function destroy$$1(detach) {
if (detach) {
detachNode(span);
detachNode(text_1);
detachNode(text1);
}
}
};
}
function get_each_context(ctx, list, i) {
const child_ctx = Object.create(ctx);
child_ctx.thing = list[i];
child_ctx.each_value = list;
child_ctx.thing_index = i;
return child_ctx;
}
function SvelteComponent(options) {
this._debugName = '<SvelteComponent>';
if (!options || (!options.target && !options.root)) throw new Error("'target' is a required option");

@ -3,8 +3,16 @@ import { addLoc, append, assign, createElement, createText, destroyEach, detachN
const file = undefined;
function get_each_context(ctx, list, i) {
const child_ctx = Object.create(ctx);
child_ctx.thing = list[i];
child_ctx.each_value = list;
child_ctx.thing_index = i;
return child_ctx;
}
function create_main_fragment(component, ctx) {
var text, p, text_1, text_2;
var text0, p, text1, text2;
var each_value = ctx.things;
@ -20,10 +28,10 @@ function create_main_fragment(component, ctx) {
each_blocks[i].c();
}
text = createText("\n\n");
text0 = createText("\n\n");
p = createElement("p");
text_1 = createText("foo: ");
text_2 = createText(ctx.foo);
text1 = createText("foo: ");
text2 = createText(ctx.foo);
addLoc(p, file, 5, 0, 74);
},
@ -32,10 +40,10 @@ function create_main_fragment(component, ctx) {
each_blocks[i].m(target, anchor);
}
insert(target, text, anchor);
insert(target, text0, anchor);
insert(target, p, anchor);
append(p, text_1);
append(p, text_2);
append(p, text1);
append(p, text2);
},
p: function update(changed, ctx) {
@ -50,7 +58,7 @@ function create_main_fragment(component, ctx) {
} else {
each_blocks[i] = create_each_block(component, child_ctx);
each_blocks[i].c();
each_blocks[i].m(text.parentNode, text);
each_blocks[i].m(text0.parentNode, text0);
}
}
@ -61,7 +69,7 @@ function create_main_fragment(component, ctx) {
}
if (changed.foo) {
setData(text_2, ctx.foo);
setData(text2, ctx.foo);
}
},
@ -69,7 +77,7 @@ function create_main_fragment(component, ctx) {
destroyEach(each_blocks, detach);
if (detach) {
detachNode(text);
detachNode(text0);
detachNode(p);
}
}
@ -78,13 +86,13 @@ function create_main_fragment(component, ctx) {
// (1:0) {#each things as thing}
function create_each_block(component, ctx) {
var span, text_value = ctx.thing.name, text, text_1;
var span, text0_value = ctx.thing.name, text0, text1;
return {
c: function create() {
span = createElement("span");
text = createText(text_value);
text_1 = createText("\n\t");
text0 = createText(text0_value);
text1 = createText("\n\t");
{
const { foo } = ctx;
@ -96,13 +104,13 @@ function create_each_block(component, ctx) {
m: function mount(target, anchor) {
insert(target, span, anchor);
append(span, text);
insert(target, text_1, anchor);
append(span, text0);
insert(target, text1, anchor);
},
p: function update(changed, ctx) {
if ((changed.things) && text_value !== (text_value = ctx.thing.name)) {
setData(text, text_value);
if ((changed.things) && text0_value !== (text0_value = ctx.thing.name)) {
setData(text0, text0_value);
}
if (changed.foo) {
@ -115,20 +123,12 @@ function create_each_block(component, ctx) {
d: function destroy(detach) {
if (detach) {
detachNode(span);
detachNode(text_1);
detachNode(text1);
}
}
};
}
function get_each_context(ctx, list, i) {
const child_ctx = Object.create(ctx);
child_ctx.thing = list[i];
child_ctx.each_value = list;
child_ctx.thing_index = i;
return child_ctx;
}
function SvelteComponent(options) {
this._debugName = '<SvelteComponent>';
if (!options || (!options.target && !options.root)) throw new Error("'target' is a required option");

@ -174,6 +174,14 @@ var proto = {
/* generated by Svelte vX.Y.Z */
function get_each_context(ctx, list, i) {
const child_ctx = Object.create(ctx);
child_ctx.node = list[i];
child_ctx.each_value = list;
child_ctx.node_index = i;
return child_ctx;
}
function create_main_fragment(component, ctx) {
var each_anchor;
@ -264,14 +272,6 @@ function create_each_block(component, ctx) {
};
}
function get_each_context(ctx, list, i) {
const child_ctx = Object.create(ctx);
child_ctx.node = list[i];
child_ctx.each_value = list;
child_ctx.node_index = i;
return child_ctx;
}
function SvelteComponent(options) {
init(this, options);
this._state = assign({}, options.data);

@ -1,6 +1,14 @@
/* generated by Svelte vX.Y.Z */
import { append, assign, createComment, createElement, createText, destroyEach, detachNode, init, insert, proto, setData } from "svelte/shared.js";
function get_each_context(ctx, list, i) {
const child_ctx = Object.create(ctx);
child_ctx.node = list[i];
child_ctx.each_value = list;
child_ctx.node_index = i;
return child_ctx;
}
function create_main_fragment(component, ctx) {
var each_anchor;
@ -91,14 +99,6 @@ function create_each_block(component, ctx) {
};
}
function get_each_context(ctx, list, i) {
const child_ctx = Object.create(ctx);
child_ctx.node = list[i];
child_ctx.each_value = list;
child_ctx.node_index = i;
return child_ctx;
}
function SvelteComponent(options) {
init(this, options);
this._state = assign({}, options.data);

@ -195,31 +195,31 @@ function bar({ foo }) {
const file = undefined;
function create_main_fragment(component, ctx) {
var p, text_value = ctx.Math.max(0, ctx.foo), text, text_1, text_2;
var p, text0_value = ctx.Math.max(0, ctx.foo), text0, text1, text2;
return {
c: function create() {
p = createElement("p");
text = createText(text_value);
text_1 = createText("\n\t");
text_2 = createText(ctx.bar);
text0 = createText(text0_value);
text1 = createText("\n\t");
text2 = createText(ctx.bar);
addLoc(p, file, 0, 0, 0);
},
m: function mount(target, anchor) {
insert(target, p, anchor);
append(p, text);
append(p, text_1);
append(p, text_2);
append(p, text0);
append(p, text1);
append(p, text2);
},
p: function update(changed, ctx) {
if ((changed.Math || changed.foo) && text_value !== (text_value = ctx.Math.max(0, ctx.foo))) {
setData(text, text_value);
if ((changed.Math || changed.foo) && text0_value !== (text0_value = ctx.Math.max(0, ctx.foo))) {
setData(text0, text0_value);
}
if (changed.bar) {
setData(text_2, ctx.bar);
setData(text2, ctx.bar);
}
},

@ -8,31 +8,31 @@ function bar({ foo }) {
const file = undefined;
function create_main_fragment(component, ctx) {
var p, text_value = ctx.Math.max(0, ctx.foo), text, text_1, text_2;
var p, text0_value = ctx.Math.max(0, ctx.foo), text0, text1, text2;
return {
c: function create() {
p = createElement("p");
text = createText(text_value);
text_1 = createText("\n\t");
text_2 = createText(ctx.bar);
text0 = createText(text0_value);
text1 = createText("\n\t");
text2 = createText(ctx.bar);
addLoc(p, file, 0, 0, 0);
},
m: function mount(target, anchor) {
insert(target, p, anchor);
append(p, text);
append(p, text_1);
append(p, text_2);
append(p, text0);
append(p, text1);
append(p, text2);
},
p: function update(changed, ctx) {
if ((changed.Math || changed.foo) && text_value !== (text_value = ctx.Math.max(0, ctx.foo))) {
setData(text, text_value);
if ((changed.Math || changed.foo) && text0_value !== (text0_value = ctx.Math.max(0, ctx.foo))) {
setData(text0, text0_value);
}
if (changed.bar) {
setData(text_2, ctx.bar);
setData(text2, ctx.bar);
}
},

@ -157,34 +157,34 @@ var proto = {
/* generated by Svelte vX.Y.Z */
function create_main_fragment(component, ctx) {
var div, text, div_1;
var div0, text, div1;
return {
c() {
div = createElement("div");
div0 = createElement("div");
text = createText("\n");
div_1 = createElement("div");
div.dataset.foo = "bar";
div_1.dataset.foo = ctx.bar;
div1 = createElement("div");
div0.dataset.foo = "bar";
div1.dataset.foo = ctx.bar;
},
m(target, anchor) {
insert(target, div, anchor);
insert(target, div0, anchor);
insert(target, text, anchor);
insert(target, div_1, anchor);
insert(target, div1, anchor);
},
p(changed, ctx) {
if (changed.bar) {
div_1.dataset.foo = ctx.bar;
div1.dataset.foo = ctx.bar;
}
},
d(detach) {
if (detach) {
detachNode(div);
detachNode(div0);
detachNode(text);
detachNode(div_1);
detachNode(div1);
}
}
};

@ -2,34 +2,34 @@
import { assign, createElement, createText, detachNode, init, insert, proto } from "svelte/shared.js";
function create_main_fragment(component, ctx) {
var div, text, div_1;
var div0, text, div1;
return {
c() {
div = createElement("div");
div0 = createElement("div");
text = createText("\n");
div_1 = createElement("div");
div.dataset.foo = "bar";
div_1.dataset.foo = ctx.bar;
div1 = createElement("div");
div0.dataset.foo = "bar";
div1.dataset.foo = ctx.bar;
},
m(target, anchor) {
insert(target, div, anchor);
insert(target, div0, anchor);
insert(target, text, anchor);
insert(target, div_1, anchor);
insert(target, div1, anchor);
},
p(changed, ctx) {
if (changed.bar) {
div_1.dataset.foo = ctx.bar;
div1.dataset.foo = ctx.bar;
}
},
d(detach) {
if (detach) {
detachNode(div);
detachNode(div0);
detachNode(text);
detachNode(div_1);
detachNode(div1);
}
}
};

@ -161,34 +161,34 @@ var proto = {
/* generated by Svelte vX.Y.Z */
function create_main_fragment(component, ctx) {
var div, text, div_1;
var div0, text, div1;
return {
c() {
div = createElement("div");
div0 = createElement("div");
text = createText("\n");
div_1 = createElement("div");
setAttribute(div, "data-foo", "bar");
setAttribute(div_1, "data-foo", ctx.bar);
div1 = createElement("div");
setAttribute(div0, "data-foo", "bar");
setAttribute(div1, "data-foo", ctx.bar);
},
m(target, anchor) {
insert(target, div, anchor);
insert(target, div0, anchor);
insert(target, text, anchor);
insert(target, div_1, anchor);
insert(target, div1, anchor);
},
p(changed, ctx) {
if (changed.bar) {
setAttribute(div_1, "data-foo", ctx.bar);
setAttribute(div1, "data-foo", ctx.bar);
}
},
d(detach) {
if (detach) {
detachNode(div);
detachNode(div0);
detachNode(text);
detachNode(div_1);
detachNode(div1);
}
}
};

@ -2,34 +2,34 @@
import { assign, createElement, createText, detachNode, init, insert, proto, setAttribute } from "svelte/shared.js";
function create_main_fragment(component, ctx) {
var div, text, div_1;
var div0, text, div1;
return {
c() {
div = createElement("div");
div0 = createElement("div");
text = createText("\n");
div_1 = createElement("div");
setAttribute(div, "data-foo", "bar");
setAttribute(div_1, "data-foo", ctx.bar);
div1 = createElement("div");
setAttribute(div0, "data-foo", "bar");
setAttribute(div1, "data-foo", ctx.bar);
},
m(target, anchor) {
insert(target, div, anchor);
insert(target, div0, anchor);
insert(target, text, anchor);
insert(target, div_1, anchor);
insert(target, div1, anchor);
},
p(changed, ctx) {
if (changed.bar) {
setAttribute(div_1, "data-foo", ctx.bar);
setAttribute(div1, "data-foo", ctx.bar);
}
},
d(detach) {
if (detach) {
detachNode(div);
detachNode(div0);
detachNode(text);
detachNode(div_1);
detachNode(div1);
}
}
};

@ -161,26 +161,26 @@ var proto = {
/* generated by Svelte vX.Y.Z */
function create_main_fragment(component, ctx) {
var svg, g, g_1;
var svg, g0, g1;
return {
c() {
svg = createSvgElement("svg");
g = createSvgElement("g");
g_1 = createSvgElement("g");
setAttribute(g, "data-foo", "bar");
setAttribute(g_1, "data-foo", ctx.bar);
g0 = createSvgElement("g");
g1 = createSvgElement("g");
setAttribute(g0, "data-foo", "bar");
setAttribute(g1, "data-foo", ctx.bar);
},
m(target, anchor) {
insert(target, svg, anchor);
append(svg, g);
append(svg, g_1);
append(svg, g0);
append(svg, g1);
},
p(changed, ctx) {
if (changed.bar) {
setAttribute(g_1, "data-foo", ctx.bar);
setAttribute(g1, "data-foo", ctx.bar);
}
},

@ -2,26 +2,26 @@
import { append, assign, createSvgElement, detachNode, init, insert, proto, setAttribute } from "svelte/shared.js";
function create_main_fragment(component, ctx) {
var svg, g, g_1;
var svg, g0, g1;
return {
c() {
svg = createSvgElement("svg");
g = createSvgElement("g");
g_1 = createSvgElement("g");
setAttribute(g, "data-foo", "bar");
setAttribute(g_1, "data-foo", ctx.bar);
g0 = createSvgElement("g");
g1 = createSvgElement("g");
setAttribute(g0, "data-foo", "bar");
setAttribute(g1, "data-foo", ctx.bar);
},
m(target, anchor) {
insert(target, svg, anchor);
append(svg, g);
append(svg, g_1);
append(svg, g0);
append(svg, g1);
},
p(changed, ctx) {
if (changed.bar) {
setAttribute(g_1, "data-foo", ctx.bar);
setAttribute(g1, "data-foo", ctx.bar);
}
},

@ -176,8 +176,16 @@ var proto = {
/* generated by Svelte vX.Y.Z */
function get_each_context(ctx, list, i) {
const child_ctx = Object.create(ctx);
child_ctx.comment = list[i];
child_ctx.each_value = list;
child_ctx.i = i;
return child_ctx;
}
function create_main_fragment(component, ctx) {
var text, p, text_1;
var text0, p, text1;
var each_value = ctx.comments;
@ -193,9 +201,9 @@ function create_main_fragment(component, ctx) {
each_blocks[i].c();
}
text = createText("\n\n");
text0 = createText("\n\n");
p = createElement("p");
text_1 = createText(ctx.foo);
text1 = createText(ctx.foo);
},
m(target, anchor) {
@ -203,9 +211,9 @@ function create_main_fragment(component, ctx) {
each_blocks[i].m(target, anchor);
}
insert(target, text, anchor);
insert(target, text0, anchor);
insert(target, p, anchor);
append(p, text_1);
append(p, text1);
},
p(changed, ctx) {
@ -220,7 +228,7 @@ function create_main_fragment(component, ctx) {
} else {
each_blocks[i] = create_each_block(component, child_ctx);
each_blocks[i].c();
each_blocks[i].m(text.parentNode, text);
each_blocks[i].m(text0.parentNode, text0);
}
}
@ -231,7 +239,7 @@ function create_main_fragment(component, ctx) {
}
if (changed.foo) {
setData(text_1, ctx.foo);
setData(text1, ctx.foo);
}
},
@ -239,7 +247,7 @@ function create_main_fragment(component, ctx) {
destroyEach(each_blocks, detach);
if (detach) {
detachNode(text);
detachNode(text0);
detachNode(p);
}
}
@ -248,20 +256,20 @@ function create_main_fragment(component, ctx) {
// (1:0) {#each comments as comment, i}
function create_each_block(component, ctx) {
var div, strong, text, text_1, span, text_2_value = ctx.comment.author, text_2, text_3, text_4_value = ctx.elapsed(ctx.comment.time, ctx.time), text_4, text_5, text_6, raw_value = ctx.comment.html, raw_before;
var div, strong, text0, text1, span, text2_value = ctx.comment.author, text2, text3, text4_value = ctx.elapsed(ctx.comment.time, ctx.time), text4, text5, text6, raw_value = ctx.comment.html, raw_before;
return {
c() {
div = createElement("div");
strong = createElement("strong");
text = createText(ctx.i);
text_1 = createText("\n\n\t\t");
text0 = createText(ctx.i);
text1 = createText("\n\n\t\t");
span = createElement("span");
text_2 = createText(text_2_value);
text_3 = createText(" wrote ");
text_4 = createText(text_4_value);
text_5 = createText(" ago:");
text_6 = createText("\n\n\t\t");
text2 = createText(text2_value);
text3 = createText(" wrote ");
text4 = createText(text4_value);
text5 = createText(" ago:");
text6 = createText("\n\n\t\t");
raw_before = createElement('noscript');
span.className = "meta";
div.className = "comment";
@ -270,25 +278,25 @@ function create_each_block(component, ctx) {
m(target, anchor) {
insert(target, div, anchor);
append(div, strong);
append(strong, text);
append(div, text_1);
append(strong, text0);
append(div, text1);
append(div, span);
append(span, text_2);
append(span, text_3);
append(span, text_4);
append(span, text_5);
append(div, text_6);
append(span, text2);
append(span, text3);
append(span, text4);
append(span, text5);
append(div, text6);
append(div, raw_before);
raw_before.insertAdjacentHTML("afterend", raw_value);
},
p(changed, ctx) {
if ((changed.comments) && text_2_value !== (text_2_value = ctx.comment.author)) {
setData(text_2, text_2_value);
if ((changed.comments) && text2_value !== (text2_value = ctx.comment.author)) {
setData(text2, text2_value);
}
if ((changed.elapsed || changed.comments || changed.time) && text_4_value !== (text_4_value = ctx.elapsed(ctx.comment.time, ctx.time))) {
setData(text_4, text_4_value);
if ((changed.elapsed || changed.comments || changed.time) && text4_value !== (text4_value = ctx.elapsed(ctx.comment.time, ctx.time))) {
setData(text4, text4_value);
}
if ((changed.comments) && raw_value !== (raw_value = ctx.comment.html)) {
@ -305,14 +313,6 @@ function create_each_block(component, ctx) {
};
}
function get_each_context(ctx, list, i) {
const child_ctx = Object.create(ctx);
child_ctx.comment = list[i];
child_ctx.each_value = list;
child_ctx.i = i;
return child_ctx;
}
function SvelteComponent(options) {
init(this, options);
this._state = assign({}, options.data);

@ -1,8 +1,16 @@
/* generated by Svelte vX.Y.Z */
import { append, assign, createElement, createText, destroyEach, detachAfter, detachNode, init, insert, proto, setData } from "svelte/shared.js";
function get_each_context(ctx, list, i) {
const child_ctx = Object.create(ctx);
child_ctx.comment = list[i];
child_ctx.each_value = list;
child_ctx.i = i;
return child_ctx;
}
function create_main_fragment(component, ctx) {
var text, p, text_1;
var text0, p, text1;
var each_value = ctx.comments;
@ -18,9 +26,9 @@ function create_main_fragment(component, ctx) {
each_blocks[i].c();
}
text = createText("\n\n");
text0 = createText("\n\n");
p = createElement("p");
text_1 = createText(ctx.foo);
text1 = createText(ctx.foo);
},
m(target, anchor) {
@ -28,9 +36,9 @@ function create_main_fragment(component, ctx) {
each_blocks[i].m(target, anchor);
}
insert(target, text, anchor);
insert(target, text0, anchor);
insert(target, p, anchor);
append(p, text_1);
append(p, text1);
},
p(changed, ctx) {
@ -45,7 +53,7 @@ function create_main_fragment(component, ctx) {
} else {
each_blocks[i] = create_each_block(component, child_ctx);
each_blocks[i].c();
each_blocks[i].m(text.parentNode, text);
each_blocks[i].m(text0.parentNode, text0);
}
}
@ -56,7 +64,7 @@ function create_main_fragment(component, ctx) {
}
if (changed.foo) {
setData(text_1, ctx.foo);
setData(text1, ctx.foo);
}
},
@ -64,7 +72,7 @@ function create_main_fragment(component, ctx) {
destroyEach(each_blocks, detach);
if (detach) {
detachNode(text);
detachNode(text0);
detachNode(p);
}
}
@ -73,20 +81,20 @@ function create_main_fragment(component, ctx) {
// (1:0) {#each comments as comment, i}
function create_each_block(component, ctx) {
var div, strong, text, text_1, span, text_2_value = ctx.comment.author, text_2, text_3, text_4_value = ctx.elapsed(ctx.comment.time, ctx.time), text_4, text_5, text_6, raw_value = ctx.comment.html, raw_before;
var div, strong, text0, text1, span, text2_value = ctx.comment.author, text2, text3, text4_value = ctx.elapsed(ctx.comment.time, ctx.time), text4, text5, text6, raw_value = ctx.comment.html, raw_before;
return {
c() {
div = createElement("div");
strong = createElement("strong");
text = createText(ctx.i);
text_1 = createText("\n\n\t\t");
text0 = createText(ctx.i);
text1 = createText("\n\n\t\t");
span = createElement("span");
text_2 = createText(text_2_value);
text_3 = createText(" wrote ");
text_4 = createText(text_4_value);
text_5 = createText(" ago:");
text_6 = createText("\n\n\t\t");
text2 = createText(text2_value);
text3 = createText(" wrote ");
text4 = createText(text4_value);
text5 = createText(" ago:");
text6 = createText("\n\n\t\t");
raw_before = createElement('noscript');
span.className = "meta";
div.className = "comment";
@ -95,25 +103,25 @@ function create_each_block(component, ctx) {
m(target, anchor) {
insert(target, div, anchor);
append(div, strong);
append(strong, text);
append(div, text_1);
append(strong, text0);
append(div, text1);
append(div, span);
append(span, text_2);
append(span, text_3);
append(span, text_4);
append(span, text_5);
append(div, text_6);
append(span, text2);
append(span, text3);
append(span, text4);
append(span, text5);
append(div, text6);
append(div, raw_before);
raw_before.insertAdjacentHTML("afterend", raw_value);
},
p(changed, ctx) {
if ((changed.comments) && text_2_value !== (text_2_value = ctx.comment.author)) {
setData(text_2, text_2_value);
if ((changed.comments) && text2_value !== (text2_value = ctx.comment.author)) {
setData(text2, text2_value);
}
if ((changed.elapsed || changed.comments || changed.time) && text_4_value !== (text_4_value = ctx.elapsed(ctx.comment.time, ctx.time))) {
setData(text_4, text_4_value);
if ((changed.elapsed || changed.comments || changed.time) && text4_value !== (text4_value = ctx.elapsed(ctx.comment.time, ctx.time))) {
setData(text4, text4_value);
}
if ((changed.comments) && raw_value !== (raw_value = ctx.comment.html)) {
@ -130,14 +138,6 @@ function create_each_block(component, ctx) {
};
}
function get_each_context(ctx, list, i) {
const child_ctx = Object.create(ctx);
child_ctx.comment = list[i];
child_ctx.each_value = list;
child_ctx.i = i;
return child_ctx;
}
function SvelteComponent(options) {
init(this, options);
this._state = assign({}, options.data);

@ -492,6 +492,14 @@ function foo(node, animation, params) {
}
};
}
function get_each_context(ctx, list, i) {
const child_ctx = Object.create(ctx);
child_ctx.thing = list[i];
child_ctx.each_value = list;
child_ctx.thing_index = i;
return child_ctx;
}
function create_main_fragment(component, ctx) {
var each_blocks_1 = [], each_lookup = blankObject(), each_anchor;
@ -583,14 +591,6 @@ function create_each_block(component, key_1, ctx) {
};
}
function get_each_context(ctx, list, i) {
const child_ctx = Object.create(ctx);
child_ctx.thing = list[i];
child_ctx.each_value = list;
child_ctx.thing_index = i;
return child_ctx;
}
function SvelteComponent(options) {
init(this, options);
this._state = assign({}, options.data);

@ -15,6 +15,14 @@ function foo(node, animation, params) {
};
};
function get_each_context(ctx, list, i) {
const child_ctx = Object.create(ctx);
child_ctx.thing = list[i];
child_ctx.each_value = list;
child_ctx.thing_index = i;
return child_ctx;
}
function create_main_fragment(component, ctx) {
var each_blocks_1 = [], each_lookup = blankObject(), each_anchor;
@ -106,14 +114,6 @@ function create_each_block(component, key_1, ctx) {
};
}
function get_each_context(ctx, list, i) {
const child_ctx = Object.create(ctx);
child_ctx.thing = list[i];
child_ctx.each_value = list;
child_ctx.thing_index = i;
return child_ctx;
}
function SvelteComponent(options) {
init(this, options);
this._state = assign({}, options.data);

@ -259,6 +259,14 @@ var proto = {
/* generated by Svelte vX.Y.Z */
function get_each_context(ctx, list, i) {
const child_ctx = Object.create(ctx);
child_ctx.thing = list[i];
child_ctx.each_value = list;
child_ctx.thing_index = i;
return child_ctx;
}
function create_main_fragment(component, ctx) {
var each_blocks_1 = [], each_lookup = blankObject(), each_anchor;
@ -334,14 +342,6 @@ function create_each_block(component, key_1, ctx) {
};
}
function get_each_context(ctx, list, i) {
const child_ctx = Object.create(ctx);
child_ctx.thing = list[i];
child_ctx.each_value = list;
child_ctx.thing_index = i;
return child_ctx;
}
function SvelteComponent(options) {
init(this, options);
this._state = assign({}, options.data);

@ -1,6 +1,14 @@
/* generated by Svelte vX.Y.Z */
import { append, assign, blankObject, createComment, createElement, createText, destroyBlock, detachNode, init, insert, proto, setData, updateKeyedEach } from "svelte/shared.js";
function get_each_context(ctx, list, i) {
const child_ctx = Object.create(ctx);
child_ctx.thing = list[i];
child_ctx.each_value = list;
child_ctx.thing_index = i;
return child_ctx;
}
function create_main_fragment(component, ctx) {
var each_blocks_1 = [], each_lookup = blankObject(), each_anchor;
@ -76,14 +84,6 @@ function create_each_block(component, key_1, ctx) {
};
}
function get_each_context(ctx, list, i) {
const child_ctx = Object.create(ctx);
child_ctx.thing = list[i];
child_ctx.each_value = list;
child_ctx.thing_index = i;
return child_ctx;
}
function SvelteComponent(options) {
init(this, options);
this._state = assign({}, options.data);

@ -153,28 +153,28 @@ var proto = {
/* generated by Svelte vX.Y.Z */
function create_main_fragment(component, ctx) {
var meta, meta_1;
var meta0, meta1;
return {
c() {
meta = createElement("meta");
meta_1 = createElement("meta");
meta.name = "twitter:creator";
meta.content = "@sveltejs";
meta_1.name = "twitter:title";
meta_1.content = "Svelte";
meta0 = createElement("meta");
meta1 = createElement("meta");
meta0.name = "twitter:creator";
meta0.content = "@sveltejs";
meta1.name = "twitter:title";
meta1.content = "Svelte";
},
m(target, anchor) {
append(document.head, meta);
append(document.head, meta_1);
append(document.head, meta0);
append(document.head, meta1);
},
p: noop,
d(detach) {
detachNode(meta);
detachNode(meta_1);
detachNode(meta0);
detachNode(meta1);
}
};
}

@ -2,28 +2,28 @@
import { append, assign, createElement, detachNode, init, noop, proto } from "svelte/shared.js";
function create_main_fragment(component, ctx) {
var meta, meta_1;
var meta0, meta1;
return {
c() {
meta = createElement("meta");
meta_1 = createElement("meta");
meta.name = "twitter:creator";
meta.content = "@sveltejs";
meta_1.name = "twitter:title";
meta_1.content = "Svelte";
meta0 = createElement("meta");
meta1 = createElement("meta");
meta0.name = "twitter:creator";
meta0.content = "@sveltejs";
meta1.name = "twitter:title";
meta1.content = "Svelte";
},
m(target, anchor) {
append(document.head, meta);
append(document.head, meta_1);
append(document.head, meta0);
append(document.head, meta1);
},
p: noop,
d(detach) {
detachNode(meta);
detachNode(meta_1);
detachNode(meta0);
detachNode(meta1);
}
};
}

@ -161,7 +161,7 @@ function create_main_fragment(component, ctx) {
function select_block_type(ctx) {
if (ctx.foo) return create_if_block;
return create_if_block_1;
return create_else_block;
}
var current_block_type = select_block_type(ctx);
@ -196,14 +196,14 @@ function create_main_fragment(component, ctx) {
};
}
// (1:0) {#if foo}
function create_if_block(component, ctx) {
// (3:0) {:else}
function create_else_block(component, ctx) {
var p;
return {
c() {
p = createElement("p");
p.textContent = "foo!";
p.textContent = "not foo!";
},
m(target, anchor) {
@ -218,14 +218,14 @@ function create_if_block(component, ctx) {
};
}
// (3:0) {:else}
function create_if_block_1(component, ctx) {
// (1:0) {#if foo}
function create_if_block(component, ctx) {
var p;
return {
c() {
p = createElement("p");
p.textContent = "not foo!";
p.textContent = "foo!";
},
m(target, anchor) {

@ -6,7 +6,7 @@ function create_main_fragment(component, ctx) {
function select_block_type(ctx) {
if (ctx.foo) return create_if_block;
return create_if_block_1;
return create_else_block;
}
var current_block_type = select_block_type(ctx);
@ -41,14 +41,14 @@ function create_main_fragment(component, ctx) {
};
}
// (1:0) {#if foo}
function create_if_block(component, ctx) {
// (3:0) {:else}
function create_else_block(component, ctx) {
var p;
return {
c() {
p = createElement("p");
p.textContent = "foo!";
p.textContent = "not foo!";
},
m(target, anchor) {
@ -63,14 +63,14 @@ function create_if_block(component, ctx) {
};
}
// (3:0) {:else}
function create_if_block_1(component, ctx) {
// (1:0) {#if foo}
function create_if_block(component, ctx) {
var p;
return {
c() {
p = createElement("p");
p.textContent = "not foo!";
p.textContent = "foo!";
},
m(target, anchor) {

@ -157,38 +157,38 @@ var proto = {
/* generated by Svelte vX.Y.Z */
function create_main_fragment(component, ctx) {
var div, text, div_1, div_1_style_value;
var div0, text, div1, div1_style_value;
return {
c() {
div = createElement("div");
div0 = createElement("div");
text = createText("\n");
div_1 = createElement("div");
div.style.cssText = ctx.style;
div_1.style.cssText = div_1_style_value = "" + ctx.key + ": " + ctx.value;
div1 = createElement("div");
div0.style.cssText = ctx.style;
div1.style.cssText = div1_style_value = "" + ctx.key + ": " + ctx.value;
},
m(target, anchor) {
insert(target, div, anchor);
insert(target, div0, anchor);
insert(target, text, anchor);
insert(target, div_1, anchor);
insert(target, div1, anchor);
},
p(changed, ctx) {
if (changed.style) {
div.style.cssText = ctx.style;
div0.style.cssText = ctx.style;
}
if ((changed.key || changed.value) && div_1_style_value !== (div_1_style_value = "" + ctx.key + ": " + ctx.value)) {
div_1.style.cssText = div_1_style_value;
if ((changed.key || changed.value) && div1_style_value !== (div1_style_value = "" + ctx.key + ": " + ctx.value)) {
div1.style.cssText = div1_style_value;
}
},
d(detach) {
if (detach) {
detachNode(div);
detachNode(div0);
detachNode(text);
detachNode(div_1);
detachNode(div1);
}
}
};

@ -2,38 +2,38 @@
import { assign, createElement, createText, detachNode, init, insert, proto } from "svelte/shared.js";
function create_main_fragment(component, ctx) {
var div, text, div_1, div_1_style_value;
var div0, text, div1, div1_style_value;
return {
c() {
div = createElement("div");
div0 = createElement("div");
text = createText("\n");
div_1 = createElement("div");
div.style.cssText = ctx.style;
div_1.style.cssText = div_1_style_value = "" + ctx.key + ": " + ctx.value;
div1 = createElement("div");
div0.style.cssText = ctx.style;
div1.style.cssText = div1_style_value = "" + ctx.key + ": " + ctx.value;
},
m(target, anchor) {
insert(target, div, anchor);
insert(target, div0, anchor);
insert(target, text, anchor);
insert(target, div_1, anchor);
insert(target, div1, anchor);
},
p(changed, ctx) {
if (changed.style) {
div.style.cssText = ctx.style;
div0.style.cssText = ctx.style;
}
if ((changed.key || changed.value) && div_1_style_value !== (div_1_style_value = "" + ctx.key + ": " + ctx.value)) {
div_1.style.cssText = div_1_style_value;
if ((changed.key || changed.value) && div1_style_value !== (div1_style_value = "" + ctx.key + ": " + ctx.value)) {
div1.style.cssText = div1_style_value;
}
},
d(detach) {
if (detach) {
detachNode(div);
detachNode(div0);
detachNode(text);
detachNode(div_1);
detachNode(div1);
}
}
};

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

Loading…
Cancel
Save