mirror of https://github.com/sveltejs/svelte
parent
be84c96eba
commit
7ef5f4f54f
@ -0,0 +1,70 @@
|
||||
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';
|
||||
|
||||
type Handler = (node: any, target: any, options: any) => 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, target: Renderer, options: CompileOptions) {
|
||||
if (options.preserveComments) {
|
||||
target.append(`<!--${node.data}-->`);
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
import { stringify } from '../../../utils/stringify';
|
||||
|
||||
export default function(node, target, 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})}`;
|
||||
|
||||
target.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, target, options) {
|
||||
target.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, target, options) {
|
||||
target.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, target, 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);
|
||||
}
|
||||
target.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>`);
|
||||
}
|
Loading…
Reference in new issue