UNPKG

72.9 kBJavaScriptView Raw
1import { promises } from 'fs';
2import { compiler as compiler$1 } from 'google-closure-compiler';
3import { join, dirname, basename, extname } from 'path';
4import { tmpdir } from 'os';
5import { createHash } from 'crypto';
6import { v4 } from 'uuid';
7import MagicString from 'magic-string';
8import remapping from '@ampproject/remapping';
9import { parse as parse$1 } from 'acorn';
10import { asyncWalk } from 'estree-walker';
11
12/**
13 * Copyright 2020 The AMP HTML Authors. All Rights Reserved.
14 *
15 * Licensed under the Apache License, Version 2.0 (the "License");
16 * you may not use this file except in compliance with the License.
17 * You may obtain a copy of the License at
18 *
19 * http://www.apache.org/licenses/LICENSE-2.0
20 *
21 * Unless required by applicable law or agreed to in writing, software
22 * distributed under the License is distributed on an "AS-IS" BASIS,
23 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
24 * See the License for the specific language governing permissions and
25 * limitations under the License.
26 */
27async function writeTempFile(content, extension = '', stableName = true) {
28 let hash;
29 if (stableName) {
30 hash = createHash('sha1')
31 .update(content)
32 .digest('hex');
33 }
34 else {
35 hash = v4();
36 }
37 const fullpath = join(tmpdir(), hash + extension);
38 await promises.mkdir(dirname(fullpath), { recursive: true });
39 await promises.writeFile(fullpath, content, 'utf-8');
40 return fullpath;
41}
42
43/**
44 * Copyright 2018 The AMP HTML Authors. All Rights Reserved.
45 *
46 * Licensed under the Apache License, Version 2.0 (the "License");
47 * you may not use this file except in compliance with the License.
48 * You may obtain a copy of the License at
49 *
50 * http://www.apache.org/licenses/LICENSE-2.0
51 *
52 * Unless required by applicable law or agreed to in writing, software
53 * distributed under the License is distributed on an "AS-IS" BASIS,
54 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
55 * See the License for the specific language governing permissions and
56 * limitations under the License.
57 */
58/* c8 ignore next 15 */
59async function logTransformChain(file, stage, messages) {
60 return;
61}
62/* c8 ignore next 7 */
63const log = (preamble, message) => {
64 return;
65};
66
67/**
68 * Copyright 2018 The AMP HTML Authors. All Rights Reserved.
69 *
70 * Licensed under the Apache License, Version 2.0 (the "License");
71 * you may not use this file except in compliance with the License.
72 * You may obtain a copy of the License at
73 *
74 * http://www.apache.org/licenses/LICENSE-2.0
75 *
76 * Unless required by applicable law or agreed to in writing, software
77 * distributed under the License is distributed on an "AS-IS" BASIS,
78 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
79 * See the License for the specific language governing permissions and
80 * limitations under the License.
81 */
82function createDecodedSourceMap(magicstring, source) {
83 return {
84 ...magicstring.generateDecodedMap({ hires: true, source }),
85 version: 3,
86 };
87}
88function createExistingRawSourceMap(maps, file) {
89 var _a;
90 const remappedSourceMap = remapping(maps, () => null);
91 return {
92 ...remappedSourceMap,
93 sources: remappedSourceMap.sources.map(source => source || ''),
94 sourcesContent: ((_a = remappedSourceMap.sourcesContent) === null || _a === void 0 ? void 0 : _a.map(content => content || '')) || undefined,
95 file,
96 };
97}
98
99/**
100 * Copyright 2020 The AMP HTML Authors. All Rights Reserved.
101 *
102 * Licensed under the Apache License, Version 2.0 (the "License");
103 * you may not use this file except in compliance with the License.
104 * You may obtain a copy of the License at
105 *
106 * http://www.apache.org/licenses/LICENSE-2.0
107 *
108 * Unless required by applicable law or agreed to in writing, software
109 * distributed under the License is distributed on an "AS-IS" BASIS,
110 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
111 * See the License for the specific language governing permissions and
112 * limitations under the License.
113 */
114class Transform {
115 constructor(context, pluginOptions, mangler, memory, inputOptions, outputOptions) {
116 this.name = 'Transform';
117 this.context = context;
118 this.pluginOptions = pluginOptions;
119 this.mangler = mangler;
120 this.memory = memory;
121 this.inputOptions = inputOptions;
122 this.outputOptions = outputOptions;
123 }
124}
125class SourceTransform extends Transform {
126 constructor() {
127 super(...arguments);
128 this.name = 'SourceTransform';
129 }
130 async transform(id, source) {
131 return source;
132 }
133}
134class ChunkTransform extends Transform {
135 constructor() {
136 super(...arguments);
137 this.name = 'ChunkTransform';
138 }
139 extern(options) {
140 return null;
141 }
142 async pre(fileName, source) {
143 return source;
144 }
145 async post(fileName, source) {
146 return source;
147 }
148}
149async function chunkLifecycle(fileName, printableName, method, code, transforms) {
150 const log = [];
151 const sourcemaps = [];
152 let source = new MagicString(code);
153 let finalSource = '';
154 log.push(['before', code]);
155 try {
156 for (const transform of transforms) {
157 const transformed = await transform[method](fileName, source);
158 const transformedSource = transformed.toString();
159 sourcemaps.push(createDecodedSourceMap(transformed, fileName));
160 source = new MagicString(transformedSource);
161 log.push([transform.name, transformedSource]);
162 }
163 finalSource = source.toString();
164 }
165 catch (e) {
166 log.push(['after', finalSource]);
167 await logTransformChain();
168 throw e;
169 }
170 log.push(['after', finalSource]);
171 await logTransformChain();
172 return {
173 code: finalSource,
174 map: createExistingRawSourceMap(sourcemaps, fileName),
175 };
176}
177async function sourceLifecycle(id, printableName, code, transforms) {
178 const fileName = basename(id);
179 const log = [];
180 const sourcemaps = [];
181 let source = new MagicString(code);
182 log.push(['before', code]);
183 for (const transform of transforms) {
184 const transformed = await transform.transform(id, source);
185 const transformedSource = transformed.toString();
186 sourcemaps.push(createDecodedSourceMap(transformed, id));
187 source = new MagicString(transformedSource);
188 log.push([transform.name, transformedSource]);
189 }
190 const finalSource = source.toString();
191 log.push(['after', finalSource]);
192 await logTransformChain();
193 return {
194 code: finalSource,
195 map: createExistingRawSourceMap(sourcemaps, fileName),
196 };
197}
198
199/**
200 * Copyright 2020 The AMP HTML Authors. All Rights Reserved.
201 *
202 * Licensed under the Apache License, Version 2.0 (the "License");
203 * you may not use this file except in compliance with the License.
204 * You may obtain a copy of the License at
205 *
206 * http://www.apache.org/licenses/LICENSE-2.0
207 *
208 * Unless required by applicable law or agreed to in writing, software
209 * distributed under the License is distributed on an "AS-IS" BASIS,
210 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
211 * See the License for the specific language governing permissions and
212 * limitations under the License.
213 */
214/**
215 * Closure Compiler will not compile code that is prefixed with a hashbang (common to rollup output for CLIs).
216 *
217 * This transform will remove the hashbang (if present) and ask Ebbinghaus to remember if for after compilation.
218 */
219class HashbangRemoveTransform extends ChunkTransform {
220 constructor() {
221 super(...arguments);
222 this.name = 'HashbangRemoveTransform';
223 }
224 /**
225 * @param source MagicString of source to process post Closure Compilation.
226 */
227 async pre(fileName, source) {
228 const stringified = source.trim().toString();
229 const match = /^#!.*/.exec(stringified);
230 if (!match) {
231 return source;
232 }
233 this.memory.hashbang = match[0];
234 source.remove(0, match[0].length);
235 return source;
236 }
237}
238
239/**
240 * Copyright 2020 The AMP HTML Authors. All Rights Reserved.
241 *
242 * Licensed under the Apache License, Version 2.0 (the "License");
243 * you may not use this file except in compliance with the License.
244 * You may obtain a copy of the License at
245 *
246 * http://www.apache.org/licenses/LICENSE-2.0
247 *
248 * Unless required by applicable law or agreed to in writing, software
249 * distributed under the License is distributed on an "AS-IS" BASIS,
250 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
251 * See the License for the specific language governing permissions and
252 * limitations under the License.
253 */
254/**
255 * Closure Compiler will not compile code that is prefixed with a hashbang (common to rollup output for CLIs).
256 *
257 * This transform will restore the hashbang if Ebbinghaus knows it exists.
258 */
259class HashbangApplyTransform extends ChunkTransform {
260 constructor() {
261 super(...arguments);
262 this.name = 'HashbangApplyTransform';
263 }
264 /**
265 * @param source MagicString of source to process post Closure Compilation.
266 */
267 async post(fileName, source) {
268 if (this.memory.hashbang === null) {
269 return source;
270 }
271 source.prepend(this.memory.hashbang + '\n');
272 return source;
273 }
274}
275
276/**
277 * Copyright 2018 The AMP HTML Authors. All Rights Reserved.
278 *
279 * Licensed under the Apache License, Version 2.0 (the "License");
280 * you may not use this file except in compliance with the License.
281 * You may obtain a copy of the License at
282 *
283 * http://www.apache.org/licenses/LICENSE-2.0
284 *
285 * Unless required by applicable law or agreed to in writing, software
286 * distributed under the License is distributed on an "AS-IS" BASIS,
287 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
288 * See the License for the specific language governing permissions and
289 * limitations under the License.
290 */
291const HEADER = `/**
292 * @fileoverview Externs built via derived configuration from Rollup or input code.
293 * This extern contains the iife name so it does not get mangled at the top level.
294 * @externs
295 */
296`;
297/**
298 * This Transform will apply only if the Rollup configuration is for a iife output with a defined name.
299 *
300 * In order to preserve the name of the iife output, derive an extern definition for Closure Compiler.
301 * This preserves the name after compilation since Closure now believes it to be a well known global.
302 */
303class IifeTransform extends ChunkTransform {
304 constructor() {
305 super(...arguments);
306 this.name = 'IifeTransform';
307 }
308 extern(options) {
309 if (options.format === 'iife' && options.name) {
310 return HEADER + `window['${options.name}'] = ${options.name};\n`;
311 }
312 return null;
313 }
314}
315
316/**
317 * Copyright 2020 The AMP HTML Authors. All Rights Reserved.
318 *
319 * Licensed under the Apache License, Version 2.0 (the "License");
320 * you may not use this file except in compliance with the License.
321 * You may obtain a copy of the License at
322 *
323 * http://www.apache.org/licenses/LICENSE-2.0
324 *
325 * Unless required by applicable law or agreed to in writing, software
326 * distributed under the License is distributed on an "AS-IS" BASIS,
327 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
328 * See the License for the specific language governing permissions and
329 * limitations under the License.
330 */
331const HEADER$1 = `/**
332* @fileoverview Externs built via derived configuration from Rollup or input code.
333* This extern contains the cjs typing info for modules.
334* @externs
335*/
336
337/**
338* @typedef {{
339* __esModule: boolean,
340* }}
341*/
342var exports;`;
343/**
344 * This Transform will apply only if the Rollup configuration is for a cjs output.
345 *
346 * In order to preserve the __esModules boolean on an Object, this typedef needs to be present.
347 */
348class CJSTransform extends ChunkTransform {
349 constructor() {
350 super(...arguments);
351 this.name = 'CJSTransform';
352 }
353 extern(options) {
354 if (options.format === 'cjs') {
355 return HEADER$1;
356 }
357 return null;
358 }
359}
360
361/**
362 * Copyright 2018 The AMP HTML Authors. All Rights Reserved.
363 *
364 * Licensed under the Apache License, Version 2.0 (the "License");
365 * you may not use this file except in compliance with the License.
366 * You may obtain a copy of the License at
367 *
368 * http://www.apache.org/licenses/LICENSE-2.0
369 *
370 * Unless required by applicable law or agreed to in writing, software
371 * distributed under the License is distributed on an "AS-IS" BASIS,
372 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
373 * See the License for the specific language governing permissions and
374 * limitations under the License.
375 */
376const acornWalk = require('acorn-walk');
377const walk = {
378 simple: acornWalk.simple,
379 ancestor: acornWalk.ancestor,
380};
381const DEFAULT_ACORN_OPTIONS = {
382 ecmaVersion: 2020,
383 sourceType: 'module',
384 preserveParens: false,
385 ranges: true,
386};
387async function parse(fileName, source) {
388 try {
389 return parse$1(source, DEFAULT_ACORN_OPTIONS);
390 }
391 catch (e) {
392 log(`parse exception in ${fileName}`, `file://${await writeTempFile(source, '.js')}`);
393 throw e;
394 }
395}
396function isIdentifier(node) {
397 return node.type === 'Identifier';
398}
399function isImportDeclaration(node) {
400 return node.type === 'ImportDeclaration';
401}
402function isImportExpression(node) {
403 // @types/estree does not yet support 2020 addons to ECMA.
404 // This includes ImportExpression ... import("thing")
405 return node.type === 'ImportExpression';
406}
407function isVariableDeclarator(node) {
408 return node.type === 'VariableDeclarator';
409}
410function isBlockStatement(node) {
411 return node.type === 'BlockStatement';
412}
413function isExportNamedDeclaration(node) {
414 return node.type === 'ExportNamedDeclaration';
415}
416function isFunctionDeclaration(node) {
417 return node.type === 'FunctionDeclaration';
418}
419function isVariableDeclaration(node) {
420 return node.type === 'VariableDeclaration';
421}
422function isClassDeclaration(node) {
423 return node.type === 'ClassDeclaration';
424}
425function isProperty(node) {
426 return node.type === 'Property';
427}
428
429/**
430 * Copyright 2018 The AMP HTML Authors. All Rights Reserved.
431 *
432 * Licensed under the Apache License, Version 2.0 (the "License");
433 * you may not use this file except in compliance with the License.
434 * You may obtain a copy of the License at
435 *
436 * http://www.apache.org/licenses/LICENSE-2.0
437 *
438 * Unless required by applicable law or agreed to in writing, software
439 * distributed under the License is distributed on an "AS-IS" BASIS,
440 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
441 * See the License for the specific language governing permissions and
442 * limitations under the License.
443 */
444/**
445 * Closure Compiler will not transform computed keys with literal values back to the literal value.
446 * e.g {[0]: 'value'} => {0: 'value'}
447 *
448 * This transform does so only if a computed key is a Literal, and thus easily known to be static.
449 * @see https://astexplorer.net/#/gist/d2414b45a81db3a41ee6902bfd09947a/d7176ac33a2733e1a4b1f65ec3ac626e24f7b60d
450 */
451class LiteralComputedKeys extends ChunkTransform {
452 constructor() {
453 super(...arguments);
454 this.name = 'LiteralComputedKeysTransform';
455 }
456 /**
457 * @param code source to parse, and modify
458 * @return modified input source with computed literal keys
459 */
460 async post(fileName, source) {
461 const program = await parse(fileName, source.toString());
462 walk.simple(program, {
463 ObjectExpression(node) {
464 for (const property of node.properties) {
465 if (isProperty(property) && property.computed && property.key.type === 'Literal') {
466 const [propertyStart] = property.range;
467 const [valueStart] = property.value.range;
468 source.overwrite(propertyStart, valueStart, `${property.key.value}${property.value.type !== 'FunctionExpression' ? ':' : ''}`);
469 }
470 }
471 },
472 });
473 return source;
474 }
475}
476
477/**
478 * Copyright 2018 The AMP HTML Authors. All Rights Reserved.
479 *
480 * Licensed under the Apache License, Version 2.0 (the "License");
481 * you may not use this file except in compliance with the License.
482 * You may obtain a copy of the License at
483 *
484 * http://www.apache.org/licenses/LICENSE-2.0
485 *
486 * Unless required by applicable law or agreed to in writing, software
487 * distributed under the License is distributed on an "AS-IS" BASIS,
488 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
489 * See the License for the specific language governing permissions and
490 * limitations under the License.
491 */
492const IMPORT_SPECIFIER = 'ImportSpecifier';
493const IMPORT_DEFAULT_SPECIFIER = 'ImportDefaultSpecifier';
494const IMPORT_NAMESPACE_SPECIFIER = 'ImportNamespaceSpecifier';
495var ExportClosureMapping;
496(function (ExportClosureMapping) {
497 ExportClosureMapping[ExportClosureMapping["NAMED_FUNCTION"] = 0] = "NAMED_FUNCTION";
498 ExportClosureMapping[ExportClosureMapping["NAMED_CLASS"] = 1] = "NAMED_CLASS";
499 ExportClosureMapping[ExportClosureMapping["NAMED_DEFAULT_FUNCTION"] = 2] = "NAMED_DEFAULT_FUNCTION";
500 ExportClosureMapping[ExportClosureMapping["DEFAULT_FUNCTION"] = 3] = "DEFAULT_FUNCTION";
501 ExportClosureMapping[ExportClosureMapping["NAMED_DEFAULT_CLASS"] = 4] = "NAMED_DEFAULT_CLASS";
502 ExportClosureMapping[ExportClosureMapping["DEFAULT_CLASS"] = 5] = "DEFAULT_CLASS";
503 ExportClosureMapping[ExportClosureMapping["NAMED_CONSTANT"] = 6] = "NAMED_CONSTANT";
504 ExportClosureMapping[ExportClosureMapping["DEFAULT"] = 7] = "DEFAULT";
505 ExportClosureMapping[ExportClosureMapping["DEFAULT_VALUE"] = 8] = "DEFAULT_VALUE";
506 ExportClosureMapping[ExportClosureMapping["DEFAULT_OBJECT"] = 9] = "DEFAULT_OBJECT";
507})(ExportClosureMapping || (ExportClosureMapping = {}));
508
509/**
510 * Copyright 2020 The AMP HTML Authors. All Rights Reserved.
511 *
512 * Licensed under the Apache License, Version 2.0 (the "License");
513 * you may not use this file except in compliance with the License.
514 * You may obtain a copy of the License at
515 *
516 * http://www.apache.org/licenses/LICENSE-2.0
517 *
518 * Unless required by applicable law or agreed to in writing, software
519 * distributed under the License is distributed on an "AS-IS" BASIS,
520 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
521 * See the License for the specific language governing permissions and
522 * limitations under the License.
523 */
524/**
525 * Locally within exporting we always need a name
526 * If value passed is not present in the mangler then use original name.
527 * @param input
528 * @param unmangle
529 */
530function getName(input, unmangle) {
531 if (unmangle) {
532 return unmangle(input) || input;
533 }
534 return input;
535}
536function NamedDeclaration(node, unmangle) {
537 var _a;
538 const exportDetails = [];
539 const range = node.range;
540 const source = typeof ((_a = node.source) === null || _a === void 0 ? void 0 : _a.value) === 'string' ? node.source.value : null;
541 const { specifiers, declaration } = node;
542 // NamedDeclarations either have specifiers or declarations.
543 if (specifiers.length > 0) {
544 for (const specifier of specifiers) {
545 const exported = getName(specifier.exported.name, unmangle);
546 exportDetails.push({
547 local: getName(specifier.local.name, unmangle),
548 exported,
549 type: ExportClosureMapping.NAMED_CONSTANT,
550 range,
551 source,
552 });
553 }
554 return exportDetails;
555 }
556 if (declaration) {
557 if (isFunctionDeclaration(declaration)) {
558 // Only default exports can be missing an identifier.
559 exportDetails.push({
560 local: getName(declaration.id.name, unmangle),
561 exported: getName(declaration.id.name, unmangle),
562 type: ExportClosureMapping.NAMED_FUNCTION,
563 range,
564 source,
565 });
566 }
567 if (isVariableDeclaration(declaration)) {
568 for (const eachDeclaration of declaration.declarations) {
569 if (isIdentifier(eachDeclaration.id)) {
570 exportDetails.push({
571 local: getName(eachDeclaration.id.name, unmangle),
572 exported: getName(eachDeclaration.id.name, unmangle),
573 type: ExportClosureMapping.NAMED_CONSTANT,
574 range,
575 source,
576 });
577 }
578 }
579 }
580 if (isClassDeclaration(declaration)) {
581 // Only default exports can be missing an identifier.
582 exportDetails.push({
583 local: getName(declaration.id.name, unmangle),
584 exported: getName(declaration.id.name, unmangle),
585 type: ExportClosureMapping.NAMED_CLASS,
586 range,
587 source,
588 });
589 }
590 }
591 return exportDetails;
592}
593function DefaultDeclaration(defaultDeclaration, unmangle) {
594 const { declaration } = defaultDeclaration;
595 if (declaration.type === 'Identifier' && declaration.name) {
596 return [
597 {
598 local: getName(declaration.name, unmangle),
599 exported: getName(declaration.name, unmangle),
600 type: ExportClosureMapping.NAMED_DEFAULT_FUNCTION,
601 range: defaultDeclaration.range,
602 source: null,
603 },
604 ];
605 }
606 return [];
607}
608function NodeIsPreservedExport(node) {
609 return (node.type === 'ExpressionStatement' &&
610 node.expression.type === 'AssignmentExpression' &&
611 node.expression.left.type === 'MemberExpression' &&
612 node.expression.left.object.type === 'Identifier' &&
613 node.expression.left.object.name === 'window');
614}
615function PreservedExportName(node) {
616 const { property } = node;
617 if (property.type === 'Identifier') {
618 return property.name;
619 }
620 if (property.type === 'Literal' && typeof property.value === 'string') {
621 return property.value;
622 }
623 return null;
624}
625
626/**
627 * Copyright 2020 The AMP HTML Authors. All Rights Reserved.
628 *
629 * Licensed under the Apache License, Version 2.0 (the "License");
630 * you may not use this file except in compliance with the License.
631 * You may obtain a copy of the License at
632 *
633 * http://www.apache.org/licenses/LICENSE-2.0
634 *
635 * Unless required by applicable law or agreed to in writing, software
636 * distributed under the License is distributed on an "AS-IS" BASIS,
637 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
638 * See the License for the specific language governing permissions and
639 * limitations under the License.
640 */
641function PreserveFunction(code, source, ancestor, exportDetails, exportInline) {
642 // Function Expressions can be inlined instead of preserved as variable references.
643 // window['foo'] = function(){}; => export function foo(){} / function foo(){}
644 const assignmentExpression = ancestor.expression;
645 const memberExpression = assignmentExpression.left;
646 const functionExpression = assignmentExpression.right;
647 const [memberExpressionObjectStart] = memberExpression.object.range;
648 const functionName = exportInline ? exportDetails.exported : exportDetails.local;
649 if (functionExpression.params.length > 0) {
650 const [paramsStart] = functionExpression.params[0].range;
651 // FunctionExpression has parameters.
652 source.overwrite(memberExpressionObjectStart, paramsStart, `${exportInline ? 'export ' : ''}function ${functionName}(`);
653 }
654 else {
655 const [bodyStart] = functionExpression.body.range;
656 source.overwrite(memberExpressionObjectStart, bodyStart, `${exportInline ? 'export ' : ''}function ${functionName}()`);
657 }
658 return !exportInline;
659}
660function PreserveIdentifier(code, source, ancestor, exportDetails, exportInline) {
661 const assignmentExpression = ancestor.expression;
662 const left = assignmentExpression.left;
663 const right = assignmentExpression.right;
664 const [ancestorStart, ancestorEnd] = ancestor.range;
665 const [leftStart] = left.range;
666 const [rightStart, rightEnd] = right.range;
667 if (exportInline) {
668 const output = (exportDetails.exported === 'default'
669 ? `export default `
670 : `export var ${exportDetails.exported}=`) + `${code.substring(rightStart, rightEnd)};`;
671 source.overwrite(ancestorStart, ancestorEnd, output);
672 }
673 else if (exportDetails.source === null && 'name' in right) {
674 // This is a locally defined identifier with a name we can use.
675 exportDetails.local = right.name;
676 source.remove(leftStart, ancestorEnd);
677 return true;
678 }
679 else {
680 source.overwrite(ancestorStart, ancestorEnd, `var ${exportDetails.local}=${code.substring(rightStart, rightEnd)};`);
681 }
682 return !exportInline;
683}
684function PreserveNamedConstant(code, source, ancestor, exportDetails, exportInline) {
685 const assignmentExpression = ancestor.expression;
686 switch (assignmentExpression.right.type) {
687 case 'FunctionExpression':
688 return PreserveFunction(code, source, ancestor, exportDetails, exportInline);
689 default:
690 return PreserveIdentifier(code, source, ancestor, exportDetails, exportInline);
691 }
692}
693
694/**
695 * Copyright 2020 The AMP HTML Authors. All Rights Reserved.
696 *
697 * Licensed under the Apache License, Version 2.0 (the "License");
698 * you may not use this file except in compliance with the License.
699 * You may obtain a copy of the License at
700 *
701 * http://www.apache.org/licenses/LICENSE-2.0
702 *
703 * Unless required by applicable law or agreed to in writing, software
704 * distributed under the License is distributed on an "AS-IS" BASIS,
705 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
706 * See the License for the specific language governing permissions and
707 * limitations under the License.
708 */
709function PreserveDefault(code, source, ancestor, exportDetails, exportInline) {
710 const assignmentExpression = ancestor.expression;
711 const [leftStart] = assignmentExpression.left.range;
712 const [rightStart] = assignmentExpression.right.range;
713 source.overwrite(leftStart, rightStart, 'export default ');
714 return false;
715}
716
717/**
718 * Copyright 2018 The AMP HTML Authors. All Rights Reserved.
719 *
720 * Licensed under the Apache License, Version 2.0 (the "License");
721 * you may not use this file except in compliance with the License.
722 * You may obtain a copy of the License at
723 *
724 * http://www.apache.org/licenses/LICENSE-2.0
725 *
726 * Unless required by applicable law or agreed to in writing, software
727 * distributed under the License is distributed on an "AS-IS" BASIS,
728 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
729 * See the License for the specific language governing permissions and
730 * limitations under the License.
731 */
732const ERROR_WARNINGS_ENABLED_LANGUAGE_OUT_UNSPECIFIED = 'Providing the warning_level=VERBOSE compile option also requires a valid language_out compile option.';
733const ERROR_WARNINGS_ENABLED_LANGUAGE_OUT_INVALID = 'Providing the warning_level=VERBOSE and language_out=NO_TRANSPILE compile options will remove warnings.';
734const OPTIONS_TO_REMOVE_FOR_CLOSURE = ['remove_strict_directive'];
735/**
736 * Checks if output format is ESM
737 * @param outputOptions
738 * @return boolean
739 */
740const isESMFormat = ({ format }) => format === 'esm' || format === 'es';
741/**
742 * Throw Errors if compile options will result in unexpected behaviour.
743 * @param compileOptions
744 */
745function validateCompileOptions(compileOptions) {
746 if ('warning_level' in compileOptions && compileOptions.warning_level === 'VERBOSE') {
747 if (!('language_out' in compileOptions)) {
748 throw new Error(ERROR_WARNINGS_ENABLED_LANGUAGE_OUT_UNSPECIFIED);
749 }
750 if (compileOptions.language_out === 'NO_TRANSPILE') {
751 throw new Error(ERROR_WARNINGS_ENABLED_LANGUAGE_OUT_INVALID);
752 }
753 }
754}
755/**
756 * Normalize the compile options given by the user into something usable.
757 * @param compileOptions
758 */
759function normalizeExternOptions(compileOptions) {
760 validateCompileOptions(compileOptions);
761 let externs = [];
762 if ('externs' in compileOptions) {
763 switch (typeof compileOptions.externs) {
764 case 'boolean':
765 externs = [];
766 break;
767 case 'string':
768 externs = [compileOptions.externs];
769 break;
770 default:
771 externs = compileOptions.externs;
772 break;
773 }
774 delete compileOptions.externs;
775 }
776 if (compileOptions) {
777 for (const optionToDelete of OPTIONS_TO_REMOVE_FOR_CLOSURE) {
778 if (optionToDelete in compileOptions) {
779 // @ts-ignore
780 delete compileOptions[optionToDelete];
781 }
782 }
783 }
784 return [externs, compileOptions];
785}
786/**
787 * Pluck the PluginOptions from the CompileOptions
788 * @param compileOptions
789 */
790function pluckPluginOptions(compileOptions) {
791 const pluginOptions = {};
792 if (!compileOptions) {
793 return pluginOptions;
794 }
795 for (const optionToDelete of OPTIONS_TO_REMOVE_FOR_CLOSURE) {
796 if (optionToDelete in compileOptions) {
797 // @ts-ignore
798 pluginOptions[optionToDelete] = compileOptions[optionToDelete];
799 }
800 }
801 return pluginOptions;
802}
803/**
804 * Generate default Closure Compiler CompileOptions an author can override if they wish.
805 * These must be derived from configuration or input sources.
806 * @param transformers
807 * @param options
808 * @return derived CompileOptions for Closure Compiler
809 */
810const defaults = async (options, providedExterns, transformers) => {
811 // Defaults for Rollup Projects are slightly different than Closure Compiler defaults.
812 // - Users of Rollup tend to transpile their code before handing it to a minifier,
813 // so no transpile is default.
814 // - When Rollup output is set to "es|esm" it is expected the code will live in a ES Module,
815 // so safely be more aggressive in minification.
816 const transformerExterns = [];
817 for (const transform of transformers || []) {
818 const extern = transform.extern(options);
819 if (extern !== null) {
820 const writtenExtern = await writeTempFile(extern);
821 transformerExterns.push(writtenExtern);
822 }
823 }
824 return {
825 language_out: 'NO_TRANSPILE',
826 assume_function_wrapper: isESMFormat(options),
827 warning_level: 'QUIET',
828 module_resolution: 'NODE',
829 externs: transformerExterns.concat(providedExterns),
830 };
831};
832/**
833 * Compile Options is the final configuration to pass into Closure Compiler.
834 * defaultCompileOptions are overrideable by ones passed in directly to the plugin
835 * but the js source and sourcemap are not overrideable, since this would break the output if passed.
836 * @param compileOptions
837 * @param outputOptions
838 * @param code
839 * @param transforms
840 */
841async function options (incomingCompileOptions, outputOptions, code, transforms) {
842 const mapFile = await writeTempFile('', '', false);
843 const [externs, compileOptions] = normalizeExternOptions({ ...incomingCompileOptions });
844 const options = {
845 ...(await defaults(outputOptions, externs, transforms)),
846 ...compileOptions,
847 js: await writeTempFile(code),
848 create_source_map: mapFile,
849 };
850 return [options, mapFile];
851}
852
853/**
854 * Copyright 2018 The AMP HTML Authors. All Rights Reserved.
855 *
856 * Licensed under the Apache License, Version 2.0 (the "License");
857 * you may not use this file except in compliance with the License.
858 * You may obtain a copy of the License at
859 *
860 * http://www.apache.org/licenses/LICENSE-2.0
861 *
862 * Unless required by applicable law or agreed to in writing, software
863 * distributed under the License is distributed on an "AS-IS" BASIS,
864 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
865 * See the License for the specific language governing permissions and
866 * limitations under the License.
867 */
868const EXTERN_OVERVIEW = `/**
869* @fileoverview Externs built via derived configuration from Rollup or input code.
870* @externs
871*/`;
872/**
873 * This Transform will apply only if the Rollup configuration is for 'esm' output.
874 *
875 * In order to preserve the export statements:
876 * 1. Create extern definitions for them (to keep them their names from being mangled).
877 * 2. Insert additional JS referencing the exported names on the window scope
878 * 3. After Closure Compilation is complete, replace the window scope references with the original export statements.
879 */
880class ExportTransform extends ChunkTransform {
881 constructor() {
882 super(...arguments);
883 this.name = 'ExportTransform';
884 this.originalExports = new Map();
885 this.currentSourceExportCount = 0;
886 /**
887 * Store an export from a source into the originalExports Map.
888 * @param mapping mapping of details from this declaration.
889 */
890 this.storeExport = (mapping) => mapping.forEach(map => {
891 if (map.source === null) {
892 this.currentSourceExportCount++;
893 this.originalExports.set(map.local, map);
894 }
895 else {
896 this.originalExports.set(map.exported, map);
897 }
898 });
899 }
900 static storeExportToAppend(collected, exportDetails) {
901 const update = collected.get(exportDetails.source) || [];
902 if (exportDetails.exported === exportDetails.local) {
903 update.push(exportDetails.exported);
904 }
905 else {
906 update.push(`${exportDetails.local} as ${exportDetails.exported}`);
907 }
908 collected.set(exportDetails.source, update);
909 return collected;
910 }
911 async deriveExports(fileName, code) {
912 const program = await parse(fileName, code);
913 walk.simple(program, {
914 ExportNamedDeclaration: (node) => {
915 this.storeExport(NamedDeclaration(node, this.mangler.getName));
916 },
917 ExportDefaultDeclaration: (node) => {
918 this.storeExport(DefaultDeclaration(node, this.mangler.getName));
919 },
920 ExportAllDeclaration: () => {
921 // TODO(KB): This case `export * from "./import"` is not currently supported.
922 this.context.error(new Error(`Rollup Plugin Closure Compiler does not support export all syntax for externals.`));
923 },
924 });
925 }
926 extern() {
927 if (Array.from(this.originalExports.keys()).length > 0) {
928 let output = EXTERN_OVERVIEW;
929 for (const key of this.originalExports.keys()) {
930 const value = this.originalExports.get(key);
931 if (value.source !== null) {
932 output += `function ${value.exported}(){};\n`;
933 }
934 }
935 return output;
936 }
937 return null;
938 }
939 /**
940 * Before Closure Compiler modifies the source, we need to ensure it has window scoped
941 * references to the named exports. This prevents Closure from mangling their names.
942 * @param code source to parse, and modify
943 * @param chunk OutputChunk from Rollup for this code.
944 * @param id Rollup id reference to the source
945 * @return modified input source with window scoped references.
946 */
947 async pre(fileName, source) {
948 if (!isESMFormat(this.outputOptions)) {
949 return super.pre(fileName, source);
950 }
951 const code = source.toString();
952 await this.deriveExports(fileName, code);
953 for (const key of this.originalExports.keys()) {
954 const value = this.originalExports.get(key);
955 // Remove export statements before Closure Compiler sees the code
956 // This prevents CC from transpiling `export` statements when the language_out is set to a value
957 // where exports were not part of the language.
958 source.remove(...value.range);
959 // Window scoped references for each key are required to ensure Closure Compilre retains the code.
960 if (value.source === null) {
961 source.append(`\nwindow['${value.local}'] = ${value.local};`);
962 }
963 else {
964 source.append(`\nwindow['${value.exported}'] = ${value.exported};`);
965 }
966 }
967 return source;
968 }
969 /**
970 * After Closure Compiler has modified the source, we need to replace the window scoped
971 * references we added with the intended export statements
972 * @param code source post Closure Compiler Compilation
973 * @return Promise containing the repaired source
974 */
975 async post(fileName, source) {
976 if (!isESMFormat(this.outputOptions)) {
977 return super.post(fileName, source);
978 }
979 const code = source.toString();
980 const program = await parse(fileName, code);
981 let collectedExportsToAppend = new Map();
982 source.trimEnd();
983 walk.ancestor(program, {
984 // We inserted window scoped assignments for all the export statements during `preCompilation`
985 // Now we need to find where Closure Compiler moved them, and restore the exports of their name.
986 // ASTExporer Link: https://astexplorer.net/#/gist/94f185d06a4105d64828f1b8480bddc8/0fc5885ae5343f964d0cdd33c7d392a70cf5fcaf
987 Identifier: (node, ancestors) => {
988 if (node.name !== 'window') {
989 return;
990 }
991 for (const ancestor of ancestors) {
992 if (!NodeIsPreservedExport(ancestor)) {
993 continue;
994 }
995 // Can cast these since they were validated with the `NodeIsPreservedExport` test.
996 const expression = ancestor.expression;
997 const left = expression.left;
998 const exportName = PreservedExportName(left);
999 if (exportName !== null && this.originalExports.get(exportName)) {
1000 const exportDetails = this.originalExports.get(exportName);
1001 const exportIsLocal = exportDetails.source === null;
1002 const exportInline = (exportIsLocal &&
1003 this.currentSourceExportCount === 1 &&
1004 exportDetails.local === exportDetails.exported) ||
1005 exportDetails.exported === 'default';
1006 switch (exportDetails.type) {
1007 case ExportClosureMapping.NAMED_DEFAULT_FUNCTION:
1008 case ExportClosureMapping.DEFAULT:
1009 if (PreserveDefault(code, source, ancestor)) ;
1010 break;
1011 case ExportClosureMapping.NAMED_CONSTANT:
1012 if (PreserveNamedConstant(code, source, ancestor, exportDetails, exportInline)) {
1013 collectedExportsToAppend = ExportTransform.storeExportToAppend(collectedExportsToAppend, exportDetails);
1014 }
1015 break;
1016 }
1017 if (!exportIsLocal) {
1018 const [leftStart] = left.range;
1019 const { 1: ancestorEnd } = ancestor.range;
1020 source.remove(leftStart, ancestorEnd);
1021 }
1022 // An Export can only be processed once.
1023 this.originalExports.delete(exportName);
1024 }
1025 }
1026 },
1027 });
1028 for (const exportSource of collectedExportsToAppend.keys()) {
1029 const toAppend = collectedExportsToAppend.get(exportSource);
1030 if (toAppend && toAppend.length > 0) {
1031 const names = toAppend.join(',');
1032 if (exportSource === null) {
1033 source.append(`export{${names}}`);
1034 }
1035 else {
1036 source.prepend(`export{${names}}from'${exportSource}';`);
1037 }
1038 }
1039 }
1040 return source;
1041 }
1042}
1043
1044/**
1045 * Copyright 2020 The AMP HTML Authors. All Rights Reserved.
1046 *
1047 * Licensed under the Apache License, Version 2.0 (the "License");
1048 * you may not use this file except in compliance with the License.
1049 * You may obtain a copy of the License at
1050 *
1051 * http://www.apache.org/licenses/LICENSE-2.0
1052 *
1053 * Unless required by applicable law or agreed to in writing, software
1054 * distributed under the License is distributed on an "AS-IS" BASIS,
1055 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1056 * See the License for the specific language governing permissions and
1057 * limitations under the License.
1058 */
1059function literalName(literal) {
1060 return literal.value;
1061}
1062
1063/**
1064 * Copyright 2020 The AMP HTML Authors. All Rights Reserved.
1065 *
1066 * Licensed under the Apache License, Version 2.0 (the "License");
1067 * you may not use this file except in compliance with the License.
1068 * You may obtain a copy of the License at
1069 *
1070 * http://www.apache.org/licenses/LICENSE-2.0
1071 *
1072 * Unless required by applicable law or agreed to in writing, software
1073 * distributed under the License is distributed on an "AS-IS" BASIS,
1074 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1075 * See the License for the specific language governing permissions and
1076 * limitations under the License.
1077 */
1078function Specifiers(specifiers) {
1079 var _a;
1080 const returnable = {
1081 default: null,
1082 specific: [],
1083 local: [],
1084 namespace: false,
1085 };
1086 for (const specifier of specifiers) {
1087 returnable.local.push(specifier.local.name);
1088 switch (specifier.type) {
1089 case IMPORT_SPECIFIER:
1090 const { name: local } = specifier.local;
1091 const { name: imported } = ((_a = specifier) === null || _a === void 0 ? void 0 : _a.imported) || {
1092 name: specifier.local,
1093 };
1094 if (local === imported) {
1095 returnable.specific.push(local);
1096 }
1097 else {
1098 returnable.specific.push(`${imported} as ${local}`);
1099 }
1100 break;
1101 case IMPORT_NAMESPACE_SPECIFIER:
1102 const { name: namespace } = specifier.local;
1103 returnable.specific.push(namespace);
1104 returnable.namespace = true;
1105 break;
1106 case IMPORT_DEFAULT_SPECIFIER:
1107 returnable.default = specifier.local.name;
1108 break;
1109 }
1110 }
1111 return returnable;
1112}
1113function FormatSpecifiers(specifiers, name) {
1114 const hasDefault = specifiers.default !== null;
1115 const hasNamespace = specifiers.namespace === true;
1116 const hasSpecifics = !hasNamespace && specifiers.specific.length > 0;
1117 const hasLocals = specifiers.local.length > 0;
1118 const includesFrom = hasNamespace || hasNamespace || hasSpecifics || hasLocals;
1119 let formatted = 'import';
1120 let values = [];
1121 if (hasDefault) {
1122 values.push(`${specifiers.default}`);
1123 }
1124 if (hasNamespace) {
1125 values.push(`* as ${specifiers.specific[0]}`);
1126 }
1127 if (hasSpecifics) {
1128 values.push(`{${specifiers.specific.join(',')}}`);
1129 }
1130 formatted += `${hasDefault || hasNamespace ? ' ' : ''}${values.join(',')}${hasSpecifics ? '' : ' '}${includesFrom ? 'from' : ''}'${name}';`;
1131 return formatted;
1132}
1133
1134/**
1135 * Copyright 2018 The AMP HTML Authors. All Rights Reserved.
1136 *
1137 * Licensed under the Apache License, Version 2.0 (the "License");
1138 * you may not use this file except in compliance with the License.
1139 * You may obtain a copy of the License at
1140 *
1141 * http://www.apache.org/licenses/LICENSE-2.0
1142 *
1143 * Unless required by applicable law or agreed to in writing, software
1144 * distributed under the License is distributed on an "AS-IS" BASIS,
1145 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1146 * See the License for the specific language governing permissions and
1147 * limitations under the License.
1148 */
1149const DYNAMIC_IMPORT_KEYWORD = 'import';
1150const DYNAMIC_IMPORT_REPLACEMENT = `import_${new Date().getMilliseconds()}`;
1151const HEADER$2 = `/**
1152* @fileoverview Externs built via derived configuration from Rollup or input code.
1153* This extern contains the external import names, to prevent compilation failures.
1154* @externs
1155*/
1156`;
1157/**
1158 * Locally within imports we always need a name
1159 * If value passed is not present in the mangler then use original name.
1160 * @param input
1161 * @param unmangle
1162 */
1163function getName$1(input, unmangle) {
1164 if (unmangle) {
1165 return unmangle(input) || input;
1166 }
1167 return input;
1168}
1169class ImportTransform extends ChunkTransform {
1170 constructor() {
1171 super(...arguments);
1172 this.importedExternalsSyntax = {};
1173 this.importedExternalsLocalNames = [];
1174 this.dynamicImportPresent = false;
1175 this.name = 'ImportTransform';
1176 /**
1177 * Before Closure Compiler modifies the source, we need to ensure external imports have been removed
1178 * since Closure will error out when it encounters them.
1179 * @param code source to parse, and modify
1180 * @return modified input source with external imports removed.
1181 */
1182 this.pre = async (fileName, source) => {
1183 const code = source.toString();
1184 let program = await parse(fileName, code);
1185 let dynamicImportPresent = false;
1186 let { mangler, importedExternalsSyntax, importedExternalsLocalNames } = this;
1187 await asyncWalk(program, {
1188 enter: async function (node) {
1189 if (isImportDeclaration(node)) {
1190 const [importDeclarationStart, importDeclarationEnd] = node.range;
1191 const originalName = literalName(node.source);
1192 let specifiers = Specifiers(node.specifiers);
1193 specifiers = {
1194 ...specifiers,
1195 default: mangler.getName(specifiers.default || '') || specifiers.default,
1196 specific: specifiers.specific.map(specific => {
1197 if (specific.includes(' as ')) {
1198 const split = specific.split(' as ');
1199 return `${getName$1(split[0])} as ${getName$1(split[1])}`;
1200 }
1201 return getName$1(specific);
1202 }),
1203 local: specifiers.local.map(local => getName$1(local)),
1204 };
1205 const unmangledName = getName$1(originalName);
1206 importedExternalsSyntax[unmangledName] = FormatSpecifiers(specifiers, unmangledName);
1207 importedExternalsLocalNames.push(...specifiers.local);
1208 source.remove(importDeclarationStart, importDeclarationEnd);
1209 this.skip();
1210 }
1211 if (isIdentifier(node)) {
1212 const unmangled = mangler.getName(node.name);
1213 if (unmangled) {
1214 const [identifierStart, identifierEnd] = node.range;
1215 source.overwrite(identifierStart, identifierEnd, unmangled);
1216 }
1217 }
1218 if (isImportExpression(node)) {
1219 const [dynamicImportStart, dynamicImportEnd] = node.range;
1220 dynamicImportPresent = true;
1221 // Rename the `import` method to something we can put in externs.
1222 // CC doesn't understand dynamic import yet.
1223 source.overwrite(dynamicImportStart, dynamicImportEnd, code
1224 .substring(dynamicImportStart, dynamicImportEnd)
1225 .replace(DYNAMIC_IMPORT_KEYWORD, DYNAMIC_IMPORT_REPLACEMENT));
1226 }
1227 },
1228 });
1229 this.dynamicImportPresent = dynamicImportPresent;
1230 return source;
1231 };
1232 }
1233 /**
1234 * Generate externs for local names of external imports.
1235 * Otherwise, advanced mode compilation will fail since the reference is unknown.
1236 * @return string representing content of generated extern.
1237 */
1238 extern() {
1239 let extern = HEADER$2;
1240 if (this.importedExternalsLocalNames.length > 0) {
1241 for (const name of this.importedExternalsLocalNames) {
1242 extern += `function ${name}(){};\n`;
1243 }
1244 }
1245 if (this.dynamicImportPresent) {
1246 extern += `
1247/**
1248 * @param {string} path
1249 * @return {!Promise<?>}
1250 */
1251function ${DYNAMIC_IMPORT_REPLACEMENT}(path) { return Promise.resolve(path) };
1252window['${DYNAMIC_IMPORT_REPLACEMENT}'] = ${DYNAMIC_IMPORT_REPLACEMENT};`;
1253 }
1254 return extern === HEADER$2 ? null : extern;
1255 }
1256 /**
1257 * After Closure Compiler has modified the source, we need to re-add the external imports
1258 * @param code source post Closure Compiler Compilation
1259 * @return Promise containing the repaired source
1260 */
1261 async post(fileName, source) {
1262 const code = source.toString();
1263 const program = await parse(fileName, code);
1264 for (const importedExternalSyntax of Object.values(this.importedExternalsSyntax)) {
1265 source.prepend(importedExternalSyntax);
1266 }
1267 walk.simple(program, {
1268 Identifier(node) {
1269 if (node.name === DYNAMIC_IMPORT_REPLACEMENT) {
1270 const [start, end] = node.range;
1271 source.overwrite(start, end, DYNAMIC_IMPORT_KEYWORD);
1272 }
1273 },
1274 });
1275 return source;
1276 }
1277}
1278
1279/**
1280 * Copyright 2018 The AMP HTML Authors. All Rights Reserved.
1281 *
1282 * Licensed under the Apache License, Version 2.0 (the "License");
1283 * you may not use this file except in compliance with the License.
1284 * You may obtain a copy of the License at
1285 *
1286 * http://www.apache.org/licenses/LICENSE-2.0
1287 *
1288 * Unless required by applicable law or agreed to in writing, software
1289 * distributed under the License is distributed on an "AS-IS" BASIS,
1290 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1291 * See the License for the specific language governing permissions and
1292 * limitations under the License.
1293 */
1294/**
1295 * Determines if Strict Mode should be removed from output.
1296 * @param pluginOptions
1297 * @param outputOptions
1298 * @param path
1299 */
1300function shouldRemoveStrictModeDeclarations(pluginOptions, outputOptions, path) {
1301 if ('remove_strict_directive' in pluginOptions) {
1302 const removeDirective = pluginOptions['remove_strict_directive'];
1303 return removeDirective === undefined || removeDirective === true;
1304 }
1305 const isESMOutput = !!(path && extname(path) === '.mjs');
1306 return isESMFormat(outputOptions) || isESMOutput;
1307}
1308class StrictTransform extends ChunkTransform {
1309 constructor() {
1310 super(...arguments);
1311 this.name = 'StrictTransform';
1312 }
1313 /**
1314 * When outputting an es module, runtimes automatically apply strict mode conventions.
1315 * This means we can safely strip the 'use strict'; declaration from the top of the file.
1316 * @param code source following closure compiler minification
1317 * @return code after removing the strict mode declaration (when safe to do so)
1318 */
1319 async post(fileName, source) {
1320 const { file } = this.outputOptions;
1321 if (shouldRemoveStrictModeDeclarations(this.pluginOptions, this.outputOptions, file)) {
1322 const program = await parse(fileName, source.toString());
1323 walk.simple(program, {
1324 ExpressionStatement(node) {
1325 const { type, value } = node.expression;
1326 const range = node.range;
1327 if (type === 'Literal' && value === 'use strict') {
1328 source.remove(...range);
1329 }
1330 },
1331 });
1332 return source;
1333 }
1334 return source;
1335 }
1336}
1337
1338/**
1339 * Copyright 2018 The AMP HTML Authors. All Rights Reserved.
1340 *
1341 * Licensed under the Apache License, Version 2.0 (the "License");
1342 * you may not use this file except in compliance with the License.
1343 * You may obtain a copy of the License at
1344 *
1345 * http://www.apache.org/licenses/LICENSE-2.0
1346 *
1347 * Unless required by applicable law or agreed to in writing, software
1348 * distributed under the License is distributed on an "AS-IS" BASIS,
1349 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1350 * See the License for the specific language governing permissions and
1351 * limitations under the License.
1352 */
1353class ConstTransform extends ChunkTransform {
1354 constructor() {
1355 super(...arguments);
1356 this.name = 'ConstTransform';
1357 }
1358 /**
1359 * When outputting ES2017+ code there is neglagible differences between `const` and `let` for runtime performance.
1360 * So, we replace all usages of `const` with `let` to enable more variable folding.
1361 * @param code source following closure compiler minification
1362 * @return code after removing the strict mode declaration (when safe to do so)
1363 */
1364 async pre(fileName, source) {
1365 const code = source.toString();
1366 const program = await parse(fileName, code);
1367 walk.simple(program, {
1368 VariableDeclaration(node) {
1369 const [start, end] = node.range;
1370 if (node.kind === 'const') {
1371 source.overwrite(start, end, code.substring(start, end).replace('const', 'let'));
1372 }
1373 },
1374 });
1375 return source;
1376 }
1377}
1378
1379/**
1380 * Copyright 2018 The AMP HTML Authors. All Rights Reserved.
1381 *
1382 * Licensed under the Apache License, Version 2.0 (the "License");
1383 * you may not use this file except in compliance with the License.
1384 * You may obtain a copy of the License at
1385 *
1386 * http://www.apache.org/licenses/LICENSE-2.0
1387 *
1388 * Unless required by applicable law or agreed to in writing, software
1389 * distributed under the License is distributed on an "AS-IS" BASIS,
1390 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1391 * See the License for the specific language governing permissions and
1392 * limitations under the License.
1393 */
1394class ASITransform extends ChunkTransform {
1395 constructor() {
1396 super(...arguments);
1397 this.name = 'ASITransform';
1398 }
1399 /**
1400 * Small reduction in semi-colons, removing from end of block statements.
1401 * @param source source following closure compiler minification
1402 */
1403 async post(fileName, source) {
1404 const code = source.toString();
1405 const program = await parse(fileName, code);
1406 if (program.body) {
1407 const lastStatement = program.body[program.body.length - 1];
1408 if (lastStatement) {
1409 const [start, end] = lastStatement.range;
1410 if (lastStatement.type === 'EmptyStatement') {
1411 source.remove(start, end);
1412 }
1413 else {
1414 const lastStatementSource = code.substring(start, end);
1415 if (lastStatementSource.endsWith(';')) {
1416 source.overwrite(start, end, code.substring(start, end - 1));
1417 }
1418 }
1419 }
1420 }
1421 return source;
1422 }
1423}
1424
1425/**
1426 * Copyright 2018 The AMP HTML Authors. All Rights Reserved.
1427 *
1428 * Licensed under the Apache License, Version 2.0 (the "License");
1429 * you may not use this file except in compliance with the License.
1430 * You may obtain a copy of the License at
1431 *
1432 * http://www.apache.org/licenses/LICENSE-2.0
1433 *
1434 * Unless required by applicable law or agreed to in writing, software
1435 * distributed under the License is distributed on an "AS-IS" BASIS,
1436 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1437 * See the License for the specific language governing permissions and
1438 * limitations under the License.
1439 */
1440const TRANSFORMS = [
1441 HashbangRemoveTransform,
1442 // Acorn can parse content starting here
1443 ConstTransform,
1444 IifeTransform,
1445 CJSTransform,
1446 LiteralComputedKeys,
1447 StrictTransform,
1448 ExportTransform,
1449 ImportTransform,
1450 ASITransform,
1451 // Acorn cannot parse content starting here.
1452 HashbangApplyTransform,
1453];
1454/**
1455 * Instantiate transform class instances for the plugin invocation.
1456 * @param context Plugin context to bind for each transform instance.
1457 * @param requestedCompileOptions Originally requested compile options from configuration.
1458 * @param mangler Mangle instance used for this transform instance.
1459 * @param memory Ebbinghaus instance used to store information that could be lost from source.
1460 * @param inputOptions Rollup input options
1461 * @param outputOptions Rollup output options
1462 * @return Instantiated transform class instances for the given entry point.
1463 */
1464function create(context, requestedCompileOptions, mangler, memory, inputOptions, outputOptions) {
1465 const pluginOptions = pluckPluginOptions(requestedCompileOptions);
1466 return TRANSFORMS.map(transform => new transform(context, pluginOptions, mangler, memory, inputOptions, outputOptions));
1467}
1468/**
1469 * Run each transform's `preCompilation` phase.
1470 * @param code
1471 * @param chunk
1472 * @param transforms
1473 * @return source code following `preCompilation`
1474 */
1475async function preCompilation(source, chunk, transforms) {
1476 return await chunkLifecycle(chunk.fileName, 'PreCompilation', 'pre', source, transforms);
1477}
1478/**
1479 * Run each transform's `postCompilation` phase.
1480 * @param code
1481 * @param chunk
1482 * @param transforms
1483 * @return source code following `postCompilation`
1484 */
1485async function postCompilation(code, chunk, transforms) {
1486 return await chunkLifecycle(chunk.fileName, 'PostCompilation', 'post', code, transforms);
1487}
1488
1489/**
1490 * Copyright 2018 The AMP HTML Authors. All Rights Reserved.
1491 *
1492 * Licensed under the Apache License, Version 2.0 (the "License");
1493 * you may not use this file except in compliance with the License.
1494 * You may obtain a copy of the License at
1495 *
1496 * http://www.apache.org/licenses/LICENSE-2.0
1497 *
1498 * Unless required by applicable law or agreed to in writing, software
1499 * distributed under the License is distributed on an "AS-IS" BASIS,
1500 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1501 * See the License for the specific language governing permissions and
1502 * limitations under the License.
1503 */
1504const { getNativeImagePath, getFirstSupportedPlatform, } = require('google-closure-compiler/lib/utils.js');
1505var Platform;
1506(function (Platform) {
1507 Platform["NATIVE"] = "native";
1508 Platform["JAVA"] = "java";
1509 Platform["JAVASCRIPT"] = "javascript";
1510})(Platform || (Platform = {}));
1511const DEFAULT_PLATFORM_PRECEDENCE = [Platform.NATIVE, Platform.JAVA, Platform.JAVASCRIPT];
1512/**
1513 * Splits user `platform` option from compiler options object
1514 * returns new object containing options and preferred platform.
1515 * @param {CompileOptions} content - compiler options object
1516 * @return {Object}
1517 * @example in rollup.config.js
1518 * compiler({
1519 * platform: 'javascript',
1520 * }),
1521 */
1522function filterContent(content) {
1523 let platform = '';
1524 if ('platform' in content && typeof content.platform === 'string') {
1525 platform = content.platform;
1526 delete content.platform;
1527 }
1528 return [content, platform];
1529}
1530/**
1531 * Reorders platform preferences based on configuration.
1532 * @param {String} platformPreference - preferred platform string
1533 * @return {Array}
1534 */
1535function orderPlatforms(platformPreference) {
1536 if (platformPreference === '') {
1537 return DEFAULT_PLATFORM_PRECEDENCE;
1538 }
1539 const index = DEFAULT_PLATFORM_PRECEDENCE.indexOf(platformPreference);
1540 const newPlatformPreferences = DEFAULT_PLATFORM_PRECEDENCE.splice(index, 1);
1541 return newPlatformPreferences.concat(DEFAULT_PLATFORM_PRECEDENCE);
1542}
1543/**
1544 * Run Closure Compiler and `postCompilation` Transforms on input source.
1545 * @param compileOptions Closure Compiler CompileOptions, normally derived from Rollup configuration
1546 * @param transforms Transforms to run rollowing compilation
1547 * @return Promise<string> source following compilation and Transforms.
1548 */
1549function compiler (compileOptions, chunk, transforms) {
1550 return new Promise((resolve, reject) => {
1551 const [config, platform] = filterContent(compileOptions);
1552 const instance = new compiler$1(config);
1553 const firstSupportedPlatform = getFirstSupportedPlatform(orderPlatforms(platform));
1554 if (firstSupportedPlatform !== Platform.JAVA) {
1555 // TODO(KB): Provide feedback on this API. It's a little strange to nullify the JAR_PATH
1556 // and provide a fake java path.
1557 instance.JAR_PATH = null;
1558 instance.javaPath = getNativeImagePath();
1559 }
1560 instance.run(async (exitCode, code, stdErr) => {
1561 if ('warning_level' in compileOptions &&
1562 compileOptions.warning_level === 'VERBOSE' &&
1563 stdErr !== '') {
1564 reject(new Error(`Google Closure Compiler ${stdErr}`));
1565 }
1566 else if (exitCode !== 0) {
1567 reject(new Error(`Google Closure Compiler exit ${exitCode}: ${stdErr}`));
1568 }
1569 else {
1570 const postCompiled = await postCompilation(code, chunk, transforms);
1571 resolve(postCompiled.code);
1572 }
1573 });
1574 });
1575}
1576
1577/**
1578 * Copyright 2020 The AMP HTML Authors. All Rights Reserved.
1579 *
1580 * Licensed under the Apache License, Version 2.0 (the "License");
1581 * you may not use this file except in compliance with the License.
1582 * You may obtain a copy of the License at
1583 *
1584 * http://www.apache.org/licenses/LICENSE-2.0
1585 *
1586 * Unless required by applicable law or agreed to in writing, software
1587 * distributed under the License is distributed on an "AS-IS" BASIS,
1588 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1589 * See the License for the specific language governing permissions and
1590 * limitations under the License.
1591 */
1592/**
1593 * Closure Compiler will not compile code that is prefixed with a hashbang (common to rollup output for CLIs).
1594 *
1595 * This transform will remove the hashbang (if present) and ask Ebbinghaus to remember if for after compilation.
1596 */
1597class HashbangTransform extends SourceTransform {
1598 constructor() {
1599 super(...arguments);
1600 this.name = 'HashbangTransform';
1601 this.transform = async (id, source) => {
1602 const stringified = source.trim().toString();
1603 const match = /^#!.*/.exec(stringified);
1604 if (!match) {
1605 return source;
1606 }
1607 this.memory.hashbang = match[0];
1608 source.remove(0, match[0].length);
1609 return source;
1610 };
1611 }
1612}
1613
1614/**
1615 * Copyright 2020 The AMP HTML Authors. All Rights Reserved.
1616 *
1617 * Licensed under the Apache License, Version 2.0 (the "License");
1618 * you may not use this file except in compliance with the License.
1619 * You may obtain a copy of the License at
1620 *
1621 * http://www.apache.org/licenses/LICENSE-2.0
1622 *
1623 * Unless required by applicable law or agreed to in writing, software
1624 * distributed under the License is distributed on an "AS-IS" BASIS,
1625 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1626 * See the License for the specific language governing permissions and
1627 * limitations under the License.
1628 */
1629const TRANSFORMS$1 = [HashbangTransform];
1630// Temporarily disabling many SourceTransforms, aligning for future release.
1631// ImportTransform, ExportTransform
1632/**
1633 * Instantiate transform class instances for the plugin invocation.
1634 * @param context Plugin context to bind for each transform instance.
1635 * @param requestedCompileOptions Originally requested compile options from configuration.
1636 * @param mangler Mangle instance used for this transform instance.
1637 * @param memory Ebbinghaus instance used to store information that could be lost from source.
1638 * @param inputOptions Rollup input options
1639 * @param outputOptions Rollup output options
1640 * @return Instantiated transform class instances for the given entry point.
1641 */
1642const create$1 = (context, requestedCompileOptions, mangler, memory, inputOptions, outputOptions) => TRANSFORMS$1.map(transform => new transform(context, {}, mangler, memory, inputOptions, outputOptions));
1643/**
1644 * Run each transform's `transform` lifecycle.
1645 * @param code
1646 * @param transforms
1647 * @return source code following `transform`
1648 */
1649async function transform(source, id, transforms) {
1650 return await sourceLifecycle(id, 'Transform', source, transforms);
1651}
1652
1653/**
1654 * Copyright 2020 The AMP HTML Authors. All Rights Reserved.
1655 *
1656 * Licensed under the Apache License, Version 2.0 (the "License");
1657 * you may not use this file except in compliance with the License.
1658 * You may obtain a copy of the License at
1659 *
1660 * http://www.apache.org/licenses/LICENSE-2.0
1661 *
1662 * Unless required by applicable law or agreed to in writing, software
1663 * distributed under the License is distributed on an "AS-IS" BASIS,
1664 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1665 * See the License for the specific language governing permissions and
1666 * limitations under the License.
1667 */
1668function createId(source) {
1669 const hash = createHash('sha1');
1670 hash.update(source);
1671 return 'f_' + hash.digest('hex');
1672}
1673function mangledValue(name, sourceId) {
1674 return `${name}_${sourceId}`;
1675}
1676class Mangle {
1677 constructor() {
1678 this.sourceToId = new Map();
1679 this.idToSource = new Map();
1680 this.nameToMangled = new Map();
1681 this.mangledToName = new Map();
1682 this.debug = () => {
1683 log('mangle state', {
1684 sourceToId: this.sourceToId,
1685 idToSource: this.idToSource,
1686 nameToMangled: this.nameToMangled,
1687 mangledToName: this.mangledToName,
1688 });
1689 };
1690 this.sourceId = (source) => {
1691 let uuid = this.sourceToId.get(source);
1692 if (!uuid) {
1693 this.sourceToId.set(source, (uuid = createId(source)));
1694 this.idToSource.set(uuid, source);
1695 }
1696 return uuid;
1697 };
1698 this.mangle = (name, sourceId) => {
1699 const mangled = mangledValue(name, sourceId);
1700 const stored = this.nameToMangled.get(name);
1701 if (stored && stored !== mangled) {
1702 console.log('SetIdentifier for Mangled Name more than once', { name, sourceId });
1703 }
1704 else {
1705 this.nameToMangled.set(name, mangled);
1706 this.mangledToName.set(mangled, name);
1707 }
1708 return mangled;
1709 };
1710 this.getMangledName = (originalName) => {
1711 return this.nameToMangled.get(originalName);
1712 };
1713 this.getName = (maybeMangledName) => {
1714 return this.mangledToName.get(maybeMangledName);
1715 };
1716 this.getSource = (sourceId) => {
1717 return this.idToSource.get(sourceId);
1718 };
1719 this.execute = async (source, program) => {
1720 const { getMangledName } = this;
1721 const mangleable = [new Set([...this.nameToMangled.keys()])];
1722 let insideNamedExport = false;
1723 await asyncWalk(program, {
1724 enter: async function (node) {
1725 const currentlyRewriteable = mangleable[mangleable.length - 1];
1726 if (isBlockStatement(node)) {
1727 mangleable.push(new Set(currentlyRewriteable));
1728 }
1729 if (isExportNamedDeclaration(node)) {
1730 insideNamedExport = true;
1731 }
1732 if (!insideNamedExport && isVariableDeclarator(node) && isIdentifier(node.id)) {
1733 currentlyRewriteable.delete(node.id.name);
1734 }
1735 if (isIdentifier(node) && currentlyRewriteable.has(node.name)) {
1736 const [start, end] = node.range;
1737 source.overwrite(start, end, getMangledName(node.name) || node.name);
1738 }
1739 },
1740 leave: async function (node) {
1741 if (isBlockStatement(node)) {
1742 mangleable.pop();
1743 }
1744 if (isExportNamedDeclaration(node)) {
1745 insideNamedExport = false;
1746 }
1747 },
1748 });
1749 };
1750 }
1751}
1752
1753/**
1754 * Copyright 2020 The AMP HTML Authors. All Rights Reserved.
1755 *
1756 * Licensed under the Apache License, Version 2.0 (the "License");
1757 * you may not use this file except in compliance with the License.
1758 * You may obtain a copy of the License at
1759 *
1760 * http://www.apache.org/licenses/LICENSE-2.0
1761 *
1762 * Unless required by applicable law or agreed to in writing, software
1763 * distributed under the License is distributed on an "AS-IS" BASIS,
1764 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1765 * See the License for the specific language governing permissions and
1766 * limitations under the License.
1767 */
1768/**
1769 * Hermann Ebbinghaus is credited with discovering the forgetting curve.
1770 *
1771 * This class stores data the compiler would typically loose to the forgetting curve.
1772 * For instance:
1773 * - original source contained a `hashbang`
1774 * - original source used external imports
1775 *
1776 * This data can be used later to inform transforms following Closure Compiler.
1777 *
1778 * For more information, visit: https://en.wikipedia.org/wiki/Hermann_Ebbinghaus
1779 */
1780class Ebbinghaus {
1781 constructor() {
1782 this.hashbang = null;
1783 }
1784}
1785
1786/**
1787 * Copyright 2018 The AMP HTML Authors. All Rights Reserved.
1788 *
1789 * Licensed under the Apache License, Version 2.0 (the "License");
1790 * you may not use this file except in compliance with the License.
1791 * You may obtain a copy of the License at
1792 *
1793 * http://www.apache.org/licenses/LICENSE-2.0
1794 *
1795 * Unless required by applicable law or agreed to in writing, software
1796 * distributed under the License is distributed on an "AS-IS" BASIS,
1797 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1798 * See the License for the specific language governing permissions and
1799 * limitations under the License.
1800 */
1801function closureCompiler(requestedCompileOptions = {}) {
1802 const mangler = new Mangle();
1803 const memory = new Ebbinghaus();
1804 let inputOptions;
1805 let context;
1806 let sourceTransforms;
1807 return {
1808 name: 'closure-compiler',
1809 options: options => (inputOptions = options),
1810 buildStart() {
1811 context = this;
1812 sourceTransforms = create$1(context, requestedCompileOptions, mangler, memory, inputOptions, {});
1813 if ('compilation_level' in requestedCompileOptions &&
1814 requestedCompileOptions.compilation_level === 'ADVANCED_OPTIMIZATIONS' &&
1815 Array.isArray(inputOptions.input)) {
1816 context.warn('Code Splitting with Closure Compiler ADVANCED_OPTIMIZATIONS is not currently supported.');
1817 }
1818 },
1819 transform: async (code, id) => {
1820 if (sourceTransforms.length > 0) {
1821 const output = await transform(code, id, sourceTransforms);
1822 return output || null;
1823 }
1824 return null;
1825 },
1826 renderChunk: async (code, chunk, outputOptions) => {
1827 mangler.debug();
1828 const renderChunkTransforms = create(context, requestedCompileOptions, mangler, memory, inputOptions, outputOptions);
1829 const preCompileOutput = (await preCompilation(code, chunk, renderChunkTransforms)).code;
1830 const [compileOptions, mapFile] = await options(requestedCompileOptions, outputOptions, preCompileOutput, renderChunkTransforms);
1831 try {
1832 return {
1833 code: await compiler(compileOptions, chunk, renderChunkTransforms),
1834 map: JSON.parse(await promises.readFile(mapFile, 'utf8')),
1835 };
1836 }
1837 catch (error) {
1838 throw error;
1839 }
1840 },
1841 };
1842}
1843
1844export default closureCompiler;