@@ -24,6 +24,7 @@ const Packaging = require('./packaging');
2424const { asyncRimRaf} = require ( './utils' ) ;
2525const codeFrame = require ( '@babel/code-frame' ) ;
2626const Wrappers = require ( './wrappers' ) ;
27+ const minify = require ( 'terser' ) . minify ;
2728
2829const RELEASE_CHANNEL = process . env . RELEASE_CHANNEL ;
2930
@@ -487,137 +488,58 @@ function getPlugins(
487488 ) ;
488489 } ,
489490 } ,
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.
507502 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
540535 prettier ( {
541536 parser : 'flow' ,
542537 singleQuote : false ,
543538 trailingComma : 'none' ,
544539 bracketSpacing : true ,
545540 } ) ,
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' ,
621543 renderChunk ( source ) {
622544 return Wrappers . wrapWithLicenseHeader (
623545 source ,
@@ -628,6 +550,87 @@ function getPlugins(
628550 ) ;
629551 } ,
630552 } ,
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+ } ,
631634 // Record bundle size.
632635 sizes ( {
633636 getSize : ( size , gzip ) => {
0 commit comments