23
const {asyncRimRaf} = require('./utils');
24
const codeFrame = require('@babel/code-frame');
25
const Wrappers = require('./wrappers');
26
+const minify = require('terser').minify;
27
28
const RELEASE_CHANNEL = process.env.RELEASE_CHANNEL;
29
456
);
457
},
458
},
458
- // License and haste headers for artifacts with sourcemaps
459
- // For artifacts with sourcemaps we apply these headers
460
- // before passing sources to the Closure compiler, which will be building sourcemaps
461
- needsSourcemaps && {
462
- name: 'license-and-signature-header-for-artifacts-with-sourcemaps',
463
- renderChunk(source) {
464
- return Wrappers.wrapWithLicenseHeader(
465
- source,
466
- bundleType,
467
- globalName,
468
- filename,
469
- moduleType
470
- );
471
- },
472
- },
473
- // Apply dead code elimination and/or minification.
474
- // closure doesn't yet support leaving ESM imports intact
459
+ // For production builds, compile with Closure. We do this even for the
460
+ // "non-minified" production builds because Closure is much better at
461
+ // minification than what most applications use. During this step, we do
462
+ // preserve the original symbol names, though, so the resulting code is
463
+ // relatively readable.
464
+ //
465
+ // For the minified builds, the names will be mangled later.
466
+ //
467
+ // We don't bother with sourcemaps at this step. The sourcemaps we publish
468
+ // are only for whitespace and symbol renaming; they don't map back to
469
+ // before Closure was applied.
470
needsMinifiedByClosure &&
476
- closure(
477
- {
478
- compilation_level: 'SIMPLE',
479
- language_in: 'ECMASCRIPT_2020',
480
- language_out:
481
- bundleType === NODE_ES2015
482
- ? 'ECMASCRIPT_2020'
483
- : bundleType === BROWSER_SCRIPT
484
- ? 'ECMASCRIPT5'
485
- : 'ECMASCRIPT5_STRICT',
486
- emit_use_strict:
487
- bundleType !== BROWSER_SCRIPT &&
488
- bundleType !== ESM_PROD &&
489
- bundleType !== ESM_DEV,
490
- env: 'CUSTOM',
491
- warning_level: 'QUIET',
492
- source_map_include_content: true,
493
- use_types_for_optimization: false,
494
- process_common_js_modules: false,
495
- rewrite_polyfills: false,
496
- inject_libraries: false,
497
- allow_dynamic_import: true,
498
-
499
- // Don't let it create global variables in the browser.
500
- // https://github.com/facebook/react/issues/10909
501
- assume_function_wrapper: true,
502
- renaming: !shouldStayReadable,
503
- },
504
- {needsSourcemaps}
505
- ),
506
- // Add the whitespace back if necessary.
507
- shouldStayReadable &&
471
+ closure({
472
+ compilation_level: 'SIMPLE',
473
+ language_in: 'ECMASCRIPT_2020',
474
+ language_out:
475
+ bundleType === NODE_ES2015
476
+ ? 'ECMASCRIPT_2020'
477
+ : bundleType === BROWSER_SCRIPT
478
+ ? 'ECMASCRIPT5'
479
+ : 'ECMASCRIPT5_STRICT',
480
+ emit_use_strict:
481
+ bundleType !== BROWSER_SCRIPT &&
482
+ bundleType !== ESM_PROD &&
483
+ bundleType !== ESM_DEV,
484
+ env: 'CUSTOM',
485
+ warning_level: 'QUIET',
486
+ source_map_include_content: true,
487
+ use_types_for_optimization: false,
488
+ process_common_js_modules: false,
489
+ rewrite_polyfills: false,
490
+ inject_libraries: false,
491
+ allow_dynamic_import: true,
492
+
493
+ // Don't let it create global variables in the browser.
494
+ // https://github.com/facebook/react/issues/10909
495
+ assume_function_wrapper: true,
496
+
497
+ // Don't rename symbols (variable names, functions, etc). This will
498
+ // be handled in a later step.
499
+ renaming: false,
500
+ }),
501
+ needsMinifiedByClosure &&
502
+ // Add the whitespace back
503
prettier({
504
parser: 'flow',
505
singleQuote: false,
506
trailingComma: 'none',
507
bracketSpacing: true,
508
}),
514
- needsSourcemaps && {
515
- name: 'generate-prod-bundle-sourcemaps',
516
- async renderChunk(minifiedCodeWithChangedHeader, chunk, options, meta) {
517
- // We want to generate a sourcemap that shows the production bundle source
518
- // as it existed before Closure Compiler minified that chunk, rather than
519
- // showing the "original" individual source files. This better shows
520
- // what is actually running in the app.
521
-
522
- // Use a path like `node_modules/react/cjs/react.production.min.js.map` for the sourcemap file
523
- const finalSourcemapPath = options.file.replace('.js', '.js.map');
524
- const finalSourcemapFilename = path.basename(finalSourcemapPath);
525
- const outputFolder = path.dirname(options.file);
526
-
527
- // Read the sourcemap that Closure wrote to disk
528
- const sourcemapAfterClosure = JSON.parse(
529
- fs.readFileSync(finalSourcemapPath, 'utf8')
530
- );
531
-
532
- // Represent the "original" bundle as a file with no `.min` in the name
533
- const filenameWithoutMin = filename.replace('.min', '');
534
- // There's _one_ artifact where the incoming filename actually contains
535
- // a folder name: "use-sync-external-store-shim/with-selector.production.js".
536
- // The output path already has the right structure, but we need to strip this
537
- // down to _just_ the JS filename.
538
- const preMinifiedFilename = path.basename(filenameWithoutMin);
539
-
540
- // CC generated a file list that only contains the tempfile name.
541
- // Replace that with a more meaningful "source" name for this bundle
542
- // that represents "the bundled source before minification".
543
- sourcemapAfterClosure.sources = [preMinifiedFilename];
544
- sourcemapAfterClosure.file = filename;
545
-
546
- // All our code is considered "third-party" and should be ignored by default.
547
- sourcemapAfterClosure.ignoreList = [0];
548
-
549
- // We'll write the pre-minified source to disk as a separate file.
550
- // Because it sits on disk, there's no need to have it in the `sourcesContent` array.
551
- // That also makes the file easier to read, and available for use by scripts.
552
- // This should be the only file in the array.
553
- const [preMinifiedBundleSource] =
554
- sourcemapAfterClosure.sourcesContent;
555
-
556
- // Remove this entirely - we're going to write the file to disk instead.
557
- delete sourcemapAfterClosure.sourcesContent;
558
-
559
- const preMinifiedBundlePath = path.join(
560
- outputFolder,
561
- preMinifiedFilename
562
- );
563
-
564
- // Write the original source to disk as a separate file
565
- fs.writeFileSync(preMinifiedBundlePath, preMinifiedBundleSource);
566
-
567
- // Overwrite the Closure-generated file with the final combined sourcemap
568
- fs.writeFileSync(
569
- finalSourcemapPath,
570
- JSON.stringify(sourcemapAfterClosure)
571
- );
572
-
573
- // Add the sourcemap URL to the actual bundle, so that tools pick it up
574
- const sourceWithMappingUrl =
575
- minifiedCodeWithChangedHeader +
576
- `\n//# sourceMappingURL=${finalSourcemapFilename}`;
577
-
578
- return {
579
- code: sourceWithMappingUrl,
580
- map: null,
581
- };
582
- },
583
- },
584
- // License and haste headers for artifacts without sourcemaps
585
- // Primarily used for FB-artifacts, which should preserve specific format of the header
586
- // Which potentially can be changed by Closure minification
587
- !needsSourcemaps && {
588
- name: 'license-and-signature-header-for-artifacts-without-sourcemaps',
509
+ {
510
+ name: 'license-and-signature-header',
511
renderChunk(source) {
512
return Wrappers.wrapWithLicenseHeader(
513
source,
518
);
519
},
520
},
521
+ isProduction &&
522
+ !shouldStayReadable && {
523
+ name: 'mangle-symbol-names',
524
+ async renderChunk(code, chunk, options, meta) {
525
+ // Minify the code by mangling symbol names. We already ran Closure
526
+ // on this code, so stuff like dead code elimination and inlining
527
+ // has already happened. This step is purely to rename the symbols,
528
+ // which we asked Closure to preserve.
529
+ //
530
+ // The only reason this is a separate step from Closure is so we
531
+ // can publish non-mangled versions of the code for easier debugging
532
+ // in production. We also publish sourcemaps that map back to the
533
+ // non-mangled code (*not* the pre-Closure code).
534
+
535
+ const outputFolder = path.dirname(options.file);
536
+
537
+ // Represent the "original" bundle as a file with no `.min` in the name
538
+ const filenameWithoutMin = filename.replace('.min', '');
539
+ // There's _one_ artifact where the incoming filename actually contains
540
+ // a folder name: "use-sync-external-store-shim/with-selector.production.js".
541
+ // The output path already has the right structure, but we need to strip this
542
+ // down to _just_ the JS filename.
543
+ const preMinifiedFilename = path.basename(filenameWithoutMin);
544
+ const preMinifiedBundlePath = path.join(
545
+ outputFolder,
546
+ preMinifiedFilename
547
+ );
548
+
549
+ // Use a path like `node_modules/react/cjs/react.production.min.js.map` for the sourcemap file
550
+ const finalSourcemapPath = options.file.replace('.js', '.js.map');
551
+ const finalSourcemapFilename = path.basename(finalSourcemapPath);
552
+
553
+ const terserOptions = {
554
+ // Don't bother compressing. Closure already did that.
555
+ compress: false,
556
+ toplevel: true,
557
+ // Mangle the symbol names.
558
+ mangle: {
559
+ toplevel: true,
560
+ },
561
+ };
562
+ if (needsSourcemaps) {
563
+ terserOptions.sourceMap = {
564
+ // Used to set the `file` field in the sourcemap
565
+ filename: filename,
566
+ // Used to set `# sourceMappingURL=` in the compiled code
567
+ url: finalSourcemapFilename,
568
+ };
569
+ }
570
+
571
+ const minifiedResult = await minify(
572
+ {[preMinifiedFilename]: code},
573
+ terserOptions
574
+ );
575
+
576
+ // Create the directory if it doesn't already exist
577
+ fs.mkdirSync(outputFolder, {recursive: true});
578
+
579
+ if (needsSourcemaps) {
580
+ const sourcemapJSON = JSON.parse(minifiedResult.map);
581
+
582
+ // All our code is considered "third-party" and should be ignored
583
+ // by default
584
+ sourcemapJSON.ignoreList = [0];
585
+
586
+ // Write the sourcemap to disk
587
+ fs.writeFileSync(
588
+ finalSourcemapPath,
589
+ JSON.stringify(sourcemapJSON)
590
+ );
591
+ }
592
+
593
+ // Write the original source to disk as a separate file
594
+ fs.writeFileSync(preMinifiedBundlePath, code);
595
+
596
+ return {
597
+ code: minifiedResult.code,
598
+ // TODO: Maybe we should use Rollup's sourcemap feature instead
599
+ // of writing it to disk manually?
600
+ map: null,
601
+ };
602
+ },
603
+ },
604
// Record bundle size.
605
sizes({
606
getSize: (size, gzip) => {