react/scripts/rollup/build.js
Joseph Savona 7d29ecbeb2
[compiler] Aggregate error reporting, separate eslint rules (#34176)
NOTE: this is a merged version of @mofeiZ's original PR along with my
edits per offline discussion. The description is updated to reflect the
latest approach.

The key problem we're trying to solve with this PR is to allow
developers more control over the compiler's various validations. The
idea is to have a number of rules targeting a specific category of
issues, such as enforcing immutability of props/state/etc or disallowing
access to refs during render. We don't want to have to run the compiler
again for every single rule, though, so @mofeiZ added an LRU cache that
caches the full compilation output of N most recent files. The first
rule to run on a given file will cause it to get cached, and then
subsequent rules can pull from the cache, with each rule filtering down
to its specific category of errors.

For the categories, I went through and assigned a category roughly 1:1
to existing validations, and then used my judgement on some places that
felt distinct enough to warrant a separate error. Every error in the
compiler now has to supply both a severity (for legacy reasons) and a
category (for ESLint). Each category corresponds 1:1 to a ESLint rule
definition, so that the set of rules is automatically populated based on
the defined categories.

Categories include a flag for whether they should be in the recommended
set or not.

Note that as with the original version of this PR, only
eslint-plugin-react-compiler is changed. We still have to update the
main lint rule.

## Test Plan

* Created a sample project using ESLint v9 and verified that the plugin
can be configured correctly and detects errors
* Edited `fixtures/eslint-v9` and introduced errors, verified that the w
latest config changes in that fixture it correctly detects the errors
* In the sample project, confirmed that the LRU caching is correctly
caching compiler output, ie compiling files just once.

Co-authored-by: Mofei Zhang <feifei0@meta.com>
2025-08-21 14:53:34 -07:00

901 lines
26 KiB
JavaScript

'use strict';
const rollup = require('rollup');
const babel = require('@rollup/plugin-babel').babel;
const closure = require('./plugins/closure-plugin');
const flowRemoveTypes = require('flow-remove-types');
const {dts} = require('rollup-plugin-dts');
const prettier = require('rollup-plugin-prettier');
const replace = require('@rollup/plugin-replace');
const typescript = require('@rollup/plugin-typescript');
const stripBanner = require('rollup-plugin-strip-banner');
const chalk = require('chalk');
const resolve = require('@rollup/plugin-node-resolve').nodeResolve;
const fs = require('fs');
const childProcess = require('child_process');
const argv = require('minimist')(process.argv.slice(2));
const Modules = require('./modules');
const Bundles = require('./bundles');
const Stats = require('./stats');
const Sync = require('./sync');
const sizes = require('./plugins/sizes-plugin');
const useForks = require('./plugins/use-forks-plugin');
const dynamicImports = require('./plugins/dynamic-imports');
const externalRuntime = require('./plugins/external-runtime-plugin');
const Packaging = require('./packaging');
const {asyncRimRaf} = require('./utils');
const codeFrame = require('@babel/code-frame').default;
const Wrappers = require('./wrappers');
const commonjs = require('@rollup/plugin-commonjs');
const RELEASE_CHANNEL = process.env.RELEASE_CHANNEL;
// Default to building in experimental mode. If the release channel is set via
// an environment variable, then check if it's "experimental".
const __EXPERIMENTAL__ =
typeof RELEASE_CHANNEL === 'string'
? RELEASE_CHANNEL === 'experimental'
: true;
// Errors in promises should be fatal.
let loggedErrors = new Set();
process.on('unhandledRejection', err => {
if (loggedErrors.has(err)) {
// No need to print it twice.
process.exit(1);
}
throw err;
});
const {
NODE_ES2015,
ESM_DEV,
ESM_PROD,
NODE_DEV,
NODE_PROD,
NODE_PROFILING,
BUN_DEV,
BUN_PROD,
FB_WWW_DEV,
FB_WWW_PROD,
FB_WWW_PROFILING,
RN_OSS_DEV,
RN_OSS_PROD,
RN_OSS_PROFILING,
RN_FB_DEV,
RN_FB_PROD,
RN_FB_PROFILING,
BROWSER_SCRIPT,
CJS_DTS,
ESM_DTS,
} = Bundles.bundleTypes;
const {getFilename} = Bundles;
function parseRequestedNames(names, toCase) {
let result = [];
for (let i = 0; i < names.length; i++) {
let splitNames = names[i].split(',');
for (let j = 0; j < splitNames.length; j++) {
let name = splitNames[j].trim();
if (!name) {
continue;
}
if (toCase === 'uppercase') {
name = name.toUpperCase();
} else if (toCase === 'lowercase') {
name = name.toLowerCase();
}
result.push(name);
}
}
return result;
}
const argvType = Array.isArray(argv.type) ? argv.type : [argv.type];
const requestedBundleTypes = parseRequestedNames(
argv.type ? argvType : [],
'uppercase'
);
const names = argv._;
const requestedBundleNames = parseRequestedNames(
names ? names : [],
'lowercase'
);
const forcePrettyOutput = argv.pretty;
const isWatchMode = argv.watch;
const syncFBSourcePath = argv['sync-fbsource'];
const syncWWWPath = argv['sync-www'];
// Non-ES2015 stuff applied before closure compiler.
const babelPlugins = [
// These plugins filter out non-ES2015.
['@babel/plugin-proposal-class-properties', {loose: true}],
'syntax-trailing-function-commas',
// These use loose mode which avoids embedding a runtime.
// TODO: Remove object spread from the source. Prefer Object.assign instead.
[
'@babel/plugin-proposal-object-rest-spread',
{loose: true, useBuiltIns: true},
],
['@babel/plugin-transform-template-literals', {loose: true}],
// TODO: Remove for...of from the source. It requires a runtime to be embedded.
'@babel/plugin-transform-for-of',
// TODO: Remove array spread from the source. Prefer .apply instead.
['@babel/plugin-transform-spread', {loose: true, useBuiltIns: true}],
'@babel/plugin-transform-parameters',
// TODO: Remove array destructuring from the source. Requires runtime.
['@babel/plugin-transform-destructuring', {loose: true, useBuiltIns: true}],
// Transform Object spread to shared/assign
require('../babel/transform-object-assign'),
];
const babelToES5Plugins = [
// These plugins transform DEV mode. Closure compiler deals with these in PROD.
'@babel/plugin-transform-literals',
'@babel/plugin-transform-arrow-functions',
'@babel/plugin-transform-block-scoped-functions',
'@babel/plugin-transform-shorthand-properties',
'@babel/plugin-transform-computed-properties',
['@babel/plugin-transform-block-scoping', {throwIfClosureRequired: true}],
];
function getBabelConfig(
updateBabelOptions,
bundleType,
packageName,
externals,
isDevelopment,
bundle
) {
let options = {
exclude: '/**/node_modules/**',
babelrc: false,
configFile: false,
presets: [],
plugins: [...babelPlugins],
babelHelpers: 'bundled',
sourcemap: false,
};
if (isDevelopment) {
options.plugins.push(...babelToES5Plugins);
}
if (updateBabelOptions) {
options = updateBabelOptions(options);
}
// Controls whether to replace error messages with error codes in production.
// By default, error messages are replaced in production.
if (!isDevelopment && bundle.minifyWithProdErrorCodes !== false) {
options.plugins.push(require('../error-codes/transform-error-messages'));
}
return options;
}
let getRollupInteropValue = id => {
// We're setting Rollup to assume that imports are ES modules unless otherwise specified.
// However, we also compile ES import syntax to `require()` using Babel.
// This causes Rollup to turn uses of `import SomeDefaultImport from 'some-module' into
// references to `SomeDefaultImport.default` due to CJS/ESM interop.
// Some CJS modules don't have a `.default` export, and the rewritten import is incorrect.
// Specifying `interop: 'default'` instead will have Rollup use the imported variable as-is,
// without adding a `.default` to the reference.
const modulesWithCommonJsExports = [
'art/core/transform',
'art/modes/current',
'art/modes/fast-noSideEffects',
'art/modes/svg',
'JSResourceReferenceImpl',
'error-stack-parser',
'neo-async',
'webpack/lib/dependencies/ModuleDependency',
'webpack/lib/dependencies/NullDependency',
'webpack/lib/Template',
];
if (modulesWithCommonJsExports.includes(id)) {
return 'default';
}
// For all other modules, handle imports without any import helper utils
return 'esModule';
};
function getRollupOutputOptions(
outputPath,
format,
globals,
globalName,
bundleType
) {
const isProduction = isProductionBundleType(bundleType);
return {
file: outputPath,
format,
globals,
freeze: !isProduction,
interop: getRollupInteropValue,
name: globalName,
sourcemap: false,
esModule: false,
exports: 'auto',
};
}
function getFormat(bundleType) {
switch (bundleType) {
case NODE_ES2015:
case NODE_DEV:
case NODE_PROD:
case NODE_PROFILING:
case BUN_DEV:
case BUN_PROD:
case FB_WWW_DEV:
case FB_WWW_PROD:
case FB_WWW_PROFILING:
case RN_OSS_DEV:
case RN_OSS_PROD:
case RN_OSS_PROFILING:
case RN_FB_DEV:
case RN_FB_PROD:
case RN_FB_PROFILING:
case CJS_DTS:
return `cjs`;
case ESM_DEV:
case ESM_PROD:
case ESM_DTS:
return `es`;
case BROWSER_SCRIPT:
return `iife`;
}
}
function isProductionBundleType(bundleType) {
switch (bundleType) {
case NODE_ES2015:
return true;
case ESM_DEV:
case NODE_DEV:
case BUN_DEV:
case FB_WWW_DEV:
case RN_OSS_DEV:
case RN_FB_DEV:
return false;
case ESM_PROD:
case NODE_PROD:
case BUN_PROD:
case NODE_PROFILING:
case FB_WWW_PROD:
case FB_WWW_PROFILING:
case RN_OSS_PROD:
case RN_OSS_PROFILING:
case RN_FB_PROD:
case RN_FB_PROFILING:
case BROWSER_SCRIPT:
case CJS_DTS:
case ESM_DTS:
return true;
default:
throw new Error(`Unknown type: ${bundleType}`);
}
}
function isProfilingBundleType(bundleType) {
switch (bundleType) {
case NODE_ES2015:
case FB_WWW_DEV:
case FB_WWW_PROD:
case NODE_DEV:
case NODE_PROD:
case BUN_DEV:
case BUN_PROD:
case RN_FB_DEV:
case RN_FB_PROD:
case RN_OSS_DEV:
case RN_OSS_PROD:
case ESM_DEV:
case ESM_PROD:
case BROWSER_SCRIPT:
case CJS_DTS:
case ESM_DTS:
return false;
case FB_WWW_PROFILING:
case NODE_PROFILING:
case RN_FB_PROFILING:
case RN_OSS_PROFILING:
return true;
default:
throw new Error(`Unknown type: ${bundleType}`);
}
}
function getBundleTypeFlags(bundleType) {
const isFBWWWBundle =
bundleType === FB_WWW_DEV ||
bundleType === FB_WWW_PROD ||
bundleType === FB_WWW_PROFILING;
const isRNBundle =
bundleType === RN_OSS_DEV ||
bundleType === RN_OSS_PROD ||
bundleType === RN_OSS_PROFILING ||
bundleType === RN_FB_DEV ||
bundleType === RN_FB_PROD ||
bundleType === RN_FB_PROFILING;
const isFBRNBundle =
bundleType === RN_FB_DEV ||
bundleType === RN_FB_PROD ||
bundleType === RN_FB_PROFILING;
const shouldStayReadable = isFBWWWBundle || isRNBundle || forcePrettyOutput;
return {
isFBWWWBundle,
isRNBundle,
isFBRNBundle,
shouldStayReadable,
};
}
function forbidFBJSImports() {
return {
name: 'forbidFBJSImports',
resolveId(importee, importer) {
if (/^fbjs\//.test(importee)) {
throw new Error(
`Don't import ${importee} (found in ${importer}). ` +
`Use the utilities in packages/shared/ instead.`
);
}
},
};
}
function getPlugins(
entry,
externals,
updateBabelOptions,
filename,
packageName,
bundleType,
globalName,
moduleType,
pureExternalModules,
bundle
) {
// Short-circuit if we're only building a .d.ts bundle
if (bundleType === CJS_DTS || bundleType === ESM_DTS) {
return [dts({tsconfig: bundle.tsconfig})];
}
try {
const forks = Modules.getForks(bundleType, entry, moduleType, bundle);
const isProduction = isProductionBundleType(bundleType);
const isProfiling = isProfilingBundleType(bundleType);
const needsMinifiedByClosure =
bundleType !== ESM_PROD &&
bundleType !== ESM_DEV &&
// TODO(@poteto) figure out ICE in closure compiler for eslint-plugin-react-hooks (ts)
bundle.tsconfig == null;
return [
// Keep dynamic imports as externals
dynamicImports(),
bundle.tsconfig != null
? typescript({tsconfig: bundle.tsconfig})
: {
name: 'rollup-plugin-flow-remove-types',
transform(code) {
const transformed = flowRemoveTypes(code);
return {
code: transformed.toString(),
map: null,
};
},
},
// See https://github.com/rollup/plugins/issues/1425
bundle.tsconfig != null ? commonjs({strictRequires: true}) : false,
// Shim any modules that need forking in this environment.
useForks(forks),
// Ensure we don't try to bundle any fbjs modules.
forbidFBJSImports(),
// Use Node resolution mechanism.
resolve({
// skip: externals, // TODO: options.skip was removed in @rollup/plugin-node-resolve 3.0.0
preferBuiltins: bundle.preferBuiltins,
}),
// Remove license headers from individual modules
stripBanner({
exclude: 'node_modules/**/*',
}),
// Compile to ES2015.
babel(
getBabelConfig(
updateBabelOptions,
bundleType,
packageName,
externals,
!isProduction,
bundle
)
),
// Remove 'use strict' from individual source files. We skip eslint-plugin-react-hooks because
// it bundles compiler-type code that may examine "use strict" used outside of a directive
// context, e.g. as a StringLiteral.
bundle.name !== 'eslint-plugin-react-hooks'
? {
name: "remove 'use strict'",
transform(source) {
return source.replace(/['"]use strict["']/g, '');
},
}
: false,
// Turn __DEV__ and process.env checks into constants.
replace({
preventAssignment: true,
values: {
__DEV__: isProduction ? 'false' : 'true',
__PROFILE__: isProfiling || !isProduction ? 'true' : 'false',
'process.env.NODE_ENV': isProduction
? "'production'"
: "'development'",
__EXPERIMENTAL__,
},
}),
// For the external runtime we turn global identifiers into local.
entry.includes('server-external-runtime') && externalRuntime(),
{
name: 'top-level-definitions',
renderChunk(source) {
return Wrappers.wrapWithTopLevelDefinitions(
source,
bundleType,
globalName,
filename,
moduleType,
bundle.wrapWithModuleBoundaries
);
},
},
// For production builds, compile with Closure. We do this even for the
// "non-minified" production builds because Closure is much better at
// minification than what most applications use. During this step, we do
// preserve the original symbol names, though, so the resulting code is
// relatively readable.
//
// For the minified builds, the names will be mangled later.
//
// We don't bother with sourcemaps at this step. The sourcemaps we publish
// are only for whitespace and symbol renaming; they don't map back to
// before Closure was applied.
needsMinifiedByClosure &&
closure({
compilation_level: 'SIMPLE',
language_in: 'ECMASCRIPT_2020',
language_out:
bundleType === NODE_ES2015
? 'ECMASCRIPT_2020'
: bundleType === BROWSER_SCRIPT
? 'ECMASCRIPT5'
: 'ECMASCRIPT5_STRICT',
emit_use_strict:
bundleType !== BROWSER_SCRIPT &&
bundleType !== ESM_PROD &&
bundleType !== ESM_DEV,
env: 'CUSTOM',
warning_level: 'QUIET',
source_map_include_content: true,
use_types_for_optimization: false,
process_common_js_modules: false,
rewrite_polyfills: false,
inject_libraries: false,
allow_dynamic_import: true,
// Don't let it create global variables in the browser.
// https://github.com/facebook/react/issues/10909
assume_function_wrapper: true,
// Don't rename symbols (variable names, functions, etc). We leave
// this up to the application to handle, if they want. Otherwise gzip
// takes care of it.
renaming: false,
}),
needsMinifiedByClosure &&
// Add the whitespace back
prettier({
parser: 'flow',
singleQuote: false,
trailingComma: 'none',
bracketSpacing: true,
}),
{
name: 'license-and-signature-header',
renderChunk(source) {
return Wrappers.wrapWithLicenseHeader(
source,
bundleType,
globalName,
filename,
moduleType
);
},
},
// Record bundle size.
sizes({
getSize: (size, gzip) => {
const currentSizes = Stats.currentBuildResults.bundleSizes;
const recordIndex = currentSizes.findIndex(
record =>
record.filename === filename && record.bundleType === bundleType
);
const index = recordIndex !== -1 ? recordIndex : currentSizes.length;
currentSizes[index] = {
filename,
bundleType,
packageName,
size,
gzip,
};
},
}),
].filter(Boolean);
} catch (error) {
console.error(
chalk.red(`There was an error preparing plugins for entry "${entry}"`)
);
throw error;
}
}
function shouldSkipBundle(bundle, bundleType) {
const shouldSkipBundleType = bundle.bundleTypes.indexOf(bundleType) === -1;
if (shouldSkipBundleType) {
return true;
}
if (requestedBundleTypes.length > 0) {
const hasRequestedBundleType = requestedBundleTypes.some(requestedType =>
bundleType.includes(requestedType)
);
if (!hasRequestedBundleType) {
return true;
}
}
if (requestedBundleNames.length > 0) {
// If the name ends with `something/index` we only match if the
// entry ends in something. Such as `react-dom/index` only matches
// `react-dom` but not `react-dom/server`. Everything else is fuzzy
// search.
const entryLowerCase = bundle.entry.toLowerCase() + '/index.js';
const isAskingForDifferentNames = requestedBundleNames.every(
requestedName => {
const matchEntry = entryLowerCase.indexOf(requestedName) !== -1;
if (!bundle.name) {
return !matchEntry;
}
const matchName =
bundle.name.toLowerCase().indexOf(requestedName) !== -1;
return !matchEntry && !matchName;
}
);
if (isAskingForDifferentNames) {
return true;
}
}
return false;
}
function resolveEntryFork(resolvedEntry, isFBBundle, isDev) {
// Pick which entry point fork to use:
// .modern.fb.js
// .classic.fb.js
// .fb.js
// .stable.js
// .experimental.js
// .js
// or any of those plus .development.js
if (isFBBundle) {
const resolvedFBEntry = resolvedEntry.replace(
'.js',
__EXPERIMENTAL__ ? '.modern.fb.js' : '.classic.fb.js'
);
const devFBEntry = resolvedFBEntry.replace('.js', '.development.js');
if (isDev && fs.existsSync(devFBEntry)) {
return devFBEntry;
}
if (fs.existsSync(resolvedFBEntry)) {
return resolvedFBEntry;
}
const resolvedGenericFBEntry = resolvedEntry.replace('.js', '.fb.js');
const devGenericFBEntry = resolvedGenericFBEntry.replace(
'.js',
'.development.js'
);
if (isDev && fs.existsSync(devGenericFBEntry)) {
return devGenericFBEntry;
}
if (fs.existsSync(resolvedGenericFBEntry)) {
return resolvedGenericFBEntry;
}
// Even if it's a FB bundle we fallthrough to pick stable or experimental if we don't have an FB fork.
}
const resolvedForkedEntry = resolvedEntry.replace(
'.js',
__EXPERIMENTAL__ ? '.experimental.js' : '.stable.js'
);
const devForkedEntry = resolvedForkedEntry.replace('.js', '.development.js');
if (isDev && fs.existsSync(devForkedEntry)) {
return devForkedEntry;
}
if (fs.existsSync(resolvedForkedEntry)) {
return resolvedForkedEntry;
}
// Just use the plain .js one.
return resolvedEntry;
}
async function createBundle(bundle, bundleType) {
const filename = getFilename(bundle, bundleType);
const logKey =
chalk.white.bold(filename) + chalk.dim(` (${bundleType.toLowerCase()})`);
const format = getFormat(bundleType);
const packageName = Packaging.getPackageName(bundle.entry);
const {isFBWWWBundle, isFBRNBundle} = getBundleTypeFlags(bundleType);
const resolvedEntry = resolveEntryFork(
require.resolve(bundle.entry),
isFBWWWBundle || isFBRNBundle,
!isProductionBundleType(bundleType)
);
const peerGlobals = Modules.getPeerGlobals(bundle.externals, bundleType);
let externals = Object.keys(peerGlobals);
const deps = Modules.getDependencies(bundleType, bundle.entry);
externals = externals.concat(deps);
const importSideEffects = Modules.getImportSideEffects();
const pureExternalModules = Object.keys(importSideEffects).filter(
module => !importSideEffects[module]
);
const rollupConfig = {
input: resolvedEntry,
treeshake: {
moduleSideEffects: (id, external) =>
!(external && pureExternalModules.includes(id)),
propertyReadSideEffects: false,
},
external(id) {
const containsThisModule = pkg => id === pkg || id.startsWith(pkg + '/');
const isProvidedByDependency = externals.some(containsThisModule);
if (isProvidedByDependency) {
if (id.indexOf('/src/') !== -1) {
throw Error(
'You are trying to import ' +
id +
' but ' +
externals.find(containsThisModule) +
' is one of npm dependencies, ' +
'so it will not contain that source file. You probably want ' +
'to create a new bundle entry point for it instead.'
);
}
return true;
}
return !!peerGlobals[id];
},
onwarn: handleRollupWarning,
plugins: getPlugins(
bundle.entry,
externals,
bundle.babel,
filename,
packageName,
bundleType,
bundle.global,
bundle.moduleType,
pureExternalModules,
bundle
),
output: {
externalLiveBindings: false,
freeze: false,
interop: getRollupInteropValue,
esModule: false,
},
};
const mainOutputPath = Packaging.getBundleOutputPath(
bundle,
bundleType,
filename,
packageName
);
const rollupOutputOptions = getRollupOutputOptions(
mainOutputPath,
format,
peerGlobals,
bundle.global,
bundleType
);
if (isWatchMode) {
rollupConfig.output = [rollupOutputOptions];
const watcher = rollup.watch(rollupConfig);
watcher.on('event', async event => {
switch (event.code) {
case 'BUNDLE_START':
console.log(`${chalk.bgYellow.black(' BUILDING ')} ${logKey}`);
break;
case 'BUNDLE_END':
console.log(`${chalk.bgGreen.black(' COMPLETE ')} ${logKey}\n`);
break;
case 'ERROR':
case 'FATAL':
console.log(`${chalk.bgRed.black(' OH NOES! ')} ${logKey}\n`);
handleRollupError(event.error);
break;
}
});
} else {
console.log(`${chalk.bgYellow.black(' BUILDING ')} ${logKey}`);
try {
const result = await rollup.rollup(rollupConfig);
await result.write(rollupOutputOptions);
} catch (error) {
console.log(`${chalk.bgRed.black(' OH NOES! ')} ${logKey}\n`);
handleRollupError(error);
throw error;
}
console.log(`${chalk.bgGreen.black(' COMPLETE ')} ${logKey}\n`);
}
}
function handleRollupWarning(warning) {
if (warning.code === 'UNUSED_EXTERNAL_IMPORT') {
const match = warning.message.match(/external module "([^"]+)"/);
if (!match || typeof match[1] !== 'string') {
throw new Error(
'Could not parse a Rollup warning. ' + 'Fix this method.'
);
}
const importSideEffects = Modules.getImportSideEffects();
const externalModule = match[1];
if (typeof importSideEffects[externalModule] !== 'boolean') {
throw new Error(
'An external module "' +
externalModule +
'" is used in a DEV-only code path ' +
'but we do not know if it is safe to omit an unused require() to it in production. ' +
'Please add it to the `importSideEffects` list in `scripts/rollup/modules.js`.'
);
}
// Don't warn. We will remove side effectless require() in a later pass.
return;
}
if (warning.code === 'CIRCULAR_DEPENDENCY') {
// Ignored
} else if (typeof warning.code === 'string') {
// This is a warning coming from Rollup itself.
// These tend to be important (e.g. clashes in namespaced exports)
// so we'll fail the build on any of them.
console.error();
console.error(warning.message || warning);
console.error();
process.exit(1);
} else {
// The warning is from one of the plugins.
// Maybe it's not important, so just print it.
console.warn(warning.message || warning);
}
}
function handleRollupError(error) {
loggedErrors.add(error);
if (!error.code) {
console.error(error);
return;
}
console.error(
`\x1b[31m-- ${error.code}${error.plugin ? ` (${error.plugin})` : ''} --`
);
console.error(error.stack);
if (error.loc && error.loc.file) {
const {file, line, column} = error.loc;
// This looks like an error from Rollup, e.g. missing export.
// We'll use the accurate line numbers provided by Rollup but
// use Babel code frame because it looks nicer.
const rawLines = fs.readFileSync(file, 'utf-8');
// column + 1 is required due to rollup counting column start position from 0
// whereas babel-code-frame counts from 1
const frame = codeFrame(rawLines, line, column + 1, {
highlightCode: true,
});
console.error(frame);
} else if (error.codeFrame) {
// This looks like an error from a plugin (e.g. Babel).
// In this case we'll resort to displaying the provided code frame
// because we can't be sure the reported location is accurate.
console.error(error.codeFrame);
}
}
function runShellCommand(command) {
console.log(chalk.dim('Running: ') + chalk.cyan(command));
childProcess.execSync(command, {stdio: 'inherit', shell: true});
}
async function buildEverything() {
if (!argv['unsafe-partial']) {
await asyncRimRaf('build');
}
// Run them serially for better console output
// and to avoid any potential race conditions.
let bundles = [];
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
for (const bundle of Bundles.bundles) {
bundles.push(
[bundle, NODE_ES2015],
[bundle, ESM_DEV],
[bundle, ESM_PROD],
[bundle, NODE_DEV],
[bundle, NODE_PROD],
[bundle, NODE_PROFILING],
[bundle, BUN_DEV],
[bundle, BUN_PROD],
[bundle, FB_WWW_DEV],
[bundle, FB_WWW_PROD],
[bundle, FB_WWW_PROFILING],
[bundle, RN_OSS_DEV],
[bundle, RN_OSS_PROD],
[bundle, RN_OSS_PROFILING],
[bundle, RN_FB_DEV],
[bundle, RN_FB_PROD],
[bundle, RN_FB_PROFILING],
[bundle, BROWSER_SCRIPT],
[bundle, CJS_DTS],
[bundle, ESM_DTS]
);
}
bundles = bundles.filter(([bundle, bundleType]) => {
return !shouldSkipBundle(bundle, bundleType);
});
if (process.env.CI_TOTAL && process.env.CI_INDEX) {
const nodeTotal = parseInt(process.env.CI_TOTAL, 10);
const nodeIndex = parseInt(process.env.CI_INDEX, 10);
bundles = bundles.filter((_, i) => i % nodeTotal === nodeIndex);
}
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
for (const [bundle, bundleType] of bundles) {
if (bundle.prebuild) {
runShellCommand(bundle.prebuild);
}
await createBundle(bundle, bundleType);
}
await Packaging.copyAllShims();
await Packaging.prepareNpmPackages();
if (syncFBSourcePath) {
await Sync.syncReactNative(syncFBSourcePath);
} else if (syncWWWPath) {
await Sync.syncReactDom('build/facebook-www', syncWWWPath);
}
console.log(Stats.printResults());
if (!forcePrettyOutput) {
Stats.saveResults();
}
}
buildEverything();