@@ -23,6 +23,7 @@ const Packaging = require('./packaging');
2323const { asyncRimRaf} = require ( './utils' ) ;
2424const codeFrame = require ( '@babel/code-frame' ) ;
2525const Wrappers = require ( './wrappers' ) ;
26+ const minify = require ( 'terser' ) . minify ;
2627
2728const RELEASE_CHANNEL = process . env . RELEASE_CHANNEL ;
2829
@@ -455,137 +456,58 @@ function getPlugins(
455456 ) ;
456457 } ,
457458 } ,
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.
475470 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
508503 prettier ( {
509504 parser : 'flow' ,
510505 singleQuote : false ,
511506 trailingComma : 'none' ,
512507 bracketSpacing : true ,
513508 } ) ,
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' ,
589511 renderChunk ( source ) {
590512 return Wrappers . wrapWithLicenseHeader (
591513 source ,
@@ -596,6 +518,89 @@ function getPlugins(
596518 ) ;
597519 } ,
598520 } ,
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+ } ,
599604 // Record bundle size.
600605 sizes ( {
601606 getSize : ( size , gzip ) => {
0 commit comments