Skip to content

Commit 5daa4ba

Browse files
committed
Generate sourcemaps for production build artifacts
1 parent 3da7a99 commit 5daa4ba

File tree

3 files changed

+130
-46
lines changed

3 files changed

+130
-46
lines changed

scripts/rollup/build.js

+105-40
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const stripBanner = require('rollup-plugin-strip-banner');
1111
const chalk = require('chalk');
1212
const resolve = require('@rollup/plugin-node-resolve').nodeResolve;
1313
const fs = require('fs');
14+
const path = require('path');
1415
const argv = require('minimist')(process.argv.slice(2));
1516
const Modules = require('./modules');
1617
const Bundles = require('./bundles');
@@ -148,6 +149,7 @@ function getBabelConfig(
148149
presets: [],
149150
plugins: [...babelPlugins],
150151
babelHelpers: 'bundled',
152+
sourcemap: false,
151153
};
152154
if (isDevelopment) {
153155
options.plugins.push(
@@ -386,6 +388,27 @@ function getPlugins(
386388

387389
const {isUMDBundle, shouldStayReadable} = getBundleTypeFlags(bundleType);
388390

391+
const needsMinifiedByClosure = isProduction && bundleType !== ESM_PROD;
392+
393+
// Any other packages that should specifically _not_ have sourcemaps
394+
const sourcemapPackageExcludes = [
395+
// Having `//#sourceMappingUrl` for the `react-debug-tools` prod bundle
396+
// breaks `ReactDevToolsHooksIntegration-test.js`,because it changes
397+
// Node's generated stack traces and thus alters the hook name parsing behavior.
398+
// Also, this is an internal-only package that doesn't need sourcemaps anyway
399+
'react-debug-tools',
400+
];
401+
402+
// Only generate sourcemaps for true "production" build artifacts
403+
// that will be used by bundlers, such as `react-dom.production.min.js`.
404+
// UMD and "profiling" builds are rarely used and not worth having sourcemaps.
405+
const needsSourcemaps =
406+
needsMinifiedByClosure &&
407+
!isProfiling &&
408+
!isUMDBundle &&
409+
!sourcemapPackageExcludes.includes(entry) &&
410+
!shouldStayReadable;
411+
389412
return [
390413
// Keep dynamic imports as externals
391414
dynamicImports(),
@@ -395,7 +418,7 @@ function getPlugins(
395418
const transformed = flowRemoveTypes(code);
396419
return {
397420
code: transformed.toString(),
398-
map: transformed.generateMap(),
421+
map: null,
399422
};
400423
},
401424
},
@@ -424,6 +447,7 @@ function getPlugins(
424447
),
425448
// Remove 'use strict' from individual source files.
426449
{
450+
name: "remove 'use strict'",
427451
transform(source) {
428452
return source.replace(/['"]use strict["']/g, '');
429453
},
@@ -443,47 +467,9 @@ function getPlugins(
443467
// I'm going to port "art" to ES modules to avoid this problem.
444468
// Please don't enable this for anything else!
445469
isUMDBundle && entry === 'react-art' && commonjs(),
446-
// Apply dead code elimination and/or minification.
447-
// closure doesn't yet support leaving ESM imports intact
448-
isProduction &&
449-
bundleType !== ESM_PROD &&
450-
closure({
451-
compilation_level: 'SIMPLE',
452-
language_in: 'ECMASCRIPT_2020',
453-
language_out:
454-
bundleType === NODE_ES2015
455-
? 'ECMASCRIPT_2020'
456-
: bundleType === BROWSER_SCRIPT
457-
? 'ECMASCRIPT5'
458-
: 'ECMASCRIPT5_STRICT',
459-
emit_use_strict:
460-
bundleType !== BROWSER_SCRIPT &&
461-
bundleType !== ESM_PROD &&
462-
bundleType !== ESM_DEV,
463-
env: 'CUSTOM',
464-
warning_level: 'QUIET',
465-
apply_input_source_maps: false,
466-
use_types_for_optimization: false,
467-
process_common_js_modules: false,
468-
rewrite_polyfills: false,
469-
inject_libraries: false,
470-
allow_dynamic_import: true,
471-
472-
// Don't let it create global variables in the browser.
473-
// https://github.com/facebook/react/issues/10909
474-
assume_function_wrapper: !isUMDBundle,
475-
renaming: !shouldStayReadable,
476-
}),
477-
// Add the whitespace back if necessary.
478-
shouldStayReadable &&
479-
prettier({
480-
parser: 'flow',
481-
singleQuote: false,
482-
trailingComma: 'none',
483-
bracketSpacing: true,
484-
}),
485470
// License and haste headers, top-level `if` blocks.
486471
{
472+
name: 'license-and-headers',
487473
renderChunk(source) {
488474
return Wrappers.wrapBundle(
489475
source,
@@ -495,6 +481,85 @@ function getPlugins(
495481
);
496482
},
497483
},
484+
// Apply dead code elimination and/or minification.
485+
// closure doesn't yet support leaving ESM imports intact
486+
needsMinifiedByClosure &&
487+
closure(
488+
{
489+
compilation_level: 'SIMPLE',
490+
language_in: 'ECMASCRIPT_2020',
491+
language_out:
492+
bundleType === NODE_ES2015
493+
? 'ECMASCRIPT_2020'
494+
: bundleType === BROWSER_SCRIPT
495+
? 'ECMASCRIPT5'
496+
: 'ECMASCRIPT5_STRICT',
497+
emit_use_strict:
498+
bundleType !== BROWSER_SCRIPT &&
499+
bundleType !== ESM_PROD &&
500+
bundleType !== ESM_DEV,
501+
env: 'CUSTOM',
502+
warning_level: 'QUIET',
503+
source_map_include_content: true,
504+
use_types_for_optimization: false,
505+
process_common_js_modules: false,
506+
rewrite_polyfills: false,
507+
inject_libraries: false,
508+
allow_dynamic_import: true,
509+
510+
// Don't let it create global variables in the browser.
511+
// https://github.com/facebook/react/issues/10909
512+
assume_function_wrapper: !isUMDBundle,
513+
renaming: !shouldStayReadable,
514+
},
515+
{needsSourcemaps}
516+
),
517+
// Add the whitespace back if necessary.
518+
shouldStayReadable &&
519+
prettier({
520+
parser: 'flow',
521+
singleQuote: false,
522+
trailingComma: 'none',
523+
bracketSpacing: true,
524+
}),
525+
needsSourcemaps && {
526+
name: 'generate-prod-bundle-sourcemaps',
527+
async renderChunk(codeAfterLicense, chunk, options, meta) {
528+
// We want to generate a sourcemap that shows the production bundle source
529+
// as it existed before Closure Compiler minified that chunk, rather than
530+
// showing the "original" individual source files. This better shows
531+
// what is actually running in the app.
532+
533+
// Use a path like `node_modules/react/cjs/react.production.min.js.map` for the sourcemap file
534+
const finalSourcemapPath = options.file.replace('.js', '.js.map');
535+
const finalSourcemapFilename = path.basename(finalSourcemapPath);
536+
537+
// Read the sourcemap that Closure wrote to disk
538+
const sourcemapAfterClosure = JSON.parse(
539+
fs.readFileSync(finalSourcemapPath, 'utf8')
540+
);
541+
542+
// CC generated a file list that only contains the tempfile name.
543+
// Replace that with a more meaningful "source" name for this bundle.
544+
sourcemapAfterClosure.sources = [filename];
545+
sourcemapAfterClosure.file = filename;
546+
547+
// Overwrite the Closure-generated file with the final combined sourcemap
548+
fs.writeFileSync(
549+
finalSourcemapPath,
550+
JSON.stringify(sourcemapAfterClosure)
551+
);
552+
553+
// Add the sourcemap URL to the actual bundle, so that tools pick it up
554+
const sourceWithMappingUrl =
555+
codeAfterLicense + `\n//# sourceMappingURL=${finalSourcemapFilename}`;
556+
557+
return {
558+
code: sourceWithMappingUrl,
559+
map: null,
560+
};
561+
},
562+
},
498563
// Record bundle size.
499564
sizes({
500565
getSize: (size, gzip) => {

scripts/rollup/plugins/closure-plugin.js

+16-6
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,25 @@ function compile(flags) {
1919
});
2020
}
2121

22-
module.exports = function closure(flags = {}) {
22+
module.exports = function closure(flags = {}, {needsSourcemaps}) {
2323
return {
2424
name: 'scripts/rollup/plugins/closure-plugin',
25-
async renderChunk(code) {
25+
async renderChunk(code, chunk, options) {
2626
const inputFile = tmp.fileSync();
27-
const tempPath = inputFile.name;
28-
flags = Object.assign({}, flags, {js: tempPath});
29-
await writeFileAsync(tempPath, code, 'utf8');
30-
const compiledCode = await compile(flags);
27+
28+
// Use a path like `node_modules/react/cjs/react.production.min.js.map` for the sourcemap file
29+
const sourcemapPath = options.file.replace('.js', '.js.map');
30+
31+
// Tell Closure what JS source file to read, and optionally what sourcemap file to write
32+
const finalFlags = {
33+
...flags,
34+
js: inputFile.name,
35+
...(needsSourcemaps && {create_source_map: sourcemapPath}),
36+
};
37+
38+
await writeFileAsync(inputFile.name, code, 'utf8');
39+
const compiledCode = await compile(finalFlags);
40+
3141
inputFile.removeCallback();
3242
return {code: compiledCode};
3343
},

scripts/rollup/wrappers.js

+9
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ ${source}`;
191191
/****************** FB_WWW_DEV ******************/
192192
[FB_WWW_DEV](source, globalName, filename, moduleType) {
193193
return `/**
194+
* @preserve
194195
${license}
195196
*
196197
* @noflow
@@ -211,6 +212,7 @@ ${source}
211212
/****************** FB_WWW_PROD ******************/
212213
[FB_WWW_PROD](source, globalName, filename, moduleType) {
213214
return `/**
215+
* @preserve
214216
${license}
215217
*
216218
* @noflow
@@ -225,6 +227,7 @@ ${source}`;
225227
/****************** FB_WWW_PROFILING ******************/
226228
[FB_WWW_PROFILING](source, globalName, filename, moduleType) {
227229
return `/**
230+
* @preserve
228231
${license}
229232
*
230233
* @noflow
@@ -239,6 +242,7 @@ ${source}`;
239242
/****************** RN_OSS_DEV ******************/
240243
[RN_OSS_DEV](source, globalName, filename, moduleType) {
241244
return `/**
245+
* @preserve
242246
${license}
243247
*
244248
* @noflow
@@ -260,6 +264,7 @@ ${source}
260264
/****************** RN_OSS_PROD ******************/
261265
[RN_OSS_PROD](source, globalName, filename, moduleType) {
262266
return `/**
267+
* @preserve
263268
${license}
264269
*
265270
* @noflow
@@ -275,6 +280,7 @@ ${source}`;
275280
/****************** RN_OSS_PROFILING ******************/
276281
[RN_OSS_PROFILING](source, globalName, filename, moduleType) {
277282
return `/**
283+
* @preserve
278284
${license}
279285
*
280286
* @noflow
@@ -290,6 +296,7 @@ ${source}`;
290296
/****************** RN_FB_DEV ******************/
291297
[RN_FB_DEV](source, globalName, filename, moduleType) {
292298
return `/**
299+
* @preserve
293300
${license}
294301
*
295302
* @noflow
@@ -310,6 +317,7 @@ ${source}
310317
/****************** RN_FB_PROD ******************/
311318
[RN_FB_PROD](source, globalName, filename, moduleType) {
312319
return `/**
320+
* @preserve
313321
${license}
314322
*
315323
* @noflow
@@ -324,6 +332,7 @@ ${source}`;
324332
/****************** RN_FB_PROFILING ******************/
325333
[RN_FB_PROFILING](source, globalName, filename, moduleType) {
326334
return `/**
335+
* @preserve
327336
${license}
328337
*
329338
* @noflow

0 commit comments

Comments
 (0)