UNPKG

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