mirror of https://github.com/sveltejs/svelte
parent
3696e0865a
commit
a690ba0b8f
@ -1,296 +0,0 @@
|
|||||||
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 Block from '../dom/Block';
|
|
||||||
import Expression from './shared/Expression';
|
|
||||||
import Compiler from '../../compile/Compiler';
|
|
||||||
|
|
||||||
const readOnlyMediaAttributes = new Set([
|
|
||||||
'duration',
|
|
||||||
'buffered',
|
|
||||||
'seekable',
|
|
||||||
'played'
|
|
||||||
]);
|
|
||||||
|
|
||||||
export default class Binding extends Node {
|
|
||||||
name: string;
|
|
||||||
value: Expression;
|
|
||||||
isContextual: boolean;
|
|
||||||
usesContext: boolean;
|
|
||||||
obj: string;
|
|
||||||
prop: string;
|
|
||||||
|
|
||||||
constructor(compiler, parent, scope, info) {
|
|
||||||
super(compiler, parent, scope, info);
|
|
||||||
|
|
||||||
this.name = info.name;
|
|
||||||
this.value = new Expression(compiler, this, scope, info.value);
|
|
||||||
|
|
||||||
let obj;
|
|
||||||
let prop;
|
|
||||||
|
|
||||||
const { name } = getObject(this.value.node);
|
|
||||||
this.isContextual = scope.names.has(name);
|
|
||||||
|
|
||||||
if (this.value.node.type === 'MemberExpression') {
|
|
||||||
prop = `[✂${this.value.node.property.start}-${this.value.node.property.end}✂]`;
|
|
||||||
if (!this.value.node.computed) prop = `'${prop}'`;
|
|
||||||
obj = `[✂${this.value.node.object.start}-${this.value.node.object.end}✂]`;
|
|
||||||
|
|
||||||
this.usesContext = true;
|
|
||||||
} else {
|
|
||||||
obj = 'ctx';
|
|
||||||
prop = `'${name}'`;
|
|
||||||
|
|
||||||
this.usesContext = scope.names.has(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
let updateCondition: 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.compiler.indirectDependencies.get(prop);
|
|
||||||
if (indirectDependencies) {
|
|
||||||
indirectDependencies.forEach(indirectDependency => {
|
|
||||||
dependencies.add(indirectDependency);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// view to model
|
|
||||||
const valueFromDom = getValueFromDom(this.compiler, node, this);
|
|
||||||
const handler = getEventHandler(this, this.compiler, 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.compiler, 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') {
|
|
||||||
updateCondition = `!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');
|
|
||||||
|
|
||||||
updateCondition = `${last} !== (${last} = ${snippet})`;
|
|
||||||
updateDom = `${node.var}[${last} ? "pause" : "play"]();`;
|
|
||||||
initialUpdate = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: this.name,
|
|
||||||
object: name,
|
|
||||||
handler,
|
|
||||||
updateDom,
|
|
||||||
initialUpdate,
|
|
||||||
needsLock: !isReadOnly && needsLock,
|
|
||||||
updateCondition,
|
|
||||||
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(compiler: Compiler, 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 = compiler.bindingGroups.indexOf(keypath);
|
|
||||||
if (index === -1) {
|
|
||||||
index = compiler.bindingGroups.length;
|
|
||||||
compiler.bindingGroups.push(keypath);
|
|
||||||
}
|
|
||||||
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getEventHandler(
|
|
||||||
binding: Binding,
|
|
||||||
compiler: Compiler,
|
|
||||||
block: Block,
|
|
||||||
name: string,
|
|
||||||
snippet: string,
|
|
||||||
dependencies: string[],
|
|
||||||
value: string,
|
|
||||||
isContextual: boolean
|
|
||||||
) {
|
|
||||||
const storeDependencies = [...dependencies].filter(prop => prop[0] === '$').map(prop => prop.slice(1));
|
|
||||||
dependencies = [...dependencies].filter(prop => prop[0] !== '$');
|
|
||||||
|
|
||||||
if (binding.isContextual) {
|
|
||||||
const tail = binding.value.node.type === 'MemberExpression'
|
|
||||||
? getTailSnippet(binding.value.node)
|
|
||||||
: '';
|
|
||||||
|
|
||||||
const list = `ctx.${block.listNames.get(name)}`;
|
|
||||||
const index = `ctx.${block.indexNames.get(name)}`;
|
|
||||||
|
|
||||||
return {
|
|
||||||
usesContext: true,
|
|
||||||
usesState: true,
|
|
||||||
usesStore: storeDependencies.length > 0,
|
|
||||||
mutation: `${list}[${index}]${tail} = ${value};`,
|
|
||||||
props: dependencies.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 `compiler.target.readonly` sooner so
|
|
||||||
// that we don't have to do the `.some()` here
|
|
||||||
dependencies = dependencies.filter(prop => !compiler.computations.some(computation => computation.key === prop));
|
|
||||||
|
|
||||||
return {
|
|
||||||
usesContext: false,
|
|
||||||
usesState: true,
|
|
||||||
usesStore: storeDependencies.length > 0,
|
|
||||||
mutation: `${snippet} = ${value}`,
|
|
||||||
props: dependencies.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(
|
|
||||||
compiler: Compiler,
|
|
||||||
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(compiler, 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,45 +0,0 @@
|
|||||||
import Node from './shared/Node';
|
|
||||||
import Compiler from '../Compiler';
|
|
||||||
import mapChildren from './shared/mapChildren';
|
|
||||||
import Block from '../dom/Block';
|
|
||||||
import TemplateScope from './shared/TemplateScope';
|
|
||||||
|
|
||||||
export default class Fragment extends Node {
|
|
||||||
block: Block;
|
|
||||||
children: Node[];
|
|
||||||
scope: TemplateScope;
|
|
||||||
|
|
||||||
constructor(compiler: Compiler, info: any) {
|
|
||||||
const scope = new TemplateScope();
|
|
||||||
super(compiler, null, scope, info);
|
|
||||||
|
|
||||||
this.scope = scope;
|
|
||||||
this.children = mapChildren(compiler, this, scope, info.children);
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
this.block = new Block({
|
|
||||||
compiler: this.compiler,
|
|
||||||
name: '@create_main_fragment',
|
|
||||||
key: null,
|
|
||||||
|
|
||||||
indexNames: new Map(),
|
|
||||||
listNames: new Map(),
|
|
||||||
|
|
||||||
dependencies: new Set(),
|
|
||||||
});
|
|
||||||
|
|
||||||
this.compiler.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,504 +0,0 @@
|
|||||||
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 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;
|
|
||||||
children: any[];
|
|
||||||
else: ElseBlock;
|
|
||||||
|
|
||||||
block: Block;
|
|
||||||
|
|
||||||
constructor(compiler, parent, scope, info) {
|
|
||||||
super(compiler, parent, scope, info);
|
|
||||||
|
|
||||||
this.expression = new Expression(compiler, this, scope, info.expression);
|
|
||||||
this.children = mapChildren(compiler, this, scope, info.children);
|
|
||||||
|
|
||||||
this.else = info.else
|
|
||||||
? new ElseBlock(compiler, this, scope, info.else)
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
|
|
||||||
init(
|
|
||||||
block: Block,
|
|
||||||
stripWhitespace: boolean,
|
|
||||||
nextSibling: Node
|
|
||||||
) {
|
|
||||||
const { compiler } = 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, compiler),
|
|
||||||
name: compiler.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.hasIntroMethod) hasIntros = true;
|
|
||||||
if (node.block.hasOutroMethod) hasOutros = true;
|
|
||||||
|
|
||||||
if (isElseIf(node.else)) {
|
|
||||||
attachBlocks(node.else.children[0]);
|
|
||||||
} else if (node.else) {
|
|
||||||
node.else.block = block.child({
|
|
||||||
comment: createDebuggingComment(node.else, compiler),
|
|
||||||
name: compiler.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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
attachBlocks(this);
|
|
||||||
|
|
||||||
blocks.forEach(block => {
|
|
||||||
block.hasUpdateMethod = dynamic;
|
|
||||||
block.hasIntroMethod = hasIntros;
|
|
||||||
block.hasOutroMethod = hasOutros;
|
|
||||||
});
|
|
||||||
|
|
||||||
compiler.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);
|
|
||||||
} else {
|
|
||||||
this.buildCompound(block, parentNode, parentNodes, branches, dynamic, vars);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.buildSimple(block, parentNode, parentNodes, branches[0], dynamic, vars);
|
|
||||||
}
|
|
||||||
|
|
||||||
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.compiler.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`
|
|
||||||
${hasElse
|
|
||||||
? deindent`
|
|
||||||
${name}.u();
|
|
||||||
${name}.d();
|
|
||||||
`
|
|
||||||
: deindent`
|
|
||||||
if (${name}) {
|
|
||||||
${name}.u();
|
|
||||||
${name}.d();
|
|
||||||
}`}
|
|
||||||
${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.unmount.addLine(`${if_name}${name}.u();`);
|
|
||||||
|
|
||||||
block.builders.destroy.addLine(`${if_name}${name}.d();`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 = block.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`
|
|
||||||
${name}.o(function() {
|
|
||||||
${if_blocks}[ ${previous_block_index} ].u();
|
|
||||||
${if_blocks}[ ${previous_block_index} ].d();
|
|
||||||
${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}].u();
|
|
||||||
${if_blocks}[${current_block_type_index}].d();
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
? 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
|
|
||||||
? 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`
|
|
||||||
${name}.o(function() {
|
|
||||||
${name}.u();
|
|
||||||
${name}.d();
|
|
||||||
${name} = null;
|
|
||||||
});
|
|
||||||
`
|
|
||||||
: deindent`
|
|
||||||
${name}.u();
|
|
||||||
${name}.d();
|
|
||||||
${name} = null;
|
|
||||||
`;
|
|
||||||
|
|
||||||
block.builders.update.addBlock(deindent`
|
|
||||||
if (${branch.condition}) {
|
|
||||||
${enter}
|
|
||||||
} else if (${name}) {
|
|
||||||
${exit}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
block.builders.unmount.addLine(`${if_name}${name}.u();`);
|
|
||||||
|
|
||||||
block.builders.destroy.addLine(`${if_name}${name}.d();`);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 { compiler } = this;
|
|
||||||
const { snippet } = this.expression;
|
|
||||||
|
|
||||||
compiler.target.append('${ ' + snippet + ' ? `');
|
|
||||||
|
|
||||||
this.children.forEach((child: Node) => {
|
|
||||||
child.ssr();
|
|
||||||
});
|
|
||||||
|
|
||||||
compiler.target.append('` : `');
|
|
||||||
|
|
||||||
if (this.else) {
|
|
||||||
this.else.children.forEach((child: Node) => {
|
|
||||||
child.ssr();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
compiler.target.append('` }');
|
|
||||||
}
|
|
||||||
|
|
||||||
visitChildren(block: Block, node: Node) {
|
|
||||||
node.children.forEach((child: Node) => {
|
|
||||||
child.build(node.block, null, 'nodes');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,172 +0,0 @@
|
|||||||
import Compiler from './../../Compiler';
|
|
||||||
import Block from '../../dom/Block';
|
|
||||||
import { trimStart, trimEnd } from '../../../utils/trim';
|
|
||||||
|
|
||||||
export default class Node {
|
|
||||||
readonly start: number;
|
|
||||||
readonly end: number;
|
|
||||||
readonly compiler: Compiler;
|
|
||||||
readonly parent: Node;
|
|
||||||
readonly type: string;
|
|
||||||
|
|
||||||
prev?: Node;
|
|
||||||
next?: Node;
|
|
||||||
|
|
||||||
canUseInnerHTML: boolean;
|
|
||||||
var: string;
|
|
||||||
|
|
||||||
constructor(compiler: Compiler, parent, scope, info: any) {
|
|
||||||
this.start = info.start;
|
|
||||||
this.end = info.end;
|
|
||||||
this.type = info.type;
|
|
||||||
|
|
||||||
// this makes properties non-enumerable, which makes logging
|
|
||||||
// bearable. might have a performance cost. TODO remove in prod?
|
|
||||||
Object.defineProperties(this, {
|
|
||||||
compiler: {
|
|
||||||
value: compiler
|
|
||||||
},
|
|
||||||
parent: {
|
|
||||||
value: parent
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
cannotUseInnerHTML() {
|
|
||||||
if (this.canUseInnerHTML !== false) {
|
|
||||||
this.canUseInnerHTML = false;
|
|
||||||
if (this.parent) this.parent.cannotUseInnerHTML();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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.compiler.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) :
|
|
||||||
false;
|
|
||||||
}
|
|
||||||
|
|
||||||
findNearest(selector: RegExp) {
|
|
||||||
if (selector.test(this.type)) return this;
|
|
||||||
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,175 +0,0 @@
|
|||||||
import deindent from '../../utils/deindent';
|
|
||||||
import Compiler from '../Compiler';
|
|
||||||
import Stats from '../../Stats';
|
|
||||||
import Stylesheet from '../../css/Stylesheet';
|
|
||||||
import { removeNode, removeObjectKey } from '../../utils/removeNode';
|
|
||||||
import getName from '../../utils/getName';
|
|
||||||
import globalWhitelist from '../../utils/globalWhitelist';
|
|
||||||
import { Ast, Node, CompileOptions } from '../../interfaces';
|
|
||||||
import { AppendTarget } from '../../interfaces';
|
|
||||||
import { stringify } from '../../utils/stringify';
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ssr(
|
|
||||||
ast: Ast,
|
|
||||||
source: string,
|
|
||||||
stylesheet: Stylesheet,
|
|
||||||
options: CompileOptions,
|
|
||||||
stats: Stats
|
|
||||||
) {
|
|
||||||
const format = options.format || 'cjs';
|
|
||||||
|
|
||||||
const target = new SsrTarget();
|
|
||||||
const compiler = new Compiler(ast, source, options.name || 'SvelteComponent', stylesheet, options, stats, false, target);
|
|
||||||
|
|
||||||
const { computations, name, templateProperties } = compiler;
|
|
||||||
|
|
||||||
// create main render() function
|
|
||||||
trim(compiler.fragment.children).forEach((node: Node) => {
|
|
||||||
node.ssr();
|
|
||||||
});
|
|
||||||
|
|
||||||
const css = compiler.customElement ?
|
|
||||||
{ code: null, map: null } :
|
|
||||||
compiler.stylesheet.render(options.filename, true);
|
|
||||||
|
|
||||||
// generate initial state object
|
|
||||||
const expectedProperties = Array.from(compiler.expectedProperties);
|
|
||||||
const globals = expectedProperties.filter(prop => globalWhitelist.has(prop));
|
|
||||||
const storeProps = expectedProperties.filter(prop => prop[0] === '$');
|
|
||||||
|
|
||||||
const initialState = [];
|
|
||||||
if (globals.length > 0) {
|
|
||||||
initialState.push(`{ ${globals.map(prop => `${prop} : ${prop}`).join(', ')} }`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (storeProps.length > 0) {
|
|
||||||
const initialize = `_init([${storeProps.map(prop => `"${prop.slice(1)}"`)}])`
|
|
||||||
initialState.push(`options.store.${initialize}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (templateProperties.data) {
|
|
||||||
initialState.push(`%data()`);
|
|
||||||
} else if (globals.length === 0 && storeProps.length === 0) {
|
|
||||||
initialState.push('{}');
|
|
||||||
}
|
|
||||||
|
|
||||||
initialState.push('ctx');
|
|
||||||
|
|
||||||
const helpers = new Set();
|
|
||||||
|
|
||||||
// TODO concatenate CSS maps
|
|
||||||
const result = deindent`
|
|
||||||
${compiler.javascript}
|
|
||||||
|
|
||||||
var ${name} = {};
|
|
||||||
|
|
||||||
${options.filename && `${name}.filename = ${stringify(options.filename)}`};
|
|
||||||
|
|
||||||
${name}.data = function() {
|
|
||||||
return ${templateProperties.data ? `%data()` : `{}`};
|
|
||||||
};
|
|
||||||
|
|
||||||
${name}.render = function(state, options = {}) {
|
|
||||||
var components = new Set();
|
|
||||||
|
|
||||||
function addComponent(component) {
|
|
||||||
components.add(component);
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = { head: '', addComponent };
|
|
||||||
var html = ${name}._render(result, state, options);
|
|
||||||
|
|
||||||
var cssCode = Array.from(components).map(c => c.css && c.css.code).filter(Boolean).join('\\n');
|
|
||||||
|
|
||||||
return {
|
|
||||||
html,
|
|
||||||
head: result.head,
|
|
||||||
css: { code: cssCode, map: null },
|
|
||||||
toString() {
|
|
||||||
return html;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
${name}._render = function(__result, ctx, options) {
|
|
||||||
${templateProperties.store && `options.store = %store();`}
|
|
||||||
__result.addComponent(${name});
|
|
||||||
|
|
||||||
ctx = Object.assign(${initialState.join(', ')});
|
|
||||||
|
|
||||||
${computations.map(
|
|
||||||
({ key, deps }) =>
|
|
||||||
`ctx.${key} = %computed-${key}(ctx);`
|
|
||||||
)}
|
|
||||||
|
|
||||||
${target.bindings.length &&
|
|
||||||
deindent`
|
|
||||||
var settled = false;
|
|
||||||
var tmp;
|
|
||||||
|
|
||||||
while (!settled) {
|
|
||||||
settled = true;
|
|
||||||
|
|
||||||
${target.bindings.join('\n\n')}
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
|
|
||||||
return \`${target.renderCode}\`;
|
|
||||||
};
|
|
||||||
|
|
||||||
${name}.css = {
|
|
||||||
code: ${css.code ? stringify(css.code) : `''`},
|
|
||||||
map: ${css.map ? stringify(css.map.toString()) : 'null'}
|
|
||||||
};
|
|
||||||
|
|
||||||
var warned = false;
|
|
||||||
|
|
||||||
${templateProperties.preload && `${name}.preload = %preload;`}
|
|
||||||
`;
|
|
||||||
|
|
||||||
return compiler.generate(result, options, { name, format });
|
|
||||||
}
|
|
||||||
|
|
||||||
function trim(nodes) {
|
|
||||||
let start = 0;
|
|
||||||
for (; start < nodes.length; start += 1) {
|
|
||||||
const node = nodes[start];
|
|
||||||
if (node.type !== 'Text') break;
|
|
||||||
|
|
||||||
node.data = node.data.replace(/^\s+/, '');
|
|
||||||
if (node.data) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
let end = nodes.length;
|
|
||||||
for (; end > start; end -= 1) {
|
|
||||||
const node = nodes[end - 1];
|
|
||||||
if (node.type !== 'Text') break;
|
|
||||||
|
|
||||||
node.data = node.data.replace(/\s+$/, '');
|
|
||||||
if (node.data) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nodes.slice(start, end);
|
|
||||||
}
|
|
Loading…
Reference in new issue