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