diff --git a/src/cli/index.ts b/src/cli/index.ts index 82d19c18f0..1ceb8e9bc1 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -7,7 +7,7 @@ prog .command('compile ') .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('-n, --name', 'Name for IIFE/UMD export (inferred from filename by default)') .option('-m, --sourcemap', 'Generate sourcemap (`-m inline` for inline map)') diff --git a/src/compile/Component.ts b/src/compile/Component.ts index b3aab8446a..af525f6a88 100644 --- a/src/compile/Component.ts +++ b/src/compile/Component.ts @@ -147,7 +147,7 @@ export default class Component { this.stylesheet.warnOnUnusedSelectors(options.onwarn); - if (!this.ast.js) { + if (!this.ast.js.find(script => get_context(script) === 'default')) { const props = [...this.expectedProperties]; this.declarations.push(...props); addToSet(this.writable_declarations, this.expectedProperties); @@ -378,9 +378,8 @@ export default class Component { extract_imports_and_exports(content, imports, exports) { 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') { this.error(node, { code: `default-export`, @@ -415,7 +414,7 @@ export default class Component { // imports need to be hoisted out of the IIFE // TODO hoist other stuff where possible 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); node.specifiers.forEach((specifier: Node) => { diff --git a/src/compile/render-dom/index.ts b/src/compile/render-dom/index.ts index a066f0c97a..afc6985c47 100644 --- a/src/compile/render-dom/index.ts +++ b/src/compile/render-dom/index.ts @@ -10,7 +10,7 @@ export default function dom( component: Component, options: CompileOptions ) { - const format = options.format || 'es'; + const format = options.format || 'esm'; const { name } = component; diff --git a/src/compile/wrapModule.ts b/src/compile/wrapModule.ts index 55fbc8e64b..b28cb1b1fc 100644 --- a/src/compile/wrapModule.ts +++ b/src/compile/wrapModule.ts @@ -8,7 +8,12 @@ interface Dependency { 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( code: string, @@ -19,60 +24,20 @@ export default function wrapModule( sharedPath: string, helpers: { name: string, alias: string }[], imports: Node[], - module_exports: string[], + module_exports: Export[], source: string ): string { - if (format === 'es') { - return es(code, name, options, banner, sharedPath, helpers, imports, module_exports, source); + if (format === 'esm') { + return esm(code, name, options, banner, sharedPath, helpers, imports, module_exports, source); } - const dependencies = imports.map((declaration, i) => { - const defaultImport = declaration.specifiers.find( - (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); + if (format === 'cjs') return cjs(code, name, banner, sharedPath, helpers, imports, module_exports); + if (format === 'eval') return expr(code, name, options, banner, imports); throw new Error(`options.format is invalid (must be ${list(Object.keys(wrappers))})`); } -function es( +function esm( code: string, name: string, options: CompileOptions, @@ -80,7 +45,7 @@ function es( sharedPath: string, helpers: { name: string, alias: string }[], imports: Node[], - module_exports: string[], + module_exports: Export[], source: string ) { const importHelpers = helpers.length > 0 && ( @@ -103,47 +68,45 @@ function es( ${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( code: string, name: string, - options: CompileOptions, banner: string, sharedPath: 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 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') + const requires = imports.map(node => { + let lhs; + + if (node.specifiers[0].type === 'ImportNamespaceSpecifier') { + lhs = node.specifiers[0].local.name; + } else { + const properties = node.specifiers.map(s => { + if (s.type === 'ImportDefaultSpecifier') { + return `default: ${s.local.name}`; + } + + return s.local.name === s.imported.name + ? s.local.name + : `${s.imported.name}: ${s.local.name}`; + }); + + lhs = `{ ${properties.join(', ')} }`; + } + + return `const ${lhs} = require("${node.source.value}");` + }); + + const exports = [`exports.default = ${name};`].concat( + module_exports.map(x => `exports.${x.as} = ${x.name};`) ); return deindent` @@ -151,84 +114,57 @@ function cjs( "use strict"; ${helperBlock} - ${requireBlock} - ${getCompatibilityStatements(dependencies)} + ${requires} ${code} - module.exports = ${name};` + ${exports}` } -function iife( - 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); - - return deindent` - ${banner} - var ${options.name} = (function(${paramString(dependencies)}) { "use strict"; - ${getCompatibilityStatements(dependencies)} - - ${code} - return ${name}; - }(${globals.join(', ')}));`; -} - -function umd( +function expr( code: string, name: string, options: CompileOptions, banner: string, - dependencies: Dependency[] + imports: Node[] ) { - if (!options.name) { - throw new Error(`Missing required 'name' option for UMD export`); - } - - const amdId = options.amd && options.amd.id ? `'${options.amd.id}', ` : ''; - - const amdDeps = dependencies.length - ? `[${dependencies.map(d => `"${removeExtension(d.source)}"`).join(', ')}], ` - : ''; + const dependencies = imports.map((declaration, i) => { + const defaultImport = declaration.specifiers.find( + (x: Node) => + x.type === 'ImportDefaultSpecifier' || + (x.type === 'ImportSpecifier' && x.imported.name === 'default') + ); - const cjsDeps = dependencies - .map(d => `require("${d.source}")`) - .join(', '); + const namespaceImport = declaration.specifiers.find( + (x: Node) => x.type === 'ImportNamespaceSpecifier' + ); - const globals = getGlobals(dependencies, options); + const namedImports = declaration.specifiers.filter( + (x: Node) => + x.type === 'ImportSpecifier' && x.imported.name !== 'default' + ); - return deindent` - ${banner} - (function(global, factory) { - 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"; + const name = defaultImport || namespaceImport + ? (defaultImport || namespaceImport).local.name + : `__import${i}`; - ${getCompatibilityStatements(dependencies)} + const statements: string[] = []; - ${code} + namedImports.forEach((specifier: Node) => { + statements.push( + `var ${specifier.local.name} = ${name}.${specifier.imported.name};` + ); + }); - return ${name}; + if (defaultImport) { + statements.push( + `${name} = (${name} && ${name}.__esModule) ? ${name}["default"] : ${name};` + ); + } - })));`; -} + return { name, statements, source: declaration.source.value }; + }); -function expr( - code: string, - name: string, - options: CompileOptions, - banner: string, - dependencies: Dependency[] -) { const globals = getGlobals(dependencies, options); return deindent` @@ -247,11 +183,6 @@ function paramString(dependencies: Dependency[]) { 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[]) { if (!dependencies.length) return null; diff --git a/src/interfaces.ts b/src/interfaces.ts index ff12dc66e7..ee709e0888 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -35,7 +35,7 @@ export interface Warning { toString: () => string; } -export type ModuleFormat = 'es' | 'amd' | 'cjs' | 'iife' | 'umd' | 'eval'; +export type ModuleFormat = 'esm' | 'cjs' | 'eval'; export interface CompileOptions { format?: ModuleFormat; diff --git a/src/utils/removeNode.ts b/src/utils/removeNode.ts index 9d5c4fe2af..4405803dcf 100644 --- a/src/utils/removeNode.ts +++ b/src/utils/removeNode.ts @@ -32,6 +32,5 @@ export function removeNode( } code.remove(a, b); - body.splice(i, 1); return; } \ No newline at end of file diff --git a/test/runtime/index.js b/test/runtime/index.js index 3d01331a21..de1bc31c0f 100644 --- a/test/runtime/index.js +++ b/test/runtime/index.js @@ -83,6 +83,7 @@ describe.only("runtime", () => { delete require.cache[file]; }); + let mod; let SvelteComponent; let unintendedError = null; @@ -115,9 +116,10 @@ describe.only("runtime", () => { }; try { - SvelteComponent = require(`./samples/${dir}/main.html`); + mod = require(`./samples/${dir}/main.html`); + SvelteComponent = mod.default; } 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; } @@ -165,6 +167,7 @@ describe.only("runtime", () => { return Promise.resolve(config.test({ assert, component, + mod, target, window, raf @@ -187,7 +190,6 @@ describe.only("runtime", () => { failed.add(dir); showOutput(cwd, { internal, - format: 'cjs', hydratable: hydrate, dev: compileOptions.dev }, svelte.compile); // eslint-disable-line no-console @@ -198,7 +200,6 @@ describe.only("runtime", () => { if (config.show) { showOutput(cwd, { internal, - format: 'cjs', hydratable: hydrate }, svelte.compile); } @@ -216,7 +217,7 @@ describe.only("runtime", () => { async function create_component(src = '
') { const { js } = svelte$.compile(src, { - format: "es", // TODO change this to esm + format: "esm", name: "SvelteComponent", dev: true }); diff --git a/test/runtime/samples/preload/_config.js b/test/runtime/samples/preload/_config.js index 105c7b9fa9..1b35ebbe74 100644 --- a/test/runtime/samples/preload/_config.js +++ b/test/runtime/samples/preload/_config.js @@ -1,6 +1,5 @@ export default { - test({ assert, component }) { - const Component = component.constructor; - assert.deepEqual(Component.preload({ foo: 1 }), { bar: 2 }); + test({ assert, mod }) { + assert.deepEqual(mod.preload({ foo: 1 }), { bar: 2 }); } }; \ No newline at end of file