main
js 917 lines 27 KB
Raw
1 'use strict';
2
3 const rollup = require('rollup');
4 const babel = require('@rollup/plugin-babel').babel;
5 const closure = require('./plugins/closure-plugin');
6 const flowRemoveTypes = require('flow-remove-types');
7 const {dts} = require('rollup-plugin-dts');
8 const prettier = require('rollup-plugin-prettier');
9 const replace = require('@rollup/plugin-replace');
10 const typescript = require('@rollup/plugin-typescript');
11 const stripBanner = require('rollup-plugin-strip-banner');
12 const chalk = require('chalk');
13 const resolve = require('@rollup/plugin-node-resolve').nodeResolve;
14 const fs = require('fs');
15 const {performance} = require('perf_hooks');
16 const childProcess = require('child_process');
17 const argv = require('minimist')(process.argv.slice(2));
18 const Modules = require('./modules');
19 const Bundles = require('./bundles');
20 const Stats = require('./stats');
21 const Sync = require('./sync');
22 const sizes = require('./plugins/sizes-plugin');
23 const useForks = require('./plugins/use-forks-plugin');
24 const dynamicImports = require('./plugins/dynamic-imports');
25 const externalRuntime = require('./plugins/external-runtime-plugin');
26 const Packaging = require('./packaging');
27 const {selectShard, writeShardTimings} = require('./sharding');
28 const {asyncRimRaf} = require('./utils');
29 const codeFrame = require('@babel/code-frame').default;
30 const Wrappers = require('./wrappers');
31 const commonjs = require('@rollup/plugin-commonjs');
32
33 const RELEASE_CHANNEL = process.env.RELEASE_CHANNEL;
34
35 // Default to building in experimental mode. If the release channel is set via
36 // an environment variable, then check if it's "experimental".
37 const __EXPERIMENTAL__ =
38 typeof RELEASE_CHANNEL === 'string'
39 ? RELEASE_CHANNEL === 'experimental'
40 : true;
41
42 // Errors in promises should be fatal.
43 let loggedErrors = new Set();
44 process.on('unhandledRejection', err => {
45 if (loggedErrors.has(err)) {
46 // No need to print it twice.
47 process.exit(1);
48 }
49 throw err;
50 });
51
52 const {
53 NODE_ES2015,
54 ESM_DEV,
55 ESM_PROD,
56 NODE_DEV,
57 NODE_PROD,
58 NODE_PROFILING,
59 BUN_DEV,
60 BUN_PROD,
61 FB_WWW_DEV,
62 FB_WWW_PROD,
63 FB_WWW_PROFILING,
64 RN_OSS_DEV,
65 RN_OSS_PROD,
66 RN_OSS_PROFILING,
67 RN_FB_DEV,
68 RN_FB_PROD,
69 RN_FB_PROFILING,
70 BROWSER_SCRIPT,
71 CJS_DTS,
72 ESM_DTS,
73 } = Bundles.bundleTypes;
74
75 const {getFilename} = Bundles;
76
77 function parseRequestedNames(names, toCase) {
78 let result = [];
79 for (let i = 0; i < names.length; i++) {
80 let splitNames = names[i].split(',');
81 for (let j = 0; j < splitNames.length; j++) {
82 let name = splitNames[j].trim();
83 if (!name) {
84 continue;
85 }
86 if (toCase === 'uppercase') {
87 name = name.toUpperCase();
88 } else if (toCase === 'lowercase') {
89 name = name.toLowerCase();
90 }
91 result.push(name);
92 }
93 }
94 return result;
95 }
96 const argvType = Array.isArray(argv.type) ? argv.type : [argv.type];
97 const requestedBundleTypes = parseRequestedNames(
98 argv.type ? argvType : [],
99 'uppercase'
100 );
101
102 const names = argv._;
103 const requestedBundleNames = parseRequestedNames(
104 names ? names : [],
105 'lowercase'
106 );
107 const forcePrettyOutput = argv.pretty;
108 const isWatchMode = argv.watch;
109 const syncFBSourcePath = argv['sync-fbsource'];
110 const syncWWWPath = argv['sync-www'];
111
112 // Non-ES2015 stuff applied before closure compiler.
113 const babelPlugins = [
114 // These plugins filter out non-ES2015.
115 ['@babel/plugin-proposal-class-properties', {loose: true}],
116 'syntax-trailing-function-commas',
117 // These use loose mode which avoids embedding a runtime.
118 // TODO: Remove object spread from the source. Prefer Object.assign instead.
119 [
120 '@babel/plugin-proposal-object-rest-spread',
121 {loose: true, useBuiltIns: true},
122 ],
123 ['@babel/plugin-transform-template-literals', {loose: true}],
124 // TODO: Remove for...of from the source. It requires a runtime to be embedded.
125 '@babel/plugin-transform-for-of',
126 // TODO: Remove array spread from the source. Prefer .apply instead.
127 ['@babel/plugin-transform-spread', {loose: true, useBuiltIns: true}],
128 '@babel/plugin-transform-parameters',
129 // TODO: Remove array destructuring from the source. Requires runtime.
130 ['@babel/plugin-transform-destructuring', {loose: true, useBuiltIns: true}],
131 // Transform Object spread to shared/assign
132 require('../babel/transform-object-assign'),
133 ];
134
135 const babelToES5Plugins = [
136 // These plugins transform DEV mode. Closure compiler deals with these in PROD.
137 '@babel/plugin-transform-literals',
138 '@babel/plugin-transform-arrow-functions',
139 '@babel/plugin-transform-block-scoped-functions',
140 '@babel/plugin-transform-shorthand-properties',
141 ['@babel/plugin-transform-block-scoping', {throwIfClosureRequired: true}],
142 ];
143
144 function getBabelConfig(
145 updateBabelOptions,
146 bundleType,
147 packageName,
148 isDevelopment,
149 bundle
150 ) {
151 let options = {
152 exclude: '/**/node_modules/**',
153 babelrc: false,
154 configFile: false,
155 presets: [],
156 plugins: [...babelPlugins],
157 babelHelpers: 'bundled',
158 sourcemap: false,
159 };
160 if (isDevelopment) {
161 options.plugins.push(...babelToES5Plugins);
162 }
163 if (updateBabelOptions) {
164 options = updateBabelOptions(options);
165 }
166 // Controls whether to replace error messages with error codes in production.
167 // By default, error messages are replaced in production.
168 if (!isDevelopment && bundle.minifyWithProdErrorCodes !== false) {
169 options.plugins.push(require('../error-codes/transform-error-messages'));
170 }
171
172 return options;
173 }
174
175 let getRollupInteropValue = id => {
176 // We're setting Rollup to assume that imports are ES modules unless otherwise specified.
177 // However, we also compile ES import syntax to `require()` using Babel.
178 // This causes Rollup to turn uses of `import SomeDefaultImport from 'some-module' into
179 // references to `SomeDefaultImport.default` due to CJS/ESM interop.
180 // Some CJS modules don't have a `.default` export, and the rewritten import is incorrect.
181 // Specifying `interop: 'default'` instead will have Rollup use the imported variable as-is,
182 // without adding a `.default` to the reference.
183 const modulesWithCommonJsExports = [
184 'art/core/transform',
185 'art/modes/current',
186 'art/modes/fast-noSideEffects',
187 'art/modes/svg',
188 'JSResourceReferenceImpl',
189 'error-stack-parser',
190 'neo-async',
191 'webpack/lib/dependencies/ModuleDependency',
192 'webpack/lib/dependencies/NullDependency',
193 'webpack/lib/Template',
194 ];
195
196 if (modulesWithCommonJsExports.includes(id)) {
197 return 'default';
198 }
199
200 // For all other modules, handle imports without any import helper utils
201 return 'esModule';
202 };
203
204 function getRollupOutputOptions(
205 outputPath,
206 format,
207 globals,
208 globalName,
209 bundleType
210 ) {
211 const isProduction = isProductionBundleType(bundleType);
212
213 return {
214 file: outputPath,
215 format,
216 globals,
217 freeze: !isProduction,
218 interop: getRollupInteropValue,
219 name: globalName,
220 sourcemap: false,
221 esModule: false,
222 exports: 'auto',
223 };
224 }
225
226 function getFormat(bundleType) {
227 switch (bundleType) {
228 case NODE_ES2015:
229 case NODE_DEV:
230 case NODE_PROD:
231 case NODE_PROFILING:
232 case BUN_DEV:
233 case BUN_PROD:
234 case FB_WWW_DEV:
235 case FB_WWW_PROD:
236 case FB_WWW_PROFILING:
237 case RN_OSS_DEV:
238 case RN_OSS_PROD:
239 case RN_OSS_PROFILING:
240 case RN_FB_DEV:
241 case RN_FB_PROD:
242 case RN_FB_PROFILING:
243 case CJS_DTS:
244 return `cjs`;
245 case ESM_DEV:
246 case ESM_PROD:
247 case ESM_DTS:
248 return `es`;
249 case BROWSER_SCRIPT:
250 return `iife`;
251 }
252 }
253
254 function isProductionBundleType(bundleType) {
255 switch (bundleType) {
256 case NODE_ES2015:
257 return true;
258 case ESM_DEV:
259 case NODE_DEV:
260 case BUN_DEV:
261 case FB_WWW_DEV:
262 case RN_OSS_DEV:
263 case RN_FB_DEV:
264 return false;
265 case ESM_PROD:
266 case NODE_PROD:
267 case BUN_PROD:
268 case NODE_PROFILING:
269 case FB_WWW_PROD:
270 case FB_WWW_PROFILING:
271 case RN_OSS_PROD:
272 case RN_OSS_PROFILING:
273 case RN_FB_PROD:
274 case RN_FB_PROFILING:
275 case BROWSER_SCRIPT:
276 case CJS_DTS:
277 case ESM_DTS:
278 return true;
279 default:
280 throw new Error(`Unknown type: ${bundleType}`);
281 }
282 }
283
284 function isProfilingBundleType(bundleType) {
285 switch (bundleType) {
286 case NODE_ES2015:
287 case FB_WWW_DEV:
288 case FB_WWW_PROD:
289 case NODE_DEV:
290 case NODE_PROD:
291 case BUN_DEV:
292 case BUN_PROD:
293 case RN_FB_DEV:
294 case RN_FB_PROD:
295 case RN_OSS_DEV:
296 case RN_OSS_PROD:
297 case ESM_DEV:
298 case ESM_PROD:
299 case BROWSER_SCRIPT:
300 case CJS_DTS:
301 case ESM_DTS:
302 return false;
303 case FB_WWW_PROFILING:
304 case NODE_PROFILING:
305 case RN_FB_PROFILING:
306 case RN_OSS_PROFILING:
307 return true;
308 default:
309 throw new Error(`Unknown type: ${bundleType}`);
310 }
311 }
312
313 function getBundleTypeFlags(bundleType) {
314 const isFBWWWBundle =
315 bundleType === FB_WWW_DEV ||
316 bundleType === FB_WWW_PROD ||
317 bundleType === FB_WWW_PROFILING;
318 const isRNBundle =
319 bundleType === RN_OSS_DEV ||
320 bundleType === RN_OSS_PROD ||
321 bundleType === RN_OSS_PROFILING ||
322 bundleType === RN_FB_DEV ||
323 bundleType === RN_FB_PROD ||
324 bundleType === RN_FB_PROFILING;
325
326 const isFBRNBundle =
327 bundleType === RN_FB_DEV ||
328 bundleType === RN_FB_PROD ||
329 bundleType === RN_FB_PROFILING;
330
331 const shouldStayReadable = isFBWWWBundle || isRNBundle || forcePrettyOutput;
332
333 return {
334 isFBWWWBundle,
335 isRNBundle,
336 isFBRNBundle,
337 shouldStayReadable,
338 };
339 }
340
341 function forbidFBJSImports() {
342 return {
343 name: 'forbidFBJSImports',
344 resolveId(importee, importer) {
345 if (/^fbjs\//.test(importee)) {
346 throw new Error(
347 `Don't import ${importee} (found in ${importer}). ` +
348 `Use the utilities in packages/shared/ instead.`
349 );
350 }
351 },
352 };
353 }
354
355 function getPlugins(
356 entry,
357 updateBabelOptions,
358 filename,
359 packageName,
360 bundleType,
361 globalName,
362 moduleType,
363 pureExternalModules,
364 bundle
365 ) {
366 // Short-circuit if we're only building a .d.ts bundle
367 if (bundleType === CJS_DTS || bundleType === ESM_DTS) {
368 return [dts({tsconfig: bundle.tsconfig})];
369 }
370 try {
371 const forks = Modules.getForks(bundleType, entry, moduleType, bundle);
372 const isProduction = isProductionBundleType(bundleType);
373 const isProfiling = isProfilingBundleType(bundleType);
374
375 const needsMinifiedByClosure =
376 bundleType !== ESM_PROD &&
377 bundleType !== ESM_DEV &&
378 // TODO(@poteto) figure out ICE in closure compiler for eslint-plugin-react-hooks (ts)
379 bundle.tsconfig == null;
380
381 return [
382 // Keep dynamic imports as externals
383 dynamicImports(),
384 bundle.tsconfig != null ? typescript({tsconfig: bundle.tsconfig}) : false,
385 {
386 name: 'rollup-plugin-flow-remove-types',
387 transform(code, id) {
388 if (bundle.tsconfig != null && !id.endsWith('.js')) {
389 return null;
390 }
391 const transformed = flowRemoveTypes(code);
392 return {
393 code: transformed.toString(),
394 map: null,
395 };
396 },
397 },
398 // See https://github.com/rollup/plugins/issues/1425
399 bundle.tsconfig != null ? commonjs({strictRequires: true}) : false,
400 // Shim any modules that need forking in this environment.
401 useForks(forks),
402 // Ensure we don't try to bundle any fbjs modules.
403 forbidFBJSImports(),
404 // Use Node resolution mechanism.
405 resolve({
406 // `external` rollup config takes care of marking builtins as externals
407 preferBuiltins: false,
408 }),
409 // Remove license headers from individual modules
410 stripBanner({
411 exclude: 'node_modules/**/*',
412 }),
413 // Compile to ES2015.
414 babel(
415 getBabelConfig(
416 updateBabelOptions,
417 bundleType,
418 packageName,
419 !isProduction,
420 bundle
421 )
422 ),
423 // Remove 'use strict' from individual source files. We skip eslint-plugin-react-hooks because
424 // it bundles compiler-type code that may examine "use strict" used outside of a directive
425 // context, e.g. as a StringLiteral.
426 bundle.name !== 'eslint-plugin-react-hooks'
427 ? {
428 name: "remove 'use strict'",
429 transform(source) {
430 return source.replace(/['"]use strict["']/g, '');
431 },
432 }
433 : false,
434 // Turn __DEV__ and process.env checks into constants.
435 replace({
436 preventAssignment: true,
437 values: {
438 __DEV__: isProduction ? 'false' : 'true',
439 __PROFILE__: isProfiling || !isProduction ? 'true' : 'false',
440 'process.env.NODE_ENV': isProduction
441 ? "'production'"
442 : "'development'",
443 __EXPERIMENTAL__,
444 },
445 }),
446 // For the external runtime we turn global identifiers into local.
447 entry.includes('server-external-runtime') && externalRuntime(),
448 {
449 name: 'top-level-definitions',
450 renderChunk(source) {
451 return Wrappers.wrapWithTopLevelDefinitions(
452 source,
453 bundleType,
454 globalName,
455 filename,
456 moduleType,
457 bundle.wrapWithModuleBoundaries,
458 bundle.wrapWithNodeDevGuard
459 );
460 },
461 },
462 // For production builds, compile with Closure. We do this even for the
463 // "non-minified" production builds because Closure is much better at
464 // minification than what most applications use. During this step, we do
465 // preserve the original symbol names, though, so the resulting code is
466 // relatively readable.
467 //
468 // For the minified builds, the names will be mangled later.
469 //
470 // We don't bother with sourcemaps at this step. The sourcemaps we publish
471 // are only for whitespace and symbol renaming; they don't map back to
472 // before Closure was applied.
473 needsMinifiedByClosure &&
474 closure({
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: true,
499
500 // Don't rename symbols (variable names, functions, etc). We leave
501 // this up to the application to handle, if they want. Otherwise gzip
502 // takes care of it.
503 renaming: false,
504 }),
505 needsMinifiedByClosure &&
506 // Add the whitespace back
507 prettier({
508 parser: 'flow',
509 singleQuote: false,
510 trailingComma: 'none',
511 bracketSpacing: true,
512 }),
513 {
514 name: 'license-and-signature-header',
515 renderChunk(source) {
516 return Wrappers.wrapWithLicenseHeader(
517 source,
518 bundleType,
519 globalName,
520 filename,
521 moduleType
522 );
523 },
524 },
525 // Record bundle size.
526 sizes({
527 getSize: (size, gzip) => {
528 const currentSizes = Stats.currentBuildResults.bundleSizes;
529 const recordIndex = currentSizes.findIndex(
530 record =>
531 record.filename === filename && record.bundleType === bundleType
532 );
533 const index = recordIndex !== -1 ? recordIndex : currentSizes.length;
534 currentSizes[index] = {
535 filename,
536 bundleType,
537 packageName,
538 size,
539 gzip,
540 };
541 },
542 }),
543 ].filter(Boolean);
544 } catch (error) {
545 console.error(
546 chalk.red(`There was an error preparing plugins for entry "${entry}"`)
547 );
548 throw error;
549 }
550 }
551
552 function shouldSkipBundle(bundle, bundleType) {
553 const shouldSkipBundleType = bundle.bundleTypes.indexOf(bundleType) === -1;
554 if (shouldSkipBundleType) {
555 return true;
556 }
557 if (requestedBundleTypes.length > 0) {
558 const hasRequestedBundleType = requestedBundleTypes.some(requestedType =>
559 bundleType.includes(requestedType)
560 );
561 if (!hasRequestedBundleType) {
562 return true;
563 }
564 }
565 if (requestedBundleNames.length > 0) {
566 // If the name ends with `something/index` we only match if the
567 // entry ends in something. Such as `react-dom/index` only matches
568 // `react-dom` but not `react-dom/server`. Everything else is fuzzy
569 // search.
570 const entryLowerCase = bundle.entry.toLowerCase() + '/index.js';
571 const isAskingForDifferentNames = requestedBundleNames.every(
572 requestedName => {
573 const matchEntry = entryLowerCase.indexOf(requestedName) !== -1;
574 if (!bundle.name) {
575 return !matchEntry;
576 }
577 const matchName =
578 bundle.name.toLowerCase().indexOf(requestedName) !== -1;
579 return !matchEntry && !matchName;
580 }
581 );
582 if (isAskingForDifferentNames) {
583 return true;
584 }
585 }
586 return false;
587 }
588
589 function resolveEntryFork(resolvedEntry, isFBBundle, isDev) {
590 // Pick which entry point fork to use:
591 // .modern.fb.js
592 // .classic.fb.js
593 // .fb.js
594 // .stable.js
595 // .experimental.js
596 // .js
597 // or any of those plus .development.js
598
599 if (isFBBundle) {
600 const resolvedFBEntry = resolvedEntry.replace(
601 '.js',
602 __EXPERIMENTAL__ ? '.modern.fb.js' : '.classic.fb.js'
603 );
604 const devFBEntry = resolvedFBEntry.replace('.js', '.development.js');
605 if (isDev && fs.existsSync(devFBEntry)) {
606 return devFBEntry;
607 }
608 if (fs.existsSync(resolvedFBEntry)) {
609 return resolvedFBEntry;
610 }
611 const resolvedGenericFBEntry = resolvedEntry.replace('.js', '.fb.js');
612 const devGenericFBEntry = resolvedGenericFBEntry.replace(
613 '.js',
614 '.development.js'
615 );
616 if (isDev && fs.existsSync(devGenericFBEntry)) {
617 return devGenericFBEntry;
618 }
619 if (fs.existsSync(resolvedGenericFBEntry)) {
620 return resolvedGenericFBEntry;
621 }
622 // Even if it's a FB bundle we fallthrough to pick stable or experimental if we don't have an FB fork.
623 }
624 const resolvedForkedEntry = resolvedEntry.replace(
625 '.js',
626 __EXPERIMENTAL__ ? '.experimental.js' : '.stable.js'
627 );
628 const devForkedEntry = resolvedForkedEntry.replace('.js', '.development.js');
629 if (isDev && fs.existsSync(devForkedEntry)) {
630 return devForkedEntry;
631 }
632 if (fs.existsSync(resolvedForkedEntry)) {
633 return resolvedForkedEntry;
634 }
635 // Just use the plain .js one.
636 return resolvedEntry;
637 }
638
639 async function createBundle(bundle, bundleType) {
640 const filename = getFilename(bundle, bundleType);
641 const logKey =
642 chalk.white.bold(filename) + chalk.dim(` (${bundleType.toLowerCase()})`);
643 const format = getFormat(bundleType);
644 const packageName = Packaging.getPackageName(bundle.entry);
645
646 const {isFBWWWBundle, isFBRNBundle} = getBundleTypeFlags(bundleType);
647
648 const resolvedEntry = resolveEntryFork(
649 require.resolve(bundle.entry),
650 isFBWWWBundle || isFBRNBundle,
651 !isProductionBundleType(bundleType)
652 );
653
654 const peerGlobals = Modules.getPeerGlobals(bundle.externals, bundleType);
655 let externals = Object.keys(peerGlobals);
656
657 const deps = Modules.getDependencies(bundleType, bundle.entry);
658 externals = externals.concat(deps);
659
660 const importSideEffects = Modules.getImportSideEffects();
661 const pureExternalModules = Object.keys(importSideEffects).filter(
662 module => !importSideEffects[module]
663 );
664
665 const rollupConfig = {
666 input: resolvedEntry,
667 treeshake: {
668 moduleSideEffects: (id, external) =>
669 !(external && pureExternalModules.includes(id)),
670 propertyReadSideEffects: false,
671 },
672 external(id) {
673 const containsThisModule = pkg => id === pkg || id.startsWith(pkg + '/');
674 const isProvidedByDependency = externals.some(containsThisModule);
675 if (isProvidedByDependency) {
676 if (id.indexOf('/src/') !== -1) {
677 throw Error(
678 'You are trying to import ' +
679 id +
680 ' but ' +
681 externals.find(containsThisModule) +
682 ' is one of npm dependencies, ' +
683 'so it will not contain that source file. You probably want ' +
684 'to create a new bundle entry point for it instead.'
685 );
686 }
687 return true;
688 }
689 return !!peerGlobals[id];
690 },
691 onwarn: handleRollupWarning,
692 plugins: getPlugins(
693 bundle.entry,
694 bundle.babel,
695 filename,
696 packageName,
697 bundleType,
698 bundle.global,
699 bundle.moduleType,
700 pureExternalModules,
701 bundle
702 ),
703 output: {
704 externalLiveBindings: false,
705 freeze: false,
706 interop: getRollupInteropValue,
707 esModule: false,
708 },
709 };
710 const mainOutputPath = Packaging.getBundleOutputPath(
711 bundle,
712 bundleType,
713 filename,
714 packageName
715 );
716
717 const rollupOutputOptions = getRollupOutputOptions(
718 mainOutputPath,
719 format,
720 peerGlobals,
721 bundle.global,
722 bundleType
723 );
724
725 if (isWatchMode) {
726 rollupConfig.output = [rollupOutputOptions];
727 const watcher = rollup.watch(rollupConfig);
728 watcher.on('event', async event => {
729 switch (event.code) {
730 case 'BUNDLE_START':
731 console.log(`${chalk.bgYellow.black(' BUILDING ')} ${logKey}`);
732 break;
733 case 'BUNDLE_END':
734 console.log(`${chalk.bgGreen.black(' COMPLETE ')} ${logKey}\n`);
735 break;
736 case 'ERROR':
737 case 'FATAL':
738 console.log(`${chalk.bgRed.black(' OH NOES! ')} ${logKey}\n`);
739 handleRollupError(event.error);
740 break;
741 }
742 });
743 } else {
744 console.log(`${chalk.bgYellow.black(' BUILDING ')} ${logKey}`);
745 try {
746 const result = await rollup.rollup(rollupConfig);
747 await result.write(rollupOutputOptions);
748 } catch (error) {
749 console.log(`${chalk.bgRed.black(' OH NOES! ')} ${logKey}\n`);
750 handleRollupError(error);
751 throw error;
752 }
753 console.log(`${chalk.bgGreen.black(' COMPLETE ')} ${logKey}\n`);
754 }
755 }
756
757 function handleRollupWarning(warning) {
758 if (warning.code === 'UNUSED_EXTERNAL_IMPORT') {
759 const match = warning.message.match(/external module "([^"]+)"/);
760 if (!match || typeof match[1] !== 'string') {
761 throw new Error(
762 'Could not parse a Rollup warning. ' + 'Fix this method.'
763 );
764 }
765 const importSideEffects = Modules.getImportSideEffects();
766 const externalModule = match[1];
767 if (typeof importSideEffects[externalModule] !== 'boolean') {
768 throw new Error(
769 'An external module "' +
770 externalModule +
771 '" is used in a DEV-only code path ' +
772 'but we do not know if it is safe to omit an unused require() to it in production. ' +
773 'Please add it to the `importSideEffects` list in `scripts/rollup/modules.js`.'
774 );
775 }
776 // Don't warn. We will remove side effectless require() in a later pass.
777 return;
778 }
779
780 if (warning.code === 'CIRCULAR_DEPENDENCY') {
781 // Ignored
782 } else if (typeof warning.code === 'string') {
783 // This is a warning coming from Rollup itself.
784 // These tend to be important (e.g. clashes in namespaced exports)
785 // so we'll fail the build on any of them.
786 console.error();
787 console.error(warning.message || warning);
788 console.error();
789 process.exit(1);
790 } else {
791 // The warning is from one of the plugins.
792 // Maybe it's not important, so just print it.
793 console.warn(warning.message || warning);
794 }
795 }
796
797 function handleRollupError(error) {
798 loggedErrors.add(error);
799 if (!error.code) {
800 console.error(error);
801 return;
802 }
803 console.error(
804 `\x1b[31m-- ${error.code}${error.plugin ? ` (${error.plugin})` : ''} --`
805 );
806 console.error(error.stack);
807 if (error.loc && error.loc.file) {
808 const {file, line, column} = error.loc;
809 // This looks like an error from Rollup, e.g. missing export.
810 // We'll use the accurate line numbers provided by Rollup but
811 // use Babel code frame because it looks nicer.
812 const rawLines = fs.readFileSync(file, 'utf-8');
813 // column + 1 is required due to rollup counting column start position from 0
814 // whereas babel-code-frame counts from 1
815 const frame = codeFrame(rawLines, line, column + 1, {
816 highlightCode: true,
817 });
818 console.error(frame);
819 } else if (error.codeFrame) {
820 // This looks like an error from a plugin (e.g. Babel).
821 // In this case we'll resort to displaying the provided code frame
822 // because we can't be sure the reported location is accurate.
823 console.error(error.codeFrame);
824 }
825 }
826
827 function runShellCommand(command) {
828 console.log(chalk.dim('Running: ') + chalk.cyan(command));
829 childProcess.execSync(command, {stdio: 'inherit', shell: true});
830 }
831
832 async function buildEverything() {
833 if (!argv['unsafe-partial']) {
834 await asyncRimRaf('build');
835 }
836
837 // Run them serially for better console output
838 // and to avoid any potential race conditions.
839
840 let bundles = [];
841 // eslint-disable-next-line no-for-of-loops/no-for-of-loops
842 for (const bundle of Bundles.bundles) {
843 bundles.push(
844 [bundle, NODE_ES2015],
845 [bundle, ESM_DEV],
846 [bundle, ESM_PROD],
847 [bundle, NODE_DEV],
848 [bundle, NODE_PROD],
849 [bundle, NODE_PROFILING],
850 [bundle, BUN_DEV],
851 [bundle, BUN_PROD],
852 [bundle, FB_WWW_DEV],
853 [bundle, FB_WWW_PROD],
854 [bundle, FB_WWW_PROFILING],
855 [bundle, RN_OSS_DEV],
856 [bundle, RN_OSS_PROD],
857 [bundle, RN_OSS_PROFILING],
858 [bundle, RN_FB_DEV],
859 [bundle, RN_FB_PROD],
860 [bundle, RN_FB_PROFILING],
861 [bundle, BROWSER_SCRIPT],
862 [bundle, CJS_DTS],
863 [bundle, ESM_DTS]
864 );
865 }
866
867 bundles = bundles.filter(([bundle, bundleType]) => {
868 return !shouldSkipBundle(bundle, bundleType);
869 });
870
871 // Prefixed with the channel because feature-flag forks change the cost of
872 // some heavy bundles.
873 const shardKeyOf = ([bundle, bundleType]) =>
874 process.env.RELEASE_CHANNEL +
875 '/' +
876 getFilename(bundle, bundleType) +
877 ' (' +
878 bundleType.toLowerCase() +
879 ')';
880
881 if (process.env.CI_TOTAL && process.env.CI_INDEX) {
882 const nodeTotal = parseInt(process.env.CI_TOTAL, 10);
883 const nodeIndex = parseInt(process.env.CI_INDEX, 10);
884 bundles = selectShard(bundles, shardKeyOf, nodeTotal, nodeIndex);
885 }
886
887 const shardTimings = [];
888 // eslint-disable-next-line no-for-of-loops/no-for-of-loops
889 for (const [bundle, bundleType] of bundles) {
890 if (bundle.prebuild) {
891 runShellCommand(bundle.prebuild);
892 }
893 const start = performance.now();
894 await createBundle(bundle, bundleType);
895 shardTimings.push({
896 key: shardKeyOf([bundle, bundleType]),
897 seconds: (performance.now() - start) / 1000,
898 });
899 }
900 writeShardTimings(shardTimings);
901
902 await Packaging.copyAllShims();
903 await Packaging.prepareNpmPackages();
904
905 if (syncFBSourcePath) {
906 await Sync.syncReactNative(syncFBSourcePath);
907 } else if (syncWWWPath) {
908 await Sync.syncReactDom('build/facebook-www', syncWWWPath);
909 }
910
911 console.log(Stats.printResults());
912 if (!forcePrettyOutput) {
913 Stats.saveResults();
914 }
915 }
916
917 buildEverything();