@ampproject/rollup-plugin-closure-compiler
Advanced tools
Comparing version 0.18.1 to 0.19.0
@@ -8,4 +8,5 @@ 'use strict'; | ||
var MagicString = _interopDefault(require('magic-string')); | ||
var tempWrite = require('temp-write'); | ||
var path = require('path'); | ||
var os = require('os'); | ||
var uuid = require('uuid'); | ||
@@ -52,3 +53,3 @@ /** | ||
extern(options) { | ||
return ''; | ||
return null; | ||
} | ||
@@ -103,3 +104,3 @@ async preCompilation(code) { | ||
} | ||
return ''; | ||
return null; | ||
} | ||
@@ -109,2 +110,47 @@ } | ||
/** | ||
* Copyright 2020 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 HEADER$1 = `/** | ||
* @fileoverview Externs built via derived configuration from Rollup or input code. | ||
* This extern contains the cjs typing info for modules. | ||
* @externs | ||
*/ | ||
/** | ||
* @typedef {{ | ||
* __esModule: boolean, | ||
* }} | ||
*/ | ||
var exports;`; | ||
/** | ||
* This Transform will apply only if the Rollup configuration is for a cjs output. | ||
* | ||
* In order to preserve the __esModules boolean on an Object, this typedef needs to be present. | ||
*/ | ||
class CJSTransform extends Transform { | ||
constructor() { | ||
super(...arguments); | ||
this.name = 'CJSTransform'; | ||
} | ||
extern(options) { | ||
if (options.format === 'cjs') { | ||
return HEADER$1; | ||
} | ||
return null; | ||
} | ||
} | ||
/** | ||
* Copyright 2018 The AMP HTML Authors. All Rights Reserved. | ||
@@ -184,11 +230,9 @@ * | ||
ObjectExpression(node) { | ||
const properties = node.properties; | ||
properties.forEach(property => { | ||
if (property.computed && | ||
property.key.type === 'Literal' && | ||
property.range && | ||
property.value.range) { | ||
source.overwrite(property.range[0], property.value.range[0], `${property.key.value}${property.value.type !== 'FunctionExpression' ? ':' : ''}`); | ||
for (const property of node.properties) { | ||
const [propertyStart] = property.range; | ||
const [valueStart] = property.value.range; | ||
if (property.computed && property.key.type === 'Literal') { | ||
source.overwrite(propertyStart, valueStart, `${property.key.value}${property.value.type !== 'FunctionExpression' ? ':' : ''}`); | ||
} | ||
}); | ||
} | ||
}, | ||
@@ -204,3 +248,3 @@ }); | ||
/** | ||
* Copyright 2018 The AMP HTML Authors. All Rights Reserved. | ||
* Copyright 2020 The AMP HTML Authors. All Rights Reserved. | ||
* | ||
@@ -219,64 +263,161 @@ * Licensed under the Apache License, Version 2.0 (the "License"); | ||
*/ | ||
function NamedDeclaration(context, declaration) { | ||
const range = declaration.range; | ||
const source = declaration.source && declaration.source.value && typeof declaration.source.value === 'string' | ||
? declaration.source.value | ||
: null; | ||
if (declaration.specifiers) { | ||
const exportDetails = []; | ||
for (const specifier of declaration.specifiers) { | ||
exportDetails.push({ | ||
local: specifier.local.name, | ||
exported: specifier.exported.name, | ||
closureName: specifier.exported.name, | ||
type: ExportClosureMapping.NAMED_CONSTANT, | ||
range, | ||
source, | ||
}); | ||
} | ||
return exportDetails; | ||
function NamedDeclaration(declaration) { | ||
var _a, _b; | ||
const exportDetails = []; | ||
const source = typeof ((_b = (_a = declaration) === null || _a === void 0 ? void 0 : _a.source) === null || _b === void 0 ? void 0 : _b.value) === 'string' ? declaration.source.value : null; | ||
for (const specifier of declaration.specifiers) { | ||
exportDetails.push({ | ||
local: specifier.local.name, | ||
exported: specifier.exported.name, | ||
type: ExportClosureMapping.NAMED_CONSTANT, | ||
range: declaration.range, | ||
source, | ||
}); | ||
} | ||
return []; | ||
return exportDetails; | ||
} | ||
function DefaultDeclaration(context, declaration) { | ||
if (declaration.declaration) { | ||
const range = declaration.range; | ||
const source = null; | ||
if (declaration.declaration.type === 'Identifier' && declaration.declaration.name) { | ||
return [ | ||
{ | ||
local: declaration.declaration.name, | ||
exported: declaration.declaration.name, | ||
closureName: declaration.declaration.name, | ||
type: ExportClosureMapping.NAMED_DEFAULT_FUNCTION, | ||
range, | ||
source, | ||
}, | ||
]; | ||
} | ||
function DefaultDeclaration(defaultDeclaration) { | ||
const { declaration } = defaultDeclaration; | ||
if (declaration.type === 'Identifier' && declaration.name) { | ||
return [ | ||
{ | ||
local: declaration.name, | ||
exported: declaration.name, | ||
type: ExportClosureMapping.NAMED_DEFAULT_FUNCTION, | ||
range: defaultDeclaration.range, | ||
source: null, | ||
}, | ||
]; | ||
} | ||
return []; | ||
} | ||
function literalName(context, 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 ''; | ||
function NodeIsPreservedExport(node) { | ||
return (node.type === 'ExpressionStatement' && | ||
node.expression.type === 'AssignmentExpression' && | ||
node.expression.left.type === 'MemberExpression' && | ||
node.expression.left.object.type === 'Identifier' && | ||
node.expression.left.object.name === 'window'); | ||
} | ||
function PreservedExportName(node) { | ||
const { property } = node; | ||
if (property.type === 'Identifier') { | ||
return property.name; | ||
} | ||
const literalValue = literal.value; | ||
return typeof literalValue === 'string' ? literalValue : ''; | ||
if (property.type === 'Literal' && typeof property.value === 'string') { | ||
return property.value; | ||
} | ||
return null; | ||
} | ||
function importLocalNames(context, declaration) { | ||
const VALID_SPECIFIERS = [IMPORT_SPECIFIER, IMPORT_NAMESPACE_SPECIFIER, IMPORT_DEFAULT_SPECIFIER]; | ||
const returnableSpecifiers = []; | ||
(declaration.specifiers || []).forEach(specifier => { | ||
if (VALID_SPECIFIERS.includes(specifier.type)) { | ||
returnableSpecifiers.push(specifier.local.name); | ||
} | ||
}); | ||
return returnableSpecifiers; | ||
/** | ||
* Copyright 2020 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. | ||
*/ | ||
function PreserveFunction(code, source, ancestor, exportDetails, exportInline) { | ||
// Function Expressions can be inlined instead of preserved as variable references. | ||
// window['foo'] = function(){}; => export function foo(){} / function foo(){} | ||
const assignmentExpression = ancestor.expression; | ||
const memberExpression = assignmentExpression.left; | ||
const functionExpression = assignmentExpression.right; | ||
const [memberExpressionObjectStart] = memberExpression.object.range; | ||
const functionName = exportInline ? exportDetails.exported : exportDetails.local; | ||
if (functionExpression.params.length > 0) { | ||
const [paramsStart] = functionExpression.params[0].range; | ||
// FunctionExpression has parameters. | ||
source.overwrite(memberExpressionObjectStart, paramsStart, `${exportInline ? 'export ' : ''}function ${functionName}(`); | ||
} | ||
else { | ||
const [bodyStart] = functionExpression.body.range; | ||
source.overwrite(memberExpressionObjectStart, bodyStart, `${exportInline ? 'export ' : ''}function ${functionName}()`); | ||
} | ||
return !exportInline; | ||
} | ||
function PreserveIdentifier(code, source, ancestor, exportDetails, exportInline) { | ||
const assignmentExpression = ancestor.expression; | ||
const left = assignmentExpression.left; | ||
const right = assignmentExpression.right; | ||
const [ancestorStart, ancestorEnd] = ancestor.range; | ||
const [rightStart, rightEnd] = right.range; | ||
if (exportInline) { | ||
source.overwrite(ancestorStart, ancestorEnd, `export var ${exportDetails.exported}=${code.substring(rightStart, rightEnd)};`); | ||
} | ||
else if (exportDetails.source === null && 'name' in right) { | ||
// This is a locally defined identifier with a name we can use. | ||
exportDetails.local = right.name; | ||
source.remove(left.range[0], rightEnd + 1); | ||
return true; | ||
} | ||
else { | ||
// exportDetails.local = | ||
source.overwrite(ancestorStart, ancestorEnd, `var ${exportDetails.local}=${code.substring(rightStart, rightEnd)};`); | ||
} | ||
return !exportInline; | ||
} | ||
function PreserveNamedConstant(code, source, ancestor, exportDetails, exportInline) { | ||
const assignmentExpression = ancestor.expression; | ||
switch (assignmentExpression.right.type) { | ||
case 'FunctionExpression': | ||
return PreserveFunction(code, source, ancestor, exportDetails, exportInline); | ||
default: | ||
return PreserveIdentifier(code, source, ancestor, exportDetails, exportInline); | ||
} | ||
} | ||
/** | ||
* Copyright 2020 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. | ||
*/ | ||
function PreserveDefault(code, source, ancestor, exportDetails, exportInline) { | ||
const assignmentExpression = ancestor.expression; | ||
const memberExpression = assignmentExpression.left; | ||
const [memberExpressionStart, memberExpressionEnd] = memberExpression.range; | ||
source.overwrite(memberExpressionStart, memberExpressionEnd + assignmentExpression.operator.length, 'export default '); | ||
return false; | ||
} | ||
/** | ||
* Copyright 2020 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. | ||
*/ | ||
async function writeTempFile(content, extension = '') { | ||
const path$1 = path.join(os.tmpdir(), uuid.v4() + extension); | ||
await fs.promises.mkdir(path.dirname(path$1), { recursive: true }); | ||
await fs.promises.writeFile(path$1, content, 'utf-8'); | ||
return path$1; | ||
} | ||
/** | ||
* Copyright 2018 The AMP HTML Authors. All Rights Reserved. | ||
@@ -296,5 +437,5 @@ * | ||
*/ | ||
/* c8 ignore next 9 */ | ||
const logSource = (preamble, source, code) => { | ||
}; | ||
/* c8 ignore next 16 */ | ||
async function logTransformChain(file, stage, messages) { | ||
} | ||
@@ -320,9 +461,6 @@ /** | ||
* Checks if output format is ESM | ||
* @param format | ||
* @param outputOptions | ||
* @return boolean | ||
*/ | ||
const isESMFormat = (format) => { | ||
// TODO: remove `| 'esm'` when rollup upgrades its typings | ||
return format === 'esm' || format === 'es'; | ||
}; | ||
const isESMFormat = ({ format }) => format === 'esm' || format === 'es'; | ||
/** | ||
@@ -332,3 +470,3 @@ * Throw Errors if compile options will result in unexpected behaviour. | ||
*/ | ||
const validateCompileOptions = (compileOptions) => { | ||
function validateCompileOptions(compileOptions) { | ||
if ('warning_level' in compileOptions && compileOptions.warning_level === 'VERBOSE') { | ||
@@ -338,7 +476,7 @@ if (!('language_out' in compileOptions)) { | ||
} | ||
else if ('language_out' in compileOptions && compileOptions.language_out === 'NO_TRANSPILE') { | ||
if (compileOptions.language_out === 'NO_TRANSPILE') { | ||
throw new Error(ERROR_WARNINGS_ENABLED_LANGUAGE_OUT_INVALID); | ||
} | ||
} | ||
}; | ||
} | ||
/** | ||
@@ -351,3 +489,3 @@ * Generate default Closure Compiler CompileOptions an author can override if they wish. | ||
*/ | ||
const defaults = (options, providedExterns, transformers) => { | ||
const defaults = async (options, providedExterns, transformers) => { | ||
// Defaults for Rollup Projects are slightly different than Closure Compiler defaults. | ||
@@ -358,19 +496,16 @@ // - Users of Rollup tend to transpile their code before handing it to a minifier, | ||
// so safely be more aggressive in minification. | ||
const externs = transformers | ||
? transformers | ||
.map(transform => { | ||
const extern = transform.extern(options); | ||
return extern !== '' ? tempWrite.sync(extern) : false; | ||
}) | ||
.filter(Boolean) | ||
.concat(providedExterns) | ||
: providedExterns.length > 0 | ||
? providedExterns | ||
: ''; | ||
const transformerExterns = []; | ||
for (const transform of transformers || []) { | ||
const extern = transform.extern(options); | ||
if (extern !== null) { | ||
const writtenExtern = await writeTempFile(extern); | ||
transformerExterns.push(writtenExtern); | ||
} | ||
} | ||
return { | ||
language_out: 'NO_TRANSPILE', | ||
assume_function_wrapper: isESMFormat(options.format), | ||
assume_function_wrapper: isESMFormat(options), | ||
warning_level: 'QUIET', | ||
module_resolution: 'NODE', | ||
externs, | ||
externs: transformerExterns.concat(providedExterns), | ||
}; | ||
@@ -387,4 +522,4 @@ }; | ||
*/ | ||
function options (incomingCompileOptions, outputOptions, code, transforms) { | ||
const mapFile = tempWrite.sync(''); | ||
async function options (incomingCompileOptions, outputOptions, code, transforms) { | ||
const mapFile = await writeTempFile(''); | ||
const compileOptions = { ...incomingCompileOptions }; | ||
@@ -408,5 +543,5 @@ let externs = []; | ||
const options = { | ||
...defaults(outputOptions, externs, transforms), | ||
...(await defaults(outputOptions, externs, transforms)), | ||
...compileOptions, | ||
js: tempWrite.sync(code), | ||
js: await writeTempFile(code), | ||
create_source_map: mapFile, | ||
@@ -436,8 +571,2 @@ }; | ||
*/`; | ||
const CJS_EXTERN = `/** | ||
* @typedef {{ | ||
* __esModule: boolean, | ||
* }} | ||
*/ | ||
var exports;`; | ||
/** | ||
@@ -464,4 +593,7 @@ * This Transform will apply only if the Rollup configuration is for 'esm' output. | ||
this.currentSourceExportCount++; | ||
this.originalExports.set(map.local, map); | ||
} | ||
this.originalExports.set(map.closureName, map); | ||
else { | ||
this.originalExports.set(map.exported, map); | ||
} | ||
}); | ||
@@ -485,6 +617,6 @@ } | ||
ExportNamedDeclaration(node) { | ||
storeExport(NamedDeclaration(context, node)); | ||
storeExport(NamedDeclaration(node)); | ||
}, | ||
ExportDefaultDeclaration(node) { | ||
storeExport(DefaultDeclaration(context, node)); | ||
storeExport(DefaultDeclaration(node)); | ||
}, | ||
@@ -497,14 +629,14 @@ ExportAllDeclaration(node) { | ||
} | ||
extern(options) { | ||
let output = EXTERN_OVERVIEW; | ||
if (options.format === 'cjs') { | ||
output += CJS_EXTERN; | ||
} | ||
for (const key of this.originalExports.keys()) { | ||
const value = this.originalExports.get(key); | ||
if (value.source !== null) { | ||
output += `function ${value.closureName}(){};\n`; | ||
extern() { | ||
if (Array.from(this.originalExports.keys()).length > 0) { | ||
let output = EXTERN_OVERVIEW; | ||
for (const key of this.originalExports.keys()) { | ||
const value = this.originalExports.get(key); | ||
if (value.source !== null) { | ||
output += `function ${value.exported}(){};\n`; | ||
} | ||
} | ||
return output; | ||
} | ||
return output; | ||
return null; | ||
} | ||
@@ -520,29 +652,26 @@ /** | ||
async preCompilation(code) { | ||
if (this.outputOptions === null) { | ||
this.context.warn('Rollup Plugin Closure Compiler, OutputOptions not known before Closure Compiler invocation.'); | ||
} | ||
else if (isESMFormat(this.outputOptions.format)) { | ||
await this.deriveExports(code); | ||
const source = new MagicString(code); | ||
for (const key of this.originalExports.keys()) { | ||
const value = this.originalExports.get(key); | ||
// Remove export statements before Closure Compiler sees the code | ||
// This prevents CC from transpiling `export` statements when the language_out is set to a value | ||
// where exports were not part of the language. | ||
source.remove(...value.range); | ||
// Window scoped references for each key are required to ensure Closure Compilre retains the code. | ||
if (value.source === null) { | ||
source.append(`\nwindow['${value.closureName}'] = ${value.local};`); | ||
} | ||
else { | ||
source.append(`\nwindow['${value.closureName}'] = ${value.exported};`); | ||
} | ||
} | ||
if (!isESMFormat(this.outputOptions)) { | ||
return { | ||
code: source.toString(), | ||
map: source.generateMap().mappings, | ||
code, | ||
}; | ||
} | ||
await this.deriveExports(code); | ||
const source = new MagicString(code); | ||
for (const key of this.originalExports.keys()) { | ||
const value = this.originalExports.get(key); | ||
// Remove export statements before Closure Compiler sees the code | ||
// This prevents CC from transpiling `export` statements when the language_out is set to a value | ||
// where exports were not part of the language. | ||
source.remove(...value.range); | ||
// Window scoped references for each key are required to ensure Closure Compilre retains the code. | ||
if (value.source === null) { | ||
source.append(`\nwindow['${value.local}'] = ${value.local};`); | ||
} | ||
else { | ||
source.append(`\nwindow['${value.exported}'] = ${value.exported};`); | ||
} | ||
} | ||
return { | ||
code, | ||
code: source.toString(), | ||
map: source.generateMap().mappings, | ||
}; | ||
@@ -559,119 +688,67 @@ } | ||
async postCompilation(code) { | ||
if (this.outputOptions === null) { | ||
this.context.warn('Rollup Plugin Closure Compiler, OutputOptions not known before Closure Compiler invocation.'); | ||
if (!isESMFormat(this.outputOptions)) { | ||
return { | ||
code, | ||
}; | ||
} | ||
else if (isESMFormat(this.outputOptions.format)) { | ||
const source = new MagicString(code); | ||
const program = parse(code); | ||
let collectedExportsToAppend = new Map(); | ||
const { originalExports, currentSourceExportCount } = this; | ||
source.trimEnd(); | ||
walk.ancestor(program, { | ||
// We inserted window scoped assignments for all the export statements during `preCompilation` | ||
// 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') { | ||
const { property: leftProperty } = ancestor.expression.left; | ||
let exportName = null; | ||
if (leftProperty.type === 'Identifier') { | ||
exportName = leftProperty.name; | ||
const source = new MagicString(code); | ||
const program = parse(code); | ||
const { originalExports, currentSourceExportCount } = this; | ||
let collectedExportsToAppend = new Map(); | ||
source.trimEnd(); | ||
walk.ancestor(program, { | ||
// We inserted window scoped assignments for all the export statements during `preCompilation` | ||
// 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') { | ||
return; | ||
} | ||
for (const ancestor of ancestors) { | ||
if (!NodeIsPreservedExport(ancestor)) { | ||
continue; | ||
} | ||
// Can cast these since they were validated with the `NodeIsPreservedExport` test. | ||
const expression = ancestor.expression; | ||
const left = expression.left; | ||
const exportName = PreservedExportName(left); | ||
if (exportName !== null && originalExports.get(exportName)) { | ||
const exportDetails = originalExports.get(exportName); | ||
const exportIsLocal = exportDetails.source === null; | ||
const exportInline = exportIsLocal && | ||
currentSourceExportCount === 1 && | ||
exportDetails.local === exportDetails.exported; | ||
switch (exportDetails.type) { | ||
case ExportClosureMapping.NAMED_DEFAULT_FUNCTION: | ||
case ExportClosureMapping.DEFAULT: | ||
if (PreserveDefault(code, source, ancestor)) ; | ||
break; | ||
case ExportClosureMapping.NAMED_CONSTANT: | ||
if (PreserveNamedConstant(code, source, ancestor, exportDetails, exportInline)) { | ||
collectedExportsToAppend = ExportTransform.storeExportToAppend(collectedExportsToAppend, exportDetails); | ||
} | ||
else if (leftProperty.type === 'Literal' && | ||
typeof leftProperty.value === 'string') { | ||
exportName = leftProperty.value; | ||
} | ||
if (exportName !== null && originalExports.get(exportName)) { | ||
const exportDetails = originalExports.get(exportName); | ||
switch (exportDetails.type) { | ||
case ExportClosureMapping.NAMED_DEFAULT_FUNCTION: | ||
case ExportClosureMapping.DEFAULT: | ||
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_CONSTANT: | ||
const exportFromCurrentSource = exportDetails.source === null; | ||
const inlineExport = exportFromCurrentSource && currentSourceExportCount === 1; | ||
let exportCollected = false; | ||
if (exportFromCurrentSource) { | ||
const { object: leftObject } = ancestor.expression.left; | ||
if (leftObject.range) { | ||
const { left, right } = ancestor.expression; | ||
switch (right.type) { | ||
case 'FunctionExpression': | ||
// Function Expressions can be inlined instead of preserved as variable references. | ||
// window['foo'] = function(){}; => export function foo(){} / function foo(){} | ||
if (right.params.length > 0) { | ||
// FunctionExpression has parameters. | ||
source.overwrite(leftObject.range[0], right.params[0].range[0], `${inlineExport ? 'export ' : ''}function ${exportDetails.exported}(`); | ||
} | ||
else { | ||
source.overwrite(leftObject.range[0], right.body.range[0], `${inlineExport ? 'export ' : ''}function ${exportDetails.exported}()`); | ||
} | ||
break; | ||
case 'Identifier': | ||
if (left.property.type === 'Identifier') { | ||
// Identifiers are present when a complex object (class) has been saved as an export. | ||
// In this case we currently opt out of inline exporting, since the identifier | ||
// is a mangled name for the export. | ||
exportDetails.local = right.name; | ||
exportDetails.closureName = left.property.name; | ||
source.remove(ancestor.expression.left.range[0], ancestor.expression.right.range[1] + 1); | ||
// Since we're manually mapping the name back from the changes done by Closure | ||
// Ensure the export isn't stored for insertion here and later on. | ||
collectedExportsToAppend = ExportTransform.storeExportToAppend(collectedExportsToAppend, exportDetails); | ||
exportCollected = true; | ||
} | ||
break; | ||
default: | ||
const statement = inlineExport ? 'export var ' : 'var '; | ||
source.overwrite(leftObject.range[0], leftObject.range[1] + 1, statement); | ||
break; | ||
} | ||
} | ||
if (exportDetails.local !== exportDetails.exported) { | ||
exportDetails.local = exportDetails.exported; | ||
exportDetails.closureName = exportDetails.local; | ||
} | ||
} | ||
else if (ancestor.expression.left.range && | ||
ancestor.expression.right.range) { | ||
source.remove(ancestor.expression.left.range[0], ancestor.expression.right.range[1] + 1); | ||
} | ||
if (!inlineExport && !exportCollected) { | ||
collectedExportsToAppend = ExportTransform.storeExportToAppend(collectedExportsToAppend, exportDetails); | ||
} | ||
break; | ||
} | ||
} | ||
} | ||
}); | ||
break; | ||
} | ||
if (!exportIsLocal) { | ||
source.remove(left.range[0], expression.right.range[1] + 1); | ||
} | ||
} | ||
}, | ||
}); | ||
for (const exportSource of collectedExportsToAppend.keys()) { | ||
const toAppend = collectedExportsToAppend.get(exportSource); | ||
if (toAppend && toAppend.length > 0) { | ||
if (exportSource === null) { | ||
source.append(`export{${toAppend.join(',')}}`); | ||
} | ||
else { | ||
source.prepend(`export{${toAppend.join(',')}}from'${exportSource}';`); | ||
} | ||
} | ||
}, | ||
}); | ||
for (const exportSource of collectedExportsToAppend.keys()) { | ||
const toAppend = collectedExportsToAppend.get(exportSource); | ||
if (toAppend && toAppend.length > 0) { | ||
const names = toAppend.join(','); | ||
if (exportSource === null) { | ||
source.append(`export{${names}}`); | ||
} | ||
else { | ||
source.prepend(`export{${names}}from'${exportSource}';`); | ||
} | ||
} | ||
return { | ||
code: source.toString(), | ||
map: source.generateMap().mappings, | ||
}; | ||
} | ||
return { | ||
code, | ||
code: source.toString(), | ||
map: source.generateMap().mappings, | ||
}; | ||
@@ -682,2 +759,76 @@ } | ||
/** | ||
* Copyright 2020 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. | ||
*/ | ||
function literalName(literal) { | ||
return literal.value; | ||
} | ||
/** | ||
* Copyright 2020 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. | ||
*/ | ||
function Specifiers(specifiers) { | ||
const returnable = { | ||
default: null, | ||
specific: [], | ||
local: [], | ||
}; | ||
for (const specifier of specifiers) { | ||
returnable.local.push(specifier.local.name); | ||
switch (specifier.type) { | ||
case IMPORT_SPECIFIER: | ||
case IMPORT_NAMESPACE_SPECIFIER: | ||
const { name: local } = specifier.local; | ||
const { name: imported } = specifier.imported; | ||
if (local === imported) { | ||
returnable.specific.push(local); | ||
} | ||
else { | ||
returnable.specific.push(`${imported} as ${local}`); | ||
} | ||
break; | ||
case IMPORT_DEFAULT_SPECIFIER: | ||
returnable.default = specifier.local.name; | ||
break; | ||
} | ||
} | ||
return returnable; | ||
} | ||
function FormatSpecifiers(specifiers, name) { | ||
let formatted = 'import '; | ||
let values = []; | ||
if (specifiers.default !== null) { | ||
values.push(specifiers.default); | ||
} | ||
if (specifiers.specific.length > 0) { | ||
values.push(`{${specifiers.specific.join(',')}}`); | ||
} | ||
formatted += `${values.join(',')} from '${name}';`; | ||
return formatted; | ||
} | ||
/** | ||
* Copyright 2018 The AMP HTML Authors. All Rights Reserved. | ||
@@ -699,3 +850,3 @@ * | ||
const DYNAMIC_IMPORT_REPLACEMENT = `import_${new Date().getMilliseconds()}`; | ||
const HEADER$1 = `/** | ||
const HEADER$2 = `/** | ||
* @fileoverview Externs built via derived configuration from Rollup or input code. | ||
@@ -720,7 +871,7 @@ * This extern contains the external import names, to prevent compilation failures. | ||
extern() { | ||
let extern = HEADER$1; | ||
let extern = HEADER$2; | ||
if (this.importedExternalsLocalNames.length > 0) { | ||
this.importedExternalsLocalNames.forEach(name => { | ||
for (const name of this.importedExternalsLocalNames) { | ||
extern += `function ${name}(){};\n`; | ||
}); | ||
} | ||
} | ||
@@ -736,3 +887,3 @@ if (this.dynamicImportPresent) { | ||
} | ||
return extern; | ||
return extern === HEADER$2 ? null : extern; | ||
} | ||
@@ -752,25 +903,9 @@ /** | ||
walk.simple(program, { | ||
async ImportDeclaration(node) { | ||
const name = literalName(self.context, node.source); | ||
ImportDeclaration(node) { | ||
const name = literalName(node.source); | ||
const range = node.range; | ||
let defaultSpecifier = null; | ||
const specificSpecifiers = []; | ||
for (const specifier of node.specifiers) { | ||
switch (specifier.type) { | ||
case IMPORT_SPECIFIER: | ||
case IMPORT_NAMESPACE_SPECIFIER: | ||
const { name: local } = specifier.local; | ||
const { name: imported } = specifier.imported; | ||
specificSpecifiers.push(local === imported ? local : `${imported} as ${local}`); | ||
break; | ||
case IMPORT_DEFAULT_SPECIFIER: | ||
defaultSpecifier = specifier.local.name; | ||
break; | ||
} | ||
} | ||
self.importedExternalsSyntax[name] = `import ${defaultSpecifier !== null | ||
? `${defaultSpecifier}${specificSpecifiers.length > 0 ? ',' : ''}` | ||
: ''}${specificSpecifiers.length > 0 ? `{${specificSpecifiers.join(',')}}` : ''} from '${name}';`; | ||
const specifiers = Specifiers(node.specifiers); | ||
self.importedExternalsSyntax[name] = FormatSpecifiers(specifiers, name); | ||
self.importedExternalsLocalNames.push(...specifiers.local); | ||
source.remove(...range); | ||
self.importedExternalsLocalNames = self.importedExternalsLocalNames.concat(importLocalNames(self.context, node)); | ||
}, | ||
@@ -798,3 +933,5 @@ Import(node) { | ||
const program = parse(code); | ||
Object.values(this.importedExternalsSyntax).forEach(importedExternalSyntax => source.prepend(importedExternalSyntax)); | ||
for (const importedExternalSyntax of Object.values(this.importedExternalsSyntax)) { | ||
source.prepend(importedExternalSyntax); | ||
} | ||
walk.simple(program, { | ||
@@ -842,7 +979,4 @@ Identifier(node) { | ||
async postCompilation(code) { | ||
if (this.outputOptions === null) { | ||
this.context.warn('Rollup Plugin Closure Compiler, OutputOptions not known before Closure Compiler invocation.'); | ||
} | ||
else if (isESMFormat(this.outputOptions.format) || | ||
(this.outputOptions.file && path.extname(this.outputOptions.file) === '.mjs')) { | ||
const { file } = this.outputOptions; | ||
if (isESMFormat(this.outputOptions) || (file && path.extname(file) === '.mjs')) { | ||
const source = new MagicString(code); | ||
@@ -852,6 +986,6 @@ const program = parse(code); | ||
ExpressionStatement(node) { | ||
if (node.expression.type === 'Literal' && | ||
node.expression.value === 'use strict' && | ||
node.range) { | ||
source.remove(node.range[0], node.range[1]); | ||
const { type, value } = node.expression; | ||
const range = node.range; | ||
if (type === 'Literal' && value === 'use strict') { | ||
source.remove(...range); | ||
} | ||
@@ -902,4 +1036,5 @@ }, | ||
VariableDeclaration(node) { | ||
if (node.kind === 'const' && node.range) { | ||
source.overwrite(node.range[0], node.range[1], code.substring(node.range[0], node.range[1]).replace('const ', 'let ')); | ||
const [start, end] = node.range; | ||
if (node.kind === 'const') { | ||
source.overwrite(start, end, code.substring(start, end).replace('const', 'let')); | ||
} | ||
@@ -940,2 +1075,3 @@ }, | ||
new IifeTransform(context, options), | ||
new CJSTransform(context, options), | ||
new LiteralComputedKeys(context, options), | ||
@@ -954,3 +1090,7 @@ new StrictTransform(context, options), | ||
*/ | ||
async function preCompilation(code, outputOptions, transforms) { | ||
async function preCompilation(code, outputOptions, chunk, transforms) { | ||
// Each transform has a 'preCompilation' step that must complete before passing | ||
// the resulting code to Closure Compiler. | ||
const log = []; | ||
log.push(['before', code]); | ||
for (const transform of transforms) { | ||
@@ -960,6 +1100,8 @@ transform.outputOptions = outputOptions; | ||
if (result && result.code) { | ||
logSource(`after ${transform.name} preCompilation`, result && result.code); | ||
log.push([transform.name, code]); | ||
code = result.code; | ||
} | ||
} | ||
log.push(['after', code]); | ||
await logTransformChain(chunk.fileName); | ||
return code; | ||
@@ -973,10 +1115,22 @@ } | ||
*/ | ||
async function postCompilation(code, transforms) { | ||
for (const transform of transforms) { | ||
const result = await transform.postCompilation(code); | ||
if (result && result.code) { | ||
logSource(`after ${transform.name} postCompilation`, result && result.code); | ||
code = result.code; | ||
async function postCompilation(code, chunk, transforms) { | ||
// Following successful Closure Compiler compilation, each transform needs an opportunity | ||
// to clean up work is performed in preCompilation via postCompilation. | ||
const log = []; | ||
try { | ||
log.push(['before', code]); | ||
for (const transform of transforms) { | ||
const result = await transform.postCompilation(code); | ||
if (result && result.code) { | ||
log.push([transform.name, result.code]); | ||
code = result.code; | ||
} | ||
} | ||
log.push(['after', code]); | ||
await logTransformChain(chunk.fileName, 'PostCompilation', log); | ||
} | ||
catch (e) { | ||
await logTransformChain(chunk.fileName); | ||
throw e; | ||
} | ||
return code; | ||
@@ -1045,3 +1199,3 @@ } | ||
*/ | ||
function compiler (compileOptions, transforms) { | ||
function compiler (compileOptions, chunk, transforms) { | ||
return new Promise((resolve, reject) => { | ||
@@ -1067,3 +1221,3 @@ const [config, platform] = filterContent(compileOptions); | ||
else { | ||
resolve(await postCompilation(code, transforms)); | ||
resolve(await postCompilation(code, chunk, transforms)); | ||
} | ||
@@ -1097,8 +1251,8 @@ }); | ||
*/ | ||
const renderChunk = async (transforms, requestedCompileOptions = {}, sourceCode, outputOptions) => { | ||
const code = await preCompilation(sourceCode, outputOptions, transforms); | ||
const [compileOptions, mapFile] = options(requestedCompileOptions, outputOptions, code, transforms); | ||
const renderChunk = async (transforms, requestedCompileOptions = {}, sourceCode, outputOptions, chunk) => { | ||
const code = await preCompilation(sourceCode, outputOptions, chunk, transforms); | ||
const [compileOptions, mapFile] = await options(requestedCompileOptions, outputOptions, code, transforms); | ||
try { | ||
return { | ||
code: await compiler(compileOptions, transforms), | ||
code: await compiler(compileOptions, chunk, transforms), | ||
map: JSON.parse(await fs.promises.readFile(mapFile, 'utf8')), | ||
@@ -1127,3 +1281,3 @@ }; | ||
const transforms = createTransforms(context, inputOptions); | ||
const output = await renderChunk(transforms, requestedCompileOptions, code, outputOptions); | ||
const output = await renderChunk(transforms, requestedCompileOptions, code, outputOptions, chunk); | ||
return output || null; | ||
@@ -1130,0 +1284,0 @@ }, |
{ | ||
"name": "@ampproject/rollup-plugin-closure-compiler", | ||
"version": "0.18.1", | ||
"version": "0.19.0", | ||
"description": "Rollup + Google Closure Compiler", | ||
@@ -23,3 +23,3 @@ "main": "dist/index.js", | ||
"scripts": { | ||
"pretest": "rimraf dist transpile && tsc -p tsconfig.test.json & wait", | ||
"pretest": "tsc -p tsconfig.test.json", | ||
"test": "ava", | ||
@@ -31,3 +31,2 @@ "precoverage": "yarn pretest && c8 ava", | ||
"postbuild": "rollup -c", | ||
"lint": "tslint -c tslint.json -p tsconfig.json", | ||
"release": "np", | ||
@@ -43,5 +42,5 @@ "prepublishOnly": "npm-run-all build" | ||
"acorn-walk": "7.0.0", | ||
"google-closure-compiler": "20191111.0.0", | ||
"magic-string": "0.25.4", | ||
"temp-write": "4.0.0" | ||
"google-closure-compiler": "20200101.0.0", | ||
"magic-string": "0.25.5", | ||
"uuid": "3.3.3" | ||
}, | ||
@@ -51,4 +50,4 @@ "devDependencies": { | ||
"@types/estree": "0.0.41", | ||
"@types/node": "12.12.22", | ||
"@types/temp-write": "3.3.0", | ||
"@types/node": "12.12.24", | ||
"@types/uuid": "3.4.6", | ||
"ava": "2.4.0", | ||
@@ -58,3 +57,3 @@ "builtins": "3.0.0", | ||
"codecov": "3.6.1", | ||
"husky": "3.1.0", | ||
"husky": "4.0.1", | ||
"lint-staged": "9.5.0", | ||
@@ -65,5 +64,4 @@ "np": "5.2.1", | ||
"rimraf": "3.0.0", | ||
"rollup": "1.27.14", | ||
"rollup": "1.29.0", | ||
"sirv-cli": "0.4.5", | ||
"tslint": "5.20.1", | ||
"typescript": "3.7.4" | ||
@@ -73,5 +71,8 @@ }, | ||
"*.ts": [ | ||
"yarn lint --fix", | ||
"prettier --config .prettierrc --write", | ||
"git add" | ||
], | ||
"*.test.js": [ | ||
"prettier --config .prettierrc --write", | ||
"git add" | ||
] | ||
@@ -92,5 +93,5 @@ }, | ||
"volta": { | ||
"node": "12.14.0", | ||
"node": "12.14.1", | ||
"yarn": "1.21.1" | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
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
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
115868
17
2450
+ Addeduuid@3.3.3
+ Addedgoogle-closure-compiler@20200101.0.0(transitive)
+ Addedgoogle-closure-compiler-java@20200101.0.0(transitive)
+ Addedgoogle-closure-compiler-js@20200101.0.0(transitive)
+ Addedgoogle-closure-compiler-linux@20200101.0.0(transitive)
+ Addedgoogle-closure-compiler-osx@20200101.0.0(transitive)
+ Addedgoogle-closure-compiler-windows@20200101.0.0(transitive)
+ Addedmagic-string@0.25.5(transitive)
+ Addeduuid@3.3.3(transitive)
- Removedtemp-write@4.0.0
- Removedgoogle-closure-compiler@20191111.0.0(transitive)
- Removedgoogle-closure-compiler-java@20191111.0.0(transitive)
- Removedgoogle-closure-compiler-js@20191111.0.0(transitive)
- Removedgoogle-closure-compiler-linux@20191111.0.0(transitive)
- Removedgoogle-closure-compiler-osx@20191111.0.0(transitive)
- Removedgoogle-closure-compiler-windows@20191111.0.0(transitive)
- Removedgraceful-fs@4.2.11(transitive)
- Removedis-stream@2.0.1(transitive)
- Removedmagic-string@0.25.4(transitive)
- Removedmake-dir@3.1.0(transitive)
- Removedsemver@6.3.1(transitive)
- Removedtemp-dir@1.0.0(transitive)
- Removedtemp-write@4.0.0(transitive)
- Removeduuid@3.4.0(transitive)
Updatedmagic-string@0.25.5