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