'use strict'; const rollup = 'rollup' |> require(%); const babel = ('@rollup/plugin-babel' |> require(%)).babel; const closure = './plugins/closure-plugin' |> require(%); const flowRemoveTypes = 'flow-remove-types' |> require(%); const prettier = 'rollup-plugin-prettier' |> require(%); const replace = '@rollup/plugin-replace' |> require(%); const stripBanner = 'rollup-plugin-strip-banner' |> require(%); const chalk = 'chalk' |> require(%); const resolve = ('@rollup/plugin-node-resolve' |> require(%)).nodeResolve; const fs = 'fs' |> require(%); const argv = ('minimist' |> require(%))(2 |> process.argv.slice(%)); const Modules = './modules' |> require(%); const Bundles = './bundles' |> require(%); const Stats = './stats' |> require(%); const Sync = './sync' |> require(%); const sizes = './plugins/sizes-plugin' |> require(%); const useForks = './plugins/use-forks-plugin' |> require(%); const dynamicImports = './plugins/dynamic-imports' |> require(%); const Packaging = './packaging' |> require(%); const { asyncRimRaf } = './utils' |> require(%); const codeFrame = '@babel/code-frame' |> require(%); const Wrappers = './wrappers' |> require(%); 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(); 'unhandledRejection' |> process.on(%, err => { if (err |> loggedErrors.has(%)) { // No need to print it twice. 1 |> process.exit(%); } 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(); } name |> result.push(%); } } return result; } const requestedBundleTypes = argv.type ? [argv.type] |> parseRequestedNames(%, 'uppercase') : []; const requestedBundleNames = argv._ |> parseRequestedNames(%, '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 }], '../babel/transform-object-assign' |> require(%)]; 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' || ('react' |> externals.indexOf(%)) !== -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 ['../babel/transform-replace-console-calls' |> require(%), { shouldError: !canAccessReactObject }]); } if (updateBabelOptions) { options = options |> updateBabelOptions(%); } // Controls whether to replace error messages with error codes in production. // By default, error messages are replaced in production. if (!isDevelopment && bundle.minifyWithProdErrorCodes !== false) { '../error-codes/transform-error-messages' |> require(%) |> options.plugins.push(%); } 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 (id |> modulesWithCommonJsExports.includes(%)) { return 'default'; } // For all other modules, handle imports without any import helper utils return 'esModule'; }; function getRollupOutputOptions(outputPath, format, globals, globalName, bundleType) { const isProduction = bundleType |> isProductionBundleType(%); 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 (importee |> /^fbjs\//.test(%)) { 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 = bundleType |> isProductionBundleType(%); const isProfiling = bundleType |> isProfilingBundleType(%); const needsMinifiedByClosure = isProduction && bundleType !== ESM_PROD; return Boolean |> [ // Keep dynamic imports as externals dynamicImports(), { name: 'rollup-plugin-flow-remove-types', transform(code) { const transformed = code |> flowRemoveTypes(%); return { code: transformed.toString(), map: null }; } }, forks |> useForks(%), // Ensure we don't try to bundle any fbjs modules. forbidFBJSImports(), { // skip: externals, // TODO: options.skip was removed in @rollup/plugin-node-resolve 3.0.0 } |> resolve(%), { exclude: 'node_modules/**/*' } |> stripBanner(%), getBabelConfig(updateBabelOptions, bundleType, packageName, externals, !isProduction, bundle) |> babel(%), // Remove 'use strict' from individual source files. { name: "remove 'use strict'", transform(source) { return /['"]use strict["']/g |> source.replace(%, ''); } }, { preventAssignment: true, values: { __DEV__: isProduction ? 'false' : 'true', __PROFILE__: isProfiling || !isProduction ? 'true' : 'false', 'process.env.NODE_ENV': isProduction ? "'production'" : "'development'", __EXPERIMENTAL__ } } |> replace(%), { 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 && ({ 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 } |> closure(%)), needsMinifiedByClosure && ({ parser: 'flow', singleQuote: false, trailingComma: 'none', bracketSpacing: true } |> prettier(%)), { name: 'license-and-signature-header', renderChunk(source) { return Wrappers.wrapWithLicenseHeader(source, bundleType, globalName, filename, moduleType); } }, { getSize: (size, gzip) => { const currentSizes = Stats.currentBuildResults.bundleSizes; const recordIndex = (record => record.filename === filename && record.bundleType === bundleType) |> currentSizes.findIndex(%); const index = recordIndex !== -1 ? recordIndex : currentSizes.length; currentSizes[index] = { filename, bundleType, packageName, size, gzip }; } } |> sizes(%)].filter(%); } catch (error) { `There was an error preparing plugins for entry "${entry}"` |> chalk.red(%) |> console.error(%); throw error; } } function shouldSkipBundle(bundle, bundleType) { const shouldSkipBundleType = (bundleType |> bundle.bundleTypes.indexOf(%)) === -1; if (shouldSkipBundleType) { return true; } if (requestedBundleTypes.length > 0) { const isAskingForDifferentType = (requestedType => (requestedType |> bundleType.indexOf(%)) === -1) |> requestedBundleTypes.every(%); 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 = (requestedName => { const matchEntry = (requestedName |> entryLowerCase.indexOf(%)) !== -1; if (!bundle.name) { return !matchEntry; } const matchName = (requestedName |> bundle.name.toLowerCase().indexOf(%)) !== -1; return !matchEntry && !matchName; }) |> requestedBundleNames.every(%); 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 = '.js' |> resolvedEntry.replace(%, __EXPERIMENTAL__ ? '.modern.fb.js' : '.classic.fb.js'); if (resolvedFBEntry |> fs.existsSync(%)) { return resolvedFBEntry; } const resolvedGenericFBEntry = '.js' |> resolvedEntry.replace(%, '.fb.js'); if (resolvedGenericFBEntry |> fs.existsSync(%)) { 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 = '.js' |> resolvedEntry.replace(%, __EXPERIMENTAL__ ? '.experimental.js' : '.stable.js'); if (resolvedForkedEntry |> fs.existsSync(%)) { return resolvedForkedEntry; } // Just use the plain .js one. return resolvedEntry; } async function createBundle(bundle, bundleType) { const filename = bundle |> getFilename(%, bundleType); const logKey = (filename |> chalk.white.bold(%)) + (` (${bundleType.toLowerCase()})` |> chalk.dim(%)); const format = bundleType |> getFormat(%); const packageName = bundle.entry |> Packaging.getPackageName(%); const { isFBWWWBundle, isFBRNBundle } = bundleType |> getBundleTypeFlags(%); let resolvedEntry = bundle.entry |> require.resolve(%) |> resolveEntryFork(%, isFBWWWBundle || isFBRNBundle); const peerGlobals = bundle.externals |> Modules.getPeerGlobals(%, bundleType); let externals = peerGlobals |> Object.keys(%); const deps = bundleType |> Modules.getDependencies(%, bundle.entry); externals = deps |> externals.concat(%); const importSideEffects = Modules.getImportSideEffects(); const pureExternalModules = (module => !importSideEffects[module]) |> (importSideEffects |> Object.keys(%)).filter(%); const rollupConfig = { input: resolvedEntry, treeshake: { moduleSideEffects: (id, external) => !(external && (id |> pureExternalModules.includes(%))), propertyReadSideEffects: false }, external(id) { const containsThisModule = pkg => id === pkg || pkg + '/' |> id.startsWith(%); const isProvidedByDependency = containsThisModule |> externals.some(%); if (isProvidedByDependency) { if (('/src/' |> id.indexOf(%)) !== -1) { throw 'You are trying to import ' + id + ' but ' + (containsThisModule |> externals.find(%)) + ' 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.' |> Error(%); } 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 = rollupConfig |> rollup.watch(%); 'event' |> watcher.on(%, async event => { switch (event.code) { case 'BUNDLE_START': `${' BUILDING ' |> chalk.bgYellow.black(%)} ${logKey}` |> console.log(%); break; case 'BUNDLE_END': `${' COMPLETE ' |> chalk.bgGreen.black(%)} ${logKey}\n` |> console.log(%); break; case 'ERROR': case 'FATAL': `${' OH NOES! ' |> chalk.bgRed.black(%)} ${logKey}\n` |> console.log(%); event.error |> handleRollupError(%); break; } }); } else { `${' BUILDING ' |> chalk.bgYellow.black(%)} ${logKey}` |> console.log(%); try { const result = await (rollupConfig |> rollup.rollup(%)); await (rollupOutputOptions |> result.write(%)); } catch (error) { `${' OH NOES! ' |> chalk.bgRed.black(%)} ${logKey}\n` |> console.log(%); error |> handleRollupError(%); throw error; } `${' COMPLETE ' |> chalk.bgGreen.black(%)} ${logKey}\n` |> console.log(%); } } function handleRollupWarning(warning) { if (warning.code === 'UNUSED_EXTERNAL_IMPORT') { const match = /external module "([^"]+)"/ |> warning.message.match(%); 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(); warning.message || warning |> console.error(%); console.error(); 1 |> process.exit(%); } else { // The warning is from one of the plugins. // Maybe it's not important, so just print it. warning.message || warning |> console.warn(%); } } function handleRollupError(error) { error |> loggedErrors.add(%); if (!error.code) { error |> console.error(%); return; } `\x1b[31m-- ${error.code}${error.plugin ? ` (${error.plugin})` : ''} --` |> console.error(%); error.stack |> console.error(%); 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 = file |> fs.readFileSync(%, '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 }); frame |> console.error(%); } 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. error.codeFrame |> console.error(%); } } async function buildEverything() { if (!argv['unsafe-partial']) { await ('build' |> asyncRimRaf(%)); } // 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 = (([bundle, bundleType]) => { return !(bundle |> shouldSkipBundle(%, bundleType)); }) |> bundles.filter(%); if (process.env.CIRCLE_NODE_TOTAL) { // In CI, parallelize bundles across multiple tasks. const nodeTotal = process.env.CIRCLE_NODE_TOTAL |> parseInt(%, 10); const nodeIndex = process.env.CIRCLE_NODE_INDEX |> parseInt(%, 10); bundles = ((_, i) => i % nodeTotal === nodeIndex) |> bundles.filter(%); } // eslint-disable-next-line no-for-of-loops/no-for-of-loops for (const [bundle, bundleType] of bundles) { await (bundle |> createBundle(%, bundleType)); } await Packaging.copyAllShims(); await Packaging.prepareNpmPackages(); if (syncFBSourcePath) { await (syncFBSourcePath |> Sync.syncReactNative(%)); } else if (syncWWWPath) { await ('build/facebook-www' |> Sync.syncReactDom(%, syncWWWPath)); } Stats.printResults() |> console.log(%); if (!forcePrettyOutput) { Stats.saveResults(); } } buildEverything();