move ssr/dom-specific logic into new Target classes, instead of subclassing Generator

pull/1367/head
Rich Harris 7 years ago
parent e0f2a4e58d
commit dd0f093582

@ -18,6 +18,8 @@ import Stylesheet from '../css/Stylesheet';
import { test } from '../config';
import Fragment from './nodes/Fragment';
import shared from './shared';
import { DomTarget } from './dom/index';
import { SsrTarget } from './server-side-rendering/index';
import { Node, GenerateOptions, ShorthandImport, Ast, CompileOptions, CustomElementOptions } from '../interfaces';
interface Computation {
@ -84,6 +86,7 @@ export default class Generator {
name: string;
options: CompileOptions;
fragment: Fragment;
target: DomTarget | SsrTarget;
customElement: CustomElementOptions;
tag: string;
@ -129,7 +132,8 @@ export default class Generator {
stylesheet: Stylesheet,
options: CompileOptions,
stats: Stats,
dom: boolean
dom: boolean,
target: DomTarget | SsrTarget
) {
stats.start('compile');
this.stats = stats;
@ -137,6 +141,7 @@ export default class Generator {
this.ast = ast;
this.source = source;
this.options = options;
this.target = target;
this.imports = [];
this.shorthandImports = [];

@ -187,7 +187,7 @@ export default class Block {
`);
}
if (this.generator.hydratable) {
if (this.generator.options.hydratable) {
if (this.builders.claim.isEmpty() && this.builders.hydrate.isEmpty()) {
properties.addBlock(`l: @noop,`);
} else {

@ -16,37 +16,19 @@ import Block from './Block';
import { test } from '../../config';
import { Ast, CompileOptions, Node } from '../../interfaces';
export class DomGenerator extends Generator {
export class DomTarget {
blocks: (Block|string)[];
readonly: Set<string>;
metaBindings: string[];
hydratable: boolean;
legacy: boolean;
hasIntroTransitions: boolean;
hasOutroTransitions: boolean;
hasComplexBindings: boolean;
needsEncapsulateHelper: boolean;
constructor(
ast: Ast,
source: string,
name: string,
stylesheet: Stylesheet,
options: CompileOptions,
stats: Stats
) {
super(ast, source, name, stylesheet, options, stats, true);
constructor() {
this.blocks = [];
this.readonly = new Set();
this.hydratable = options.hydratable;
this.legacy = options.legacy;
this.needsEncapsulateHelper = false;
// initial values for e.g. window.innerWidth, if there's a <svelte:window> meta tag
this.metaBindings = [];
}
@ -61,7 +43,8 @@ export default function dom(
) {
const format = options.format || 'es';
const generator = new DomGenerator(ast, source, options.name || 'SvelteComponent', stylesheet, options, stats);
const target = new DomTarget();
const generator = new Generator(ast, source, options.name || 'SvelteComponent', stylesheet, options, stats, true, target);
const {
computations,
@ -86,14 +69,14 @@ export default function dom(
computationDeps.add(dep);
});
if (generator.readonly.has(key)) {
if (target.readonly.has(key)) {
// <svelte:window> bindings
throw new Error(
`Cannot have a computed value '${key}' that clashes with a read-only property`
);
}
generator.readonly.add(key);
target.readonly.add(key);
const condition = `${deps.map(dep => `changed.${dep}`).join(' || ')}`;
@ -123,7 +106,7 @@ export default function dom(
`);
}
generator.blocks.forEach(block => {
target.blocks.forEach(block => {
builder.addBlock(block.toString());
});
@ -177,7 +160,7 @@ export default function dom(
${generator.usesRefs && `this.refs = {};`}
this._state = ${initialState.reduce((state, piece) => `@assign(${state}, ${piece})`)};
${storeProps.length > 0 && `this.store._add(this, [${storeProps.map(prop => `"${prop.slice(1)}"`)}]);`}
${generator.metaBindings}
${target.metaBindings}
${computations.length && `this._recompute({ ${Array.from(computationDeps).map(dep => `${dep}: 1`).join(', ')} }, this._state);`}
${options.dev &&
Array.from(generator.expectedProperties).map(prop => {
@ -216,11 +199,11 @@ export default function dom(
`if (!document.getElementById("${generator.stylesheet.id}-style")) @add_css();`)
}
${(hasInitHooks || generator.hasComponents || generator.hasComplexBindings || generator.hasIntroTransitions) && deindent`
${(hasInitHooks || generator.hasComponents || target.hasComplexBindings || target.hasIntroTransitions) && deindent`
if (!options.root) {
this._oncreate = [];
${(generator.hasComponents || generator.hasComplexBindings) && `this._beforecreate = [];`}
${(generator.hasComponents || generator.hasIntroTransitions) && `this._aftercreate = [];`}
${(generator.hasComponents || target.hasComplexBindings) && `this._beforecreate = [];`}
${(generator.hasComponents || target.hasIntroTransitions) && `this._aftercreate = [];`}
}
`}
@ -243,7 +226,7 @@ export default function dom(
if (options.target) this._mount(options.target, options.anchor);
` : deindent`
if (options.target) {
${generator.hydratable
${generator.options.hydratable
? deindent`
var nodes = @children(options.target);
options.hydrate ? this._fragment.l(nodes) : this._fragment.c();
@ -255,11 +238,11 @@ export default function dom(
`}
this._mount(options.target, options.anchor);
${(generator.hasComponents || generator.hasComplexBindings || hasInitHooks || generator.hasIntroTransitions) && deindent`
${(generator.hasComponents || target.hasComplexBindings || hasInitHooks || target.hasIntroTransitions) && deindent`
${generator.hasComponents && `this._lock = true;`}
${(generator.hasComponents || generator.hasComplexBindings) && `@callAll(this._beforecreate);`}
${(generator.hasComponents || target.hasComplexBindings) && `@callAll(this._beforecreate);`}
${(generator.hasComponents || hasInitHooks) && `@callAll(this._oncreate);`}
${(generator.hasComponents || generator.hasIntroTransitions) && `@callAll(this._aftercreate);`}
${(generator.hasComponents || target.hasIntroTransitions) && `@callAll(this._aftercreate);`}
${generator.hasComponents && `this._lock = false;`}
`}
}
@ -301,12 +284,12 @@ export default function dom(
this.set({ [attr]: newValue });
}
${(generator.hasComponents || generator.hasComplexBindings || templateProperties.oncreate || generator.hasIntroTransitions) && deindent`
${(generator.hasComponents || target.hasComplexBindings || templateProperties.oncreate || target.hasIntroTransitions) && deindent`
connectedCallback() {
${generator.hasComponents && `this._lock = true;`}
${(generator.hasComponents || generator.hasComplexBindings) && `@callAll(this._beforecreate);`}
${(generator.hasComponents || target.hasComplexBindings) && `@callAll(this._beforecreate);`}
${(generator.hasComponents || templateProperties.oncreate) && `@callAll(this._oncreate);`}
${(generator.hasComponents || generator.hasIntroTransitions) && `@callAll(this._aftercreate);`}
${(generator.hasComponents || target.hasIntroTransitions) && `@callAll(this._aftercreate);`}
${generator.hasComponents && `this._lock = false;`}
}
`}
@ -339,7 +322,7 @@ export default function dom(
builder.addBlock(deindent`
${options.dev && deindent`
${name}.prototype._checkReadOnly = function _checkReadOnly(newState) {
${Array.from(generator.readonly).map(
${Array.from(generator.target.readonly).map(
prop =>
`if ('${prop}' in newState && !this._updatingReadonlyProperty) throw new Error("${debugName}: Cannot set read-only property '${prop}'");`
)}

@ -138,9 +138,9 @@ export default class Attribute extends Node {
? '@setXlinkAttribute'
: '@setAttribute';
const isLegacyInputType = this.compiler.legacy && name === 'type' && this.parent.name === 'input';
const isLegacyInputType = this.compiler.options.legacy && name === 'type' && this.parent.name === 'input';
const isDataSet = /^data-/.test(name) && !this.compiler.legacy && !node.namespace;
const isDataSet = /^data-/.test(name) && !this.compiler.options.legacy && !node.namespace;
const camelCaseName = isDataSet ? name.replace('data-', '').replace(/(-\w)/g, function (m) {
return m[1].toUpperCase();
}) : name;

@ -52,7 +52,7 @@ export default class AwaitBlock extends Node {
});
child.initChildren(child.block, stripWhitespace, nextSibling);
this.compiler.blocks.push(child.block);
this.compiler.target.blocks.push(child.block);
if (child.block.dependencies.size > 0) {
isDynamic = true;
@ -224,18 +224,18 @@ export default class AwaitBlock extends Node {
const { compiler } = this;
const { snippet } = this.expression;
compiler.append('${(function(__value) { if(@isPromise(__value)) return `');
compiler.target.append('${(function(__value) { if(@isPromise(__value)) return `');
this.pending.children.forEach((child: Node) => {
child.ssr();
});
compiler.append('`; return function(ctx) { return `');
compiler.target.append('`; return function(ctx) { return `');
this.then.children.forEach((child: Node) => {
child.ssr();
});
compiler.append(`\`;}(Object.assign({}, ctx, { ${this.value}: __value }));}(${snippet})) }`);
compiler.target.append(`\`;}(Object.assign({}, ctx, { ${this.value}: __value }));}(${snippet})) }`);
}
}

@ -214,7 +214,7 @@ function getEventHandler(
// 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.readonly` sooner so
// 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));

@ -12,7 +12,7 @@ export default class Comment extends Node {
ssr() {
// Allow option to preserve comments, otherwise ignore
if (this.compiler.options.preserveComments) {
this.compiler.append(`<!--${this.data}-->`);
this.compiler.target.append(`<!--${this.data}-->`);
}
}
}

@ -220,7 +220,7 @@ export default class Component extends Node {
}
if (this.bindings.length) {
compiler.hasComplexBindings = true;
compiler.target.hasComplexBindings = true;
name_updating = block.alias(`${name}_updating`);
block.addVariable(name_updating, '{}');
@ -560,7 +560,7 @@ export default class Component extends Node {
const { name } = getObject(binding.value.node);
this.compiler.bindings.push(deindent`
this.compiler.target.bindings.push(deindent`
if (${conditions.reverse().join('&&')}) {
tmp = ${expression}.data();
if ('${name}' in tmp) {
@ -582,7 +582,7 @@ export default class Component extends Node {
slotStack: ['default']
};
this.compiler.appendTargets.push(appendTarget);
this.compiler.target.appendTargets.push(appendTarget);
this.children.forEach((child: Node) => {
child.ssr();
@ -594,15 +594,15 @@ export default class Component extends Node {
options.push(`slotted: { ${slotted} }`);
this.compiler.appendTargets.pop();
this.compiler.target.appendTargets.pop();
}
if (options.length) {
open += `, { ${options.join(', ')} }`;
}
this.compiler.append(open);
this.compiler.append(')}');
this.compiler.target.append(open);
this.compiler.target.append(')}');
}
}

@ -99,7 +99,7 @@ export default class EachBlock extends Node {
}
}
this.compiler.blocks.push(this.block);
this.compiler.target.blocks.push(this.block);
this.initChildren(this.block, stripWhitespace, nextSibling);
block.addDependencies(this.block.dependencies);
this.block.hasUpdateMethod = this.block.dependencies.size > 0;
@ -110,7 +110,7 @@ export default class EachBlock extends Node {
name: this.compiler.getUniqueName(`${this.block.name}_else`),
});
this.compiler.blocks.push(this.else.block);
this.compiler.target.blocks.push(this.else.block);
this.else.initChildren(
this.else.block,
stripWhitespace,
@ -485,23 +485,23 @@ export default class EachBlock extends Node {
: `item => Object.assign({}, ctx, { ${props.join(', ')} })`;
const open = `\${ ${this.else ? `${snippet}.length ? ` : ''}@each(${snippet}, ${getContext}, ctx => \``;
compiler.append(open);
compiler.target.append(open);
this.children.forEach((child: Node) => {
child.ssr();
});
const close = `\`)`;
compiler.append(close);
compiler.target.append(close);
if (this.else) {
compiler.append(` : \``);
compiler.target.append(` : \``);
this.else.children.forEach((child: Node) => {
child.ssr();
});
compiler.append(`\``);
compiler.target.append(`\``);
}
compiler.append('}');
compiler.target.append('}');
}
}

@ -180,12 +180,12 @@ export default class Element extends Node {
if (this.intro) {
this.parent.cannotUseInnerHTML();
this.compiler.hasIntroTransitions = block.hasIntroMethod = true;
this.compiler.target.hasIntroTransitions = block.hasIntroMethod = true;
}
if (this.outro) {
this.parent.cannotUseInnerHTML();
this.compiler.hasOutroTransitions = block.hasOutroMethod = true;
this.compiler.target.hasOutroTransitions = block.hasOutroMethod = true;
block.outros += 1;
}
@ -266,7 +266,7 @@ export default class Element extends Node {
`${name} = ${renderStatement};`
);
if (this.compiler.hydratable) {
if (this.compiler.options.hydratable) {
if (parentNodes) {
block.builders.claim.addBlock(deindent`
${name} = ${getClaimStatement(compiler, this.namespace, parentNodes, this)};
@ -407,7 +407,7 @@ export default class Element extends Node {
) {
if (this.bindings.length === 0) return;
if (this.name === 'select' || this.isMediaNode()) this.compiler.hasComplexBindings = true;
if (this.name === 'select' || this.isMediaNode()) this.compiler.target.hasComplexBindings = true;
const needsLock = this.name !== 'input' || !/radio|checkbox|range|color/.test(this.getStaticAttributeValue('type'));
@ -502,7 +502,7 @@ export default class Element extends Node {
.join(' && ');
if (this.name === 'select' || group.bindings.find(binding => binding.name === 'indeterminate' || binding.isReadOnlyMediaAttribute)) {
this.compiler.hasComplexBindings = true;
this.compiler.target.hasComplexBindings = true;
block.builders.hydrate.addLine(
`if (!(${allInitialStateIsDefined})) #component.root._beforecreate.push(${handler});`
@ -627,7 +627,7 @@ export default class Element extends Node {
`;
if (handler.shouldHoist) {
compiler.blocks.push(handlerFunction);
compiler.target.blocks.push(handlerFunction);
} else {
block.builders.init.addBlock(handlerFunction);
}
@ -841,7 +841,7 @@ export default class Element extends Node {
if (slot && this.hasAncestor('Component')) {
const slot = this.attributes.find((attribute: Node) => attribute.name === 'slot');
const slotName = slot.chunks[0].data;
const appendTarget = compiler.appendTargets[compiler.appendTargets.length - 1];
const appendTarget = compiler.target.appendTargets[compiler.target.appendTargets.length - 1];
appendTarget.slotStack.push(slotName);
appendTarget.slots[slotName] = '';
}
@ -898,10 +898,10 @@ export default class Element extends Node {
openingTag += '>';
compiler.append(openingTag);
compiler.target.append(openingTag);
if (this.name === 'textarea' && textareaContents !== undefined) {
compiler.append(textareaContents);
compiler.target.append(textareaContents);
} else {
this.children.forEach((child: Node) => {
child.ssr();
@ -909,7 +909,7 @@ export default class Element extends Node {
}
if (!isVoidElementName(this.name)) {
compiler.append(`</${this.name}>`);
compiler.target.append(`</${this.name}>`);
}
}
}

@ -30,7 +30,7 @@ export default class Fragment extends Node {
dependencies: new Set(),
});
this.compiler.blocks.push(this.block);
this.compiler.target.blocks.push(this.block);
this.initChildren(this.block, true, null);
this.block.hasUpdateMethod = true;

@ -37,12 +37,12 @@ export default class Head extends Node {
}
ssr() {
this.compiler.append('${(__result.head += `');
this.compiler.target.append('${(__result.head += `');
this.children.forEach((child: Node) => {
child.ssr();
});
this.compiler.append('`, "")}');
this.compiler.target.append('`, "")}');
}
}

@ -101,7 +101,7 @@ export default class IfBlock extends Node {
block.hasOutroMethod = hasOutros;
});
compiler.blocks.push(...blocks);
compiler.target.blocks.push(...blocks);
}
build(
@ -480,13 +480,13 @@ export default class IfBlock extends Node {
const { compiler } = this;
const { snippet } = this.expression;
compiler.append('${ ' + snippet + ' ? `');
compiler.target.append('${ ' + snippet + ' ? `');
this.children.forEach((child: Node) => {
child.ssr();
});
compiler.append('` : `');
compiler.target.append('` : `');
if (this.else) {
this.else.children.forEach((child: Node) => {
@ -494,7 +494,7 @@ export default class IfBlock extends Node {
});
}
compiler.append('` }');
compiler.target.append('` }');
}
visitChildren(block: Block, node: Node) {

@ -26,7 +26,7 @@ export default class MustacheTag extends Tag {
}
ssr() {
this.compiler.append(
this.compiler.target.append(
this.parent &&
this.parent.type === 'Element' &&
this.parent.name === 'style'

@ -89,6 +89,6 @@ export default class RawMustacheTag extends Tag {
}
ssr() {
this.compiler.append('${' + this.expression.snippet + '}');
this.compiler.target.append('${' + this.expression.snippet + '}');
}
}

@ -155,12 +155,12 @@ export default class Slot extends Element {
const name = this.attributes.find(attribute => attribute.name === 'name');
const slotName = name && name.chunks[0].data || 'default';
this.compiler.append(`\${options && options.slotted && options.slotted.${slotName} ? options.slotted.${slotName}() : \``);
this.compiler.target.append(`\${options && options.slotted && options.slotted.${slotName} ? options.slotted.${slotName}() : \``);
this.children.forEach((child: Node) => {
child.ssr();
});
this.compiler.append(`\`}`);
this.compiler.target.append(`\`}`);
}
}

@ -78,6 +78,6 @@ export default class Text extends Node {
// unless this Text node is inside a <script> or <style> element, escape &,<,>
text = escapeHTML(text);
}
this.compiler.append(escape(escapeTemplate(text)));
this.compiler.target.append(escape(escapeTemplate(text)));
}
}

@ -103,12 +103,12 @@ export default class Title extends Node {
}
ssr() {
this.compiler.append(`<title>`);
this.compiler.target.append(`<title>`);
this.children.forEach((child: Node) => {
child.ssr();
});
this.compiler.append(`</title>`);
this.compiler.target.append(`</title>`);
}
}

@ -109,7 +109,7 @@ export default class Window extends Node {
this.bindings.forEach(binding => {
// in dev mode, throw if read-only values are written to
if (readonly.has(binding.name)) {
compiler.readonly.add(binding.value.node.name);
compiler.target.readonly.add(binding.value.node.name);
}
bindings[binding.name] = binding.value.node.name;
@ -126,7 +126,7 @@ export default class Window extends Node {
);
// add initial value
compiler.metaBindings.push(
compiler.target.metaBindings.push(
`this._state.${binding.value.node.name} = window.${property};`
);
});
@ -207,7 +207,7 @@ export default class Window extends Node {
`);
// add initial value
compiler.metaBindings.push(
compiler.target.metaBindings.push(
`this._state.${bindings.online} = navigator.onLine;`
);

@ -87,7 +87,7 @@ export default class Node {
lastChild = null;
cleaned.forEach((child: Node, i: number) => {
child.canUseInnerHTML = !this.compiler.hydratable;
child.canUseInnerHTML = !this.compiler.options.hydratable;
child.init(block, stripWhitespace, cleaned[i + 1] || nextSibling);

@ -2,7 +2,6 @@ import deindent from '../../utils/deindent';
import Generator from '../Generator';
import Stats from '../../Stats';
import Stylesheet from '../../css/Stylesheet';
import visit from './visit';
import { removeNode, removeObjectKey } from '../../utils/removeNode';
import getName from '../../utils/getName';
import globalWhitelist from '../../utils/globalWhitelist';
@ -10,20 +9,12 @@ import { Ast, Node, CompileOptions } from '../../interfaces';
import { AppendTarget } from '../../interfaces';
import { stringify } from '../../utils/stringify';
export class SsrGenerator extends Generator {
export class SsrTarget {
bindings: string[];
renderCode: string;
appendTargets: AppendTarget[];
constructor(
ast: Ast,
source: string,
name: string,
stylesheet: Stylesheet,
options: CompileOptions,
stats: Stats
) {
super(ast, source, name, stylesheet, options, stats, false);
constructor() {
this.bindings = [];
this.renderCode = '';
this.appendTargets = [];
@ -49,7 +40,8 @@ export default function ssr(
) {
const format = options.format || 'cjs';
const generator = new SsrGenerator(ast, source, options.name || 'SvelteComponent', stylesheet, options, stats);
const target = new SsrTarget();
const generator = new Generator(ast, source, options.name || 'SvelteComponent', stylesheet, options, stats, false, target);
const { computations, name, templateProperties } = generator;
@ -132,7 +124,7 @@ export default function ssr(
`ctx.${key} = %computed-${key}(ctx);`
)}
${generator.bindings.length &&
${target.bindings.length &&
deindent`
var settled = false;
var tmp;
@ -140,11 +132,11 @@ export default function ssr(
while (!settled) {
settled = true;
${generator.bindings.join('\n\n')}
${target.bindings.join('\n\n')}
}
`}
return \`${generator.renderCode}\`;
return \`${target.renderCode}\`;
};
${name}.css = {

Loading…
Cancel
Save