use same shared helpers mechanism for SSR compiler

pull/1367/head
Rich Harris 7 years ago
parent b2b3bda85a
commit ebf1fe3233

@ -1,3 +1,4 @@
import { parseExpressionAt } from 'acorn';
import MagicString, { Bundle } from 'magic-string';
import isReference from 'is-reference';
import { walk, childKeys } from 'estree-walker';
@ -16,6 +17,7 @@ import getName from '../utils/getName';
import Stylesheet from '../css/Stylesheet';
import { test } from '../config';
import Fragment from './nodes/Fragment';
import shared from './dom/shared'; // TODO move this file
import { Node, GenerateOptions, ShorthandImport, Ast, CompileOptions, CustomElementOptions } from '../interfaces';
interface Computation {
@ -210,10 +212,84 @@ export default class Generator {
return this.aliases.get(name);
}
generate(result: string, options: CompileOptions, { banner = '', sharedPath, helpers, name, format }: GenerateOptions ) {
generate(result: string, options: CompileOptions, { banner = '', helpers, name, format }: GenerateOptions ) {
const pattern = /\[✂(\d+)-(\d+)$/;
const module = wrapModule(result, format, name, options, banner, sharedPath, helpers, this.imports, this.shorthandImports, this.source);
let importedHelpers;
if (options.shared) {
if (format !== 'es' && format !== 'cjs') {
throw new Error(`Components with shared helpers must be compiled with \`format: 'es'\` or \`format: 'cjs'\``);
}
importedHelpers = Array.from(helpers).sort().map(name => {
const alias = this.alias(name);
return { name, alias };
});
} else {
let inlineHelpers = '';
const compiler = this;
importedHelpers = [];
helpers.forEach(key => {
const str = shared[key];
const code = new MagicString(str);
const expression = parseExpressionAt(str, 0);
let { scope } = annotateWithScopes(expression);
walk(expression, {
enter(node: Node, parent: Node) {
if (node._scope) scope = node._scope;
if (
node.type === 'Identifier' &&
isReference(node, parent) &&
!scope.has(node.name)
) {
if (node.name in shared) {
// this helper function depends on another one
const dependency = node.name;
helpers.add(dependency);
const alias = compiler.alias(dependency);
if (alias !== node.name) {
code.overwrite(node.start, node.end, alias);
}
}
}
},
leave(node: Node) {
if (node._scope) scope = scope.parent;
},
});
if (key === 'transitionManager') {
// special case
const global = `_svelteTransitionManager`;
inlineHelpers += `\n\nvar ${this.alias('transitionManager')} = window.${global} || (window.${global} = ${code});\n\n`;
} else {
const alias = this.alias(expression.id.name);
if (alias !== expression.id.name) {
code.overwrite(expression.id.start, expression.id.end, alias);
}
inlineHelpers += `\n\n${code}`;
}
});
result += inlineHelpers;
}
const sharedPath = options.shared === true
? 'svelte/shared.js'
: options.shared || '';
const module = wrapModule(result, format, name, options, banner, sharedPath, importedHelpers, this.imports, this.shorthandImports, this.source);
const parts = module.split('✂]');
const finalChunk = parts.pop();

@ -361,7 +361,7 @@ export default function dom(
${immutable && `${name}.prototype._differs = @_differsImmutable;`}
`);
const usedHelpers = new Set();
const helpers = new Set();
let result = builder
.toString()
@ -369,7 +369,7 @@ export default function dom(
if (sigil === '@') {
if (name in shared) {
if (options.dev && `${name}Dev` in shared) name = `${name}Dev`;
usedHelpers.add(name);
helpers.add(name);
}
return generator.alias(name);
@ -382,70 +382,6 @@ export default function dom(
return sigil.slice(1) + name;
});
let helpers;
if (sharedPath) {
if (format !== 'es' && format !== 'cjs') {
throw new Error(`Components with shared helpers must be compiled with \`format: 'es'\` or \`format: 'cjs'\``);
}
const used = Array.from(usedHelpers).sort();
helpers = used.map(name => {
const alias = generator.alias(name);
return { name, alias };
});
} else {
let inlineHelpers = '';
usedHelpers.forEach(key => {
const str = shared[key];
const code = new MagicString(str);
const expression = parseExpressionAt(str, 0);
let { scope } = annotateWithScopes(expression);
walk(expression, {
enter(node: Node, parent: Node) {
if (node._scope) scope = node._scope;
if (
node.type === 'Identifier' &&
isReference(node, parent) &&
!scope.has(node.name)
) {
if (node.name in shared) {
// this helper function depends on another one
const dependency = node.name;
usedHelpers.add(dependency);
const alias = generator.alias(dependency);
if (alias !== node.name)
code.overwrite(node.start, node.end, alias);
}
}
},
leave(node: Node) {
if (node._scope) scope = scope.parent;
},
});
if (key === 'transitionManager') {
// special case
const global = `_svelteTransitionManager`;
inlineHelpers += `\n\nvar ${generator.alias('transitionManager')} = window.${global} || (window.${global} = ${code});\n\n`;
} else {
const alias = generator.alias(expression.id.name);
if (alias !== expression.id.name)
code.overwrite(expression.id.start, expression.id.end, alias);
inlineHelpers += `\n\n${code}`;
}
});
result += inlineHelpers;
}
const filename = options.filename && (
typeof process !== 'undefined' ? options.filename.replace(process.cwd(), '').replace(/^[\/\\]/, '') : options.filename
);

@ -868,7 +868,7 @@ export default class Element extends Node {
}
});
openingTag += "${__spread([" + args.join(', ') + "])}";
openingTag += "${@spread([" + args.join(', ') + "])}";
} else {
this.attributes.forEach((attribute: Node) => {
if (attribute.type !== 'Attribute') return;

@ -93,6 +93,8 @@ export default function ssr(
initialState.push('ctx');
const helpers = new Set();
// TODO concatenate CSS maps
const result = deindent`
${generator.javascript}
@ -206,31 +208,17 @@ export default function ssr(
};
`
}
${
/__spread/.test(generator.renderCode) && deindent`
function __spread(args) {
const attributes = Object.assign({}, ...args);
let str = '';
Object.keys(attributes).forEach(name => {
const value = attributes[name];
if (value === undefined) return;
if (value === true) str += " " + name;
str += " " + name + "=" + JSON.stringify(value);
});
return str;
}
`
}
`.replace(/(@+|#+|%+)(\w*(?:-\w*)?)/g, (match: string, sigil: string, name: string) => {
if (sigil === '@') return generator.alias(name);
if (sigil === '@') {
helpers.add(name);
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, helpers });
}
function trim(nodes) {

@ -90,7 +90,7 @@ function es(
shorthandImports: ShorthandImport[],
source: string
) {
const importHelpers = helpers && (
const importHelpers = helpers.length > 0 && (
`import { ${helpers.map(h => h.name === h.alias ? h.name : `${h.name} as ${h.alias}`).join(', ')} } from ${JSON.stringify(sharedPath)};`
);
@ -145,11 +145,9 @@ function cjs(
helpers: { name: string, alias: string }[],
dependencies: Dependency[]
) {
const helperDeclarations = helpers && (
helpers.map(h => `${h.alias === h.name ? h.name : `${h.name}: ${h.alias}`}`).join(', ')
);
const helperDeclarations = helpers.map(h => `${h.alias === h.name ? h.name : `${h.name}: ${h.alias}`}`).join(', ');
const helperBlock = helpers && (
const helperBlock = helpers.length > 0 && (
`var { ${helperDeclarations} } = require(${JSON.stringify(sharedPath)});\n`
);

@ -71,7 +71,7 @@ export interface GenerateOptions {
format: ModuleFormat;
banner?: string;
sharedPath?: string;
helpers?: { name: string, alias: string }[];
helpers: Set<string>;
}
export interface ShorthandImport {

@ -0,0 +1,13 @@
export function spread(args) {
const attributes = Object.assign({}, ...args);
let str = '';
Object.keys(attributes).forEach(name => {
const value = attributes[name];
if (value === undefined) return;
if (value === true) str += " " + name;
str += " " + name + "=" + JSON.stringify(value);
});
return str;
}
Loading…
Cancel
Save