mirror of https://github.com/sveltejs/svelte
parent
1636f1733b
commit
2e56d10b39
@ -0,0 +1,34 @@
|
|||||||
|
import { assign } from '../../shared/index.js';
|
||||||
|
|
||||||
|
interface StateData {
|
||||||
|
namespace?: string;
|
||||||
|
parentNode?: string;
|
||||||
|
parentNodes?: string;
|
||||||
|
parentNodeName?: string;
|
||||||
|
inEachBlock?: boolean;
|
||||||
|
allUsedContexts?: string[];
|
||||||
|
usesComponent?: boolean;
|
||||||
|
selectBindingDependencies?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class State {
|
||||||
|
namespace?: string;
|
||||||
|
parentNode?: string;
|
||||||
|
parentNodes?: string;
|
||||||
|
parentNodeName?: string;
|
||||||
|
inEachBlock?: boolean;
|
||||||
|
allUsedContexts?: string[];
|
||||||
|
usesComponent?: boolean;
|
||||||
|
selectBindingDependencies?: string[];
|
||||||
|
|
||||||
|
constructor(data: StateData = {}) {
|
||||||
|
assign(this, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
child(data: StateData) {
|
||||||
|
return new State(assign({}, this, {
|
||||||
|
parentNode: null,
|
||||||
|
parentNodes: 'nodes'
|
||||||
|
}, data));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
import Node from './shared/Node';
|
||||||
|
import { DomGenerator } from '../dom/index';
|
||||||
|
import Block from '../dom/Block';
|
||||||
|
import PendingBlock from './PendingBlock';
|
||||||
|
import ThenBlock from './ThenBlock';
|
||||||
|
import CatchBlock from './CatchBlock';
|
||||||
|
import { State } from '../dom/interfaces';
|
||||||
|
import createDebuggingComment from '../../utils/createDebuggingComment';
|
||||||
|
|
||||||
|
export default class AwaitBlock extends Node {
|
||||||
|
value: string;
|
||||||
|
error: string;
|
||||||
|
|
||||||
|
pending: PendingBlock;
|
||||||
|
then: ThenBlock;
|
||||||
|
catch: CatchBlock;
|
||||||
|
|
||||||
|
init(
|
||||||
|
block: Block,
|
||||||
|
state: State,
|
||||||
|
inEachBlock: boolean,
|
||||||
|
elementStack: Node[],
|
||||||
|
componentStack: Node[],
|
||||||
|
stripWhitespace: boolean,
|
||||||
|
nextSibling: Node
|
||||||
|
) {
|
||||||
|
this.cannotUseInnerHTML();
|
||||||
|
|
||||||
|
this.var = block.getUniqueName('await_block');
|
||||||
|
block.addDependencies(this.metadata.dependencies);
|
||||||
|
|
||||||
|
let dynamic = false;
|
||||||
|
|
||||||
|
[
|
||||||
|
['pending', null],
|
||||||
|
['then', this.value],
|
||||||
|
['catch', this.error]
|
||||||
|
].forEach(([status, arg]) => {
|
||||||
|
const child = this[status];
|
||||||
|
|
||||||
|
const context = block.getUniqueName(arg || '_');
|
||||||
|
const contexts = new Map(block.contexts);
|
||||||
|
contexts.set(arg, context);
|
||||||
|
|
||||||
|
child._block = block.child({
|
||||||
|
comment: createDebuggingComment(child, this.generator),
|
||||||
|
name: this.generator.getUniqueName(`create_${status}_block`),
|
||||||
|
params: block.params.concat(context),
|
||||||
|
context,
|
||||||
|
contexts
|
||||||
|
});
|
||||||
|
|
||||||
|
child._state = state.child();
|
||||||
|
|
||||||
|
child.initChildren(child._block, child._state, inEachBlock, elementStack, componentStack, stripWhitespace, nextSibling);
|
||||||
|
this.generator.blocks.push(child._block);
|
||||||
|
|
||||||
|
if (child._block.dependencies.size > 0) {
|
||||||
|
dynamic = true;
|
||||||
|
block.addDependencies(child._block.dependencies);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.pending._block.hasUpdateMethod = dynamic;
|
||||||
|
this.then._block.hasUpdateMethod = dynamic;
|
||||||
|
this.catch._block.hasUpdateMethod = dynamic;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
import Node from './shared/Node';
|
||||||
|
import Block from '../dom/Block';
|
||||||
|
|
||||||
|
export default class CatchBlock extends Node {
|
||||||
|
_block: Block;
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
import Node from './shared/Node';
|
||||||
|
|
||||||
|
export default class Comment extends Node {
|
||||||
|
type: 'Comment'
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
import Node from './shared/Node';
|
||||||
|
import Block from '../dom/Block';
|
||||||
|
import State from '../dom/State';
|
||||||
|
|
||||||
|
export default class Component extends Node {
|
||||||
|
type: 'Component'; // TODO fix this?
|
||||||
|
name: string;
|
||||||
|
attributes: Node[]; // TODO have more specific Attribute type
|
||||||
|
children: Node[];
|
||||||
|
|
||||||
|
init(
|
||||||
|
block: Block,
|
||||||
|
state: State,
|
||||||
|
inEachBlock: boolean,
|
||||||
|
elementStack: Node[],
|
||||||
|
componentStack: Node[],
|
||||||
|
stripWhitespace: boolean,
|
||||||
|
nextSibling: Node
|
||||||
|
) {
|
||||||
|
this.cannotUseInnerHTML();
|
||||||
|
|
||||||
|
this.attributes.forEach((attribute: Node) => {
|
||||||
|
if (attribute.type === 'Attribute' && attribute.value !== true) {
|
||||||
|
attribute.value.forEach((chunk: Node) => {
|
||||||
|
if (chunk.type !== 'Text') {
|
||||||
|
const dependencies = chunk.metadata.dependencies;
|
||||||
|
block.addDependencies(dependencies);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (attribute.type === 'EventHandler' && attribute.expression) {
|
||||||
|
attribute.expression.arguments.forEach((arg: Node) => {
|
||||||
|
block.addDependencies(arg.metadata.dependencies);
|
||||||
|
});
|
||||||
|
} else if (attribute.type === 'Binding') {
|
||||||
|
block.addDependencies(attribute.metadata.dependencies);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.var = block.getUniqueName(
|
||||||
|
(
|
||||||
|
this.name === ':Self' ? this.generator.name :
|
||||||
|
this.name === ':Component' ? 'switch_instance' :
|
||||||
|
this.name
|
||||||
|
).toLowerCase()
|
||||||
|
);
|
||||||
|
|
||||||
|
this._state = state.child({
|
||||||
|
parentNode: `${this.var}._slotted.default`
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.children.length) {
|
||||||
|
this._slots = new Set(['default']);
|
||||||
|
|
||||||
|
this.children.forEach(child => {
|
||||||
|
child.init(block, state, inEachBlock, elementStack, componentStack.concat(this), stripWhitespace, nextSibling);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,115 @@
|
|||||||
|
import Node from './shared/Node';
|
||||||
|
import ElseBlock from './ElseBlock';
|
||||||
|
import { DomGenerator } from '../dom/index';
|
||||||
|
import Block from '../dom/Block';
|
||||||
|
import State from '../dom/State';
|
||||||
|
import createDebuggingComment from '../../utils/createDebuggingComment';
|
||||||
|
|
||||||
|
export default class EachBlock extends Node {
|
||||||
|
_block: Block;
|
||||||
|
_state: State;
|
||||||
|
expression: Node;
|
||||||
|
|
||||||
|
iterations: string;
|
||||||
|
index: string;
|
||||||
|
context: string;
|
||||||
|
key: string;
|
||||||
|
destructuredContexts: string[];
|
||||||
|
|
||||||
|
else?: ElseBlock;
|
||||||
|
|
||||||
|
init(
|
||||||
|
block: Block,
|
||||||
|
state: State,
|
||||||
|
inEachBlock: boolean,
|
||||||
|
elementStack: Node[],
|
||||||
|
componentStack: Node[],
|
||||||
|
stripWhitespace: boolean,
|
||||||
|
nextSibling: Node
|
||||||
|
) {
|
||||||
|
this.cannotUseInnerHTML();
|
||||||
|
|
||||||
|
this.var = block.getUniqueName(`each`);
|
||||||
|
this.iterations = block.getUniqueName(`${this.var}_blocks`);
|
||||||
|
|
||||||
|
const { dependencies } = this.metadata;
|
||||||
|
block.addDependencies(dependencies);
|
||||||
|
|
||||||
|
const indexNames = new Map(block.indexNames);
|
||||||
|
const indexName =
|
||||||
|
this.index || block.getUniqueName(`${this.context}_index`);
|
||||||
|
indexNames.set(this.context, indexName);
|
||||||
|
|
||||||
|
const listNames = new Map(block.listNames);
|
||||||
|
const listName = block.getUniqueName(
|
||||||
|
(this.expression.type === 'MemberExpression' && !this.expression.computed) ? this.expression.property.name :
|
||||||
|
this.expression.type === 'Identifier' ? this.expression.name :
|
||||||
|
`each_value`
|
||||||
|
);
|
||||||
|
listNames.set(this.context, listName);
|
||||||
|
|
||||||
|
const context = block.getUniqueName(this.context);
|
||||||
|
const contexts = new Map(block.contexts);
|
||||||
|
contexts.set(this.context, context);
|
||||||
|
|
||||||
|
const indexes = new Map(block.indexes);
|
||||||
|
if (this.index) indexes.set(this.index, this.context);
|
||||||
|
|
||||||
|
const changeableIndexes = new Map(block.changeableIndexes);
|
||||||
|
if (this.index) changeableIndexes.set(this.index, this.key);
|
||||||
|
|
||||||
|
if (this.destructuredContexts) {
|
||||||
|
for (let i = 0; i < this.destructuredContexts.length; i += 1) {
|
||||||
|
contexts.set(this.destructuredContexts[i], `${context}[${i}]`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._block = block.child({
|
||||||
|
comment: createDebuggingComment(this, this.generator),
|
||||||
|
name: this.generator.getUniqueName('create_each_block'),
|
||||||
|
context: this.context,
|
||||||
|
key: this.key,
|
||||||
|
|
||||||
|
contexts,
|
||||||
|
indexes,
|
||||||
|
changeableIndexes,
|
||||||
|
|
||||||
|
listName,
|
||||||
|
indexName,
|
||||||
|
|
||||||
|
indexNames,
|
||||||
|
listNames,
|
||||||
|
params: block.params.concat(listName, context, indexName),
|
||||||
|
});
|
||||||
|
|
||||||
|
this._state = state.child({
|
||||||
|
inEachBlock: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.generator.blocks.push(this._block);
|
||||||
|
this.initChildren(this._block, this._state, true, elementStack, componentStack, stripWhitespace, nextSibling);
|
||||||
|
block.addDependencies(this._block.dependencies);
|
||||||
|
this._block.hasUpdateMethod = this._block.dependencies.size > 0;
|
||||||
|
|
||||||
|
if (this.else) {
|
||||||
|
this.else._block = block.child({
|
||||||
|
comment: '// TODO', // createDebuggingComment(this.else, generator),
|
||||||
|
name: this.generator.getUniqueName(`${this._block.name}_else`),
|
||||||
|
});
|
||||||
|
|
||||||
|
this.else._state = state.child();
|
||||||
|
|
||||||
|
this.generator.blocks.push(this.else._block);
|
||||||
|
this.else.initChildren(
|
||||||
|
this.else._block,
|
||||||
|
this.else._state,
|
||||||
|
inEachBlock,
|
||||||
|
elementStack,
|
||||||
|
componentStack,
|
||||||
|
stripWhitespace,
|
||||||
|
nextSibling
|
||||||
|
);
|
||||||
|
this.else._block.hasUpdateMethod = this.else._block.dependencies.size > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,151 @@
|
|||||||
|
import Node from './shared/Node';
|
||||||
|
import Block from '../dom/Block';
|
||||||
|
import State from '../dom/State';
|
||||||
|
|
||||||
|
export default class Element extends Node {
|
||||||
|
type: 'Element';
|
||||||
|
name: string;
|
||||||
|
attributes: Node[]; // TODO have more specific Attribute type
|
||||||
|
children: Node[];
|
||||||
|
|
||||||
|
init(
|
||||||
|
block: Block,
|
||||||
|
state: State,
|
||||||
|
inEachBlock: boolean,
|
||||||
|
elementStack: Node[],
|
||||||
|
componentStack: Node[],
|
||||||
|
stripWhitespace: boolean,
|
||||||
|
nextSibling: Node
|
||||||
|
) {
|
||||||
|
if (this.name === 'slot' || this.name === 'option') {
|
||||||
|
this.cannotUseInnerHTML();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.attributes.forEach((attribute: Node) => {
|
||||||
|
if (attribute.type === 'Attribute' && attribute.value !== true) {
|
||||||
|
attribute.value.forEach((chunk: Node) => {
|
||||||
|
if (chunk.type !== 'Text') {
|
||||||
|
if (this.parent) this.parent.cannotUseInnerHTML();
|
||||||
|
|
||||||
|
const dependencies = chunk.metadata.dependencies;
|
||||||
|
block.addDependencies(dependencies);
|
||||||
|
|
||||||
|
// special case — <option value='{{foo}}'> — see below
|
||||||
|
if (
|
||||||
|
this.name === 'option' &&
|
||||||
|
attribute.name === 'value' &&
|
||||||
|
state.selectBindingDependencies
|
||||||
|
) {
|
||||||
|
state.selectBindingDependencies.forEach(prop => {
|
||||||
|
dependencies.forEach((dependency: string) => {
|
||||||
|
this.generator.indirectDependencies.get(prop).add(dependency);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (this.parent) this.parent.cannotUseInnerHTML();
|
||||||
|
|
||||||
|
if (attribute.type === 'EventHandler' && attribute.expression) {
|
||||||
|
attribute.expression.arguments.forEach((arg: Node) => {
|
||||||
|
block.addDependencies(arg.metadata.dependencies);
|
||||||
|
});
|
||||||
|
} else if (attribute.type === 'Binding') {
|
||||||
|
block.addDependencies(attribute.metadata.dependencies);
|
||||||
|
} else if (attribute.type === 'Transition') {
|
||||||
|
if (attribute.intro)
|
||||||
|
this.generator.hasIntroTransitions = block.hasIntroMethod = true;
|
||||||
|
if (attribute.outro) {
|
||||||
|
this.generator.hasOutroTransitions = block.hasOutroMethod = true;
|
||||||
|
block.outros += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const valueAttribute = this.attributes.find((attribute: Node) => attribute.name === 'value');
|
||||||
|
|
||||||
|
// Treat these the same way:
|
||||||
|
// <option>{{foo}}</option>
|
||||||
|
// <option value='{{foo}}'>{{foo}}</option>
|
||||||
|
if (this.name === 'option' && !valueAttribute) {
|
||||||
|
this.attributes.push({
|
||||||
|
type: 'Attribute',
|
||||||
|
name: 'value',
|
||||||
|
value: this.children
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// special case — in a case like this...
|
||||||
|
//
|
||||||
|
// <select bind:value='foo'>
|
||||||
|
// <option value='{{bar}}'>bar</option>
|
||||||
|
// <option value='{{baz}}'>baz</option>
|
||||||
|
// </option>
|
||||||
|
//
|
||||||
|
// ...we need to know that `foo` depends on `bar` and `baz`,
|
||||||
|
// so that if `foo.qux` changes, we know that we need to
|
||||||
|
// mark `bar` and `baz` as dirty too
|
||||||
|
if (this.name === 'select') {
|
||||||
|
const binding = this.attributes.find((node: Node) => node.type === 'Binding' && node.name === 'value');
|
||||||
|
if (binding) {
|
||||||
|
// TODO does this also apply to e.g. `<input type='checkbox' bind:group='foo'>`?
|
||||||
|
const dependencies = binding.metadata.dependencies;
|
||||||
|
state.selectBindingDependencies = dependencies;
|
||||||
|
dependencies.forEach((prop: string) => {
|
||||||
|
this.generator.indirectDependencies.set(prop, new Set());
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
state.selectBindingDependencies = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const slot = this.getStaticAttributeValue('slot');
|
||||||
|
if (slot && this.isChildOfComponent()) {
|
||||||
|
this.cannotUseInnerHTML();
|
||||||
|
this.slotted = true;
|
||||||
|
// TODO validate slots — no nesting, no dynamic names...
|
||||||
|
const component = componentStack[componentStack.length - 1];
|
||||||
|
component._slots.add(slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.var = block.getUniqueName(
|
||||||
|
this.name.replace(/[^a-zA-Z0-9_$]/g, '_')
|
||||||
|
);
|
||||||
|
|
||||||
|
this._state = state.child({
|
||||||
|
parentNode: this.var,
|
||||||
|
parentNodes: block.getUniqueName(`${this.var}_nodes`),
|
||||||
|
parentNodeName: this.name,
|
||||||
|
namespace: this.name === 'svg'
|
||||||
|
? 'http://www.w3.org/2000/svg'
|
||||||
|
: state.namespace,
|
||||||
|
allUsedContexts: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
this.generator.stylesheet.apply(this, elementStack);
|
||||||
|
|
||||||
|
if (this.children.length) {
|
||||||
|
if (this.name === 'pre' || this.name === 'textarea') stripWhitespace = false;
|
||||||
|
this.initChildren(block, this._state, inEachBlock, elementStack.concat(this), componentStack, stripWhitespace, nextSibling);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getStaticAttributeValue(name: string) {
|
||||||
|
const attribute = this.attributes.find(
|
||||||
|
(attr: Node) => attr.name.toLowerCase() === name
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!attribute) return null;
|
||||||
|
|
||||||
|
if (attribute.value === true) return true;
|
||||||
|
if (attribute.value.length === 0) return '';
|
||||||
|
|
||||||
|
if (attribute.value.length === 1 && attribute.value[0].type === 'Text') {
|
||||||
|
return attribute.value[0].data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
import Node from './shared/Node';
|
||||||
|
|
||||||
|
export default class ElseBlock extends Node {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
import Node from './shared/Node';
|
||||||
|
import { DomGenerator } from '../dom/index';
|
||||||
|
import Block from '../dom/Block';
|
||||||
|
import State from '../dom/State';
|
||||||
|
|
||||||
|
export default class Fragment extends Node {
|
||||||
|
block: Block;
|
||||||
|
state: State;
|
||||||
|
children: Node[];
|
||||||
|
|
||||||
|
init(
|
||||||
|
namespace: string
|
||||||
|
) {
|
||||||
|
this.block = new Block({
|
||||||
|
generator: this.generator,
|
||||||
|
name: '@create_main_fragment',
|
||||||
|
key: null,
|
||||||
|
|
||||||
|
contexts: new Map(),
|
||||||
|
indexes: new Map(),
|
||||||
|
changeableIndexes: new Map(),
|
||||||
|
|
||||||
|
params: ['state'],
|
||||||
|
indexNames: new Map(),
|
||||||
|
listNames: new Map(),
|
||||||
|
|
||||||
|
dependencies: new Set(),
|
||||||
|
});
|
||||||
|
|
||||||
|
this.state = new State({
|
||||||
|
namespace,
|
||||||
|
parentNode: null,
|
||||||
|
parentNodes: 'nodes'
|
||||||
|
});
|
||||||
|
|
||||||
|
this.generator.blocks.push(this.block);
|
||||||
|
this.initChildren(this.block, this.state, false, [], [], true, null);
|
||||||
|
|
||||||
|
this.block.hasUpdateMethod = true;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
import Node from './shared/Node';
|
||||||
|
import { DomGenerator } from '../dom/index';
|
||||||
|
import Block from '../dom/Block';
|
||||||
|
import { State } from '../dom/interfaces';
|
||||||
|
import createDebuggingComment from '../../utils/createDebuggingComment';
|
||||||
|
|
||||||
|
function isElseIf(node: Node) {
|
||||||
|
return (
|
||||||
|
node && node.children.length === 1 && node.children[0].type === 'IfBlock'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class IfBlock extends Node {
|
||||||
|
init(
|
||||||
|
block: Block,
|
||||||
|
state: State,
|
||||||
|
inEachBlock: boolean,
|
||||||
|
elementStack: Node[],
|
||||||
|
componentStack: Node[],
|
||||||
|
stripWhitespace: boolean,
|
||||||
|
nextSibling: Node
|
||||||
|
) {
|
||||||
|
const { generator } = this;
|
||||||
|
|
||||||
|
this.cannotUseInnerHTML();
|
||||||
|
|
||||||
|
const blocks: Block[] = [];
|
||||||
|
let dynamic = false;
|
||||||
|
let hasIntros = false;
|
||||||
|
let hasOutros = false;
|
||||||
|
|
||||||
|
function attachBlocks(node: Node) {
|
||||||
|
node.var = block.getUniqueName(`if_block`);
|
||||||
|
|
||||||
|
block.addDependencies(node.metadata.dependencies);
|
||||||
|
|
||||||
|
node._block = block.child({
|
||||||
|
comment: createDebuggingComment(node, generator),
|
||||||
|
name: generator.getUniqueName(`create_if_block`),
|
||||||
|
});
|
||||||
|
|
||||||
|
node._state = state.child();
|
||||||
|
|
||||||
|
blocks.push(node._block);
|
||||||
|
node.initChildren(node._block, node._state, inEachBlock, elementStack, componentStack, 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, generator),
|
||||||
|
name: generator.getUniqueName(`create_if_block`),
|
||||||
|
});
|
||||||
|
|
||||||
|
node.else._state = state.child();
|
||||||
|
|
||||||
|
blocks.push(node.else._block);
|
||||||
|
node.else.initChildren(
|
||||||
|
node.else._block,
|
||||||
|
node.else._state,
|
||||||
|
inEachBlock,
|
||||||
|
elementStack,
|
||||||
|
componentStack,
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
|
||||||
|
generator.blocks.push(...blocks);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
import Node from './shared/Node';
|
||||||
|
import Block from '../dom/Block';
|
||||||
|
|
||||||
|
export default class MustacheTag extends Node {
|
||||||
|
init(block: Block) {
|
||||||
|
this.cannotUseInnerHTML();
|
||||||
|
this.var = block.getUniqueName('text');
|
||||||
|
block.addDependencies(this.metadata.dependencies);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
import Node from './shared/Node';
|
||||||
|
import Block from '../dom/Block';
|
||||||
|
|
||||||
|
export default class PendingBlock extends Node {
|
||||||
|
_block: Block;
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
import Node from './shared/Node';
|
||||||
|
import Block from '../dom/Block';
|
||||||
|
|
||||||
|
export default class RawMustacheTag extends Node {
|
||||||
|
init(block: Block) {
|
||||||
|
this.cannotUseInnerHTML();
|
||||||
|
this.var = block.getUniqueName('raw');
|
||||||
|
block.addDependencies(this.metadata.dependencies);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
import Node from './shared/Node';
|
||||||
|
|
||||||
|
export default class Slot extends Node {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
import Node from './shared/Node';
|
||||||
|
import Block from '../dom/Block';
|
||||||
|
import { State } from '../dom/interfaces';
|
||||||
|
|
||||||
|
// 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',
|
||||||
|
'ol',
|
||||||
|
'optgroup',
|
||||||
|
'select',
|
||||||
|
'ul',
|
||||||
|
'video',
|
||||||
|
]);
|
||||||
|
|
||||||
|
export default class Text extends Node {
|
||||||
|
data: string;
|
||||||
|
shouldSkip: boolean;
|
||||||
|
|
||||||
|
init(block: Block, state: State) {
|
||||||
|
if (!/\S/.test(this.data) && (state.namespace || elementsWithoutText.has(state.parentNodeName))) {
|
||||||
|
this.shouldSkip = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.var = block.getUniqueName(`text`);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
import Node from './shared/Node';
|
||||||
|
import Block from '../dom/Block';
|
||||||
|
|
||||||
|
export default class ThenBlock extends Node {
|
||||||
|
_block: Block;
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
import Node from './shared/Node';
|
||||||
|
import AwaitBlock from './AwaitBlock';
|
||||||
|
import CatchBlock from './CatchBlock';
|
||||||
|
import Comment from './Comment';
|
||||||
|
import Component from './Component';
|
||||||
|
import EachBlock from './EachBlock';
|
||||||
|
import Element from './Element';
|
||||||
|
import ElseBlock from './ElseBlock';
|
||||||
|
import Fragment from './Fragment';
|
||||||
|
import IfBlock from './IfBlock';
|
||||||
|
import MustacheTag from './MustacheTag';
|
||||||
|
import PendingBlock from './PendingBlock';
|
||||||
|
import RawMustacheTag from './RawMustacheTag';
|
||||||
|
import Slot from './Slot';
|
||||||
|
import Text from './Text';
|
||||||
|
import ThenBlock from './ThenBlock';
|
||||||
|
|
||||||
|
const nodes: Record<string, any> = {
|
||||||
|
AwaitBlock,
|
||||||
|
CatchBlock,
|
||||||
|
Comment,
|
||||||
|
Component,
|
||||||
|
EachBlock,
|
||||||
|
Element,
|
||||||
|
ElseBlock,
|
||||||
|
Fragment,
|
||||||
|
IfBlock,
|
||||||
|
MustacheTag,
|
||||||
|
PendingBlock,
|
||||||
|
RawMustacheTag,
|
||||||
|
Slot,
|
||||||
|
Text,
|
||||||
|
ThenBlock
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nodes;
|
@ -0,0 +1,121 @@
|
|||||||
|
import { DomGenerator } from '../../dom/index';
|
||||||
|
import Block from '../../dom/Block';
|
||||||
|
import State from '../../dom/State';
|
||||||
|
import { trimStart, trimEnd } from '../../../utils/trim';
|
||||||
|
|
||||||
|
export default class Node {
|
||||||
|
metadata: {
|
||||||
|
dependencies: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type: string;
|
||||||
|
parent: Node;
|
||||||
|
generator: DomGenerator;
|
||||||
|
|
||||||
|
canUseInnerHTML: boolean;
|
||||||
|
var: string;
|
||||||
|
|
||||||
|
cannotUseInnerHTML() {
|
||||||
|
if (this.canUseInnerHTML !== false) {
|
||||||
|
this.canUseInnerHTML = false;
|
||||||
|
if (this.parent) {
|
||||||
|
if (!this.parent.cannotUseInnerHTML) console.log(this.parent.type, this.type);
|
||||||
|
this.parent.cannotUseInnerHTML();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init(
|
||||||
|
block: Block,
|
||||||
|
state: State,
|
||||||
|
inEachBlock: boolean,
|
||||||
|
elementStack: Node[],
|
||||||
|
componentStack: Node[],
|
||||||
|
stripWhitespace: boolean,
|
||||||
|
nextSibling: Node
|
||||||
|
) {
|
||||||
|
// implemented by subclasses
|
||||||
|
}
|
||||||
|
|
||||||
|
initChildren(
|
||||||
|
block: Block,
|
||||||
|
state: State,
|
||||||
|
inEachBlock: boolean,
|
||||||
|
elementStack: Node[],
|
||||||
|
componentStack: Node[],
|
||||||
|
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
|
||||||
|
// <:Window/>. lil hacky but it works
|
||||||
|
if (child.type === 'Element' && child.name === ':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.generator.hydratable;
|
||||||
|
|
||||||
|
child.init(block, state, inEachBlock, elementStack, componentStack, 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)) : !inEachBlock
|
||||||
|
);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
isChildOfComponent() {
|
||||||
|
return this.parent ?
|
||||||
|
this.parent.type === 'Component' || this.parent.isChildOfComponent() :
|
||||||
|
false;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
import { DomGenerator } from '../generators/dom/index';
|
||||||
|
import { Node } from '../interfaces';
|
||||||
|
|
||||||
|
export default function createDebuggingComment(node: Node, generator: DomGenerator) {
|
||||||
|
const { locate, source } = generator;
|
||||||
|
|
||||||
|
let c = node.start;
|
||||||
|
if (node.type === 'ElseBlock') {
|
||||||
|
while (source[c] !== '{') c -= 1;
|
||||||
|
c -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let d = node.expression ? node.expression.end : c;
|
||||||
|
while (source[d] !== '}') d += 1;
|
||||||
|
d += 2;
|
||||||
|
|
||||||
|
const start = locate(c);
|
||||||
|
const loc = `(${start.line + 1}:${start.column})`;
|
||||||
|
|
||||||
|
return `${loc} ${source.slice(c, d)}`.replace(/\n/g, ' ');
|
||||||
|
}
|
Loading…
Reference in new issue