mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
In CI, we run our test suite against multiple build configurations. For example, we run our tests in both dev and prod, and in both the experimental and stable release channels. This is to prevent accidental deviations in behavior between the different builds. If there's an intentional deviation in behavior, the test author must account for them. However, we currently don't run tests against the www builds. That's a problem, because it's common for features to land in www before they land anywhere else, including the experimental release channel. Typically we do this so we can gradually roll out the feature behind a flag before deciding to enable it. The way we test those features today is by mutating the `shared/ReactFeatureFlags` module. There are a few downsides to this approach, though. The flag is only overridden for the specific tests or test suites where you apply the override. But usually what you want is to run *all* tests with the flag enabled, to protect against unexpected regressions. Also, mutating the feature flags module only works when running the tests against source, not against the final build artifacts, because the ReactFeatureFlags module is inlined by the build script. Instead, we should run the test suite against the www configuration, just like we do for prod, experimental, and so on. I've added a new command, `yarn test-www`. It automatically runs in CI. Some of the www feature flags are dynamic; that is, they depend on a runtime condition (i.e. a GK). These flags are imported from an external module that lives in www. Those flags will be enabled for some clients and disabled for others, so we should run the tests against *both* modes. So I've added a new global `__VARIANT__`, and a new test command `yarn test-www-variant`. `__VARIANT__` is set to false by default; when running `test-www-variant`, it's set to true. If we were going for *really* comprehensive coverage, we would run the tests against every possible configuration of feature flags: 2 ^ numberOfFlags total combinations. That's not practical, though, so instead we only run against two combinations: once with `__VARIANT__` set to `true`, and once with it set to `false`. We generally assume that flags can be toggled independently, so in practice this should be enough. You can also refer to `__VARIANT__` in tests to detect which mode you're running in. Or, you can import `shared/ReactFeatureFlags` and read the specific flag you can about. However, we should stop mutating that module going forward. Treat it as read-only. In this commit, I have only setup the www tests to run against source. I'll leave running against build for a follow up. Many of our tests currently assume they run only in the default configuration, and break when certain flags are toggled. Rather than fix these all up front, I've hard-coded the relevant flags to the default values. We can incrementally migrate those tests later.
783 lines
22 KiB
JavaScript
783 lines
22 KiB
JavaScript
'use strict';
|
|
|
|
const rollup = require('rollup');
|
|
const babel = require('rollup-plugin-babel');
|
|
const closure = require('./plugins/closure-plugin');
|
|
const commonjs = require('rollup-plugin-commonjs');
|
|
const prettier = require('rollup-plugin-prettier');
|
|
const replace = require('rollup-plugin-replace');
|
|
const stripBanner = require('rollup-plugin-strip-banner');
|
|
const chalk = require('chalk');
|
|
const path = require('path');
|
|
const resolve = require('rollup-plugin-node-resolve');
|
|
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 stripUnusedImports = require('./plugins/strip-unused-imports');
|
|
const extractErrorCodes = require('../error-codes/extract-errors');
|
|
const Packaging = require('./packaging');
|
|
const {asyncCopyTo, 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 {
|
|
UMD_DEV,
|
|
UMD_PROD,
|
|
UMD_PROFILING,
|
|
NODE_DEV,
|
|
NODE_PROD,
|
|
NODE_PROFILING,
|
|
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,
|
|
} = Bundles.bundleTypes;
|
|
|
|
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 requestedBundleTypes = argv.type
|
|
? parseRequestedNames([argv.type], 'uppercase')
|
|
: [];
|
|
const requestedBundleNames = parseRequestedNames(argv._, 'lowercase');
|
|
const forcePrettyOutput = argv.pretty;
|
|
const isWatchMode = argv.watch;
|
|
const syncFBSourcePath = argv['sync-fbsource'];
|
|
const syncWWWPath = argv['sync-www'];
|
|
const shouldExtractErrors = argv['extract-errors'];
|
|
const errorCodeOpts = {
|
|
errorMapFilePath: 'scripts/error-codes/codes.json',
|
|
};
|
|
|
|
const closureOptions = {
|
|
compilation_level: 'SIMPLE',
|
|
language_in: 'ECMASCRIPT5_STRICT',
|
|
language_out: 'ECMASCRIPT5_STRICT',
|
|
env: 'CUSTOM',
|
|
warning_level: 'QUIET',
|
|
apply_input_source_maps: false,
|
|
use_types_for_optimization: false,
|
|
process_common_js_modules: false,
|
|
rewrite_polyfills: false,
|
|
};
|
|
|
|
function getBabelConfig(
|
|
updateBabelOptions,
|
|
bundleType,
|
|
packageName,
|
|
externals,
|
|
isDevelopment
|
|
) {
|
|
const canAccessReactObject =
|
|
packageName === 'react' || externals.indexOf('react') !== -1;
|
|
let options = {
|
|
exclude: '/**/node_modules/**',
|
|
presets: [],
|
|
plugins: [],
|
|
};
|
|
if (isDevelopment) {
|
|
options.plugins.push(
|
|
// Turn console.error/warn() into a custom wrapper
|
|
[
|
|
require('../babel/transform-replace-console-calls'),
|
|
{
|
|
shouldError: !canAccessReactObject,
|
|
},
|
|
]
|
|
);
|
|
}
|
|
if (updateBabelOptions) {
|
|
options = updateBabelOptions(options);
|
|
}
|
|
switch (bundleType) {
|
|
case FB_WWW_DEV:
|
|
case FB_WWW_PROD:
|
|
case FB_WWW_PROFILING:
|
|
return Object.assign({}, options, {
|
|
plugins: options.plugins.concat([
|
|
// Minify invariant messages
|
|
require('../error-codes/transform-error-messages'),
|
|
]),
|
|
});
|
|
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 Object.assign({}, options, {
|
|
plugins: options.plugins.concat([
|
|
[
|
|
require('../error-codes/transform-error-messages'),
|
|
// Preserve full error messages in React Native build
|
|
{noMinify: true},
|
|
],
|
|
]),
|
|
});
|
|
case UMD_DEV:
|
|
case UMD_PROD:
|
|
case UMD_PROFILING:
|
|
case NODE_DEV:
|
|
case NODE_PROD:
|
|
case NODE_PROFILING:
|
|
return Object.assign({}, options, {
|
|
plugins: options.plugins.concat([
|
|
// Use object-assign polyfill in open source
|
|
path.resolve('./scripts/babel/transform-object-assign-require'),
|
|
// Minify invariant messages
|
|
require('../error-codes/transform-error-messages'),
|
|
]),
|
|
});
|
|
default:
|
|
return options;
|
|
}
|
|
}
|
|
|
|
function getRollupOutputOptions(
|
|
outputPath,
|
|
format,
|
|
globals,
|
|
globalName,
|
|
bundleType
|
|
) {
|
|
const isProduction = isProductionBundleType(bundleType);
|
|
|
|
return {
|
|
file: outputPath,
|
|
format,
|
|
globals,
|
|
freeze: !isProduction,
|
|
interop: false,
|
|
name: globalName,
|
|
sourcemap: false,
|
|
esModule: false,
|
|
};
|
|
}
|
|
|
|
function getFormat(bundleType) {
|
|
switch (bundleType) {
|
|
case UMD_DEV:
|
|
case UMD_PROD:
|
|
case UMD_PROFILING:
|
|
return `umd`;
|
|
case NODE_DEV:
|
|
case NODE_PROD:
|
|
case NODE_PROFILING:
|
|
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`;
|
|
}
|
|
}
|
|
|
|
function getFilename(name, globalName, bundleType) {
|
|
// we do this to replace / to -, for react-dom/server
|
|
name = name.replace('/', '-');
|
|
switch (bundleType) {
|
|
case UMD_DEV:
|
|
return `${name}.development.js`;
|
|
case UMD_PROD:
|
|
return `${name}.production.min.js`;
|
|
case UMD_PROFILING:
|
|
return `${name}.profiling.min.js`;
|
|
case NODE_DEV:
|
|
return `${name}.development.js`;
|
|
case NODE_PROD:
|
|
return `${name}.production.min.js`;
|
|
case NODE_PROFILING:
|
|
return `${name}.profiling.min.js`;
|
|
case FB_WWW_DEV:
|
|
case RN_OSS_DEV:
|
|
case RN_FB_DEV:
|
|
return `${globalName}-dev.js`;
|
|
case FB_WWW_PROD:
|
|
case RN_OSS_PROD:
|
|
case RN_FB_PROD:
|
|
return `${globalName}-prod.js`;
|
|
case FB_WWW_PROFILING:
|
|
case RN_FB_PROFILING:
|
|
case RN_OSS_PROFILING:
|
|
return `${globalName}-profiling.js`;
|
|
}
|
|
}
|
|
|
|
function isProductionBundleType(bundleType) {
|
|
switch (bundleType) {
|
|
case UMD_DEV:
|
|
case NODE_DEV:
|
|
case FB_WWW_DEV:
|
|
case RN_OSS_DEV:
|
|
case RN_FB_DEV:
|
|
return false;
|
|
case UMD_PROD:
|
|
case NODE_PROD:
|
|
case UMD_PROFILING:
|
|
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:
|
|
return true;
|
|
default:
|
|
throw new Error(`Unknown type: ${bundleType}`);
|
|
}
|
|
}
|
|
|
|
function isProfilingBundleType(bundleType) {
|
|
switch (bundleType) {
|
|
case FB_WWW_DEV:
|
|
case FB_WWW_PROD:
|
|
case NODE_DEV:
|
|
case NODE_PROD:
|
|
case RN_FB_DEV:
|
|
case RN_FB_PROD:
|
|
case RN_OSS_DEV:
|
|
case RN_OSS_PROD:
|
|
case UMD_DEV:
|
|
case UMD_PROD:
|
|
return false;
|
|
case FB_WWW_PROFILING:
|
|
case NODE_PROFILING:
|
|
case RN_FB_PROFILING:
|
|
case RN_OSS_PROFILING:
|
|
case UMD_PROFILING:
|
|
return true;
|
|
default:
|
|
throw new Error(`Unknown type: ${bundleType}`);
|
|
}
|
|
}
|
|
|
|
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
|
|
) {
|
|
const findAndRecordErrorCodes = extractErrorCodes(errorCodeOpts);
|
|
const forks = Modules.getForks(bundleType, entry, moduleType);
|
|
const isProduction = isProductionBundleType(bundleType);
|
|
const isProfiling = isProfilingBundleType(bundleType);
|
|
const isUMDBundle =
|
|
bundleType === UMD_DEV ||
|
|
bundleType === UMD_PROD ||
|
|
bundleType === UMD_PROFILING;
|
|
const isFBBundle =
|
|
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 shouldStayReadable = isFBBundle || isRNBundle || forcePrettyOutput;
|
|
return [
|
|
// Extract error codes from invariant() messages into a file.
|
|
shouldExtractErrors && {
|
|
transform(source) {
|
|
findAndRecordErrorCodes(source);
|
|
return source;
|
|
},
|
|
},
|
|
// Shim any modules that need forking in this environment.
|
|
useForks(forks),
|
|
// Ensure we don't try to bundle any fbjs modules.
|
|
forbidFBJSImports(),
|
|
// Replace any externals with their valid internal FB mappings
|
|
isFBBundle && replace(Bundles.fbBundleExternalsMap),
|
|
// Use Node resolution mechanism.
|
|
resolve({
|
|
skip: externals,
|
|
}),
|
|
// Remove license headers from individual modules
|
|
stripBanner({
|
|
exclude: 'node_modules/**/*',
|
|
}),
|
|
// Compile to ES5.
|
|
babel(
|
|
getBabelConfig(
|
|
updateBabelOptions,
|
|
bundleType,
|
|
packageName,
|
|
externals,
|
|
!isProduction
|
|
)
|
|
),
|
|
// Remove 'use strict' from individual source files.
|
|
{
|
|
transform(source) {
|
|
return source.replace(/['"]use strict["']/g, '');
|
|
},
|
|
},
|
|
// Turn __DEV__ and process.env checks into constants.
|
|
replace({
|
|
__DEV__: isProduction ? 'false' : 'true',
|
|
__PROFILE__: isProfiling || !isProduction ? 'true' : 'false',
|
|
__UMD__: isUMDBundle ? 'true' : 'false',
|
|
'process.env.NODE_ENV': isProduction ? "'production'" : "'development'",
|
|
__EXPERIMENTAL__,
|
|
__VARIANT__: false,
|
|
}),
|
|
// The CommonJS plugin *only* exists to pull "art" into "react-art".
|
|
// I'm going to port "art" to ES modules to avoid this problem.
|
|
// Please don't enable this for anything else!
|
|
isUMDBundle && entry === 'react-art' && commonjs(),
|
|
// Apply dead code elimination and/or minification.
|
|
isProduction &&
|
|
closure(
|
|
Object.assign({}, closureOptions, {
|
|
// Don't let it create global variables in the browser.
|
|
// https://github.com/facebook/react/issues/10909
|
|
assume_function_wrapper: !isUMDBundle,
|
|
renaming: !shouldStayReadable,
|
|
})
|
|
),
|
|
// HACK to work around the fact that Rollup isn't removing unused, pure-module imports.
|
|
// Note that this plugin must be called after closure applies DCE.
|
|
isProduction && stripUnusedImports(pureExternalModules),
|
|
// Add the whitespace back if necessary.
|
|
shouldStayReadable &&
|
|
prettier({
|
|
parser: 'babel',
|
|
singleQuote: false,
|
|
trailingComma: 'none',
|
|
bracketSpacing: true,
|
|
}),
|
|
// License and haste headers, top-level `if` blocks.
|
|
{
|
|
renderChunk(source) {
|
|
return Wrappers.wrapBundle(
|
|
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);
|
|
}
|
|
|
|
function shouldSkipBundle(bundle, bundleType) {
|
|
const shouldSkipBundleType = bundle.bundleTypes.indexOf(bundleType) === -1;
|
|
if (shouldSkipBundleType) {
|
|
return true;
|
|
}
|
|
if (requestedBundleTypes.length > 0) {
|
|
const isAskingForDifferentType = requestedBundleTypes.every(
|
|
requestedType => bundleType.indexOf(requestedType) === -1
|
|
);
|
|
if (isAskingForDifferentType) {
|
|
return true;
|
|
}
|
|
}
|
|
if (requestedBundleNames.length > 0) {
|
|
const isAskingForDifferentNames = requestedBundleNames.every(
|
|
// 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.
|
|
requestedName =>
|
|
(bundle.entry + '/index.js').indexOf(requestedName) === -1
|
|
);
|
|
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
|
|
|
|
if (isFBBundle) {
|
|
const resolvedFBEntry = resolvedEntry.replace(
|
|
'.js',
|
|
__EXPERIMENTAL__ ? '.modern.fb.js' : '.classic.fb.js'
|
|
);
|
|
if (fs.existsSync(resolvedFBEntry)) {
|
|
return resolvedFBEntry;
|
|
}
|
|
const resolvedGenericFBEntry = resolvedEntry.replace('.js', '.fb.js');
|
|
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'
|
|
);
|
|
if (fs.existsSync(resolvedForkedEntry)) {
|
|
return resolvedForkedEntry;
|
|
}
|
|
// Just use the plain .js one.
|
|
return resolvedEntry;
|
|
}
|
|
|
|
async function createBundle(bundle, bundleType) {
|
|
if (shouldSkipBundle(bundle, bundleType)) {
|
|
return;
|
|
}
|
|
|
|
const filename = getFilename(bundle.entry, bundle.global, bundleType);
|
|
const logKey =
|
|
chalk.white.bold(filename) + chalk.dim(` (${bundleType.toLowerCase()})`);
|
|
const format = getFormat(bundleType);
|
|
const packageName = Packaging.getPackageName(bundle.entry);
|
|
|
|
const isFBBundle =
|
|
bundleType === FB_WWW_DEV ||
|
|
bundleType === FB_WWW_PROD ||
|
|
bundleType === FB_WWW_PROFILING;
|
|
|
|
let resolvedEntry = resolveEntryFork(
|
|
require.resolve(bundle.entry),
|
|
isFBBundle
|
|
);
|
|
|
|
const shouldBundleDependencies =
|
|
bundleType === UMD_DEV ||
|
|
bundleType === UMD_PROD ||
|
|
bundleType === UMD_PROFILING;
|
|
const peerGlobals = Modules.getPeerGlobals(bundle.externals, bundleType);
|
|
let externals = Object.keys(peerGlobals);
|
|
if (!shouldBundleDependencies) {
|
|
const deps = Modules.getDependencies(bundleType, bundle.entry);
|
|
externals = externals.concat(deps);
|
|
}
|
|
if (isFBBundle) {
|
|
// Add any mapped fb bundle externals
|
|
externals = externals.concat(Object.values(Bundles.fbBundleExternalsMap));
|
|
}
|
|
|
|
const importSideEffects = Modules.getImportSideEffects();
|
|
const pureExternalModules = Object.keys(importSideEffects).filter(
|
|
module => !importSideEffects[module]
|
|
);
|
|
|
|
const rollupConfig = {
|
|
input: resolvedEntry,
|
|
treeshake: {
|
|
pureExternalModules,
|
|
},
|
|
external(id) {
|
|
const containsThisModule = pkg => id === pkg || id.startsWith(pkg + '/');
|
|
const isProvidedByDependency = externals.some(containsThisModule);
|
|
if (!shouldBundleDependencies && isProvidedByDependency) {
|
|
return true;
|
|
}
|
|
return !!peerGlobals[id];
|
|
},
|
|
onwarn: handleRollupWarning,
|
|
plugins: getPlugins(
|
|
bundle.entry,
|
|
externals,
|
|
bundle.babel,
|
|
filename,
|
|
packageName,
|
|
bundleType,
|
|
bundle.global,
|
|
bundle.moduleType,
|
|
pureExternalModules
|
|
),
|
|
output: {
|
|
externalLiveBindings: false,
|
|
freeze: false,
|
|
interop: false,
|
|
esModule: false,
|
|
},
|
|
};
|
|
const [mainOutputPath, ...otherOutputPaths] = Packaging.getBundleOutputPaths(
|
|
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':
|
|
for (let i = 0; i < otherOutputPaths.length; i++) {
|
|
await asyncCopyTo(mainOutputPath, otherOutputPaths[i]);
|
|
}
|
|
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;
|
|
}
|
|
for (let i = 0; i < otherOutputPaths.length; i++) {
|
|
await asyncCopyTo(mainOutputPath, otherOutputPaths[i]);
|
|
}
|
|
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() {
|
|
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, UMD_DEV],
|
|
[bundle, UMD_PROD],
|
|
[bundle, UMD_PROFILING],
|
|
[bundle, NODE_DEV],
|
|
[bundle, NODE_PROD],
|
|
[bundle, NODE_PROFILING],
|
|
[bundle, FB_WWW_DEV],
|
|
[bundle, FB_WWW_PROD],
|
|
[bundle, FB_WWW_PROFILING],
|
|
[bundle, RN_OSS_DEV],
|
|
[bundle, RN_OSS_PROD],
|
|
[bundle, RN_OSS_PROFILING]
|
|
);
|
|
|
|
if (__EXPERIMENTAL__) {
|
|
// FB-specific RN builds are experimental-only.
|
|
bundles.push(
|
|
[bundle, RN_FB_DEV],
|
|
[bundle, RN_FB_PROD],
|
|
[bundle, RN_FB_PROFILING]
|
|
);
|
|
}
|
|
}
|
|
|
|
if (!shouldExtractErrors && process.env.CIRCLE_NODE_TOTAL) {
|
|
// In CI, parallelize bundles across multiple tasks.
|
|
const nodeTotal = parseInt(process.env.CIRCLE_NODE_TOTAL, 10);
|
|
const nodeIndex = parseInt(process.env.CIRCLE_NODE_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();
|
|
}
|
|
|
|
if (shouldExtractErrors) {
|
|
console.warn(
|
|
'\nWarning: this build was created with --extract-errors enabled.\n' +
|
|
'this will result in extremely slow builds and should only be\n' +
|
|
'used when the error map needs to be rebuilt.\n'
|
|
);
|
|
}
|
|
}
|
|
|
|
buildEverything();
|