Merge pull request #864 from sveltejs/no-template-iife

Remove template IIFE
pull/867/head
Rich Harris 8 years ago committed by GitHub
commit 262456d41c

@ -1,6 +1,8 @@
import MagicString, { Bundle } from 'magic-string'; import MagicString, { Bundle } from 'magic-string';
import { walk } from 'estree-walker'; import { walk } from 'estree-walker';
import { getLocator } from 'locate-character'; import { getLocator } from 'locate-character';
import deindent from '../utils/deindent';
import CodeBuilder from '../utils/CodeBuilder';
import getCodeFrame from '../utils/getCodeFrame'; import getCodeFrame from '../utils/getCodeFrame';
import isReference from '../utils/isReference'; import isReference from '../utils/isReference';
import flattenReference from '../utils/flattenReference'; import flattenReference from '../utils/flattenReference';
@ -23,6 +25,51 @@ interface Computation {
deps: string[] deps: string[]
} }
function detectIndentation(str: string) {
const pattern = /^[\t\s]{1,4}/gm;
let match;
while (match = pattern.exec(str)) {
if (match[0][0] === '\t') return '\t';
if (match[0].length === 2) return ' ';
}
return ' ';
}
function getIndentationLevel(str: string, b: number) {
let a = b;
while (a > 0 && str[a - 1] !== '\n') a -= 1;
return /^\s*/.exec(str.slice(a, b))[0];
}
function getIndentExclusionRanges(node: Node) {
const ranges: Node[] = [];
walk(node, {
enter(node: Node) {
if (node.type === 'TemplateElement') ranges.push(node);
}
});
return ranges;
}
function removeIndentation(
code: MagicString,
start: number,
end: number,
indentationLevel: string,
ranges: Node[]
) {
const str = code.original.slice(start, end);
const pattern = new RegExp(`^${indentationLevel}`, 'gm');
let match;
while (match = pattern.exec(str)) {
// TODO bail if we're inside an exclusion range
code.remove(start + match.index, start + match.index + indentationLevel.length);
}
}
export default class Generator { export default class Generator {
ast: Parsed; ast: Parsed;
parsed: Parsed; parsed: Parsed;
@ -43,10 +90,10 @@ export default class Generator {
importedComponents: Map<string, string>; importedComponents: Map<string, string>;
namespace: string; namespace: string;
hasComponents: boolean; hasComponents: boolean;
hasJs: boolean;
computations: Computation[]; computations: Computation[];
templateProperties: Record<string, Node>; templateProperties: Record<string, Node>;
slots: Set<string>; slots: Set<string>;
javascript: string;
code: MagicString; code: MagicString;
@ -59,7 +106,8 @@ export default class Generator {
stylesheet: Stylesheet; stylesheet: Stylesheet;
importedNames: Set<string>; userVars: Set<string>;
templateVars: Map<string, string>;
aliases: Map<string, string>; aliases: Map<string, string>;
usedNames: Set<string>; usedNames: Set<string>;
@ -68,7 +116,8 @@ export default class Generator {
source: string, source: string,
name: string, name: string,
stylesheet: Stylesheet, stylesheet: Stylesheet,
options: CompileOptions options: CompileOptions,
dom: boolean
) { ) {
this.ast = clone(parsed); this.ast = clone(parsed);
@ -101,11 +150,12 @@ export default class Generator {
// allow compiler to deconflict user's `import { get } from 'whatever'` and // allow compiler to deconflict user's `import { get } from 'whatever'` and
// Svelte's builtin `import { get, ... } from 'svelte/shared.ts'`; // Svelte's builtin `import { get, ... } from 'svelte/shared.ts'`;
this.importedNames = new Set(); this.userVars = new Set();
this.templateVars = new Map();
this.aliases = new Map(); this.aliases = new Map();
this.usedNames = new Set(); this.usedNames = new Set();
this.parseJs(); this.parseJs(dom);
this.name = this.alias(name); this.name = this.alias(name);
if (options.customElement === true) { if (options.customElement === true) {
@ -198,7 +248,11 @@ export default class Generator {
usedContexts.add(name); usedContexts.add(name);
} else if (helpers.has(name)) { } else if (helpers.has(name)) {
code.prependRight(node.start, `${self.alias('template')}.helpers.`); let object = node;
while (object.type === 'MemberExpression') object = object.object;
const alias = self.templateVars.get(`helpers-${name}`);
if (alias !== name) code.overwrite(object.start, object.end, alias);
} else if (indexes.has(name)) { } else if (indexes.has(name)) {
const context = indexes.get(name); const context = indexes.get(name);
usedContexts.add(context); // TODO is this right? usedContexts.add(context); // TODO is this right?
@ -367,7 +421,7 @@ export default class Generator {
for ( for (
let i = 1; let i = 1;
reservedNames.has(alias) || reservedNames.has(alias) ||
this.importedNames.has(alias) || this.userVars.has(alias) ||
this.usedNames.has(alias); this.usedNames.has(alias);
alias = `${name}_${i++}` alias = `${name}_${i++}`
); );
@ -383,7 +437,7 @@ export default class Generator {
} }
reservedNames.forEach(add); reservedNames.forEach(add);
this.importedNames.forEach(add); this.userVars.forEach(add);
return (name: string) => { return (name: string) => {
if (test) name = `${name}$`; if (test) name = `${name}$`;
@ -399,30 +453,40 @@ export default class Generator {
}; };
} }
parseJs() { parseJs(dom: boolean) {
const { source } = this; const { code, source } = this;
const { js } = this.parsed; const { js } = this.parsed;
const imports = this.imports; const imports = this.imports;
const computations: Computation[] = []; const computations: Computation[] = [];
const templateProperties: Record<string, Node> = {}; const templateProperties: Record<string, Node> = {};
const componentDefinition = new CodeBuilder();
let namespace = null; let namespace = null;
let hasJs = !!js;
if (js) { if (js) {
this.addSourcemapLocations(js.content); this.addSourcemapLocations(js.content);
const indentation = detectIndentation(source.slice(js.start, js.end));
const indentationLevel = getIndentationLevel(source, js.content.body[0].start);
const indentExclusionRanges = getIndentExclusionRanges(js.content);
const scope = annotateWithScopes(js.content);
scope.declarations.forEach(name => {
this.userVars.add(name);
});
const body = js.content.body.slice(); // slice, because we're going to be mutating the original const body = js.content.body.slice(); // slice, because we're going to be mutating the original
// imports need to be hoisted out of the IIFE // imports need to be hoisted out of the IIFE
for (let i = 0; i < body.length; i += 1) { for (let i = 0; i < body.length; i += 1) {
const node = body[i]; const node = body[i];
if (node.type === 'ImportDeclaration') { if (node.type === 'ImportDeclaration') {
removeNode(this.code, js.content, node); removeNode(code, js.content, node);
imports.push(node); imports.push(node);
node.specifiers.forEach((specifier: Node) => { node.specifiers.forEach((specifier: Node) => {
this.importedNames.add(specifier.local.name); this.userVars.add(specifier.local.name);
}); });
} }
} }
@ -435,176 +499,197 @@ export default class Generator {
defaultExport.declaration.properties.forEach((prop: Node) => { defaultExport.declaration.properties.forEach((prop: Node) => {
templateProperties[prop.key.name] = prop; templateProperties[prop.key.name] = prop;
}); });
}
['helpers', 'events', 'components', 'transitions'].forEach(key => { ['helpers', 'events', 'components', 'transitions'].forEach(key => {
if (templateProperties[key]) { if (templateProperties[key]) {
templateProperties[key].value.properties.forEach((prop: Node) => { templateProperties[key].value.properties.forEach((prop: Node) => {
this[key].add(prop.key.name); this[key].add(prop.key.name);
});
}
});
const addArrowFunctionExpression = (name: string, node: Node) => {
const { body, params } = node;
const paramString = params.length ?
`[✂${params[0].start}-${params[params.length - 1].end}✂]` :
``;
if (body.type === 'BlockStatement') {
componentDefinition.addBlock(deindent`
function ${name}(${paramString}) [${body.start}-${body.end}]
`);
} else {
componentDefinition.addBlock(deindent`
function ${name}(${paramString}) {
return [${body.start}-${body.end}];
}
`);
}
};
const addFunctionExpression = (name: string, node: Node) => {
let c = node.start;
while (this.source[c] !== '(') c += 1;
componentDefinition.addBlock(deindent`
function ${name}[${c}-${node.end}];
`);
};
const addValue = (name: string, node: Node) => {
componentDefinition.addBlock(deindent`
var ${name} = [${node.start}-${node.end}];
`);
};
const addDeclaration = (key: string, node: Node, disambiguator?: string) => {
const qualified = disambiguator ? `${disambiguator}-${key}` : key;
if (node.type === 'Identifier' && node.name === key) {
this.templateVars.set(qualified, key);
return;
}
let name = this.getUniqueName(key);
this.templateVars.set(qualified, name);
// deindent
const indentationLevel = getIndentationLevel(source, node.start);
if (indentationLevel) {
removeIndentation(code, node.start, node.end, indentationLevel, indentExclusionRanges);
}
if (node.type === 'ArrowFunctionExpression') {
addArrowFunctionExpression(name, node);
} else if (node.type === 'FunctionExpression') {
addFunctionExpression(name, node);
} else {
addValue(name, node);
}
};
if (templateProperties.components) {
templateProperties.components.value.properties.forEach((property: Node) => {
addDeclaration(property.key.name, property.value, 'components');
}); });
} }
});
if (templateProperties.computed) { if (templateProperties.computed) {
const dependencies = new Map(); const dependencies = new Map();
templateProperties.computed.value.properties.forEach((prop: Node) => { templateProperties.computed.value.properties.forEach((prop: Node) => {
const key = prop.key.name; const key = prop.key.name;
const value = prop.value; const value = prop.value;
const deps = value.params.map( const deps = value.params.map(
(param: Node) => (param: Node) =>
param.type === 'AssignmentPattern' ? param.left.name : param.name param.type === 'AssignmentPattern' ? param.left.name : param.name
); );
dependencies.set(key, deps); dependencies.set(key, deps);
}); });
const visited = new Set(); const visited = new Set();
const visit = function visit(key: string) { const visit = (key: string) => {
if (!dependencies.has(key)) return; // not a computation if (!dependencies.has(key)) return; // not a computation
if (visited.has(key)) return; if (visited.has(key)) return;
visited.add(key); visited.add(key);
const deps = dependencies.get(key); const deps = dependencies.get(key);
deps.forEach(visit); deps.forEach(visit);
computations.push({ key, deps }); computations.push({ key, deps });
}
templateProperties.computed.value.properties.forEach((prop: Node) => const prop = templateProperties.computed.value.properties.find((prop: Node) => prop.key.name === key);
visit(prop.key.name) addDeclaration(key, prop.value, 'computed');
); };
}
if (templateProperties.namespace) { templateProperties.computed.value.properties.forEach((prop: Node) =>
const ns = templateProperties.namespace.value.value; visit(prop.key.name)
namespace = namespaces[ns] || ns; );
}
removeObjectKey(this.code, defaultExport.declaration, 'namespace'); if (templateProperties.data) {
} addDeclaration('data', templateProperties.data.value);
}
if (templateProperties.components) { if (templateProperties.events && dom) {
let hasNonImportedComponent = false; templateProperties.events.value.properties.forEach((property: Node) => {
templateProperties.components.value.properties.forEach( addDeclaration(property.key.name, property.value, 'events');
(property: Node) => {
const key = property.key.name;
const value = source.slice(
property.value.start,
property.value.end
);
if (this.importedNames.has(value)) {
this.importedComponents.set(key, value);
} else {
hasNonImportedComponent = true;
}
}
);
if (hasNonImportedComponent) {
// remove the specific components that were imported, as we'll refer to them directly
Array.from(this.importedComponents.keys()).forEach(key => {
removeObjectKey(
this.code,
templateProperties.components.value,
key
);
}); });
} else {
// remove the entire components portion of the export
removeObjectKey(this.code, defaultExport.declaration, 'components');
} }
}
// Remove these after version 2 if (templateProperties.helpers) {
if (templateProperties.onrender) { templateProperties.helpers.value.properties.forEach((property: Node) => {
const { key } = templateProperties.onrender; addDeclaration(property.key.name, property.value, 'helpers');
this.code.overwrite(key.start, key.end, 'oncreate', { });
storeName: true, }
contentOnly: false,
});
templateProperties.oncreate = templateProperties.onrender;
}
if (templateProperties.onteardown) { if (templateProperties.methods && dom) {
const { key } = templateProperties.onteardown; addDeclaration('methods', templateProperties.methods.value);
this.code.overwrite(key.start, key.end, 'ondestroy', { }
storeName: true,
contentOnly: false,
});
templateProperties.ondestroy = templateProperties.onteardown;
}
if (templateProperties.tag) { if (templateProperties.namespace) {
this.tag = templateProperties.tag.value.value; const ns = templateProperties.namespace.value.value;
removeObjectKey(this.code, defaultExport.declaration, 'tag'); namespace = namespaces[ns] || ns;
} }
if (templateProperties.props) { if (templateProperties.onrender) templateProperties.oncreate = templateProperties.onrender; // remove after v2
this.props = templateProperties.props.value.elements.map((element: Node) => element.value); if (templateProperties.oncreate && dom) {
removeObjectKey(this.code, defaultExport.declaration, 'props'); addDeclaration('oncreate', templateProperties.oncreate.value);
} }
// now that we've analysed the default export, we can determine whether or not we need to keep it if (templateProperties.onteardown) templateProperties.ondestroy = templateProperties.onteardown; // remove after v2
let hasDefaultExport = !!defaultExport; if (templateProperties.ondestroy && dom) {
if (defaultExport && defaultExport.declaration.properties.length === 0) { addDeclaration('ondestroy', templateProperties.ondestroy.value);
hasDefaultExport = false; }
removeNode(this.code, js.content, defaultExport);
}
// if we do need to keep it, then we need to generate a return statement if (templateProperties.props) {
if (hasDefaultExport) { this.props = templateProperties.props.value.elements.map((element: Node) => element.value);
const finalNode = body[body.length - 1]; }
if (defaultExport === finalNode) {
// export is last property, we can just return it
this.code.overwrite(
defaultExport.start,
defaultExport.declaration.start,
`return `
);
} else {
const { declarations } = annotateWithScopes(js);
let template = 'template';
for (
let i = 1;
declarations.has(template);
template = `template_${i++}`
);
this.code.overwrite( if (templateProperties.setup) {
defaultExport.start, addDeclaration('setup', templateProperties.setup.value);
defaultExport.declaration.start, }
`var ${template} = `
);
let i = defaultExport.start; if (templateProperties.tag) {
while (/\s/.test(source[i - 1])) i--; this.tag = templateProperties.tag.value.value;
}
const indentation = source.slice(i, defaultExport.start); if (templateProperties.transitions) {
this.code.appendLeft( templateProperties.transitions.value.properties.forEach((property: Node) => {
finalNode.end, addDeclaration(property.key.name, property.value, 'transitions');
`\n\n${indentation}return ${template};` });
); }
}
if (indentationLevel) {
if (defaultExport) {
removeIndentation(code, js.content.start, defaultExport.start, indentationLevel, indentExclusionRanges);
removeIndentation(code, defaultExport.end, js.content.end, indentationLevel, indentExclusionRanges);
} else {
removeIndentation(code, js.content.start, js.content.end, indentationLevel, indentExclusionRanges);
} }
} }
// user code gets wrapped in an IIFE let a = js.content.start;
if (js.content.body.length) { while (/\s/.test(source[a])) a += 1;
const prefix = hasDefaultExport
? `var ${this.alias('template')} = (function() {` let b = js.content.end;
: `(function() {`; while (/\s/.test(source[b - 1])) b -= 1;
this.code
.prependRight(js.content.start, prefix) if (defaultExport) {
.appendLeft(js.content.end, '}());'); this.javascript = '';
if (a !== defaultExport.start) this.javascript += `[✂${a}-${defaultExport.start}✂]`;
if (!componentDefinition.isEmpty()) this.javascript += componentDefinition;
if (defaultExport.end !== b) this.javascript += `[✂${defaultExport.end}-${b}✂]`;
} else { } else {
// if there's no need to include user code, remove it altogether this.javascript = a === b ? null : `[✂${a}-${b}✂]`;
this.code.remove(js.content.start, js.content.end);
hasJs = false;
} }
} }
this.computations = computations; this.computations = computations;
this.hasJs = hasJs;
this.namespace = namespace; this.namespace = namespace;
this.templateProperties = templateProperties; this.templateProperties = templateProperties;
} }

@ -39,7 +39,7 @@ export class DomGenerator extends Generator {
stylesheet: Stylesheet, stylesheet: Stylesheet,
options: CompileOptions options: CompileOptions
) { ) {
super(parsed, source, name, stylesheet, options); super(parsed, source, name, stylesheet, options, true);
this.blocks = []; this.blocks = [];
this.readonly = new Set(); this.readonly = new Set();
@ -60,7 +60,7 @@ export class DomGenerator extends Generator {
} }
reservedNames.forEach(add); reservedNames.forEach(add);
this.importedNames.forEach(add); this.userVars.forEach(add);
for (const name in shared) { for (const name in shared) {
localUsedNames.add(test ? `${name}$` : name); localUsedNames.add(test ? `${name}$` : name);
} }
@ -92,7 +92,6 @@ export default function dom(
const { const {
computations, computations,
hasJs,
name, name,
templateProperties, templateProperties,
namespace, namespace,
@ -127,7 +126,7 @@ export default function dom(
const condition = `${deps.map(dep => `changed.${dep}`).join(' || ')}`; const condition = `${deps.map(dep => `changed.${dep}`).join(' || ')}`;
const statement = `if (@differs(state.${key}, (state.${key} = @template.computed.${key}(${deps const statement = `if (@differs(state.${key}, (state.${key} = %computed-${key}(${deps
.map(dep => `state.${dep}`) .map(dep => `state.${dep}`)
.join(', ')})))) changed.${key} = true;`; .join(', ')})))) changed.${key} = true;`;
@ -135,8 +134,8 @@ export default function dom(
}); });
} }
if (hasJs) { if (generator.javascript) {
builder.addBlock(`[✂${parsed.js.content.start}-${parsed.js.content.end}✂]`); builder.addBlock(generator.javascript);
} }
if (generator.needsEncapsulateHelper) { if (generator.needsEncapsulateHelper) {
@ -173,7 +172,7 @@ export default function dom(
const prototypeBase = const prototypeBase =
`${name}.prototype` + `${name}.prototype` +
(templateProperties.methods ? `, @template.methods` : ''); (templateProperties.methods ? `, %methods` : '');
const proto = sharedPath const proto = sharedPath
? `@proto` ? `@proto`
: deindent` : deindent`
@ -192,7 +191,7 @@ export default function dom(
@init(this, options); @init(this, options);
${generator.usesRefs && `this.refs = {};`} ${generator.usesRefs && `this.refs = {};`}
this._state = ${templateProperties.data this._state = ${templateProperties.data
? `@assign(@template.data(), options.data)` ? `@assign(%data(), options.data)`
: `options.data || {}`}; : `options.data || {}`};
${generator.metaBindings} ${generator.metaBindings}
${computations.length && `this._recompute({ ${Array.from(computationDeps).map(dep => `${dep}: 1`).join(', ')} }, this._state);`} ${computations.length && `this._recompute({ ${Array.from(computationDeps).map(dep => `${dep}: 1`).join(', ')} }, this._state);`}
@ -204,7 +203,7 @@ export default function dom(
${generator.bindingGroups.length && ${generator.bindingGroups.length &&
`this._bindingGroups = [${Array(generator.bindingGroups.length).fill('[]').join(', ')}];`} `this._bindingGroups = [${Array(generator.bindingGroups.length).fill('[]').join(', ')}];`}
${templateProperties.ondestroy && `this._handlers.destroy = [@template.ondestroy]`} ${templateProperties.ondestroy && `this._handlers.destroy = [%ondestroy]`}
${generator.slots.size && `this._slotted = options.slots || {};`} ${generator.slots.size && `this._slotted = options.slots || {};`}
@ -217,16 +216,16 @@ export default function dom(
`if (!document.getElementById("${generator.stylesheet.id}-style")) @add_css();`) `if (!document.getElementById("${generator.stylesheet.id}-style")) @add_css();`)
} }
${templateProperties.oncreate && `var oncreate = @template.oncreate.bind(this);`} ${templateProperties.oncreate && `var _oncreate = %oncreate.bind(this);`}
${(templateProperties.oncreate || generator.hasComponents || generator.hasComplexBindings || generator.hasIntroTransitions) && deindent` ${(templateProperties.oncreate || generator.hasComponents || generator.hasComplexBindings || generator.hasIntroTransitions) && deindent`
if (!options._root) { if (!options._root) {
this._oncreate = [${templateProperties.oncreate && `oncreate`}]; this._oncreate = [${templateProperties.oncreate && `_oncreate`}];
${(generator.hasComponents || generator.hasComplexBindings) && `this._beforecreate = [];`} ${(generator.hasComponents || generator.hasComplexBindings) && `this._beforecreate = [];`}
${(generator.hasComponents || generator.hasIntroTransitions) && `this._aftercreate = [];`} ${(generator.hasComponents || generator.hasIntroTransitions) && `this._aftercreate = [];`}
} ${templateProperties.oncreate && deindent` } ${templateProperties.oncreate && deindent`
else { else {
this._root._oncreate.push(oncreate); this._root._oncreate.push(_oncreate);
} }
`} `}
`} `}
@ -338,22 +337,28 @@ export default function dom(
} }
` : (!sharedPath && `${name}.prototype._recompute = @noop;`)} ` : (!sharedPath && `${name}.prototype._recompute = @noop;`)}
${templateProperties.setup && `@template.setup(${name});`} ${templateProperties.setup && `%setup(${name});`}
`); `);
const usedHelpers = new Set(); const usedHelpers = new Set();
let result = builder let result = builder
.toString() .toString()
.replace(/(@+)(\w*)/g, (match: string, sigil: string, name: string) => { .replace(/(%+|@+)(\w*(?:-\w*)?)/g, (match: string, sigil: string, name: string) => {
if (sigil !== '@') return sigil.slice(1) + name; if (sigil === '@') {
if (name in shared) {
if (options.dev && `${name}Dev` in shared) name = `${name}Dev`;
usedHelpers.add(name);
}
return generator.alias(name);
}
if (name in shared) { if (sigil === '%') {
if (options.dev && `${name}Dev` in shared) name = `${name}Dev`; return generator.templateVars.get(name);
usedHelpers.add(name);
} }
return generator.alias(name); return sigil.slice(1) + name;
}); });
let helpers; let helpers;

@ -216,10 +216,7 @@ export default function visitComponent(
} }
} }
const expression = node.name === ':Self' const expression = node.name === ':Self' ? generator.name : `%components-${node.name}`;
? generator.name
: generator.importedComponents.get(node.name) ||
`@template.components.${node.name}`;
block.builders.init.addBlock(deindent` block.builders.init.addBlock(deindent`
${statements.join('\n')} ${statements.join('\n')}

@ -79,7 +79,7 @@ export default function visitEventHandler(
block.addVariable(handlerName); block.addVariable(handlerName);
block.builders.hydrate.addBlock(deindent` block.builders.hydrate.addBlock(deindent`
${handlerName} = @template.events.${name}.call(#component, ${state.parentNode}, function(event) { ${handlerName} = %events-${name}.call(#component, ${state.parentNode}, function(event) {
${handlerBody} ${handlerBody}
}); });
`); `);

@ -20,7 +20,7 @@ export default function addTransitions(
block.addVariable(name); block.addVariable(name);
const fn = `@template.transitions.${intro.name}`; const fn = `%transitions-${intro.name}`;
block.builders.intro.addBlock(deindent` block.builders.intro.addBlock(deindent`
#component._root._aftercreate.push(function() { #component._root._aftercreate.push(function() {
@ -48,7 +48,7 @@ export default function addTransitions(
? block.contextualise(intro.expression).snippet ? block.contextualise(intro.expression).snippet
: '{}'; : '{}';
const fn = `@template.transitions.${intro.name}`; // TODO add built-in transitions? const fn = `%transitions-${intro.name}`; // TODO add built-in transitions?
if (outro) { if (outro) {
block.builders.intro.addBlock(deindent` block.builders.intro.addBlock(deindent`
@ -73,7 +73,7 @@ export default function addTransitions(
? block.contextualise(outro.expression).snippet ? block.contextualise(outro.expression).snippet
: '{}'; : '{}';
const fn = `@template.transitions.${outro.name}`; const fn = `%transitions-${outro.name}`;
// TODO hide elements that have outro'd (unless they belong to a still-outroing // TODO hide elements that have outro'd (unless they belong to a still-outroing
// group) prior to their removal from the DOM // group) prior to their removal from the DOM

@ -21,42 +21,14 @@ export class SsrGenerator extends Generator {
stylesheet: Stylesheet, stylesheet: Stylesheet,
options: CompileOptions options: CompileOptions
) { ) {
super(parsed, source, name, stylesheet, options); super(parsed, source, name, stylesheet, options, false);
this.bindings = []; this.bindings = [];
this.renderCode = ''; this.renderCode = '';
this.appendTargets = []; this.appendTargets = [];
// in an SSR context, we don't need to include events, methods, oncreate or ondestroy
const { templateProperties, defaultExport } = this;
preprocess(this, parsed.html); preprocess(this, parsed.html);
this.stylesheet.warnOnUnusedSelectors(options.onwarn); this.stylesheet.warnOnUnusedSelectors(options.onwarn);
if (templateProperties.oncreate)
removeNode(
this.code,
defaultExport.declaration,
templateProperties.oncreate
);
if (templateProperties.ondestroy)
removeNode(
this.code,
defaultExport.declaration,
templateProperties.ondestroy
);
if (templateProperties.methods)
removeNode(
this.code,
defaultExport.declaration,
templateProperties.methods
);
if (templateProperties.events)
removeNode(
this.code,
defaultExport.declaration,
templateProperties.events
);
} }
append(code: string) { append(code: string) {
@ -80,7 +52,7 @@ export default function ssr(
const generator = new SsrGenerator(parsed, source, options.name || 'SvelteComponent', stylesheet, options); const generator = new SsrGenerator(parsed, source, options.name || 'SvelteComponent', stylesheet, options);
const { computations, name, hasJs, templateProperties } = generator; const { computations, name, templateProperties } = generator;
// create main render() function // create main render() function
const mainBlock = new Block({ const mainBlock = new Block({
@ -99,24 +71,24 @@ export default function ssr(
generator.stylesheet.render(options.filename, true); generator.stylesheet.render(options.filename, true);
const result = deindent` const result = deindent`
${hasJs && `[✂${parsed.js.content.start}-${parsed.js.content.end}✂]`} ${generator.javascript}
var ${name} = {}; var ${name} = {};
${options.filename && `${name}.filename = ${stringify(options.filename)}`}; ${options.filename && `${name}.filename = ${stringify(options.filename)}`};
${name}.data = function() { ${name}.data = function() {
return ${templateProperties.data ? `@template.data()` : `{}`}; return ${templateProperties.data ? `%data()` : `{}`};
}; };
${name}.render = function(state, options) { ${name}.render = function(state, options) {
${templateProperties.data ${templateProperties.data
? `state = Object.assign(@template.data(), state || {});` ? `state = Object.assign(%data(), state || {});`
: `state = state || {};`} : `state = state || {};`}
${computations.map( ${computations.map(
({ key, deps }) => ({ key, deps }) =>
`state.${key} = @template.computed.${key}(${deps.map(dep => `state.${dep}`).join(', ')});` `state.${key} = %computed-${key}(${deps.map(dep => `state.${dep}`).join(', ')});`
)} )}
${generator.bindings.length && ${generator.bindings.length &&
@ -159,10 +131,8 @@ export default function ssr(
}); });
} }
${templateProperties.components.value.properties.map(prop => { ${templateProperties.components.value.properties.map((prop: Node) => {
const { name } = prop.key; return `addComponent(%components-${prop.key.name});`;
const expression = generator.importedComponents.get(name) || `@template.components.${name}`;
return `addComponent(${expression});`;
})} })}
`} `}
@ -184,8 +154,10 @@ export default function ssr(
function __escape(html) { function __escape(html) {
return String(html).replace(/["'&<>]/g, match => escaped[match]); return String(html).replace(/["'&<>]/g, match => escaped[match]);
} }
`.replace(/(@+|#+)(\w*)/g, (match: string, sigil: string, name: string) => { `.replace(/(@+|#+|%+)(\w*(?:-\w*)?)/g, (match: string, sigil: string, name: string) => {
return sigil === '@' ? generator.alias(name) : sigil.slice(1) + name; if (sigil === '@') return generator.alias(name);
if (sigil === '%') return generator.templateVars.get(name);
return sigil.slice(1) + name;
}); });
return generator.generate(result, options, { name, format }); return generator.generate(result, options, { name, format });

@ -69,10 +69,7 @@ export default function visitComponent(
) )
.join(', '); .join(', ');
const expression = node.name === ':Self' const expression = node.name === ':Self' ? generator.name : `%components-${node.name}`;
? generator.name
: generator.importedComponents.get(node.name) ||
`@template.components.${node.name}`;
bindings.forEach(binding => { bindings.forEach(binding => {
block.addBinding(binding, expression); block.addBinding(binding, expression);

@ -3,7 +3,7 @@ export function stringify(data: string, options = {}) {
} }
export function escape(data: string, { onlyEscapeAtSymbol = false } = {}) { export function escape(data: string, { onlyEscapeAtSymbol = false } = {}) {
return data.replace(onlyEscapeAtSymbol ? /(@+)/g : /(@+|#+)/g, (match: string) => { return data.replace(onlyEscapeAtSymbol ? /(%+|@+)/g : /(%+|@+|#+)/g, (match: string) => {
return match + match[0]; return match + match[0];
}); });
} }

@ -101,34 +101,44 @@ describe('css', () => {
if (expected.html !== null) { if (expected.html !== null) {
const window = env(); const window = env();
const Component = eval(
`(function () { ${dom.code}; return SvelteComponent; }())`
);
const target = window.document.querySelector('main');
new Component({ target, data: config.data });
const html = target.innerHTML;
fs.writeFileSync(`test/css/samples/${dir}/_actual.html`, html);
// dom // dom
assert.equal( try {
normalizeHtml(window, html.replace(/svelte-\d+/g, 'svelte-xyz')), const Component = eval(
normalizeHtml(window, expected.html) `(function () { ${dom.code}; return SvelteComponent; }())`
); );
const target = window.document.querySelector('main');
new Component({ target, data: config.data });
const html = target.innerHTML;
fs.writeFileSync(`test/css/samples/${dir}/_actual.html`, html);
assert.equal(
normalizeHtml(window, html.replace(/svelte-\d+/g, 'svelte-xyz')),
normalizeHtml(window, expected.html)
);
} catch (err) {
console.log(dom.code);
throw err;
}
// ssr // ssr
const component = eval( try {
`(function () { ${ssr.code}; return SvelteComponent; }())` const component = eval(
); `(function () { ${ssr.code}; return SvelteComponent; }())`
);
assert.equal(
normalizeHtml( assert.equal(
window, normalizeHtml(
component.render(config.data).replace(/svelte-\d+/g, 'svelte-xyz') window,
), component.render(config.data).replace(/svelte-\d+/g, 'svelte-xyz')
normalizeHtml(window, expected.html) ),
); normalizeHtml(window, expected.html)
);
} catch (err) {
console.log(ssr.code);
throw err;
}
} }
}); });
}); });

@ -62,13 +62,13 @@ describe("js", () => {
); );
assert.equal( assert.equal(
actual.trim().replace(/^\s+$/gm, ""), actual.trim().replace(/^[ \t]+$/gm, ""),
expected.trim().replace(/^\s+$/gm, "") expected.trim().replace(/^[ \t]+$/gm, "")
); );
assert.equal( assert.equal(
code.trim().replace(/^\s+$/gm, ""), code.trim().replace(/^[ \t]+$/gm, ""),
expectedBundle.trim().replace(/^\s+$/gm, "") expectedBundle.trim().replace(/^[ \t]+$/gm, "")
); );
}).catch(err => { }).catch(err => {
if (err.loc) console.error(err.loc); if (err.loc) console.error(err.loc);

@ -190,13 +190,9 @@ var proto = {
}; };
/* generated by Svelte vX.Y.Z */ /* generated by Svelte vX.Y.Z */
var template = (function() { function data() {
return { return { foo: 42 }
data: function () { }
return { foo: 42 }
}
};
}());
function encapsulateStyles(node) { function encapsulateStyles(node) {
setAttribute(node, "svelte-3590263702", ""); setAttribute(node, "svelte-3590263702", "");
@ -244,7 +240,7 @@ function create_main_fragment(state, component) {
function SvelteComponent(options) { function SvelteComponent(options) {
init(this, options); init(this, options);
this._state = assign(template.data(), options.data); this._state = assign(data(), options.data);
if (!document.getElementById("svelte-3590263702-style")) add_css(); if (!document.getElementById("svelte-3590263702-style")) add_css();

@ -1,13 +1,9 @@
/* generated by Svelte vX.Y.Z */ /* generated by Svelte vX.Y.Z */
import { appendNode, assign, createElement, createText, detachNode, init, insertNode, noop, proto, setAttribute } from "svelte/shared.js"; import { appendNode, assign, createElement, createText, detachNode, init, insertNode, noop, proto, setAttribute } from "svelte/shared.js";
var template = (function() { function data() {
return { return { foo: 42 }
data: function () { };
return { foo: 42 }
}
};
}());
function encapsulateStyles(node) { function encapsulateStyles(node) {
setAttribute(node, "svelte-3590263702", ""); setAttribute(node, "svelte-3590263702", "");
@ -55,7 +51,7 @@ function create_main_fragment(state, component) {
function SvelteComponent(options) { function SvelteComponent(options) {
init(this, options); init(this, options);
this._state = assign(template.data(), options.data); this._state = assign(data(), options.data);
if (!document.getElementById("svelte-3590263702-style")) add_css(); if (!document.getElementById("svelte-3590263702-style")) add_css();

@ -166,17 +166,11 @@ var proto = {
}; };
/* generated by Svelte vX.Y.Z */ /* generated by Svelte vX.Y.Z */
var template = (function() { var Nested = window.Nested;
return {
components: {
Nested: window.Nested
}
};
}());
function create_main_fragment(state, component) { function create_main_fragment(state, component) {
var nested = new template.components.Nested({ var nested = new Nested({
_root: component._root, _root: component._root,
data: { foo: "bar" } data: { foo: "bar" }
}); });

@ -1,17 +1,11 @@
/* generated by Svelte vX.Y.Z */ /* generated by Svelte vX.Y.Z */
import { assign, callAll, init, noop, proto } from "svelte/shared.js"; import { assign, callAll, init, noop, proto } from "svelte/shared.js";
var template = (function() { var Nested = window.Nested;
return {
components: {
Nested: window.Nested
}
};
}());
function create_main_fragment(state, component) { function create_main_fragment(state, component) {
var nested = new template.components.Nested({ var nested = new Nested({
_root: component._root, _root: component._root,
data: { foo: "bar" } data: { foo: "bar" }
}); });

@ -166,14 +166,13 @@ var proto = {
}; };
/* generated by Svelte vX.Y.Z */ /* generated by Svelte vX.Y.Z */
var template = (function() { function a(x) {
return { return x * 2;
computed: { }
a: x => x * 2,
b: x => x * 3 function b(x) {
} return x * 3;
}; }
}());
function create_main_fragment(state, component) { function create_main_fragment(state, component) {
@ -207,8 +206,8 @@ assign(SvelteComponent.prototype, proto);
SvelteComponent.prototype._recompute = function _recompute(changed, state) { SvelteComponent.prototype._recompute = function _recompute(changed, state) {
if (changed.x) { if (changed.x) {
if (differs(state.a, (state.a = template.computed.a(state.x)))) changed.a = true; if (differs(state.a, (state.a = a(state.x)))) changed.a = true;
if (differs(state.b, (state.b = template.computed.b(state.x)))) changed.b = true; if (differs(state.b, (state.b = b(state.x)))) changed.b = true;
} }
}; };

@ -1,14 +1,13 @@
/* generated by Svelte vX.Y.Z */ /* generated by Svelte vX.Y.Z */
import { assign, differs, init, noop, proto } from "svelte/shared.js"; import { assign, differs, init, noop, proto } from "svelte/shared.js";
var template = (function() { function a(x) {
return { return x * 2;
computed: { }
a: x => x * 2,
b: x => x * 3 function b(x) {
} return x * 3;
}; }
}());
function create_main_fragment(state, component) { function create_main_fragment(state, component) {
@ -42,8 +41,8 @@ assign(SvelteComponent.prototype, proto);
SvelteComponent.prototype._recompute = function _recompute(changed, state) { SvelteComponent.prototype._recompute = function _recompute(changed, state) {
if (changed.x) { if (changed.x) {
if (differs(state.a, (state.a = template.computed.a(state.x)))) changed.a = true; if (differs(state.a, (state.a = a(state.x)))) changed.a = true;
if (differs(state.b, (state.b = template.computed.b(state.x)))) changed.b = true; if (differs(state.b, (state.b = b(state.x)))) changed.b = true;
} }
} }
export default SvelteComponent; export default SvelteComponent;

@ -178,20 +178,15 @@ var proto = {
}; };
/* generated by Svelte vX.Y.Z */ /* generated by Svelte vX.Y.Z */
var template = (function() { function foo( node, callback ) {
return { // code goes here
methods: { }
foo ( bar ) {
console.log( bar ); var methods = {
} foo ( bar ) {
}, console.log( bar );
events: { }
foo ( node, callback ) { };
// code goes here
}
}
};
}());
function create_main_fragment(state, component) { function create_main_fragment(state, component) {
var button, foo_handler; var button, foo_handler;
@ -204,7 +199,7 @@ function create_main_fragment(state, component) {
}, },
h: function hydrate() { h: function hydrate() {
foo_handler = template.events.foo.call(component, button, function(event) { foo_handler = foo.call(component, button, function(event) {
var state = component.get(); var state = component.get();
component.foo( state.bar ); component.foo( state.bar );
}); });
@ -238,6 +233,6 @@ function SvelteComponent(options) {
} }
} }
assign(SvelteComponent.prototype, template.methods, proto); assign(SvelteComponent.prototype, methods, proto);
export default SvelteComponent; export default SvelteComponent;

@ -1,20 +1,15 @@
/* generated by Svelte vX.Y.Z */ /* generated by Svelte vX.Y.Z */
import { assign, createElement, detachNode, init, insertNode, noop, proto } from "svelte/shared.js"; import { assign, createElement, detachNode, init, insertNode, noop, proto } from "svelte/shared.js";
var template = (function() { function foo( node, callback ) {
return { // code goes here
methods: { };
foo ( bar ) {
console.log( bar ); var methods = {
} foo ( bar ) {
}, console.log( bar );
events: { }
foo ( node, callback ) { };
// code goes here
}
}
};
}());
function create_main_fragment(state, component) { function create_main_fragment(state, component) {
var button, foo_handler; var button, foo_handler;
@ -27,7 +22,7 @@ function create_main_fragment(state, component) {
}, },
h: function hydrate() { h: function hydrate() {
foo_handler = template.events.foo.call(component, button, function(event) { foo_handler = foo.call(component, button, function(event) {
var state = component.get(); var state = component.get();
component.foo( state.bar ); component.foo( state.bar );
}); });
@ -61,5 +56,5 @@ function SvelteComponent(options) {
} }
} }
assign(SvelteComponent.prototype, template.methods, proto); assign(SvelteComponent.prototype, methods, proto);
export default SvelteComponent; export default SvelteComponent;

@ -180,14 +180,6 @@ var proto = {
}; };
/* generated by Svelte vX.Y.Z */ /* generated by Svelte vX.Y.Z */
var template = (function() {
return {
components: {
NonImported
}
};
}());
function create_main_fragment(state, component) { function create_main_fragment(state, component) {
var text; var text;
@ -195,7 +187,7 @@ function create_main_fragment(state, component) {
_root: component._root _root: component._root
}); });
var nonimported = new template.components.NonImported({ var nonimported = new NonImported({
_root: component._root _root: component._root
}); });

@ -2,13 +2,7 @@
import { assign, callAll, createText, detachNode, init, insertNode, noop, proto } from "svelte/shared.js"; import { assign, callAll, createText, detachNode, init, insertNode, noop, proto } from "svelte/shared.js";
import Imported from 'Imported.html'; import Imported from 'Imported.html';
var template = (function() {
return {
components: {
NonImported
}
};
}());
function create_main_fragment(state, component) { function create_main_fragment(state, component) {
var text; var text;
@ -17,7 +11,7 @@ function create_main_fragment(state, component) {
_root: component._root _root: component._root
}); });
var nonimported = new template.components.NonImported({ var nonimported = new NonImported({
_root: component._root _root: component._root
}); });

@ -166,13 +166,9 @@ var proto = {
}; };
/* generated by Svelte vX.Y.Z */ /* generated by Svelte vX.Y.Z */
var template = (function() { function oncreate() {}
return {
// this test should be removed in v2 function ondestroy() {}
oncreate () {},
ondestroy () {}
};
}());
function create_main_fragment(state, component) { function create_main_fragment(state, component) {
@ -193,14 +189,14 @@ function SvelteComponent(options) {
init(this, options); init(this, options);
this._state = options.data || {}; this._state = options.data || {};
this._handlers.destroy = [template.ondestroy]; this._handlers.destroy = [ondestroy];
var oncreate = template.oncreate.bind(this); var _oncreate = oncreate.bind(this);
if (!options._root) { if (!options._root) {
this._oncreate = [oncreate]; this._oncreate = [_oncreate];
} else { } else {
this._root._oncreate.push(oncreate); this._root._oncreate.push(_oncreate);
} }
this._fragment = create_main_fragment(this._state, this); this._fragment = create_main_fragment(this._state, this);

@ -1,13 +1,9 @@
/* generated by Svelte vX.Y.Z */ /* generated by Svelte vX.Y.Z */
import { assign, callAll, init, noop, proto } from "svelte/shared.js"; import { assign, callAll, init, noop, proto } from "svelte/shared.js";
var template = (function() { function oncreate() {};
return {
// this test should be removed in v2 function ondestroy() {};
oncreate () {},
ondestroy () {}
};
}());
function create_main_fragment(state, component) { function create_main_fragment(state, component) {
@ -28,14 +24,14 @@ function SvelteComponent(options) {
init(this, options); init(this, options);
this._state = options.data || {}; this._state = options.data || {};
this._handlers.destroy = [template.ondestroy] this._handlers.destroy = [ondestroy]
var oncreate = template.oncreate.bind(this); var _oncreate = oncreate.bind(this);
if (!options._root) { if (!options._root) {
this._oncreate = [oncreate]; this._oncreate = [_oncreate];
} else { } else {
this._root._oncreate.push(oncreate); this._root._oncreate.push(_oncreate);
} }
this._fragment = create_main_fragment(this._state, this); this._fragment = create_main_fragment(this._state, this);

@ -166,24 +166,21 @@ var proto = {
}; };
/* generated by Svelte vX.Y.Z */ /* generated by Svelte vX.Y.Z */
var template = (function() { var methods = {
return { foo ( bar ) {
methods: { console.log( bar );
foo ( bar ) { }
console.log( bar ); };
}
}, function setup(Component) {
setup: (Component) => { Component.SOME_CONSTANT = 42;
Component.SOME_CONSTANT = 42; Component.factory = function (target) {
Component.factory = function (target) { return new Component({
return new Component({ target: target
target: target });
});
};
Component.prototype.foo( 'baz' );
}
}; };
}()); Component.prototype.foo( 'baz' );
}
function create_main_fragment(state, component) { function create_main_fragment(state, component) {
@ -212,8 +209,8 @@ function SvelteComponent(options) {
} }
} }
assign(SvelteComponent.prototype, template.methods, proto); assign(SvelteComponent.prototype, methods, proto);
template.setup(SvelteComponent); setup(SvelteComponent);
export default SvelteComponent; export default SvelteComponent;

@ -1,24 +1,21 @@
/* generated by Svelte vX.Y.Z */ /* generated by Svelte vX.Y.Z */
import { assign, init, noop, proto } from "svelte/shared.js"; import { assign, init, noop, proto } from "svelte/shared.js";
var template = (function() { var methods = {
return { foo ( bar ) {
methods: { console.log( bar );
foo ( bar ) { }
console.log( bar ); };
}
}, function setup(Component) {
setup: (Component) => { Component.SOME_CONSTANT = 42;
Component.SOME_CONSTANT = 42; Component.factory = function (target) {
Component.factory = function (target) { return new Component({
return new Component({ target: target
target: target });
}); }
} Component.prototype.foo( 'baz' );
Component.prototype.foo( 'baz' ); }
}
};
}());
function create_main_fragment(state, component) { function create_main_fragment(state, component) {
@ -47,7 +44,7 @@ function SvelteComponent(options) {
} }
} }
assign(SvelteComponent.prototype, template.methods, proto); assign(SvelteComponent.prototype, methods, proto);
template.setup(SvelteComponent); setup(SvelteComponent);
export default SvelteComponent; export default SvelteComponent;

@ -0,0 +1,5 @@
export default {
options: {
generate: 'ssr'
}
};

@ -0,0 +1,23 @@
var SvelteComponent = {};
SvelteComponent.data = function() {
return {};
};
SvelteComponent.render = function(state, options) {
state = state || {};
return ``.trim();
};
SvelteComponent.renderCss = function() {
var components = [];
return {
css: components.map(x => x.css).join('\n'),
map: null,
components
};
};
module.exports = SvelteComponent;

@ -0,0 +1,37 @@
"use strict";
var SvelteComponent = {};;
SvelteComponent.data = function() {
return {};
};
SvelteComponent.render = function(state, options) {
state = state || {};
return ``.trim();
};
SvelteComponent.renderCss = function() {
var components = [];
return {
css: components.map(x => x.css).join('\n'),
map: null,
components
};
};
var escaped = {
'"': '&quot;',
"'": '&#39;',
'&': '&amp;',
'<': '&lt;',
'>': '&gt;'
};
function __escape(html) {
return String(html).replace(/["'&<>]/g, match => escaped[match]);
}
module.exports = SvelteComponent;

@ -0,0 +1,23 @@
<script>
export default {
oncreate() {
console.log('oncreate');
},
ondestroy() {
console.log('ondestroy');
},
methods: {
foo() {
console.log('foo');
}
},
events: {
swipe(node, callback) {
// TODO implement
}
}
};
</script>

@ -0,0 +1,3 @@
export default {
html: `<p>ab</p>`
};

@ -0,0 +1,26 @@
<p>{{value}}</p>
<script>
function throwError() {
throw new Error('nope');
}
const createElement = throwError;
const createElement$ = throwError;
export default {
data() {
return {
value: template() + template$()
};
}
};
function template() {
return 'a';
}
function template$() {
return 'b';
}
</script>

@ -1,3 +1,7 @@
export default { export default {
html: `@@x` html: `
@@x
%1
%%2
`
}; };

@ -0,0 +1,5 @@
export default {
test(assert, component) {
assert.ok(component.constructor.FOO);
}
};

@ -0,0 +1,7 @@
<script>
export default {
setup(Component) {
Component.FOO = true;
}
};
</script>
Loading…
Cancel
Save