include module exports e.g. preload. remove AMD, UMD and IIFE output

pull/1864/head
Rich Harris 7 years ago
parent 0ddfbed783
commit 210173e429

@ -7,7 +7,7 @@ prog
.command('compile <input>') .command('compile <input>')
.option('-o, --output', 'Output (if absent, prints to stdout)') .option('-o, --output', 'Output (if absent, prints to stdout)')
.option('-f, --format', 'Type of output (amd, cjs, es, iife, umd)') .option('-f, --format', 'Type of output (cjs or esm)', 'esm')
.option('-g, --globals', 'Comma-separate list of `module ID:Global` pairs') .option('-g, --globals', 'Comma-separate list of `module ID:Global` pairs')
.option('-n, --name', 'Name for IIFE/UMD export (inferred from filename by default)') .option('-n, --name', 'Name for IIFE/UMD export (inferred from filename by default)')
.option('-m, --sourcemap', 'Generate sourcemap (`-m inline` for inline map)') .option('-m, --sourcemap', 'Generate sourcemap (`-m inline` for inline map)')

@ -147,7 +147,7 @@ export default class Component {
this.stylesheet.warnOnUnusedSelectors(options.onwarn); this.stylesheet.warnOnUnusedSelectors(options.onwarn);
if (!this.ast.js) { if (!this.ast.js.find(script => get_context(script) === 'default')) {
const props = [...this.expectedProperties]; const props = [...this.expectedProperties];
this.declarations.push(...props); this.declarations.push(...props);
addToSet(this.writable_declarations, this.expectedProperties); addToSet(this.writable_declarations, this.expectedProperties);
@ -378,9 +378,8 @@ export default class Component {
extract_imports_and_exports(content, imports, exports) { extract_imports_and_exports(content, imports, exports) {
const { code } = this; const { code } = this;
const body = content.body.slice(); // TODO do we need to mutate the original?
body.forEach(node => { content.body.forEach(node => {
if (node.type === 'ExportDefaultDeclaration') { if (node.type === 'ExportDefaultDeclaration') {
this.error(node, { this.error(node, {
code: `default-export`, code: `default-export`,
@ -415,7 +414,7 @@ export default class Component {
// imports need to be hoisted out of the IIFE // imports need to be hoisted out of the IIFE
// TODO hoist other stuff where possible // TODO hoist other stuff where possible
else if (node.type === 'ImportDeclaration') { else if (node.type === 'ImportDeclaration') {
removeNode(code, content.start, content.end, body, node); removeNode(code, content.start, content.end, content.body, node);
imports.push(node); imports.push(node);
node.specifiers.forEach((specifier: Node) => { node.specifiers.forEach((specifier: Node) => {

@ -10,7 +10,7 @@ export default function dom(
component: Component, component: Component,
options: CompileOptions options: CompileOptions
) { ) {
const format = options.format || 'es'; const format = options.format || 'esm';
const { name } = component; const { name } = component;

@ -8,7 +8,12 @@ interface Dependency {
source: string; source: string;
} }
const wrappers = { es, amd, cjs, iife, umd, eval: expr }; const wrappers = { esm, cjs, eval: expr };
type Export = {
name: string;
as: string;
};
export default function wrapModule( export default function wrapModule(
code: string, code: string,
@ -19,60 +24,20 @@ export default function wrapModule(
sharedPath: string, sharedPath: string,
helpers: { name: string, alias: string }[], helpers: { name: string, alias: string }[],
imports: Node[], imports: Node[],
module_exports: string[], module_exports: Export[],
source: string source: string
): string { ): string {
if (format === 'es') { if (format === 'esm') {
return es(code, name, options, banner, sharedPath, helpers, imports, module_exports, source); return esm(code, name, options, banner, sharedPath, helpers, imports, module_exports, source);
} }
const dependencies = imports.map((declaration, i) => { if (format === 'cjs') return cjs(code, name, banner, sharedPath, helpers, imports, module_exports);
const defaultImport = declaration.specifiers.find( if (format === 'eval') return expr(code, name, options, banner, imports);
(x: Node) =>
x.type === 'ImportDefaultSpecifier' ||
(x.type === 'ImportSpecifier' && x.imported.name === 'default')
);
const namespaceImport = declaration.specifiers.find(
(x: Node) => x.type === 'ImportNamespaceSpecifier'
);
const namedImports = declaration.specifiers.filter(
(x: Node) =>
x.type === 'ImportSpecifier' && x.imported.name !== 'default'
);
const name = defaultImport || namespaceImport
? (defaultImport || namespaceImport).local.name
: `__import${i}`;
const statements: string[] = [];
namedImports.forEach((specifier: Node) => {
statements.push(
`var ${specifier.local.name} = ${name}.${specifier.imported.name};`
);
});
if (defaultImport) {
statements.push(
`${name} = (${name} && ${name}.__esModule) ? ${name}["default"] : ${name};`
);
}
return { name, statements, source: declaration.source.value };
});
if (format === 'amd') return amd(code, name, options, banner, dependencies);
if (format === 'cjs') return cjs(code, name, options, banner, sharedPath, helpers, dependencies);
if (format === 'iife') return iife(code, name, options, banner, dependencies);
if (format === 'umd') return umd(code, name, options, banner, dependencies);
if (format === 'eval') return expr(code, name, options, banner, dependencies);
throw new Error(`options.format is invalid (must be ${list(Object.keys(wrappers))})`); throw new Error(`options.format is invalid (must be ${list(Object.keys(wrappers))})`);
} }
function es( function esm(
code: string, code: string,
name: string, name: string,
options: CompileOptions, options: CompileOptions,
@ -80,7 +45,7 @@ function es(
sharedPath: string, sharedPath: string,
helpers: { name: string, alias: string }[], helpers: { name: string, alias: string }[],
imports: Node[], imports: Node[],
module_exports: string[], module_exports: Export[],
source: string source: string
) { ) {
const importHelpers = helpers.length > 0 && ( const importHelpers = helpers.length > 0 && (
@ -103,132 +68,103 @@ function es(
${module_exports.length > 0 && `export { ${module_exports.join(', ')} };`}`; ${module_exports.length > 0 && `export { ${module_exports.join(', ')} };`}`;
} }
function amd(
code: string,
name: string,
options: CompileOptions,
banner: string,
dependencies: Dependency[]
) {
const sourceString = dependencies.length
? `[${dependencies.map(d => `"${removeExtension(d.source)}"`).join(', ')}], `
: '';
const id = options.amd && options.amd.id;
return deindent`
define(${id ? `"${id}", ` : ''}${sourceString}function(${paramString(dependencies)}) { "use strict";
${getCompatibilityStatements(dependencies)}
${code}
return ${name};
});`;
}
function cjs( function cjs(
code: string, code: string,
name: string, name: string,
options: CompileOptions,
banner: string, banner: string,
sharedPath: string, sharedPath: string,
helpers: { name: string, alias: string }[], helpers: { name: string, alias: string }[],
dependencies: Dependency[] imports: Node[],
module_exports: Export[]
) { ) {
const helperDeclarations = 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.length > 0 && ( const helperBlock = helpers.length > 0 && (
`var { ${helperDeclarations} } = require(${JSON.stringify(sharedPath)});\n` `const { ${helperDeclarations} } = require(${JSON.stringify(sharedPath)});\n`
);
const requireBlock = dependencies.length > 0 && (
dependencies
.map(d => `var ${d.name} = require("${d.source}");`)
.join('\n\n')
); );
return deindent` const requires = imports.map(node => {
${banner} let lhs;
"use strict";
${helperBlock} if (node.specifiers[0].type === 'ImportNamespaceSpecifier') {
${requireBlock} lhs = node.specifiers[0].local.name;
${getCompatibilityStatements(dependencies)} } else {
const properties = node.specifiers.map(s => {
if (s.type === 'ImportDefaultSpecifier') {
return `default: ${s.local.name}`;
}
${code} return s.local.name === s.imported.name
? s.local.name
: `${s.imported.name}: ${s.local.name}`;
});
module.exports = ${name};` lhs = `{ ${properties.join(', ')} }`;
} }
function iife( return `const ${lhs} = require("${node.source.value}");`
code: string, });
name: string,
options: CompileOptions,
banner: string,
dependencies: Dependency[]
) {
if (!options.name) {
throw new Error(`Missing required 'name' option for IIFE export`);
}
const globals = getGlobals(dependencies, options); const exports = [`exports.default = ${name};`].concat(
module_exports.map(x => `exports.${x.as} = ${x.name};`)
);
return deindent` return deindent`
${banner} ${banner}
var ${options.name} = (function(${paramString(dependencies)}) { "use strict"; "use strict";
${getCompatibilityStatements(dependencies)}
${helperBlock}
${requires}
${code} ${code}
return ${name};
}(${globals.join(', ')}));`; ${exports}`
} }
function umd( function expr(
code: string, code: string,
name: string, name: string,
options: CompileOptions, options: CompileOptions,
banner: string, banner: string,
dependencies: Dependency[] imports: Node[]
) { ) {
if (!options.name) { const dependencies = imports.map((declaration, i) => {
throw new Error(`Missing required 'name' option for UMD export`); const defaultImport = declaration.specifiers.find(
} (x: Node) =>
x.type === 'ImportDefaultSpecifier' ||
const amdId = options.amd && options.amd.id ? `'${options.amd.id}', ` : ''; (x.type === 'ImportSpecifier' && x.imported.name === 'default')
);
const amdDeps = dependencies.length
? `[${dependencies.map(d => `"${removeExtension(d.source)}"`).join(', ')}], `
: '';
const cjsDeps = dependencies
.map(d => `require("${d.source}")`)
.join(', ');
const globals = getGlobals(dependencies, options); const namespaceImport = declaration.specifiers.find(
(x: Node) => x.type === 'ImportNamespaceSpecifier'
);
return deindent` const namedImports = declaration.specifiers.filter(
${banner} (x: Node) =>
(function(global, factory) { x.type === 'ImportSpecifier' && x.imported.name !== 'default'
typeof exports === "object" && typeof module !== "undefined" ? module.exports = factory(${cjsDeps}) : );
typeof define === "function" && define.amd ? define(${amdId}${amdDeps}factory) :
(global.${options.name} = factory(${globals.join(', ')}));
}(this, (function (${paramString(dependencies)}) { "use strict";
${getCompatibilityStatements(dependencies)} const name = defaultImport || namespaceImport
? (defaultImport || namespaceImport).local.name
: `__import${i}`;
${code} const statements: string[] = [];
return ${name}; namedImports.forEach((specifier: Node) => {
statements.push(
`var ${specifier.local.name} = ${name}.${specifier.imported.name};`
);
});
})));`; if (defaultImport) {
statements.push(
`${name} = (${name} && ${name}.__esModule) ? ${name}["default"] : ${name};`
);
} }
function expr( return { name, statements, source: declaration.source.value };
code: string, });
name: string,
options: CompileOptions,
banner: string,
dependencies: Dependency[]
) {
const globals = getGlobals(dependencies, options); const globals = getGlobals(dependencies, options);
return deindent` return deindent`
@ -247,11 +183,6 @@ function paramString(dependencies: Dependency[]) {
return dependencies.map(dep => dep.name).join(', '); return dependencies.map(dep => dep.name).join(', ');
} }
function removeExtension(file: string) {
const index = file.lastIndexOf('.');
return ~index ? file.slice(0, index) : file;
}
function getCompatibilityStatements(dependencies: Dependency[]) { function getCompatibilityStatements(dependencies: Dependency[]) {
if (!dependencies.length) return null; if (!dependencies.length) return null;

@ -35,7 +35,7 @@ export interface Warning {
toString: () => string; toString: () => string;
} }
export type ModuleFormat = 'es' | 'amd' | 'cjs' | 'iife' | 'umd' | 'eval'; export type ModuleFormat = 'esm' | 'cjs' | 'eval';
export interface CompileOptions { export interface CompileOptions {
format?: ModuleFormat; format?: ModuleFormat;

@ -32,6 +32,5 @@ export function removeNode(
} }
code.remove(a, b); code.remove(a, b);
body.splice(i, 1);
return; return;
} }

@ -83,6 +83,7 @@ describe.only("runtime", () => {
delete require.cache[file]; delete require.cache[file];
}); });
let mod;
let SvelteComponent; let SvelteComponent;
let unintendedError = null; let unintendedError = null;
@ -115,9 +116,10 @@ describe.only("runtime", () => {
}; };
try { try {
SvelteComponent = require(`./samples/${dir}/main.html`); mod = require(`./samples/${dir}/main.html`);
SvelteComponent = mod.default;
} catch (err) { } catch (err) {
showOutput(cwd, { internal, format: 'cjs', hydratable: hydrate }, svelte.compile); // eslint-disable-line no-console showOutput(cwd, { internal, hydratable: hydrate }, svelte.compile); // eslint-disable-line no-console
throw err; throw err;
} }
@ -165,6 +167,7 @@ describe.only("runtime", () => {
return Promise.resolve(config.test({ return Promise.resolve(config.test({
assert, assert,
component, component,
mod,
target, target,
window, window,
raf raf
@ -187,7 +190,6 @@ describe.only("runtime", () => {
failed.add(dir); failed.add(dir);
showOutput(cwd, { showOutput(cwd, {
internal, internal,
format: 'cjs',
hydratable: hydrate, hydratable: hydrate,
dev: compileOptions.dev dev: compileOptions.dev
}, svelte.compile); // eslint-disable-line no-console }, svelte.compile); // eslint-disable-line no-console
@ -198,7 +200,6 @@ describe.only("runtime", () => {
if (config.show) { if (config.show) {
showOutput(cwd, { showOutput(cwd, {
internal, internal,
format: 'cjs',
hydratable: hydrate hydratable: hydrate
}, svelte.compile); }, svelte.compile);
} }
@ -216,7 +217,7 @@ describe.only("runtime", () => {
async function create_component(src = '<div></div>') { async function create_component(src = '<div></div>') {
const { js } = svelte$.compile(src, { const { js } = svelte$.compile(src, {
format: "es", // TODO change this to esm format: "esm",
name: "SvelteComponent", name: "SvelteComponent",
dev: true dev: true
}); });

@ -1,6 +1,5 @@
export default { export default {
test({ assert, component }) { test({ assert, mod }) {
const Component = component.constructor; assert.deepEqual(mod.preload({ foo: 1 }), { bar: 2 });
assert.deepEqual(Component.preload({ foo: 1 }), { bar: 2 });
} }
}; };
Loading…
Cancel
Save