move properties out of the template IIFE (#756)

pull/864/head
Rich Harris 8 years ago
parent fcf2b03ba6
commit 2865a98e57

@ -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';
@ -47,6 +49,7 @@ export default class Generator {
computations: Computation[]; computations: Computation[];
templateProperties: Record<string, Node>; templateProperties: Record<string, Node>;
slots: Set<string>; slots: Set<string>;
javascript: string;
code: MagicString; code: MagicString;
@ -198,7 +201,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.alias(name);
if (alias !== name) code.overwrite(object.start, object.end, `${self.alias(name)}`);
} 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?
@ -406,6 +413,7 @@ export default class Generator {
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; let hasJs = !!js;
@ -441,7 +449,6 @@ 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]) {
@ -451,6 +458,78 @@ export default class Generator {
} }
}); });
const addArrowFunctionExpression = (key: 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 @${key}(${paramString}) [${body.start}-${body.end}]
`);
} else {
componentDefinition.addBlock(deindent`
function @${key}(${paramString}) {
return [${body.start}-${body.end}];
}
`);
}
};
const addFunctionExpression = (key: string, node: Node) => {
let c = node.start;
while (this.source[c] !== '(') c += 1;
componentDefinition.addBlock(deindent`
function @${key}[${c}-${node.end}];
`);
};
const addValue = (key: string, node: Node) => {
const alias = this.alias(key);
if (node.type !== 'Identifier' || node.name !== alias) {
componentDefinition.addBlock(deindent`
var ${alias} = [${node.start}-${node.end}];
`);
}
};
const addDeclaration = (key: string, node: Node) => {
// TODO disambiguate between different categories, and ensure
// no conflicts with existing aliases
if (node.type === 'ArrowFunctionExpression') {
addArrowFunctionExpression(key, node);
} else if (node.type === 'FunctionExpression') {
addFunctionExpression(key, node);
} else {
addValue(key, node);
}
};
if (templateProperties.components) {
templateProperties.components.value.properties.forEach((property: Node) => {
// TODO replace all the guff below with this:
// addValue(property.key.name, property.value);
const key = property.key.name;
const value = source.slice(
property.value.start,
property.value.end
);
if (key !== value) {
const alias = this.alias(key);
componentDefinition.addLine(
`var ${alias} = [✂${property.value.start}-${property.value.end}✂];`
);
this.importedComponents.set(key, alias);
} else {
this.importedComponents.set(key, key);
}
});
}
if (templateProperties.computed) { if (templateProperties.computed) {
const dependencies = new Map(); const dependencies = new Map();
@ -467,7 +546,7 @@ export default class Generator {
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;
@ -477,78 +556,68 @@ export default class Generator {
deps.forEach(visit); deps.forEach(visit);
computations.push({ key, deps }); computations.push({ key, deps });
}
const prop = templateProperties.computed.value.properties.find((prop: Node) => prop.key.name === key);
addDeclaration(key, prop.value);
};
templateProperties.computed.value.properties.forEach((prop: Node) => templateProperties.computed.value.properties.forEach((prop: Node) =>
visit(prop.key.name) visit(prop.key.name)
); );
} }
if (templateProperties.namespace) { if (templateProperties.data) {
const ns = templateProperties.namespace.value.value; addDeclaration('data', templateProperties.data.value);
namespace = namespaces[ns] || ns; }
removeObjectKey(this.code, defaultExport.declaration, 'namespace'); if (templateProperties.events) {
templateProperties.events.value.properties.forEach((property: Node) => {
addDeclaration(property.key.name, property.value);
});
} }
if (templateProperties.components) { if (templateProperties.helpers) {
let hasNonImportedComponent = false; templateProperties.helpers.value.properties.forEach((property: Node) => {
templateProperties.components.value.properties.forEach( addDeclaration(property.key.name, property.value);
(property: Node) => { });
const key = property.key.name;
const value = source.slice(
property.value.start,
property.value.end
);
if (this.userVars.has(value)) {
this.importedComponents.set(key, value);
} else {
hasNonImportedComponent = true;
} }
if (templateProperties.methods) {
addDeclaration('methods', templateProperties.methods.value);
} }
);
if (hasNonImportedComponent) { if (templateProperties.namespace) {
// remove the specific components that were imported, as we'll refer to them directly const ns = templateProperties.namespace.value.value;
Array.from(this.importedComponents.keys()).forEach(key => { namespace = namespaces[ns] || ns;
removeObjectKey(
this.code,
templateProperties.components.value,
key
);
});
} else {
// remove the entire components portion of the export
removeObjectKey(this.code, defaultExport.declaration, 'components');
} }
if (templateProperties.onrender) templateProperties.oncreate = templateProperties.onrender; // remove after v2
if (templateProperties.oncreate) {
addDeclaration('oncreate', templateProperties.oncreate.value);
} }
// Remove these after version 2 if (templateProperties.onteardown) templateProperties.ondestroy = templateProperties.onteardown; // remove after v2
if (templateProperties.onrender) { if (templateProperties.ondestroy) {
const { key } = templateProperties.onrender; addDeclaration('ondestroy', templateProperties.ondestroy.value);
this.code.overwrite(key.start, key.end, 'oncreate', {
storeName: true,
contentOnly: false,
});
templateProperties.oncreate = templateProperties.onrender;
} }
if (templateProperties.onteardown) { if (templateProperties.props) {
const { key } = templateProperties.onteardown; this.props = templateProperties.props.value.elements.map((element: Node) => element.value);
this.code.overwrite(key.start, key.end, 'ondestroy', { }
storeName: true,
contentOnly: false, if (templateProperties.setup) {
}); addDeclaration('setup', templateProperties.setup.value);
templateProperties.ondestroy = templateProperties.onteardown;
} }
if (templateProperties.tag) { if (templateProperties.tag) {
this.tag = templateProperties.tag.value.value; this.tag = templateProperties.tag.value.value;
removeObjectKey(this.code, defaultExport.declaration, 'tag');
} }
if (templateProperties.props) { if (templateProperties.transitions) {
this.props = templateProperties.props.value.elements.map((element: Node) => element.value); templateProperties.transitions.value.properties.forEach((property: Node) => {
removeObjectKey(this.code, defaultExport.declaration, 'props'); addDeclaration(property.key.name, property.value);
});
}
} }
// now that we've analysed the default export, we can determine whether or not we need to keep it // now that we've analysed the default export, we can determine whether or not we need to keep it
@ -572,6 +641,10 @@ export default class Generator {
this.code.remove(js.content.start, js.content.end); this.code.remove(js.content.start, js.content.end);
hasJs = false; hasJs = false;
} }
this.javascript = hasDefaultExport ?
`[✂${js.content.start}-${defaultExport.start}✂]${componentDefinition}[✂${defaultExport.end}-${js.content.end}✂]` :
`[✂${js.content.start}-${js.content.end}✂]`;
} }
this.computations = computations; this.computations = computations;

@ -127,7 +127,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} = @${key}(${deps
.map(dep => `state.${dep}`) .map(dep => `state.${dep}`)
.join(', ')})))) changed.${key} = true;`; .join(', ')})))) changed.${key} = true;`;
@ -135,8 +135,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 +173,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 +192,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 +204,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 +217,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);
} }
`} `}
`} `}

@ -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} = @${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 = `@${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 = `@${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 = `@${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

@ -33,30 +33,32 @@ export class SsrGenerator extends Generator {
this.stylesheet.warnOnUnusedSelectors(options.onwarn); this.stylesheet.warnOnUnusedSelectors(options.onwarn);
if (templateProperties.oncreate) // TODO how to exclude non-SSR-able stuff?
removeNode(
this.code, // if (templateProperties.oncreate)
defaultExport.declaration, // removeNode(
templateProperties.oncreate // this.code,
); // defaultExport.declaration,
if (templateProperties.ondestroy) // templateProperties.oncreate
removeNode( // );
this.code, // if (templateProperties.ondestroy)
defaultExport.declaration, // removeNode(
templateProperties.ondestroy // this.code,
); // defaultExport.declaration,
if (templateProperties.methods) // templateProperties.ondestroy
removeNode( // );
this.code, // if (templateProperties.methods)
defaultExport.declaration, // removeNode(
templateProperties.methods // this.code,
); // defaultExport.declaration,
if (templateProperties.events) // templateProperties.methods
removeNode( // );
this.code, // if (templateProperties.events)
defaultExport.declaration, // removeNode(
templateProperties.events // this.code,
); // defaultExport.declaration,
// templateProperties.events
// );
} }
append(code: string) { append(code: string) {
@ -99,24 +101,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} = @${key}(${deps.map(dep => `state.${dep}`).join(', ')});`
)} )}
${generator.bindings.length && ${generator.bindings.length &&

@ -101,6 +101,8 @@ describe('css', () => {
if (expected.html !== null) { if (expected.html !== null) {
const window = env(); const window = env();
// dom
try {
const Component = eval( const Component = eval(
`(function () { ${dom.code}; return SvelteComponent; }())` `(function () { ${dom.code}; return SvelteComponent; }())`
); );
@ -111,13 +113,17 @@ describe('css', () => {
fs.writeFileSync(`test/css/samples/${dir}/_actual.html`, html); fs.writeFileSync(`test/css/samples/${dir}/_actual.html`, html);
// dom
assert.equal( assert.equal(
normalizeHtml(window, html.replace(/svelte-\d+/g, 'svelte-xyz')), normalizeHtml(window, html.replace(/svelte-\d+/g, 'svelte-xyz')),
normalizeHtml(window, expected.html) normalizeHtml(window, expected.html)
); );
} catch (err) {
console.log(dom.code);
throw err;
}
// ssr // ssr
try {
const component = eval( const component = eval(
`(function () { ${ssr.code}; return SvelteComponent; }())` `(function () { ${ssr.code}; return SvelteComponent; }())`
); );
@ -129,6 +135,10 @@ describe('css', () => {
), ),
normalizeHtml(window, expected.html) normalizeHtml(window, expected.html)
); );
} catch (err) {
console.log(ssr.code);
throw err;
}
} }
}); });
}); });

Loading…
Cancel
Save