@@ -10,7 +10,10 @@ const replace = require('@rollup/plugin-replace');
1010const stripBanner = require ( 'rollup-plugin-strip-banner' ) ;
1111const chalk = require ( 'chalk' ) ;
1212const resolve = require ( '@rollup/plugin-node-resolve' ) . nodeResolve ;
13+ const MagicString = require ( 'magic-string' ) ;
14+ const remapping = require ( '@ampproject/remapping' ) ;
1315const fs = require ( 'fs' ) ;
16+ const path = require ( 'path' ) ;
1417const argv = require ( 'minimist' ) ( process . argv . slice ( 2 ) ) ;
1518const Modules = require ( './modules' ) ;
1619const Bundles = require ( './bundles' ) ;
@@ -148,6 +151,7 @@ function getBabelConfig(
148151 presets : [ ] ,
149152 plugins : [ ...babelPlugins ] ,
150153 babelHelpers : 'bundled' ,
154+ sourcemap : false ,
151155 } ;
152156 if ( isDevelopment ) {
153157 options . plugins . push (
@@ -382,6 +386,29 @@ function getPlugins(
382386
383387 const { isUMDBundle, shouldStayReadable} = getBundleTypeFlags ( bundleType ) ;
384388
389+ const needsMinifiedByClosure = isProduction && bundleType !== ESM_PROD ;
390+
391+ // Any other packages that should specifically _not_ have sourcemaps
392+ const sourcemapPackageExcludes = [
393+ // Having `//#sourceMappingUrl` in this file breaks `ReactDevToolsHooksIntegration-test.js`,
394+ // and this is an internal
395+ 'react-debug-tools' ,
396+ ] ;
397+
398+ // Only generate sourcemaps for true "production" build artifacts
399+ // that will be used by bundlers, such as `react-dom.production.min.js`.
400+ // UMD and "profiling" builds are rarely used and not worth having sourcemaps.
401+ const needsSourcemaps =
402+ needsMinifiedByClosure &&
403+ ! isProfiling &&
404+ ! isUMDBundle &&
405+ ! sourcemapPackageExcludes . includes ( entry ) &&
406+ ! shouldStayReadable ;
407+
408+ // For builds with sourcemaps, capture the minified code Closure generated
409+ // so it can be used to help construct the final sourcemap contents.
410+ let chunkCodeAfterClosureCompiler = undefined ;
411+
385412 return [
386413 // Keep dynamic imports as externals
387414 dynamicImports ( ) ,
@@ -391,7 +418,7 @@ function getPlugins(
391418 const transformed = flowRemoveTypes ( code ) ;
392419 return {
393420 code : transformed . toString ( ) ,
394- map : transformed . generateMap ( ) ,
421+ map : null ,
395422 } ;
396423 } ,
397424 } ,
@@ -420,6 +447,7 @@ function getPlugins(
420447 ) ,
421448 // Remove 'use strict' from individual source files.
422449 {
450+ name : "remove 'use strict'" ,
423451 transform ( source ) {
424452 return source . replace ( / [ ' " ] u s e s t r i c t [ " ' ] / g, '' ) ;
425453 } ,
@@ -441,35 +469,44 @@ function getPlugins(
441469 isUMDBundle && entry === 'react-art' && commonjs ( ) ,
442470 // Apply dead code elimination and/or minification.
443471 // closure doesn't yet support leaving ESM imports intact
444- isProduction &&
445- bundleType !== ESM_PROD &&
446- closure ( {
447- compilation_level : 'SIMPLE' ,
448- language_in : 'ECMASCRIPT_2020' ,
449- language_out :
450- bundleType === NODE_ES2015
451- ? 'ECMASCRIPT_2020'
452- : bundleType === BROWSER_SCRIPT
453- ? 'ECMASCRIPT5'
454- : 'ECMASCRIPT5_STRICT' ,
455- emit_use_strict :
456- bundleType !== BROWSER_SCRIPT &&
457- bundleType !== ESM_PROD &&
458- bundleType !== ESM_DEV ,
459- env : 'CUSTOM' ,
460- warning_level : 'QUIET' ,
461- apply_input_source_maps : false ,
462- use_types_for_optimization : false ,
463- process_common_js_modules : false ,
464- rewrite_polyfills : false ,
465- inject_libraries : false ,
466- allow_dynamic_import : true ,
467-
468- // Don't let it create global variables in the browser.
469- // https://github.com/facebook/react/issues/10909
470- assume_function_wrapper : ! isUMDBundle ,
471- renaming : ! shouldStayReadable ,
472- } ) ,
472+ needsMinifiedByClosure &&
473+ closure (
474+ {
475+ compilation_level : 'SIMPLE' ,
476+ language_in : 'ECMASCRIPT_2020' ,
477+ language_out :
478+ bundleType === NODE_ES2015
479+ ? 'ECMASCRIPT_2020'
480+ : bundleType === BROWSER_SCRIPT
481+ ? 'ECMASCRIPT5'
482+ : 'ECMASCRIPT5_STRICT' ,
483+ emit_use_strict :
484+ bundleType !== BROWSER_SCRIPT &&
485+ bundleType !== ESM_PROD &&
486+ bundleType !== ESM_DEV ,
487+ env : 'CUSTOM' ,
488+ warning_level : 'QUIET' ,
489+ source_map_include_content : true ,
490+ use_types_for_optimization : false ,
491+ process_common_js_modules : false ,
492+ rewrite_polyfills : false ,
493+ inject_libraries : false ,
494+ allow_dynamic_import : true ,
495+
496+ // Don't let it create global variables in the browser.
497+ // https://github.com/facebook/react/issues/10909
498+ assume_function_wrapper : ! isUMDBundle ,
499+ renaming : ! shouldStayReadable ,
500+ } ,
501+ { needsSourcemaps}
502+ ) ,
503+ needsSourcemaps && {
504+ name : 'chunk-after-closure' ,
505+ renderChunk ( code , config , options ) {
506+ // Side effect - grab the code as Closure mangled it
507+ chunkCodeAfterClosureCompiler = code ;
508+ } ,
509+ } ,
473510 // Add the whitespace back if necessary.
474511 shouldStayReadable &&
475512 prettier ( {
@@ -480,6 +517,7 @@ function getPlugins(
480517 } ) ,
481518 // License and haste headers, top-level `if` blocks.
482519 {
520+ name : 'license-and-headers' ,
483521 renderChunk ( source ) {
484522 return Wrappers . wrapBundle (
485523 source ,
@@ -491,6 +529,76 @@ function getPlugins(
491529 ) ;
492530 } ,
493531 } ,
532+ needsSourcemaps && {
533+ name : 'generate-prod-bundle-sourcemaps' ,
534+ async renderChunk ( codeAfterLicense , chunk , options , meta ) {
535+ // We want to generate a sourcemap that shows the production bundle source
536+ // as it existed before Closure Compiler minified that chunk.
537+ // We also need to apply any license/wrapper text adjustments to that
538+ // sourcemap, so that the mapped locations line up correctly.
539+
540+ // We can split the final chunk code to figure out what got added around
541+ // the code from the Closure step.
542+ const [ licensePrefix , licensePostfix ] = codeAfterLicense . split (
543+ chunkCodeAfterClosureCompiler
544+ ) ;
545+
546+ const transformedSource = new MagicString (
547+ chunkCodeAfterClosureCompiler
548+ ) ;
549+
550+ // Apply changes so we can generate a sourcemap for this step
551+ if ( licensePrefix ) {
552+ transformedSource . prepend ( licensePrefix ) ;
553+ }
554+
555+ if ( licensePostfix ) {
556+ transformedSource . append ( licensePostfix ) ;
557+ }
558+
559+ // Use a path like `node_modules/react/cjs/react.production.min.js.map` for the sourcemap file
560+ const finalSourcemapPath = options . file . replace ( '.js' , '.js.map' ) ;
561+ const finalSourcemapFilename = path . basename ( finalSourcemapPath ) ;
562+
563+ // Read the sourcemap that Closure wrote to disk
564+ const sourcemapAfterClosure = JSON . parse (
565+ fs . readFileSync ( finalSourcemapPath , 'utf8' )
566+ ) ;
567+
568+ // CC generated a file list that only contains the tempfile name.
569+ // Replace that with a more meaningful "source" name for this bundle.
570+ sourcemapAfterClosure . sources = [ filename ] ;
571+ sourcemapAfterClosure . file = filename ;
572+
573+ // Create an additional sourcemap adjusted for the license header contents
574+ const mapAfterLicense = transformedSource . generateMap ( {
575+ file : filename ,
576+ includeContent : true ,
577+ hires : true ,
578+ } ) ;
579+
580+ // Merge the Closure sourcemap and the with-license sourcemap together
581+ const finalCombinedSourcemap = remapping (
582+ [ mapAfterLicense , sourcemapAfterClosure ] ,
583+ ( ) => null
584+ ) ;
585+
586+ // Overwrite the Closure-generated file with the final combined sourcemap
587+ fs . writeFileSync (
588+ finalSourcemapPath ,
589+ JSON . stringify ( finalCombinedSourcemap )
590+ ) ;
591+
592+ // Add the sourcemap URL to the actual bundle, so that tools pick it up
593+ const sourceWithMappingUrl =
594+ codeAfterLicense + `\n//# sourceMappingURL=${ finalSourcemapFilename } ` ;
595+
596+ return {
597+ code : sourceWithMappingUrl ,
598+ map : null ,
599+ } ;
600+ } ,
601+ } ,
494602 // Record bundle size.
495603 sizes ( {
496604 getSize : ( size , gzip ) => {
0 commit comments