diff --git a/src/generators/Generator.ts b/src/generators/Generator.ts index 91b73b6162..e8d2ce47b0 100644 --- a/src/generators/Generator.ts +++ b/src/generators/Generator.ts @@ -8,8 +8,7 @@ import globalWhitelist from '../utils/globalWhitelist'; import reservedNames from '../utils/reservedNames'; import namespaces from '../utils/namespaces'; import { removeNode, removeObjectKey } from '../utils/removeNode'; -import getIntro from './shared/utils/getIntro'; -import getOutro from './shared/utils/getOutro'; +import getModuleWrapper from './shared/utils/getModuleWrapper'; import annotateWithScopes from '../utils/annotateWithScopes'; import clone from '../utils/clone'; import DomBlock from './dom/Block'; @@ -304,51 +303,6 @@ export default class Generator { } generate(result: string, options: CompileOptions, { name, format }: GenerateOptions ) { - if (this.imports.length) { - const statements: string[] = []; - - this.imports.forEach((declaration, i) => { - if (format === 'es') { - statements.push( - this.source.slice(declaration.start, declaration.end) - ); - return; - } - - 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}`; - declaration.name = name; // hacky but makes life a bit easier later - - 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};` - ); - } - }); - - result = `${statements.join('\n')}\n\n${result}`; - } - const pattern = /\[✂(\d+)-(\d+)$/; const parts = result.split('✂]'); @@ -362,8 +316,9 @@ export default class Generator { }); } - const intro = getIntro(format, options, this.imports); - if (intro) addString(intro); + const { intro, outro } = getModuleWrapper(format, name, options, this.imports, this.source); + + addString(intro + '\n\n'); const { filename } = options; @@ -391,7 +346,7 @@ export default class Generator { }); addString(finalChunk); - addString('\n\n' + getOutro(format, name, options, this.imports)); + addString('\n\n' + outro); const { css, cssMap } = this.customElement ? { css: null, cssMap: null } : diff --git a/src/generators/shared/utils/getGlobals.ts b/src/generators/shared/utils/getGlobals.ts deleted file mode 100644 index eb0db21276..0000000000 --- a/src/generators/shared/utils/getGlobals.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { CompileOptions, Node } from '../../../interfaces'; - - -export default function getGlobals(imports: Node[], options: CompileOptions) { - const { globals, onerror, onwarn } = options; - const globalFn = getGlobalFn(globals); - - return imports.map(x => { - let name = globalFn(x.source.value); - - if (!name) { - if (x.name.startsWith('__import')) { - const error = new Error( - `Could not determine name for imported module '${x.source.value}' – use options.globals` - ); - if (onerror) { - onerror(error); - } else { - throw error; - } - } else { - const warning = { - message: `No name was supplied for imported module '${x.source.value}'. Guessing '${x.name}', but you should use options.globals`, - }; - - if (onwarn) { - onwarn(warning); - } else { - console.warn(warning); // eslint-disable-line no-console - } - } - - name = x.name; - } - - return name; - }); -} - -function getGlobalFn(globals: any): (id: string) => string { - if (typeof globals === 'function') return globals; - if (typeof globals === 'object') { - return id => globals[id]; - } - - return () => undefined; -} diff --git a/src/generators/shared/utils/getIntro.ts b/src/generators/shared/utils/getIntro.ts deleted file mode 100644 index 599bc5f7b3..0000000000 --- a/src/generators/shared/utils/getIntro.ts +++ /dev/null @@ -1,95 +0,0 @@ -import deindent from '../../../utils/deindent'; -import getGlobals from './getGlobals'; -import { CompileOptions, ModuleFormat, Node } from '../../../interfaces'; - -export default function getIntro( - format: ModuleFormat, - options: CompileOptions, - imports: Node[] -) { - if (format === 'es') return ''; - if (format === 'amd') return getAmdIntro(options, imports); - if (format === 'cjs') return getCjsIntro(options, imports); - if (format === 'iife') return getIifeIntro(options, imports); - if (format === 'umd') return getUmdIntro(options, imports); - if (format === 'eval') return getEvalIntro(options, imports); - - throw new Error(`Not implemented: ${format}`); -} - -function getAmdIntro(options: CompileOptions, imports: Node[]) { - const sourceString = imports.length - ? `[${imports - .map(declaration => `'${removeExtension(declaration.source.value)}'`) - .join(', ')}], ` - : ''; - - const id = options.amd && options.amd.id; - - return `define(${id - ? `"${id}", ` - : ''}${sourceString}function(${paramString(imports)}) { 'use strict';\n\n`; -} - -function getCjsIntro(options: CompileOptions, imports: Node[]) { - const requireBlock = imports - .map( - declaration => - `var ${declaration.name} = require('${declaration.source.value}');` - ) - .join('\n\n'); - - if (requireBlock) { - return `'use strict';\n\n${requireBlock}\n\n`; - } - - return `'use strict';\n\n`; -} - -function getIifeIntro(options: CompileOptions, imports: Node[]) { - if (!options.name) { - throw new Error(`Missing required 'name' option for IIFE export`); - } - - return `var ${options.name} = (function(${paramString(imports)}) { 'use strict';\n\n`; -} - -function getUmdIntro(options: CompileOptions, 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 = imports.length - ? `[${imports - .map(declaration => `'${removeExtension(declaration.source.value)}'`) - .join(', ')}], ` - : ''; - const cjsDeps = imports - .map(declaration => `require('${declaration.source.value}')`) - .join(', '); - const globalDeps = getGlobals(imports, options); - - return ( - deindent` - (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(${globalDeps})); - }(this, (function (${paramString(imports)}) { 'use strict';` + '\n\n' - ); -} - -function getEvalIntro(options: CompileOptions, imports: Node[]) { - return `(function (${paramString(imports)}) { 'use strict';\n\n`; -} - -function paramString(imports: Node[]) { - return imports.map(dep => dep.name).join(', '); -} - -function removeExtension(file: string) { - const index = file.lastIndexOf('.'); - return ~index ? file.slice(0, index) : file; -} diff --git a/src/generators/shared/utils/getModuleWrapper.ts b/src/generators/shared/utils/getModuleWrapper.ts new file mode 100644 index 0000000000..ae9166122f --- /dev/null +++ b/src/generators/shared/utils/getModuleWrapper.ts @@ -0,0 +1,253 @@ +import deindent from '../../../utils/deindent'; +import { CompileOptions, ModuleFormat, Node } from '../../../interfaces'; + +interface Dependency { + name: string; + statements: string[]; + source: string; +} + +export default function getModuleWrapper( + format: ModuleFormat, + name: string, + options: CompileOptions, + imports: Node[], + source: string +) { + if (format === 'es') return getEsWrapper(name, options, imports, 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 getAmdWrapper(name, options, dependencies); + if (format === 'cjs') return getCjsWrapper(name, options, dependencies); + if (format === 'iife') return getIifeWrapper(name, options, dependencies); + if (format === 'umd') return getUmdWrapper(name, options, dependencies); + if (format === 'eval') return getEvalWrapper(name, options, dependencies); + + throw new Error(`Not implemented: ${format}`); +} + +function getEsWrapper(name: string, options: CompileOptions, imports: Node[], source: string) { + const importBlock = imports + .map((declaration: Node) => source.slice(declaration.start, declaration.end)) + .join('\n'); + + return { + intro: importBlock ? importBlock + '\n\n' : '', + outro: `export default ${name};` + }; +} + +function getAmdWrapper(name: string, options: CompileOptions, dependencies: Dependency[]) { + const sourceString = dependencies.length + ? `[${dependencies.map(d => `"${removeExtension(d.source)}"`).join(', ')}], ` + : ''; + + const id = options.amd && options.amd.id; + + return { + intro: deindent` + define(${id ? `"${id}", ` : ''}${sourceString}function(${paramString(dependencies)}) { "use strict"; + + ${getCompatibilityStatements(dependencies)} + + `, + outro: deindent` + return ${name}; + });` + }; +} + +function getCjsWrapper(name: string, options: CompileOptions, dependencies: Dependency[]) { + const requireBlock = dependencies + .map(d => `var ${d.name} = require("${d.source}");`) + .join('\n\n'); + + const intro = requireBlock ? + deindent` + "use strict"; + + ${requireBlock} + ${getCompatibilityStatements(dependencies)} + + ` : + deindent` + "use strict"; + + `; + + const outro = `module.exports = ${name};` + + return { intro, outro }; +} + +function getIifeWrapper(name: string, options: CompileOptions, dependencies: Dependency[]) { + if (!options.name) { + throw new Error(`Missing required 'name' option for IIFE export`); + } + + const globals = getGlobals(dependencies, options); + + return { + intro: deindent` + var ${options.name} = (function(${paramString(dependencies)}) { "use strict"; + + ${getCompatibilityStatements(dependencies)} + + `, + + outro: `return ${name};\n\n}(${globals.join(', ')}));` + }; +} + +function getUmdWrapper(name: string, options: CompileOptions, dependencies: Dependency[]) { + 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 cjsDeps = dependencies + .map(d => `require("${d.source}")`) + .join(', '); + + const globals = getGlobals(dependencies, options); + + return { + intro: deindent` + (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})); + }(this, (function (${paramString(dependencies)}) { "use strict"; + + ${getCompatibilityStatements(dependencies)} + + `, + + outro: deindent` + return ${name}; + + })));` + }; +} + +function getEvalWrapper(name: string, options: CompileOptions, dependencies: Dependency[]) { + const globals = getGlobals(dependencies, options); + + return { + intro: deindent` + (function (${paramString(dependencies)}) { "use strict"; + + ${getCompatibilityStatements(dependencies)} + + `, + + outro: `return ${name};\n\n}(${globals.join(', ')}));` + }; +} + +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; + + const statements: string[] = []; + + dependencies.forEach(dependency => { + statements.push(...dependency.statements); + }); + + return statements.join('\n'); +} + +function getGlobals(dependencies: Dependency[], options: CompileOptions) { + const { globals, onerror, onwarn } = options; + const globalFn = getGlobalFn(globals); + + return dependencies.map(d => { + let name = globalFn(d.source); + + if (!name) { + if (d.name.startsWith('__import')) { + const error = new Error( + `Could not determine name for imported module '${d.source}' – use options.globals` + ); + if (onerror) { + onerror(error); + } else { + throw error; + } + } else { + const warning = { + message: `No name was supplied for imported module '${d.source}'. Guessing '${d.name}', but you should use options.globals`, + }; + + if (onwarn) { + onwarn(warning); + } else { + console.warn(warning); // eslint-disable-line no-console + } + } + + name = d.name; + } + + return name; + }); +} + +function getGlobalFn(globals: any): (id: string) => string { + if (typeof globals === 'function') return globals; + if (typeof globals === 'object') { + return id => globals[id]; + } + + return () => undefined; +} \ No newline at end of file diff --git a/src/generators/shared/utils/getOutro.ts b/src/generators/shared/utils/getOutro.ts deleted file mode 100644 index 682a3cc870..0000000000 --- a/src/generators/shared/utils/getOutro.ts +++ /dev/null @@ -1,37 +0,0 @@ -import getGlobals from './getGlobals'; -import { CompileOptions, Node } from '../../../interfaces'; - -export default function getOutro( - format: string, - name: string, - options: CompileOptions, - imports: Node[] -) { - if (format === 'es') { - return `export default ${name};`; - } - - if (format === 'amd') { - return `return ${name};\n\n});`; - } - - if (format === 'cjs') { - return `module.exports = ${name};`; - } - - if (format === 'iife') { - const globals = getGlobals(imports, options); - return `return ${name};\n\n}(${globals.join(', ')}));`; - } - - if (format === 'eval') { - const globals = getGlobals(imports, options); - return `return ${name};\n\n}(${globals.join(', ')}));`; - } - - if (format === 'umd') { - return `return ${name};\n\n})));`; - } - - throw new Error(`Not implemented: ${format}`); -}