@@ -10,7 +10,10 @@ const replace = require('@rollup/plugin-replace');
10
10
const stripBanner = require ( 'rollup-plugin-strip-banner' ) ;
11
11
const chalk = require ( 'chalk' ) ;
12
12
const resolve = require ( '@rollup/plugin-node-resolve' ) . nodeResolve ;
13
+ const MagicString = require ( 'magic-string' ) ;
14
+ const remapping = require ( '@ampproject/remapping' ) ;
13
15
const fs = require ( 'fs' ) ;
16
+ const path = require ( 'path' ) ;
14
17
const argv = require ( 'minimist' ) ( process . argv . slice ( 2 ) ) ;
15
18
const Modules = require ( './modules' ) ;
16
19
const Bundles = require ( './bundles' ) ;
@@ -148,6 +151,7 @@ function getBabelConfig(
148
151
presets : [ ] ,
149
152
plugins : [ ...babelPlugins ] ,
150
153
babelHelpers : 'bundled' ,
154
+ sourcemap : false ,
151
155
} ;
152
156
if ( isDevelopment ) {
153
157
options . plugins . push (
@@ -382,6 +386,21 @@ function getPlugins(
382
386
383
387
const { isUMDBundle, shouldStayReadable} = getBundleTypeFlags ( bundleType ) ;
384
388
389
+ const needsMinifiedByClosure = isProduction && bundleType !== ESM_PROD ;
390
+
391
+ // Only generate sourcemaps for true "production" build artifacts
392
+ // that will be used by bundlers, such as `react-dom.production.min.js`.
393
+ // UMD and "profiling" builds are rarely used and not worth having sourcemaps.
394
+ const needsSourcemaps =
395
+ needsMinifiedByClosure &&
396
+ ! isProfiling &&
397
+ ! isUMDBundle &&
398
+ ! shouldStayReadable ;
399
+
400
+ // For builds with sourcemaps, capture the minified code Closure generated
401
+ // so it can be used to help construct the final sourcemap contents.
402
+ let chunkCodeAfterClosureCompiler = undefined ;
403
+
385
404
return [
386
405
// Keep dynamic imports as externals
387
406
dynamicImports ( ) ,
@@ -391,7 +410,7 @@ function getPlugins(
391
410
const transformed = flowRemoveTypes ( code ) ;
392
411
return {
393
412
code : transformed . toString ( ) ,
394
- map : transformed . generateMap ( ) ,
413
+ map : null ,
395
414
} ;
396
415
} ,
397
416
} ,
@@ -420,6 +439,7 @@ function getPlugins(
420
439
) ,
421
440
// Remove 'use strict' from individual source files.
422
441
{
442
+ name : "remove 'use strict'" ,
423
443
transform ( source ) {
424
444
return source . replace ( / [ ' " ] u s e s t r i c t [ " ' ] / g, '' ) ;
425
445
} ,
@@ -441,35 +461,44 @@ function getPlugins(
441
461
isUMDBundle && entry === 'react-art' && commonjs ( ) ,
442
462
// Apply dead code elimination and/or minification.
443
463
// 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
- } ) ,
464
+ needsMinifiedByClosure &&
465
+ closure (
466
+ {
467
+ compilation_level : 'SIMPLE' ,
468
+ language_in : 'ECMASCRIPT_2020' ,
469
+ language_out :
470
+ bundleType === NODE_ES2015
471
+ ? 'ECMASCRIPT_2020'
472
+ : bundleType === BROWSER_SCRIPT
473
+ ? 'ECMASCRIPT5'
474
+ : 'ECMASCRIPT5_STRICT' ,
475
+ emit_use_strict :
476
+ bundleType !== BROWSER_SCRIPT &&
477
+ bundleType !== ESM_PROD &&
478
+ bundleType !== ESM_DEV ,
479
+ env : 'CUSTOM' ,
480
+ warning_level : 'QUIET' ,
481
+ source_map_include_content : true ,
482
+ use_types_for_optimization : false ,
483
+ process_common_js_modules : false ,
484
+ rewrite_polyfills : false ,
485
+ inject_libraries : false ,
486
+ allow_dynamic_import : true ,
487
+
488
+ // Don't let it create global variables in the browser.
489
+ // https://github.com/facebook/react/issues/10909
490
+ assume_function_wrapper : ! isUMDBundle ,
491
+ renaming : ! shouldStayReadable ,
492
+ } ,
493
+ { needsSourcemaps}
494
+ ) ,
495
+ needsSourcemaps && {
496
+ name : 'chunk-after-closure' ,
497
+ renderChunk ( code , config , options ) {
498
+ // Side effect - grab the code as Closure mangled it
499
+ chunkCodeAfterClosureCompiler = code ;
500
+ } ,
501
+ } ,
473
502
// Add the whitespace back if necessary.
474
503
shouldStayReadable &&
475
504
prettier ( {
@@ -480,6 +509,7 @@ function getPlugins(
480
509
} ) ,
481
510
// License and haste headers, top-level `if` blocks.
482
511
{
512
+ name : 'license-and-headers' ,
483
513
renderChunk ( source ) {
484
514
return Wrappers . wrapBundle (
485
515
source ,
@@ -491,6 +521,76 @@ function getPlugins(
491
521
) ;
492
522
} ,
493
523
} ,
524
+ needsSourcemaps && {
525
+ name : 'generate-prod-bundle-sourcemaps' ,
526
+ async renderChunk ( codeAfterLicense , chunk , options , meta ) {
527
+ // We want to generate a sourcemap that shows the production bundle source
528
+ // as it existed before Closure Compiler minified that chunk.
529
+ // We also need to apply any license/wrapper text adjustments to that
530
+ // sourcemap, so that the mapped locations line up correctly.
531
+
532
+ // We can split the final chunk code to figure out what got added around
533
+ // the code from the Closure step.
534
+ const [ licensePrefix , licensePostfix ] = codeAfterLicense . split (
535
+ chunkCodeAfterClosureCompiler
536
+ ) ;
537
+
538
+ const transformedSource = new MagicString (
539
+ chunkCodeAfterClosureCompiler
540
+ ) ;
541
+
542
+ // Apply changes so we can generate a sourcemap for this step
543
+ if ( licensePrefix ) {
544
+ transformedSource . prepend ( licensePrefix ) ;
545
+ }
546
+
547
+ if ( licensePostfix ) {
548
+ transformedSource . append ( licensePostfix ) ;
549
+ }
550
+
551
+ // Use a path like `node_modules/react/cjs/react.production.min.js.map` for the sourcemap file
552
+ const finalSourcemapPath = options . file . replace ( '.js' , '.js.map' ) ;
553
+ const finalSourcemapFilename = path . basename ( finalSourcemapPath ) ;
554
+
555
+ // Read the sourcemap that Closure wrote to disk
556
+ const sourcemapAfterClosure = JSON . parse (
557
+ fs . readFileSync ( finalSourcemapPath , 'utf8' )
558
+ ) ;
559
+
560
+ // CC generated a file list that only contains the tempfile name.
561
+ // Replace that with a more meaningful "source" name for this bundle.
562
+ sourcemapAfterClosure . sources = [ filename ] ;
563
+ sourcemapAfterClosure . file = filename ;
564
+
565
+ // Create an additional sourcemap adjusted for the license header contents
566
+ const mapAfterLicense = transformedSource . generateMap ( {
567
+ file : filename ,
568
+ includeContent : true ,
569
+ hires : true ,
570
+ } ) ;
571
+
572
+ // Merge the Closure sourcemap and the with-license sourcemap together
573
+ const finalCombinedSourcemap = remapping (
574
+ [ mapAfterLicense , sourcemapAfterClosure ] ,
575
+ ( ) => null
576
+ ) ;
577
+
578
+ // Overwrite the Closure-generated file with the final combined sourcemap
579
+ fs . writeFileSync (
580
+ finalSourcemapPath ,
581
+ JSON . stringify ( finalCombinedSourcemap )
582
+ ) ;
583
+
584
+ // Add the sourcemap URL to the actual bundle, so that tools pick it up
585
+ const sourceWithMappingUrl =
586
+ codeAfterLicense + `\n//# sourceMappingURL=${ finalSourcemapFilename } ` ;
587
+
588
+ return {
589
+ code : sourceWithMappingUrl ,
590
+ map : null ,
591
+ } ;
592
+ } ,
593
+ } ,
494
594
// Record bundle size.
495
595
sizes ( {
496
596
getSize : ( size , gzip ) => {
0 commit comments