@@ -23,6 +23,7 @@ const Packaging = require('./packaging');
23
23
const { asyncRimRaf} = require ( './utils' ) ;
24
24
const codeFrame = require ( '@babel/code-frame' ) ;
25
25
const Wrappers = require ( './wrappers' ) ;
26
+ const minify = require ( 'terser' ) . minify ;
26
27
27
28
const RELEASE_CHANNEL = process . env . RELEASE_CHANNEL ;
28
29
@@ -455,137 +456,58 @@ function getPlugins(
455
456
) ;
456
457
} ,
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.
475
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
508
503
prettier ( {
509
504
parser : 'flow' ,
510
505
singleQuote : false ,
511
506
trailingComma : 'none' ,
512
507
bracketSpacing : true ,
513
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' ,
589
511
renderChunk ( source ) {
590
512
return Wrappers . wrapWithLicenseHeader (
591
513
source ,
@@ -596,6 +518,89 @@ function getPlugins(
596
518
) ;
597
519
} ,
598
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
+ } ,
599
604
// Record bundle size.
600
605
sizes ( {
601
606
getSize : ( size , gzip ) => {
0 commit comments