mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[ci] Parallelize yarn build
ghstack-source-id: 8a13b456f1
Pull Request resolved: https://github.com/facebook/react/pull/30071
This commit is contained in:
@@ -97,7 +97,7 @@ jobs:
|
||||
steps:
|
||||
- checkout
|
||||
- setup_node_modules
|
||||
- run: yarn build
|
||||
- run: yarn build --ci=circleci
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
|
||||
@@ -14,6 +14,11 @@ jobs:
|
||||
build_and_lint:
|
||||
name: yarn build and lint
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
# yml is dumb. update the --total arg to yarn build if you change the number of workers
|
||||
worker_id: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19]
|
||||
release_channel: [stable, experimental]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
@@ -32,11 +37,18 @@ jobs:
|
||||
path: "**/node_modules"
|
||||
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn build
|
||||
- run: yarn lint-build
|
||||
- name: Cache build
|
||||
uses: actions/cache@v4
|
||||
id: build_cache
|
||||
- run: yarn build --index=${{ matrix.worker_id }} --total=20 --r=${{ matrix.release_channel }} --ci=github
|
||||
env:
|
||||
CI: github
|
||||
RELEASE_CHANNEL: ${{ matrix.release_channel }}
|
||||
NODE_INDEX: ${{ matrix.worker_id }}
|
||||
- name: Lint build
|
||||
run: yarn lint-build
|
||||
- name: Display structure of build
|
||||
run: ls -R build
|
||||
- name: Archive build
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
path: build/**
|
||||
key: yarn-build-${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
|
||||
name: build_${{ matrix.worker_id }}_${{ matrix.release_channel }}
|
||||
path: |
|
||||
build
|
||||
|
||||
@@ -15,6 +15,8 @@ const {
|
||||
canaryChannelLabel,
|
||||
rcNumber,
|
||||
} = require('../../ReactVersions');
|
||||
const yargs = require('yargs');
|
||||
const {buildEverything} = require('./build-ghaction');
|
||||
|
||||
// Runs the build script for both stable and experimental release channels,
|
||||
// by configuring an environment variable.
|
||||
@@ -51,44 +53,88 @@ fs.writeFileSync(
|
||||
`export default '${PLACEHOLDER_REACT_VERSION}';\n`
|
||||
);
|
||||
|
||||
if (process.env.CIRCLE_NODE_TOTAL) {
|
||||
// In CI, we use multiple concurrent processes. Allocate half the processes to
|
||||
// build the stable channel, and the other half for experimental. Override
|
||||
// the environment variables to "trick" the underlying build script.
|
||||
const total = parseInt(process.env.CIRCLE_NODE_TOTAL, 10);
|
||||
const halfTotal = Math.floor(total / 2);
|
||||
const index = parseInt(process.env.CIRCLE_NODE_INDEX, 10);
|
||||
if (index < halfTotal) {
|
||||
const nodeTotal = halfTotal;
|
||||
const nodeIndex = index;
|
||||
buildForChannel('stable', nodeTotal, nodeIndex);
|
||||
processStable('./build');
|
||||
const argv = yargs.wrap(yargs.terminalWidth()).options({
|
||||
releaseChannel: {
|
||||
alias: 'r',
|
||||
describe: 'Build the given release channel.',
|
||||
requiresArg: true,
|
||||
type: 'string',
|
||||
choices: ['experimental', 'stable'],
|
||||
},
|
||||
index: {
|
||||
alias: 'i',
|
||||
describe: 'Worker id.',
|
||||
requiresArg: true,
|
||||
type: 'number',
|
||||
},
|
||||
total: {
|
||||
alias: 't',
|
||||
describe: 'Total number of workers.',
|
||||
requiresArg: true,
|
||||
type: 'number',
|
||||
},
|
||||
ci: {
|
||||
describe: 'Run tests in CI',
|
||||
requiresArg: false,
|
||||
type: 'choices',
|
||||
choices: ['circleci', 'github'],
|
||||
},
|
||||
}).argv;
|
||||
|
||||
async function main() {
|
||||
if (argv.ci === 'github') {
|
||||
await buildEverything(argv.index, argv.total);
|
||||
switch (argv.releaseChannel) {
|
||||
case 'stable': {
|
||||
processStable('./build');
|
||||
break;
|
||||
}
|
||||
case 'experimental': {
|
||||
processExperimental('./build');
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unknown release channel ${argv.releaseChannel}`);
|
||||
}
|
||||
} else if (argv.ci === 'circleci') {
|
||||
// In CI, we use multiple concurrent processes. Allocate half the processes to
|
||||
// build the stable channel, and the other half for experimental. Override
|
||||
// the environment variables to "trick" the underlying build script.
|
||||
const total = parseInt(process.env.CIRCLE_NODE_TOTAL, 10);
|
||||
const halfTotal = Math.floor(total / 2);
|
||||
const index = parseInt(process.env.CIRCLE_NODE_INDEX, 10);
|
||||
if (index < halfTotal) {
|
||||
const nodeTotal = halfTotal;
|
||||
const nodeIndex = index;
|
||||
buildForChannel('stable', nodeTotal, nodeIndex);
|
||||
processStable('./build');
|
||||
} else {
|
||||
const nodeTotal = total - halfTotal;
|
||||
const nodeIndex = index - halfTotal;
|
||||
buildForChannel('experimental', nodeTotal, nodeIndex);
|
||||
processExperimental('./build');
|
||||
}
|
||||
} else {
|
||||
const nodeTotal = total - halfTotal;
|
||||
const nodeIndex = index - halfTotal;
|
||||
buildForChannel('experimental', nodeTotal, nodeIndex);
|
||||
processExperimental('./build');
|
||||
// Running locally, no concurrency. Move each channel's build artifacts into
|
||||
// a temporary directory so that they don't conflict.
|
||||
buildForChannel('stable', '', '');
|
||||
const stableDir = tmp.dirSync().name;
|
||||
crossDeviceRenameSync('./build', stableDir);
|
||||
processStable(stableDir);
|
||||
buildForChannel('experimental', '', '');
|
||||
const experimentalDir = tmp.dirSync().name;
|
||||
crossDeviceRenameSync('./build', experimentalDir);
|
||||
processExperimental(experimentalDir);
|
||||
|
||||
// Then merge the experimental folder into the stable one. processExperimental
|
||||
// will have already removed conflicting files.
|
||||
//
|
||||
// In CI, merging is handled automatically by CircleCI's workspace feature.
|
||||
mergeDirsSync(experimentalDir + '/', stableDir + '/');
|
||||
|
||||
// Now restore the combined directory back to its original name
|
||||
crossDeviceRenameSync(stableDir, './build');
|
||||
}
|
||||
} else {
|
||||
// Running locally, no concurrency. Move each channel's build artifacts into
|
||||
// a temporary directory so that they don't conflict.
|
||||
buildForChannel('stable', '', '');
|
||||
const stableDir = tmp.dirSync().name;
|
||||
crossDeviceRenameSync('./build', stableDir);
|
||||
processStable(stableDir);
|
||||
buildForChannel('experimental', '', '');
|
||||
const experimentalDir = tmp.dirSync().name;
|
||||
crossDeviceRenameSync('./build', experimentalDir);
|
||||
processExperimental(experimentalDir);
|
||||
|
||||
// Then merge the experimental folder into the stable one. processExperimental
|
||||
// will have already removed conflicting files.
|
||||
//
|
||||
// In CI, merging is handled automatically by CircleCI's workspace feature.
|
||||
mergeDirsSync(experimentalDir + '/', stableDir + '/');
|
||||
|
||||
// Now restore the combined directory back to its original name
|
||||
crossDeviceRenameSync(stableDir, './build');
|
||||
}
|
||||
|
||||
function buildForChannel(channel, nodeTotal, nodeIndex) {
|
||||
@@ -455,3 +501,5 @@ function mergeDirsSync(source, destination) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
@@ -0,0 +1,869 @@
|
||||
'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 prettier = require('rollup-plugin-prettier');
|
||||
const replace = require('@rollup/plugin-replace');
|
||||
const stripBanner = require('rollup-plugin-strip-banner');
|
||||
const chalk = require('chalk');
|
||||
const resolve = require('@rollup/plugin-node-resolve').nodeResolve;
|
||||
const fs = require('fs');
|
||||
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 Packaging = require('./packaging');
|
||||
const {asyncRimRaf} = require('./utils');
|
||||
const codeFrame = require('@babel/code-frame');
|
||||
const Wrappers = require('./wrappers');
|
||||
|
||||
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,
|
||||
} = 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 = argv.type
|
||||
? parseRequestedNames(argvType, 'uppercase')
|
||||
: [];
|
||||
|
||||
const requestedBundleNames = parseRequestedNames(argv._, '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
|
||||
) {
|
||||
const canAccessReactObject =
|
||||
packageName === 'react' || externals.indexOf('react') !== -1;
|
||||
let options = {
|
||||
exclude: '/**/node_modules/**',
|
||||
babelrc: false,
|
||||
configFile: false,
|
||||
presets: [],
|
||||
plugins: [...babelPlugins],
|
||||
babelHelpers: 'bundled',
|
||||
sourcemap: false,
|
||||
};
|
||||
if (isDevelopment) {
|
||||
options.plugins.push(
|
||||
...babelToES5Plugins,
|
||||
// Turn console.error/warn() into a custom wrapper
|
||||
[
|
||||
require('../babel/transform-replace-console-calls'),
|
||||
{
|
||||
shouldError: !canAccessReactObject,
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
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:
|
||||
return `cjs`;
|
||||
case ESM_DEV:
|
||||
case ESM_PROD:
|
||||
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:
|
||||
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:
|
||||
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
|
||||
) {
|
||||
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;
|
||||
|
||||
return [
|
||||
// Keep dynamic imports as externals
|
||||
dynamicImports(),
|
||||
{
|
||||
name: 'rollup-plugin-flow-remove-types',
|
||||
transform(code) {
|
||||
const transformed = flowRemoveTypes(code);
|
||||
return {
|
||||
code: transformed.toString(),
|
||||
map: null,
|
||||
};
|
||||
},
|
||||
},
|
||||
// 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
|
||||
}),
|
||||
// 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.
|
||||
{
|
||||
name: "remove 'use strict'",
|
||||
transform(source) {
|
||||
return source.replace(/['"]use strict["']/g, '');
|
||||
},
|
||||
},
|
||||
// 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__,
|
||||
},
|
||||
}),
|
||||
{
|
||||
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 isAskingForDifferentType = requestedBundleTypes.some(
|
||||
requestedType => !bundleType.includes(requestedType)
|
||||
);
|
||||
if (isAskingForDifferentType) {
|
||||
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) {
|
||||
// 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 developmentFBEntry = resolvedFBEntry.replace(
|
||||
'.js',
|
||||
'.development.js'
|
||||
);
|
||||
if (fs.existsSync(developmentFBEntry)) {
|
||||
return developmentFBEntry;
|
||||
}
|
||||
if (fs.existsSync(resolvedFBEntry)) {
|
||||
return resolvedFBEntry;
|
||||
}
|
||||
const resolvedGenericFBEntry = resolvedEntry.replace('.js', '.fb.js');
|
||||
const developmentGenericFBEntry = resolvedGenericFBEntry.replace(
|
||||
'.js',
|
||||
'.development.js'
|
||||
);
|
||||
if (fs.existsSync(developmentGenericFBEntry)) {
|
||||
return developmentGenericFBEntry;
|
||||
}
|
||||
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 (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);
|
||||
|
||||
let 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);
|
||||
}
|
||||
}
|
||||
|
||||
async function buildEverything(index, total) {
|
||||
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]
|
||||
);
|
||||
}
|
||||
|
||||
bundles = bundles.filter(([bundle, bundleType]) => {
|
||||
return !shouldSkipBundle(bundle, bundleType);
|
||||
});
|
||||
|
||||
const nodeTotal = parseInt(total, 10);
|
||||
const nodeIndex = parseInt(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) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
buildEverything,
|
||||
};
|
||||
@@ -28,6 +28,12 @@ function saveResults() {
|
||||
join('build', 'sizes', `bundle-sizes-${nodeIndex}.json`),
|
||||
JSON.stringify(currentBuildResults, null, 2)
|
||||
);
|
||||
} else if (process.env.CI === 'github') {
|
||||
mkdirp.sync('build/sizes');
|
||||
fs.writeFileSync(
|
||||
join('build', 'sizes', `bundle-sizes-${process.env.NODE_INDEX}.json`),
|
||||
JSON.stringify(currentBuildResults, null, 2)
|
||||
);
|
||||
} else {
|
||||
// Write all the bundle sizes to a single JSON file.
|
||||
fs.writeFileSync(
|
||||
|
||||
Reference in New Issue
Block a user