refactor module wrapping

pull/859/head
Rich Harris 8 years ago
parent 3b68d1f5dc
commit 240291604b

@ -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 } :

@ -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;
}

@ -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;
}

@ -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;
}

@ -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}`);
}
Loading…
Cancel
Save