1 | ;
|
2 |
|
3 | function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
|
4 |
|
5 | var fs = require('fs');
|
6 | var googleClosureCompiler = require('google-closure-compiler');
|
7 | var path = require('path');
|
8 | var os = require('os');
|
9 | var crypto = require('crypto');
|
10 | var uuid = require('uuid');
|
11 | var MagicString = _interopDefault(require('magic-string'));
|
12 | var remapping = _interopDefault(require('@ampproject/remapping'));
|
13 | var acorn = require('acorn');
|
14 | var 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 | */
|
31 | async 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 */
|
63 | async function logTransformChain(file, stage, messages) {
|
64 | return;
|
65 | }
|
66 | /* c8 ignore next 7 */
|
67 | const 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 | */
|
86 | function createDecodedSourceMap(magicstring, source) {
|
87 | return {
|
88 | ...magicstring.generateDecodedMap({ hires: true, source }),
|
89 | version: 3,
|
90 | };
|
91 | }
|
92 | function 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 | */
|
118 | class 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 | }
|
129 | class SourceTransform extends Transform {
|
130 | constructor() {
|
131 | super(...arguments);
|
132 | this.name = 'SourceTransform';
|
133 | }
|
134 | async transform(id, source) {
|
135 | return source;
|
136 | }
|
137 | }
|
138 | class 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 | }
|
153 | async 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 | }
|
181 | async 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 | */
|
223 | class 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 | */
|
263 | class 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 | */
|
295 | const 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 | */
|
307 | class 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 | */
|
335 | const 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 | */
|
346 | var 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 | */
|
352 | class 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 | */
|
380 | const acornWalk = require('acorn-walk');
|
381 | const walk = {
|
382 | simple: acornWalk.simple,
|
383 | ancestor: acornWalk.ancestor,
|
384 | };
|
385 | const DEFAULT_ACORN_OPTIONS = {
|
386 | ecmaVersion: 2020,
|
387 | sourceType: 'module',
|
388 | preserveParens: false,
|
389 | ranges: true,
|
390 | };
|
391 | async 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 | }
|
400 | function isIdentifier(node) {
|
401 | return node.type === 'Identifier';
|
402 | }
|
403 | function isImportDeclaration(node) {
|
404 | return node.type === 'ImportDeclaration';
|
405 | }
|
406 | function 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 | }
|
411 | function isVariableDeclarator(node) {
|
412 | return node.type === 'VariableDeclarator';
|
413 | }
|
414 | function isBlockStatement(node) {
|
415 | return node.type === 'BlockStatement';
|
416 | }
|
417 | function isExportNamedDeclaration(node) {
|
418 | return node.type === 'ExportNamedDeclaration';
|
419 | }
|
420 | function isFunctionDeclaration(node) {
|
421 | return node.type === 'FunctionDeclaration';
|
422 | }
|
423 | function isVariableDeclaration(node) {
|
424 | return node.type === 'VariableDeclaration';
|
425 | }
|
426 | function isClassDeclaration(node) {
|
427 | return node.type === 'ClassDeclaration';
|
428 | }
|
429 | function 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 | */
|
455 | class 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 | */
|
496 | const IMPORT_SPECIFIER = 'ImportSpecifier';
|
497 | const IMPORT_DEFAULT_SPECIFIER = 'ImportDefaultSpecifier';
|
498 | const IMPORT_NAMESPACE_SPECIFIER = 'ImportNamespaceSpecifier';
|
499 | var 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 | */
|
534 | function getName(input, unmangle) {
|
535 | if (unmangle) {
|
536 | return unmangle(input) || input;
|
537 | }
|
538 | return input;
|
539 | }
|
540 | function 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 | }
|
597 | function 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 | }
|
612 | function 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 | }
|
619 | function 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 | */
|
645 | function 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 | }
|
664 | function 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 | }
|
688 | function 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 | */
|
713 | function 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 | */
|
736 | const ERROR_WARNINGS_ENABLED_LANGUAGE_OUT_UNSPECIFIED = 'Providing the warning_level=VERBOSE compile option also requires a valid language_out compile option.';
|
737 | const ERROR_WARNINGS_ENABLED_LANGUAGE_OUT_INVALID = 'Providing the warning_level=VERBOSE and language_out=NO_TRANSPILE compile options will remove warnings.';
|
738 | const OPTIONS_TO_REMOVE_FOR_CLOSURE = ['remove_strict_directive'];
|
739 | /**
|
740 | * Checks if output format is ESM
|
741 | * @param outputOptions
|
742 | * @return boolean
|
743 | */
|
744 | const isESMFormat = ({ format }) => format === 'esm' || format === 'es';
|
745 | /**
|
746 | * Throw Errors if compile options will result in unexpected behaviour.
|
747 | * @param compileOptions
|
748 | */
|
749 | function 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 | */
|
763 | function 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 | */
|
794 | function 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 | */
|
814 | const 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 | */
|
845 | async 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 | */
|
872 | const 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 | */
|
884 | class 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 | */
|
1063 | function 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 | */
|
1082 | function 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 | }
|
1117 | function 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 | */
|
1153 | const DYNAMIC_IMPORT_KEYWORD = 'import';
|
1154 | const DYNAMIC_IMPORT_REPLACEMENT = `import_${new Date().getMilliseconds()}`;
|
1155 | const 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 | */
|
1167 | function getName$1(input, unmangle) {
|
1168 | if (unmangle) {
|
1169 | return unmangle(input) || input;
|
1170 | }
|
1171 | return input;
|
1172 | }
|
1173 | class 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 | */
|
1255 | function ${DYNAMIC_IMPORT_REPLACEMENT}(path) { return Promise.resolve(path) };
|
1256 | window['${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 | */
|
1304 | function 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 | }
|
1312 | class 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 | */
|
1357 | class 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 | */
|
1398 | class 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 | */
|
1444 | const 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 | */
|
1468 | function 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 | */
|
1479 | async 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 | */
|
1489 | async 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 | */
|
1508 | const { getNativeImagePath, getFirstSupportedPlatform, } = require('google-closure-compiler/lib/utils.js');
|
1509 | var Platform;
|
1510 | (function (Platform) {
|
1511 | Platform["NATIVE"] = "native";
|
1512 | Platform["JAVA"] = "java";
|
1513 | Platform["JAVASCRIPT"] = "javascript";
|
1514 | })(Platform || (Platform = {}));
|
1515 | const 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 | */
|
1526 | function 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 | */
|
1539 | function 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 | */
|
1553 | function 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 | */
|
1601 | class 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 | */
|
1633 | const 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 | */
|
1646 | const 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 | */
|
1653 | async 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 | */
|
1672 | function createId(source) {
|
1673 | const hash = crypto.createHash('sha1');
|
1674 | hash.update(source);
|
1675 | return 'f_' + hash.digest('hex');
|
1676 | }
|
1677 | function mangledValue(name, sourceId) {
|
1678 | return `${name}_${sourceId}`;
|
1679 | }
|
1680 | class 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 | */
|
1784 | class 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 | */
|
1805 | function 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 |
|
1848 | module.exports = closureCompiler;
|