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 MagicString, { Bundle } from 'magic-string';
import isReference from 'is-reference'; import isReference from 'is-reference';
import { walk, childKeys } from 'estree-walker'; import { walk, childKeys } from 'estree-walker';
@ -16,6 +17,7 @@ import getName from '../utils/getName';
import Stylesheet from '../css/Stylesheet'; import Stylesheet from '../css/Stylesheet';
import { test } from '../config'; import { test } from '../config';
import Fragment from './nodes/Fragment'; import Fragment from './nodes/Fragment';
import shared from './dom/shared'; // TODO move this file
import { Node, GenerateOptions, ShorthandImport, Ast, CompileOptions, CustomElementOptions } from '../interfaces'; import { Node, GenerateOptions, ShorthandImport, Ast, CompileOptions, CustomElementOptions } from '../interfaces';
interface Computation { interface Computation {
@ -210,10 +212,84 @@ export default class Generator {
return this.aliases.get(name); 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 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 parts = module.split('✂]');
const finalChunk = parts.pop(); const finalChunk = parts.pop();

@ -361,7 +361,7 @@ export default function dom(
${immutable && `${name}.prototype._differs = @_differsImmutable;`} ${immutable && `${name}.prototype._differs = @_differsImmutable;`}
`); `);
const usedHelpers = new Set(); const helpers = new Set();
let result = builder let result = builder
.toString() .toString()
@ -369,7 +369,7 @@ export default function dom(
if (sigil === '@') { if (sigil === '@') {
if (name in shared) { if (name in shared) {
if (options.dev && `${name}Dev` in shared) name = `${name}Dev`; if (options.dev && `${name}Dev` in shared) name = `${name}Dev`;
usedHelpers.add(name); helpers.add(name);
} }
return generator.alias(name); return generator.alias(name);
@ -382,70 +382,6 @@ export default function dom(
return sigil.slice(1) + name; 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 && ( const filename = options.filename && (
typeof process !== 'undefined' ? options.filename.replace(process.cwd(), '').replace(/^[\/\\]/, '') : 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 { } else {
this.attributes.forEach((attribute: Node) => { this.attributes.forEach((attribute: Node) => {
if (attribute.type !== 'Attribute') return; if (attribute.type !== 'Attribute') return;

@ -93,6 +93,8 @@ export default function ssr(
initialState.push('ctx'); initialState.push('ctx');
const helpers = new Set();
// TODO concatenate CSS maps // TODO concatenate CSS maps
const result = deindent` const result = deindent`
${generator.javascript} ${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) => { `.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); if (sigil === '%') return generator.templateVars.get(name);
return sigil.slice(1) + name; return sigil.slice(1) + name;
}); });
return generator.generate(result, options, { name, format }); return generator.generate(result, options, { name, format, helpers });
} }
function trim(nodes) { function trim(nodes) {

@ -90,7 +90,7 @@ function es(
shorthandImports: ShorthandImport[], shorthandImports: ShorthandImport[],
source: string 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)};` `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 }[], helpers: { name: string, alias: string }[],
dependencies: Dependency[] dependencies: Dependency[]
) { ) {
const helperDeclarations = helpers && ( const helperDeclarations = helpers.map(h => `${h.alias === h.name ? h.name : `${h.name}: ${h.alias}`}`).join(', ');
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` `var { ${helperDeclarations} } = require(${JSON.stringify(sharedPath)});\n`
); );

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