@ampproject/rollup-plugin-closure-compiler
Advanced tools
Comparing version 0.4.3 to 0.5.0-alpha.1
'use strict'; | ||
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } | ||
var path = require('path'); | ||
var tempWrite = require('temp-write'); | ||
var MagicString = _interopDefault(require('magic-string')); | ||
var googleClosureCompiler = require('google-closure-compiler'); | ||
@@ -23,13 +27,5 @@ var fs = require('fs'); | ||
*/ | ||
// @see https://github.com/estree/estree/blob/master/es2015.md#exports | ||
const EXPORT_NAMED_DECLARATION = 'ExportNamedDeclaration'; | ||
const EXPORT_SPECIFIER = 'ExportSpecifier'; | ||
const EXPORT_DEFAULT_DECLARATION = 'ExportDefaultDeclaration'; | ||
const EXPORT_ALL_DECLARATION = 'ExportAllDeclaration'; | ||
const ALL_EXPORT_TYPES = [ | ||
EXPORT_NAMED_DECLARATION, | ||
EXPORT_SPECIFIER, | ||
EXPORT_DEFAULT_DECLARATION, | ||
EXPORT_ALL_DECLARATION, | ||
]; | ||
// @see https://github.com/estree/estree/blob/master/es2015.md#imports | ||
const IMPORT_DECLARATION = 'ImportDeclaration'; | ||
const ALL_IMPORT_DECLARATIONS = [IMPORT_DECLARATION]; | ||
var ExportClosureMapping; | ||
@@ -40,9 +36,12 @@ (function (ExportClosureMapping) { | ||
ExportClosureMapping[ExportClosureMapping["NAMED_DEFAULT_FUNCTION"] = 2] = "NAMED_DEFAULT_FUNCTION"; | ||
ExportClosureMapping[ExportClosureMapping["NAMED_DEFAULT_CLASS"] = 3] = "NAMED_DEFAULT_CLASS"; | ||
ExportClosureMapping[ExportClosureMapping["NAMED_CONSTANT"] = 4] = "NAMED_CONSTANT"; | ||
ExportClosureMapping[ExportClosureMapping["DEFAULT"] = 5] = "DEFAULT"; | ||
ExportClosureMapping[ExportClosureMapping["DEFAULT_FUNCTION"] = 3] = "DEFAULT_FUNCTION"; | ||
ExportClosureMapping[ExportClosureMapping["NAMED_DEFAULT_CLASS"] = 4] = "NAMED_DEFAULT_CLASS"; | ||
ExportClosureMapping[ExportClosureMapping["DEFAULT_CLASS"] = 5] = "DEFAULT_CLASS"; | ||
ExportClosureMapping[ExportClosureMapping["NAMED_CONSTANT"] = 6] = "NAMED_CONSTANT"; | ||
ExportClosureMapping[ExportClosureMapping["DEFAULT"] = 7] = "DEFAULT"; | ||
})(ExportClosureMapping || (ExportClosureMapping = {})); | ||
class Transform { | ||
constructor(context) { | ||
constructor(context, inputOptions) { | ||
this.context = context; | ||
this.inputOptions = inputOptions; | ||
} | ||
@@ -65,2 +64,18 @@ extern(options) { | ||
} | ||
isEntryPoint(id) { | ||
const inputs = (input) => { | ||
if (typeof this.inputOptions.input === 'string') { | ||
return [this.inputOptions.input]; | ||
} | ||
else if (typeof this.inputOptions.input === 'object') { | ||
return Object.values(this.inputOptions.input); | ||
} | ||
else { | ||
return this.inputOptions.input; | ||
} | ||
}; | ||
return inputs(this.inputOptions.input) | ||
.map(input => path.resolve(input)) | ||
.includes(id); | ||
} | ||
} | ||
@@ -121,13 +136,13 @@ | ||
const exportSpecifierName = (exportSpecifier) => exportSpecifier.exported.name; | ||
function functionDeclarationName(context, declaration) { | ||
const camelcase = (input) => input | ||
.replace(/^[_.\- ]+/, '') | ||
.toLowerCase() | ||
.replace(/[_.\- ]+(\w|$)/g, (m, p1) => p1.toUpperCase()); | ||
function functionDeclarationName(context, id, declaration) { | ||
// For the Declaration passed, there can be a function declaration. | ||
if (declaration.declaration && declaration.declaration.type === 'FunctionDeclaration') { | ||
const functionDeclaration = declaration.declaration; | ||
if (functionDeclaration === null || | ||
functionDeclaration.id === null || | ||
functionDeclaration.id.name === null) { | ||
context.error(`Plugin requires exports to be named, 'export function Foo(){}' not 'export function(){}'`); | ||
} | ||
else { | ||
// This function declaration is the export name we need to know. | ||
if (functionDeclaration !== null && | ||
functionDeclaration.id !== null && | ||
functionDeclaration.id.name !== null) { | ||
return functionDeclaration.id.name; | ||
@@ -138,12 +153,9 @@ } | ||
} | ||
function classDeclarationName(context, declaration) { | ||
function classDeclarationName(context, id, declaration) { | ||
// For the Declaration passed, there can be a function declaration. | ||
if (declaration.declaration && declaration.declaration.type === 'ClassDeclaration') { | ||
const classDeclaration = declaration.declaration; | ||
if (classDeclaration === null || | ||
classDeclaration.id === null || | ||
classDeclaration.id.name === null) { | ||
context.error(`Plugin requires exports to be named, 'export class Foo(){}' not 'export class(){}'`); | ||
} | ||
else { | ||
if (classDeclaration !== null && | ||
classDeclaration.id !== null && | ||
classDeclaration.id.name !== null) { | ||
// This class declaration is the export name we need to know. | ||
@@ -155,5 +167,8 @@ return classDeclaration.id.name; | ||
} | ||
function NamedDeclaration(context, declaration) { | ||
const functionName = functionDeclarationName(context, declaration); | ||
const className = classDeclarationName(context, declaration); | ||
function NamedDeclaration(context, id, declaration) { | ||
const functionName = functionDeclarationName(context, id, declaration); | ||
const className = classDeclarationName(context, id, declaration); | ||
// console.log(functionName, className); | ||
// TODO(KB): This logic isn't great. If something has a named declaration, lets instead use the AST to find out what it is. | ||
// var Foo=function(){}export{Foo as default} => default export function | ||
if (functionName !== null) { | ||
@@ -170,2 +185,3 @@ return { | ||
else if (declaration.specifiers) { | ||
// console.log(declaration.specifiers); | ||
const exportMap = {}; | ||
@@ -179,7 +195,7 @@ declaration.specifiers.forEach(exportSpecifier => { | ||
} | ||
function DefaultDeclaration(context, declaration) { | ||
function DefaultDeclaration(context, id, declaration) { | ||
if (declaration.declaration) { | ||
switch (declaration.declaration.type) { | ||
case 'FunctionDeclaration': | ||
const functionName = functionDeclarationName(context, declaration); | ||
const functionName = functionDeclarationName(context, id, declaration); | ||
if (functionName !== null) { | ||
@@ -190,3 +206,11 @@ return { | ||
} | ||
break; | ||
else { | ||
// When Rollup encounters a default export that is unnamed, | ||
// it uses camelCase(filename) of the id to name the function. | ||
// THIS IS NOT INTUITIVE! | ||
const functionName = camelcase(path.basename(id, '.js')); | ||
return { | ||
[functionName]: ExportClosureMapping.DEFAULT_FUNCTION, | ||
}; | ||
} | ||
case 'Identifier': | ||
@@ -200,3 +224,3 @@ if (declaration.declaration.name) { | ||
case 'ClassDeclaration': | ||
const className = classDeclarationName(context, declaration); | ||
const className = classDeclarationName(context, id, declaration); | ||
if (className !== null) { | ||
@@ -207,3 +231,11 @@ return { | ||
} | ||
break; | ||
else { | ||
// When Rollup encounters a default export that is unnamed, | ||
// it uses camelCase(filename) of the id to name the class. | ||
// THIS IS NOT INTUITIVE! | ||
const className = camelcase(path.basename(id, '.js')); | ||
return { | ||
[className]: ExportClosureMapping.DEFAULT_CLASS, | ||
}; | ||
} | ||
} | ||
@@ -213,2 +245,17 @@ } | ||
} | ||
function literalName(context, id, literal) { | ||
// Literal can either be a SimpleLiteral, or RegExpLiteral | ||
if ('regex' in literal) { | ||
// This is a RegExpLiteral | ||
context.warn('Rollup Plugin Closure Compiler found a Regex Literal Named Import. `import foo from "*/.hbs"`'); | ||
return ''; | ||
} | ||
const literalValue = literal.value; | ||
return typeof literalValue === 'string' ? literalValue : ''; | ||
} | ||
// export function ImportDeclaration( | ||
// context: PluginContext, | ||
// id: string, | ||
// declaration: ImportDeclaration, | ||
// ): | ||
@@ -246,3 +293,3 @@ /** | ||
*/ | ||
const defaults = (options, transformers) => { | ||
const defaults = (options, providedExterns, transformers) => { | ||
// Defaults for Rollup Projects are slightly different than Closure Compiler defaults. | ||
@@ -256,8 +303,11 @@ // - Users of Rollup tend to transpile their code before handing it to a minifier, | ||
const externs = transformers | ||
? transformers.map(transform => tempWrite.sync(transform.extern(options))) | ||
: ''; | ||
? transformers.map(transform => tempWrite.sync(transform.extern(options))).concat(providedExterns) | ||
: providedExterns.length > 0 | ||
? providedExterns | ||
: ''; | ||
return { | ||
language_out: 'NO_TRANSPILE', | ||
assume_function_wrapper: isESMFormat(options.format) ? true : false, | ||
assume_function_wrapper: isESMFormat(options.format), | ||
warning_level: 'QUIET', | ||
module_resolution: 'NODE', | ||
externs, | ||
@@ -277,11 +327,17 @@ }; | ||
const mapFile = tempWrite.sync(''); | ||
return [ | ||
{ | ||
...defaults(outputOptions, transforms), | ||
...compileOptions, | ||
js: tempWrite.sync(code), | ||
create_source_map: mapFile, | ||
}, | ||
mapFile, | ||
]; | ||
const externs = (compileOptions) => { | ||
if ('externs' in compileOptions) { | ||
switch (typeof compileOptions.externs) { | ||
case 'boolean': | ||
return []; | ||
case 'string': | ||
return [compileOptions.externs]; | ||
default: | ||
return compileOptions.externs; | ||
} | ||
} | ||
return []; | ||
}; | ||
const options = Object.assign({}, defaults(outputOptions, externs(compileOptions), transforms), compileOptions, { js: tempWrite.sync(code), create_source_map: mapFile }); | ||
return [options, mapFile]; | ||
} | ||
@@ -304,8 +360,3 @@ | ||
*/ | ||
const HEADER$1 = `/** | ||
* @fileoverview Externs built via derived configuration from Rollup or input code. | ||
* This extern contains top level exported members. | ||
* @externs | ||
*/ | ||
`; | ||
const walk = require('acorn/dist/walk'); | ||
/** | ||
@@ -322,48 +373,35 @@ * This Transform will apply only if the Rollup configuration is for 'esm' output. | ||
super(...arguments); | ||
this.exported = {}; | ||
this.originalExports = {}; | ||
} | ||
extern(options$$1) { | ||
let content = HEADER$1; | ||
if (isESMFormat(options$$1.format)) { | ||
Object.keys(this.exported).forEach(key => { | ||
content += `window['${key}'] = ${key};\n`; | ||
}); | ||
} | ||
return content; | ||
} | ||
/** | ||
* Before Closure Compiler is given a chance to look at the code, we need to | ||
* find and store all export statements with their correct type | ||
* @param code source to parse, and modify | ||
* @param code source to parse | ||
* @param id Rollup id reference to the source | ||
* @return Promise containing the modified source | ||
*/ | ||
async deriveFromInputSource(code, id) { | ||
const program = this.context.parse(code, {}); | ||
const exportNodes = program.body.filter(node => ALL_EXPORT_TYPES.includes(node.type)); | ||
exportNodes.forEach((node) => { | ||
switch (node.type) { | ||
case EXPORT_NAMED_DECLARATION: | ||
const namedDeclarationValues = NamedDeclaration(this.context, node); | ||
if (this.isEntryPoint(id)) { | ||
const context = this.context; | ||
let originalExports = {}; | ||
const program = context.parse(code, { ranges: true }); | ||
walk.simple(program, { | ||
ExportNamedDeclaration(node) { | ||
const namedDeclarationValues = NamedDeclaration(context, id, node); | ||
if (namedDeclarationValues !== null) { | ||
this.exported = { ...this.exported, ...namedDeclarationValues }; | ||
originalExports = Object.assign({}, originalExports, namedDeclarationValues); | ||
} | ||
break; | ||
case EXPORT_DEFAULT_DECLARATION: | ||
// TODO(KB): This case is not fully supported – only named default exports. | ||
// `export default Foo(){};`, or `export default Foo;`, not `export default function(){};` | ||
const defaultDeclarationValue = DefaultDeclaration(this.context, node); | ||
}, | ||
ExportDefaultDeclaration(node) { | ||
const defaultDeclarationValue = DefaultDeclaration(context, id, node); | ||
if (defaultDeclarationValue !== null) { | ||
this.exported = { ...this.exported, ...defaultDeclarationValue }; | ||
originalExports = Object.assign({}, originalExports, defaultDeclarationValue); | ||
} | ||
break; | ||
case EXPORT_ALL_DECLARATION: | ||
}, | ||
ExportAllDeclaration(node) { | ||
// TODO(KB): This case `export * from "./import"` is not currently supported. | ||
this.context.error(new Error(`Rollup Plugin Closure Compiler does not support export all syntax.`)); | ||
break; | ||
default: | ||
this.context.error(new Error(`Rollup Plugin Closure Compiler found unsupported module declaration type, ${node.type}`)); | ||
break; | ||
} | ||
}); | ||
context.error(new Error(`Rollup Plugin Closure Compiler does not support export all syntax.`)); | ||
}, | ||
}); | ||
this.originalExports = originalExports; | ||
} | ||
return void 0; | ||
@@ -383,7 +421,12 @@ } | ||
else if (isESMFormat(this.outputOptions.format)) { | ||
Object.keys(this.exported).forEach(key => { | ||
code += `\nwindow['${key}'] = ${key}`; | ||
const source = new MagicString(code); | ||
// Window scoped references for each key are required to ensure Closure Compilre retains the code. | ||
Object.keys(this.originalExports).forEach(key => { | ||
source.append(`\nwindow['${key}'] = ${key}`); | ||
}); | ||
return { | ||
code: source.toString(), | ||
map: source.generateMap(), | ||
}; | ||
} | ||
// TODO(KB): Sourcemaps fail :( | ||
return { | ||
@@ -405,50 +448,185 @@ code, | ||
else if (isESMFormat(this.outputOptions.format)) { | ||
const exportedConstants = []; | ||
Object.keys(this.exported).forEach(key => { | ||
switch (this.exported[key]) { | ||
case ExportClosureMapping.NAMED_FUNCTION: | ||
code = code.replace(`window.${key}=function`, `export function ${key}`); | ||
break; | ||
case ExportClosureMapping.NAMED_CLASS: | ||
const namedClassMatch = new RegExp(`window.${key}=(\\w+);`).exec(code); | ||
if (namedClassMatch && namedClassMatch.length > 0) { | ||
// Remove the declaration on window scope, i.e. `window.Exported=a;` | ||
code = code.replace(namedClassMatch[0], ''); | ||
// Store a new export constant to output at the end. `a as Exported` | ||
exportedConstants.push(`${namedClassMatch[1]} as ${key}`); | ||
} | ||
break; | ||
case ExportClosureMapping.NAMED_DEFAULT_FUNCTION: | ||
code = code.replace(`window.${key}=function`, `export default function ${key}`); | ||
break; | ||
case ExportClosureMapping.NAMED_DEFAULT_CLASS: | ||
const namedDefaultClassMatch = new RegExp(`window.${key}=(\\w+);`).exec(code); | ||
if (namedDefaultClassMatch && namedDefaultClassMatch.length > 0) { | ||
// Remove the declaration on window scope, i.e. `window.ExportedTwo=a;` | ||
// Replace it with an export statement `export default a;` | ||
code = code.replace(namedDefaultClassMatch[0], `export default ${namedDefaultClassMatch[1]};`); | ||
} | ||
break; | ||
case ExportClosureMapping.NAMED_CONSTANT: | ||
// Remove the declaration on the window scope, i.e. `window.ExportedThree=value` | ||
// Replace it with a const declaration, i.e `const ExportedThree=value` | ||
code = code.replace(`window.${key}=`, `const ${key}=`); | ||
// Store a new export constant to output at the end, i.e `ExportedThree` | ||
exportedConstants.push(key); | ||
break; | ||
default: | ||
this.context.warn('Rollup Plugin Closure Compiler could not restore all exports statements.'); | ||
break; | ||
} | ||
const source = new MagicString(code); | ||
const program = this.context.parse(code, { ranges: true }); | ||
const collectedExportsToAppend = []; | ||
const originalExports = this.originalExports; | ||
const originalExportIdentifiers = Object.keys(originalExports); | ||
source.trimEnd(); | ||
walk.ancestor(program, { | ||
// We inserted window scoped assignments for all the export statements during `preCompilation` | ||
// window['exportName'] = exportName; | ||
// Now we need to find where Closure Compiler moved them, and restore the exports of their name. | ||
// ASTExporer Link: https://astexplorer.net/#/gist/94f185d06a4105d64828f1b8480bddc8/0fc5885ae5343f964d0cdd33c7d392a70cf5fcaf | ||
Identifier(node, ancestors) { | ||
if (node.name === 'window') { | ||
ancestors.forEach((ancestor) => { | ||
if (ancestor.type === 'ExpressionStatement' && | ||
ancestor.expression.type === 'AssignmentExpression' && | ||
ancestor.expression.left.type === 'MemberExpression' && | ||
ancestor.expression.left.object.type === 'Identifier' && | ||
ancestor.expression.left.object.name === 'window' && | ||
ancestor.expression.left.property.type === 'Identifier' && | ||
originalExportIdentifiers.includes(ancestor.expression.left.property.name)) { | ||
const exportName = ancestor.expression.left.property.name; | ||
switch (originalExports[exportName]) { | ||
case ExportClosureMapping.DEFAULT_FUNCTION: | ||
case ExportClosureMapping.NAMED_DEFAULT_FUNCTION: | ||
if (ancestor.expression.left.range) { | ||
source.overwrite(ancestor.expression.left.range[0], ancestor.expression.left.range[1] + ancestor.expression.operator.length, `export default `); | ||
} | ||
break; | ||
case ExportClosureMapping.NAMED_FUNCTION: | ||
if (ancestor.expression.right.type === 'FunctionExpression' && | ||
ancestor.expression.right.params.length > 0) { | ||
const firstParameter = ancestor.expression.right.params[0]; | ||
if (ancestor.expression.range && firstParameter.range) { | ||
source.overwrite(ancestor.expression.range[0], firstParameter.range[0] - 1, `export function ${ancestor.expression.left.property.name}`); | ||
} | ||
} | ||
break; | ||
case ExportClosureMapping.DEFAULT_CLASS: | ||
case ExportClosureMapping.NAMED_DEFAULT_CLASS: | ||
if (ancestor.expression.right.type === 'Identifier') { | ||
const mangledName = ancestor.expression.right.name; | ||
walk.simple(program, { | ||
ClassDeclaration(node) { | ||
if (node.id && | ||
node.id.name === mangledName && | ||
node.range && | ||
node.body.range && | ||
ancestor.range) { | ||
if (node.superClass && node.superClass.type === 'Identifier') { | ||
source.overwrite(node.range[0], node.body.range[0], `export default class extends ${node.superClass.name}`); | ||
} | ||
else { | ||
source.overwrite(node.range[0], node.body.range[0], `export default class`); | ||
} | ||
source.remove(ancestor.range[0], ancestor.range[1]); | ||
} | ||
}, | ||
}); | ||
} | ||
break; | ||
case ExportClosureMapping.NAMED_CONSTANT: | ||
if (ancestor.expression.left.object.range) { | ||
source.overwrite(ancestor.expression.left.object.range[0], ancestor.expression.left.object.range[1] + 1, 'var '); | ||
} | ||
collectedExportsToAppend.push(ancestor.expression.left.property.name); | ||
break; | ||
default: | ||
if (ancestor.range) { | ||
source.remove(ancestor.range[0], ancestor.range[1]); | ||
} | ||
if (ancestor.expression.right.type === 'Identifier') { | ||
collectedExportsToAppend.push(`${ancestor.expression.right.name} as ${ancestor.expression.left.property.name}`); | ||
} | ||
break; | ||
} | ||
} | ||
}); | ||
} | ||
}, | ||
}); | ||
if (exportedConstants.length > 0) { | ||
// Remove the newline at the end since we are going to append exports. | ||
if (code.endsWith('\n')) { | ||
code = code.substr(0, code.lastIndexOf('\n')); | ||
} | ||
// Append the exports that were gathered, i.e `export {a as Exported, ExportedThree};` | ||
code += `export {${exportedConstants.join(',')}};`; | ||
if (collectedExportsToAppend.length > 0) { | ||
source.append(`export {${collectedExportsToAppend.join(',')}};`); | ||
} | ||
// Object.keys(exportedConstants).forEach(exportedConstant => { | ||
// let range: [number, number] | undefined = undefined; | ||
// if ((range = exportedConstants[exportedConstant].range)) { | ||
// switch(this.exported[exportedConstant]) { | ||
// case ExportClosureMapping.NAMED_FUNCTION: | ||
// // replace(`window.${key}=function`, `export function ${key}`, code, source); | ||
// source.overwrite(range[0], range[1], `export function ${exportedConstant}`); | ||
// break; | ||
// case ExportClosureMapping.NAMED_CLASS: | ||
// break; | ||
// case ExportClosureMapping.DEFAULT_FUNCTION: | ||
// break; | ||
// case ExportClosureMapping.NAMED_DEFAULT_FUNCTION: | ||
// break; | ||
// case ExportClosureMapping.DEFAULT_CLASS: | ||
// break; | ||
// case ExportClosureMapping.NAMED_DEFAULT_CLASS: | ||
// break; | ||
// case ExportClosureMapping.NAMED_CONSTANT: | ||
// break; | ||
// default: | ||
// this.context.warn( | ||
// 'Rollup Plugin Closure Compiler could not restore all exports statements.', | ||
// ); | ||
// break; | ||
// } | ||
// range && source.overwrite(range[0], range[1], 'new text'); | ||
// } | ||
// }); | ||
// console.log('exportedConstants', exportedConstants); | ||
// Object.keys(this.exported).forEach(key => { | ||
// switch (this.exported[key]) { | ||
// case ExportClosureMapping.NAMED_FUNCTION: | ||
// replace(`window.${key}=function`, `export function ${key}`, code, source); | ||
// break; | ||
// case ExportClosureMapping.NAMED_CLASS: | ||
// const namedClassMatch = new RegExp(`window.${key}=(\\w+);`).exec(code); | ||
// if (namedClassMatch && namedClassMatch.length > 0) { | ||
// // Remove the declaration on window scope, i.e. `window.Exported=a;` | ||
// replace(namedClassMatch[0], '', code, source); | ||
// // Store a new export constant to output at the end. `a as Exported` | ||
// exportedConstants.push(`${namedClassMatch[1]} as ${key}`); | ||
// } | ||
// break; | ||
// case ExportClosureMapping.DEFAULT_FUNCTION: | ||
// replace(`window.${key}=function`, `export default function`, code, source); | ||
// break; | ||
// case ExportClosureMapping.NAMED_DEFAULT_FUNCTION: | ||
// replace(`window.${key}=function`, `export default function ${key}`, code, source); | ||
// break; | ||
// case ExportClosureMapping.DEFAULT_CLASS: | ||
// const defaultClassMatch = new RegExp(`window.${key}=(\\w+);`).exec(code); | ||
// if (defaultClassMatch && defaultClassMatch.length > 0) { | ||
// // Remove the declaration on window scope, i.e. `window.ExportedTwo=a;` | ||
// // Replace it with an export statement `export default a;` | ||
// replace(`class ${defaultClassMatch[1]}`, `export default class`, code, source); | ||
// replace(defaultClassMatch[0], '', code, source); | ||
// } | ||
// break; | ||
// case ExportClosureMapping.NAMED_DEFAULT_CLASS: | ||
// const namedDefaultClassMatch = new RegExp(`window.${key}=(\\w+);`).exec(code); | ||
// if (namedDefaultClassMatch && namedDefaultClassMatch.length > 0) { | ||
// // Remove the declaration on window scope, i.e. `window.ExportedTwo=a;` | ||
// // Replace it with an export statement `export default a;` | ||
// replace( | ||
// namedDefaultClassMatch[0], | ||
// `export default ${namedDefaultClassMatch[1]};`, | ||
// code, | ||
// source, | ||
// ); | ||
// } | ||
// break; | ||
// case ExportClosureMapping.NAMED_CONSTANT: | ||
// // Remove the declaration on the window scope, i.e. `window.ExportedThree=value` | ||
// // Replace it with a const declaration, i.e `const ExportedThree=value` | ||
// replace(`window.${key}=`, `const ${key}=`, code, source); | ||
// // Store a new export constant to output at the end, i.e `ExportedThree` | ||
// exportedConstants.push(key); | ||
// break; | ||
// default: | ||
// this.context.warn( | ||
// 'Rollup Plugin Closure Compiler could not restore all exports statements.', | ||
// ); | ||
// break; | ||
// } | ||
// }); | ||
// if (exportedConstants.length > 0) { | ||
// // Remove the newline at the end since we are going to append exports. | ||
// if (code.endsWith('\n')) { | ||
// source.trimLines(); | ||
// } | ||
// // Append the exports that were gathered, i.e `export {a as Exported, ExportedThree};` | ||
// source.append(`export {${exportedConstants.join(',')}};`); | ||
// } | ||
return { | ||
code: source.toString(), | ||
map: source.generateMap(), | ||
}; | ||
} | ||
// TODO(KB): Sourcemaps fail :( | ||
return { | ||
@@ -475,2 +653,98 @@ code, | ||
*/ | ||
// TODO(KB): Need to generate externs for external members if we do not have an extern passed in. | ||
// Otherwise, advanced mode compilation will fail. | ||
// Likely this means moving scanning for external imports from `preCompilation` to the `derive` phase. | ||
// const HEADER = `/** | ||
// * @fileoverview Externs built via derived configuration from Rollup or input code. | ||
// * This extern contains the external import names, to prevent compilation failures. | ||
// * @externs | ||
// */ | ||
// `; | ||
class ImportTransform extends Transform { | ||
constructor() { | ||
super(...arguments); | ||
this.importedExternalsSyntax = {}; | ||
} | ||
/** | ||
* Rollup allows configuration for 'external' imports. | ||
* These are items that will not be bundled with the resulting code, but instead are expected | ||
* to already be available via `import` or other means. | ||
* @param source parsed from import statements, this is the source of a particular import statement. | ||
* @param parent parent id requesting the import. | ||
* @return Promise<boolean> if the import is listed in the external list. | ||
*/ | ||
async isExternalImport(source, parent) { | ||
if (this.inputOptions.external === undefined) { | ||
return false; | ||
} | ||
if (Array.isArray(this.inputOptions.external)) { | ||
return this.inputOptions.external.includes(source); | ||
} | ||
if (typeof this.inputOptions.external === 'function') { | ||
const configDrivenExternal = await this.inputOptions.external(source, parent, true); | ||
return configDrivenExternal !== undefined && configDrivenExternal === true; | ||
} | ||
return false; | ||
} | ||
/** | ||
* Before Closure Compiler modifies the source, we need to ensure external imports have been removed | ||
* since Closure will error out when it encounters them. | ||
* @param code source to parse, and modify | ||
* @param id Rollup id reference to the source | ||
* @return modified input source with external imports removed. | ||
*/ | ||
async preCompilation(code, id) { | ||
const source = new MagicString(code); | ||
const program = this.context.parse(code, { ranges: true }); | ||
const importNodes = program.body.filter(node => ALL_IMPORT_DECLARATIONS.includes(node.type)); | ||
for (const node of importNodes) { | ||
switch (node.type) { | ||
case IMPORT_DECLARATION: | ||
const name = literalName(this.context, id, node.source); | ||
if (await this.isExternalImport(name, id)) { | ||
const range = node.range ? [node.range[0], node.range[1]] : [0, 0]; | ||
this.importedExternalsSyntax[name] = code.slice(range[0], range[1]); | ||
source.remove(range[0], range[1]); | ||
} | ||
break; | ||
default: | ||
break; | ||
} | ||
} | ||
return { | ||
code: source.toString(), | ||
map: source.generateMap(), | ||
}; | ||
} | ||
/** | ||
* After Closure Compiler has modified the source, we need to re-add the external imports | ||
* @param code source post Closure Compiler Compilation | ||
* @param id Rollup identifier for the source | ||
* @return Promise containing the repaired source | ||
*/ | ||
async postCompilation(code, id) { | ||
const source = new MagicString(code); | ||
Object.values(this.importedExternalsSyntax).forEach(importedExternalSyntax => source.prepend(importedExternalSyntax)); | ||
return { | ||
code: source.toString(), | ||
map: source.generateMap(), | ||
}; | ||
} | ||
} | ||
/** | ||
* Copyright 2018 The AMP HTML Authors. All Rights Reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS-IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
const STRICT_MODE_DECLARATION = `'use strict';`; | ||
@@ -491,5 +765,10 @@ const STRICT_MODE_DECLARATION_LENGTH = STRICT_MODE_DECLARATION.length; | ||
else if (isESMFormat(this.outputOptions.format) && code.startsWith(STRICT_MODE_DECLARATION)) { | ||
const source = new MagicString(code); | ||
// This will only remove the top level 'use strict' directive since we cannot | ||
// be certain source does not contain strings with the intended content. | ||
code = code.slice(STRICT_MODE_DECLARATION_LENGTH, code.length); | ||
source.remove(0, STRICT_MODE_DECLARATION_LENGTH); | ||
return { | ||
code: source.toString(), | ||
map: source.generateMap(), | ||
}; | ||
} | ||
@@ -517,11 +796,31 @@ return { | ||
*/ | ||
/** | ||
* Copyright 2018 The AMP HTML Authors. All Rights Reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS-IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
/** | ||
* Instantiate transform class instances for the plugin invocation. | ||
* @param context Plugin context to bind for each transform instance. | ||
* @param options Rollup input options | ||
* @param id Rollup's id entry for this source. | ||
* @return Instantiated transform class instances for the given entry point. | ||
*/ | ||
const createTransforms = (context) => { | ||
return [new IifeTransform(context), new ExportTransform(context), new StrictTransform(context)]; | ||
const createTransforms = (context, options) => { | ||
return [ | ||
new IifeTransform(context, options), | ||
new StrictTransform(context, options), | ||
new ExportTransform(context, options), | ||
new ImportTransform(context, options), | ||
]; | ||
}; | ||
@@ -554,4 +853,2 @@ /** | ||
async function postCompilation(code, transforms) { | ||
// Following successful Closure Compiler compilation, each transform needs an opportunity | ||
// to clean up work is performed in preCompilation via postCompilation. | ||
for (const transform of transforms) { | ||
@@ -568,6 +865,7 @@ const result = await transform.postCompilation(code, 'none'); | ||
* @param code source code to derive information from, pre Closure Compiler minification. | ||
* @param id Rollup identifier for this input source. | ||
* @param transforms Transforms to execute. | ||
*/ | ||
async function deriveFromInputSource(code, transforms) { | ||
await Promise.all(transforms.map(transform => transform.deriveFromInputSource(code, 'none'))).then(_ => void 0); | ||
async function deriveFromInputSource(code, id, transforms) { | ||
await Promise.all(transforms.map(transform => transform.deriveFromInputSource(code, id))).then(_ => void 0); | ||
} | ||
@@ -633,3 +931,3 @@ | ||
*/ | ||
const transformChunk = async (transforms, requestedCompileOptions, sourceCode, outputOptions) => { | ||
const transformChunk = async (transforms, requestedCompileOptions = {}, sourceCode, outputOptions) => { | ||
const code = await preCompilation(sourceCode, outputOptions, transforms); | ||
@@ -644,9 +942,11 @@ const [compileOptions, mapFile] = options(requestedCompileOptions, outputOptions, code, transforms); | ||
function closureCompiler(requestedCompileOptions = {}) { | ||
let inputOptions; | ||
let transforms; | ||
return { | ||
name: 'closure-compiler', | ||
options: options$$1 => (inputOptions = options$$1), | ||
load() { | ||
transforms = transforms || createTransforms(this); | ||
transforms = transforms || createTransforms(this, inputOptions); | ||
}, | ||
transform: async (code) => deriveFromInputSource(code, transforms), | ||
transform: async (code, id) => deriveFromInputSource(code, id, transforms), | ||
transformChunk: async (code, outputOptions, chunk) => await transformChunk(transforms, requestedCompileOptions, code, outputOptions), | ||
@@ -653,0 +953,0 @@ }; |
{ | ||
"name": "@ampproject/rollup-plugin-closure-compiler", | ||
"version": "0.4.3", | ||
"version": "0.5.0-alpha.1", | ||
"description": "Rollup + Google Closure Compiler", | ||
@@ -21,3 +21,3 @@ "main": "dist/index.js", | ||
"pretest": "rimraf dist transpile && tsc -p tsconfig.test.json & wait", | ||
"test": "ava test/**/*.js", | ||
"test": "ava -v test/**/*.js", | ||
"build": "rimraf dist transpile && tsc -p tsconfig.json & wait", | ||
@@ -28,20 +28,26 @@ "postbuild": "rollup --config rollup.config.js", | ||
"dependencies": { | ||
"google-closure-compiler": "^20180610.0.2", | ||
"temp-write": "^3.4.0" | ||
"google-closure-compiler": "20180610.0.2", | ||
"magic-string": "0.25.0", | ||
"temp-write": "3.4.0" | ||
}, | ||
"devDependencies": { | ||
"@types/estree": "^0.0.39", | ||
"@types/google-closure-compiler": "^0.0.18", | ||
"@types/temp-write": "^3.3.0", | ||
"@types/acorn": "4.0.3", | ||
"@types/estree": "0.0.39", | ||
"@types/google-closure-compiler": "0.0.18", | ||
"@types/temp-write": "3.3.0", | ||
"acorn": "5.7.1", | ||
"ava": "1.0.0-beta.6", | ||
"builtins": "2.0.0", | ||
"husky": "1.0.0-rc.13", | ||
"lint-staged": "^7.2.0", | ||
"prettier": "^1.13.5", | ||
"rimraf": "^2.6.2", | ||
"rollup": "^0.62.0", | ||
"rollup-plugin-commonjs": "^9.1.3", | ||
"rollup-plugin-node-builtins": "^2.1.2", | ||
"tslint": "^5.10.0", | ||
"typescript": "^2.9.2" | ||
"lint-staged": "7.2.0", | ||
"prettier": "1.13.7", | ||
"rimraf": "2.6.2", | ||
"rollup": "0.62.0", | ||
"tslint": "5.10.0", | ||
"typescript": "2.9.2" | ||
}, | ||
"files": [ | ||
"dist/index.js", | ||
"dist/index.mjs" | ||
], | ||
"lint-staged": { | ||
@@ -48,0 +54,0 @@ "*.ts": [ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
2
101075
3
14
5
1830
1
+ Addedmagic-string@0.25.0
+ Addedmagic-string@0.25.0(transitive)
+ Addedsourcemap-codec@1.4.8(transitive)
Updatedtemp-write@3.4.0