diff --git a/.flowconfig b/.flowconfig index e67a199a5773f..a577fa72c607d 100644 --- a/.flowconfig +++ b/.flowconfig @@ -8,9 +8,6 @@ /scripts/rollup/shims/facebook-www/.* /scripts/rollup/shims/react-native/.* -# Note: intentionally *don't* ignore /scripts/rollup/shims/rollup/ -# because it is part of the build and isn't external. - /.*/node_modules/y18n/.* /node_modules/chrome-devtools-frontend/.* /node_modules/devtools-timeline-model/.* diff --git a/package.json b/package.json index df774812b03ab..132bd2801d27a 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,6 @@ "prop-types": "^15.6.0", "rimraf": "^2.6.1", "rollup": "^0.51.7", - "rollup-plugin-alias": "^1.2.1", "rollup-plugin-babel": "^2.7.1", "rollup-plugin-closure-compiler-js": "^1.0.4", "rollup-plugin-commonjs": "^8.2.6", diff --git a/packages/react-cs-renderer/src/__tests__/ReactNativeCS-test.internal.js b/packages/react-cs-renderer/src/__tests__/ReactNativeCS-test.internal.js index 998a54107e7eb..825de238f5f80 100644 --- a/packages/react-cs-renderer/src/__tests__/ReactNativeCS-test.internal.js +++ b/packages/react-cs-renderer/src/__tests__/ReactNativeCS-test.internal.js @@ -13,7 +13,7 @@ var React; var ReactNativeCS; jest.mock('shared/ReactFeatureFlags', () => - require('../ReactNativeCSFeatureFlags'), + require('shared/forks/ReactFeatureFlags.native-cs'), ); describe('ReactNativeCS', () => { diff --git a/scripts/rollup/shims/rollup/ReactCurrentOwner-www.js b/packages/react/src/forks/ReactCurrentOwner.www.js similarity index 100% rename from scripts/rollup/shims/rollup/ReactCurrentOwner-www.js rename to packages/react/src/forks/ReactCurrentOwner.www.js diff --git a/packages/react-cs-renderer/src/ReactNativeCSFeatureFlags.js b/packages/shared/forks/ReactFeatureFlags.native-cs.js similarity index 93% rename from packages/react-cs-renderer/src/ReactNativeCSFeatureFlags.js rename to packages/shared/forks/ReactFeatureFlags.native-cs.js index b3428288290af..2b1d771729a3b 100644 --- a/packages/react-cs-renderer/src/ReactNativeCSFeatureFlags.js +++ b/packages/shared/forks/ReactFeatureFlags.native-cs.js @@ -10,7 +10,7 @@ import invariant from 'fbjs/lib/invariant'; import typeof * as FeatureFlagsType from 'shared/ReactFeatureFlags'; -import typeof * as CSFeatureFlagsType from './ReactNativeCSFeatureFlags'; +import typeof * as CSFeatureFlagsType from './ReactFeatureFlags.native-cs'; export const debugRenderPhaseSideEffects = false; export const enableAsyncSubtreeAPI = true; diff --git a/packages/react-native-renderer/src/ReactNativeFeatureFlags.js b/packages/shared/forks/ReactFeatureFlags.native.js similarity index 94% rename from packages/react-native-renderer/src/ReactNativeFeatureFlags.js rename to packages/shared/forks/ReactFeatureFlags.native.js index f5e6e3a187bac..90447cfcecafd 100644 --- a/packages/react-native-renderer/src/ReactNativeFeatureFlags.js +++ b/packages/shared/forks/ReactFeatureFlags.native.js @@ -10,7 +10,7 @@ import invariant from 'fbjs/lib/invariant'; import typeof * as FeatureFlagsType from 'shared/ReactFeatureFlags'; -import typeof * as FeatureFlagsShimType from './ReactNativeFeatureFlags'; +import typeof * as FeatureFlagsShimType from './ReactFeatureFlags.native'; // Re-export dynamic flags from the fbsource version. export const {debugRenderPhaseSideEffects} = require('ReactFeatureFlags'); diff --git a/scripts/rollup/shims/rollup/ReactFeatureFlags-www.js b/packages/shared/forks/ReactFeatureFlags.www.js similarity index 99% rename from scripts/rollup/shims/rollup/ReactFeatureFlags-www.js rename to packages/shared/forks/ReactFeatureFlags.www.js index 607ad30b724af..5858fad2a574f 100644 --- a/scripts/rollup/shims/rollup/ReactFeatureFlags-www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -8,7 +8,7 @@ */ import typeof * as FeatureFlagsType from 'shared/ReactFeatureFlags'; -import typeof * as FeatureFlagsShimType from './ReactFeatureFlags-www'; +import typeof * as FeatureFlagsShimType from './ReactFeatureFlags.www'; // Re-export dynamic flags from the www version. export const { diff --git a/scripts/rollup/shims/rollup/lowPriorityWarning-www.js b/packages/shared/forks/lowPriorityWarning.www.js similarity index 100% rename from scripts/rollup/shims/rollup/lowPriorityWarning-www.js rename to packages/shared/forks/lowPriorityWarning.www.js diff --git a/scripts/rollup/shims/rollup/assign-umd.js b/packages/shared/forks/object-assign.umd.js similarity index 100% rename from scripts/rollup/shims/rollup/assign-umd.js rename to packages/shared/forks/object-assign.umd.js diff --git a/scripts/rollup/build.js b/scripts/rollup/build.js index eda06fb8fd6ab..f57d425034e07 100644 --- a/scripts/rollup/build.js +++ b/scripts/rollup/build.js @@ -4,20 +4,19 @@ const rollup = require('rollup').rollup; const babel = require('rollup-plugin-babel'); const closure = require('rollup-plugin-closure-compiler-js'); const commonjs = require('rollup-plugin-commonjs'); -const alias = require('rollup-plugin-alias'); const prettier = require('rollup-plugin-prettier'); const replace = require('rollup-plugin-replace'); const stripBanner = require('rollup-plugin-strip-banner'); const chalk = require('chalk'); -const join = require('path').join; -const resolve = require('path').resolve; -const resolvePlugin = require('rollup-plugin-node-resolve'); +const path = require('path'); +const resolve = require('rollup-plugin-node-resolve'); const fs = require('fs'); const rimraf = require('rimraf'); const argv = require('minimist')(process.argv.slice(2)); const Modules = require('./modules'); const Bundles = require('./bundles'); const sizes = require('./plugins/sizes-plugin'); +const useForks = require('./plugins/use-forks-plugin'); const Stats = require('./stats'); const extractErrorCodes = require('../error-codes/extract-errors'); const syncReactDom = require('./sync').syncReactDom; @@ -88,7 +87,7 @@ function getBabelConfig(updateBabelOptions, bundleType, filename) { return Object.assign({}, options, { plugins: options.plugins.concat([ // Use object-assign polyfill in open source - resolve('./scripts/babel/transform-object-assign-require'), + path.resolve('./scripts/babel/transform-object-assign-require'), // Minify invariant messages require('../error-codes/replace-invariant-error-codes'), // Wrap warning() calls in a __DEV__ check so they are stripped from production. @@ -113,11 +112,11 @@ function handleRollupWarnings(warning) { ); } const importSideEffects = Modules.getImportSideEffects(); - const path = match[1]; - if (typeof importSideEffects[path] !== 'boolean') { + const externalModule = match[1]; + if (typeof importSideEffects[externalModule] !== 'boolean') { throw new Error( 'An external module "' + - path + + 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`.' @@ -218,11 +217,10 @@ function getPlugins( bundleType, globalName, moduleType, - modulesToStub, - featureFlags + modulesToStub ) { const findAndRecordErrorCodes = extractErrorCodes(errorCodeOpts); - const shims = Modules.getShims(bundleType, entry, featureFlags); + const forks = Modules.getForks(bundleType, entry); const isProduction = isProductionBundleType(bundleType); const isInGlobalScope = bundleType === UMD_DEV || bundleType === UMD_PROD; const isFBBundle = bundleType === FB_DEV || bundleType === FB_PROD; @@ -236,10 +234,10 @@ function getPlugins( return source; }, }, - // Shim some modules for www custom behavior and optimizations. - alias(shims), + // Shim any modules that need forking in this environment. + useForks(forks), // Use Node resolution mechanism. - resolvePlugin({ + resolve({ skip: externals, }), // Remove license headers from individual modules @@ -384,8 +382,7 @@ async function createBundle(bundle, bundleType) { bundleType, bundle.global, bundle.moduleType, - bundle.modulesToStub, - bundle.featureFlags + bundle.modulesToStub ), // We can't use getters in www. legacy: bundleType === FB_DEV || bundleType === FB_PROD, @@ -439,9 +436,9 @@ rimraf('build', async () => { // create a new build directory fs.mkdirSync('build'); // create the packages folder for NODE+UMD bundles - fs.mkdirSync(join('build', 'packages')); + fs.mkdirSync(path.join('build', 'packages')); // create the dist folder for UMD bundles - fs.mkdirSync(join('build', 'dist')); + fs.mkdirSync(path.join('build', 'dist')); await Packaging.createFacebookWWWBuild(); await Packaging.createReactNativeBuild(); @@ -462,11 +459,11 @@ rimraf('build', async () => { } if (syncFbsource) { - await syncReactNative(join('build', 'react-native'), syncFbsource); - await syncReactNativeRT(join('build', 'react-rt'), syncFbsource); - await syncReactNativeCS(join('build', 'react-cs'), syncFbsource); + await syncReactNative(path.join('build', 'react-native'), syncFbsource); + await syncReactNativeRT(path.join('build', 'react-rt'), syncFbsource); + await syncReactNativeCS(path.join('build', 'react-cs'), syncFbsource); } else if (syncWww) { - await syncReactDom(join('build', 'facebook-www'), syncWww); + await syncReactDom(path.join('build', 'facebook-www'), syncWww); } console.log(Stats.printResults()); diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index abab79daafc15..335ebf6aec1c7 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -129,7 +129,6 @@ const bundles = [ 'deepFreezeAndThrowOnMutationInDev', 'flattenStyle', ], - featureFlags: 'react-native-renderer/src/ReactNativeFeatureFlags', }, /******* React Native RT *******/ @@ -156,7 +155,6 @@ const bundles = [ entry: 'react-cs-renderer', global: 'ReactCSRenderer', externals: ['CSStatefulComponent'], - featureFlags: 'react-cs-renderer/src/ReactNativeCSFeatureFlags', }, /******* React Test Renderer *******/ diff --git a/scripts/rollup/forks.js b/scripts/rollup/forks.js new file mode 100644 index 0000000000000..0c3845353d008 --- /dev/null +++ b/scripts/rollup/forks.js @@ -0,0 +1,71 @@ +'use strict'; + +const bundleTypes = require('./bundles').bundleTypes; + +const UMD_DEV = bundleTypes.UMD_DEV; +const UMD_PROD = bundleTypes.UMD_PROD; +const FB_DEV = bundleTypes.FB_DEV; +const FB_PROD = bundleTypes.FB_PROD; + +// If you need to replace a file with another file for a specific environment, +// add it to this list with the logic for choosing the right replacement. +const forks = Object.freeze({ + // Optimization: for UMDs, use object-assign polyfill that is already a part + // of the React package instead of bundling it again. + 'object-assign': (bundleType, entry, dependencies) => { + if (bundleType !== UMD_DEV && bundleType !== UMD_PROD) { + // It's only relevant for UMD bundles since that's where the duplication + // happens. Other bundles just require('object-assign') anyway. + return null; + } + if (dependencies.indexOf('react') === -1) { + // We can only apply the optimizations to bundle that depend on React + // because we read assign() from an object exposed on React internals. + return null; + } + // We can use the fork! + return 'shared/forks/object-assign.umd.js'; + }, + + // We have a few forks for different environments. + 'shared/ReactFeatureFlags': (bundleType, entry) => { + switch (entry) { + case 'react-native-renderer': + return 'shared/forks/ReactFeatureFlags.native.js'; + case 'react-cs-renderer': + return 'shared/forks/ReactFeatureFlags.native-cs.js'; + default: + switch (bundleType) { + case FB_DEV: + case FB_PROD: + return 'shared/forks/ReactFeatureFlags.www.js'; + } + } + return null; + }, + + // This logic is forked on www to blacklist warnings. + 'shared/lowPriorityWarning': (bundleType, entry) => { + switch (bundleType) { + case FB_DEV: + case FB_PROD: + return 'shared/forks/lowPriorityWarning.www.js'; + default: + return null; + } + }, + + // In FB bundles, we preserve an inline require to ReactCurrentOwner. + // See the explanation in FB version of ReactCurrentOwner in www: + 'react/src/ReactCurrentOwner': (bundleType, entry) => { + switch (bundleType) { + case FB_DEV: + case FB_PROD: + return 'react/src/forks/ReactCurrentOwner.www.js'; + default: + return null; + } + }, +}); + +module.exports = forks; diff --git a/scripts/rollup/modules.js b/scripts/rollup/modules.js index a60f8c94dddad..e49cb2c4d6646 100644 --- a/scripts/rollup/modules.js +++ b/scripts/rollup/modules.js @@ -1,31 +1,11 @@ 'use strict'; const path = require('path'); +const forks = require('./forks'); const bundleTypes = require('./bundles').bundleTypes; const UMD_DEV = bundleTypes.UMD_DEV; const UMD_PROD = bundleTypes.UMD_PROD; -const FB_DEV = bundleTypes.FB_DEV; -const FB_PROD = bundleTypes.FB_PROD; - -// Bundles exporting globals that other modules rely on. -const knownGlobals = Object.freeze({ - react: 'React', - 'react-dom': 'ReactDOM', -}); - -// Redirect some modules to Haste forks in www. -// Assuming their names in www are the same, and also match -// imported names in corresponding ./shims/rollup/*-www.js shims. -const forkedFBModules = Object.freeze([ - // At FB, we don't know them statically: - 'shared/ReactFeatureFlags', - // This logic is also forked internally. - 'shared/lowPriorityWarning', - // In FB bundles, we preserve an inline require to ReactCurrentOwner. - // See the explanation in FB version of ReactCurrentOwner in www: - 'react/src/ReactCurrentOwner', -]); // For any external that is used in a DEV-only condition, explicitly // specify whether it has side effects during import or not. This lets @@ -41,6 +21,12 @@ const importSideEffects = Object.freeze({ deepFreezeAndThrowOnMutationInDev: HAS_NO_SIDE_EFFECTS_ON_IMPORT, }); +// Bundles exporting globals that other modules rely on. +const knownGlobals = Object.freeze({ + react: 'React', + 'react-dom': 'ReactDOM', +}); + // Given ['react'] in bundle externals, returns { 'react': 'React' }. function getPeerGlobals(externals, moduleType) { const peerGlobals = {}; @@ -62,72 +48,35 @@ function getDependencies(bundleType, entry) { path.dirname(require.resolve(entry)) ) + '/package.json'); // Both deps and peerDeps are assumed as accessible. - let deps = Array.from( + return Array.from( new Set([ ...Object.keys(packageJson.dependencies || {}), ...Object.keys(packageJson.peerDependencies || {}), ]) ); - // In www, forked modules are also require-able. - if (bundleType === FB_DEV || bundleType === FB_PROD) { - deps = [...deps, ...forkedFBModules.map(name => path.basename(name))]; - } - return deps; } -function getImportSideEffects() { - return importSideEffects; +// Hijacks some modules for optimization and integration reasons. +function getForks(bundleType, entry) { + const forksForBundle = {}; + Object.keys(forks).forEach(srcModule => { + const dependencies = getDependencies(bundleType, entry); + const targetModule = forks[srcModule](bundleType, entry, dependencies); + if (targetModule === null) { + return; + } + forksForBundle[srcModule] = targetModule; + }); + return forksForBundle; } -// Hijacks some modules for optimization and integration reasons. -function getShims(bundleType, entry, featureFlags) { - const shims = {}; - switch (bundleType) { - case UMD_DEV: - case UMD_PROD: - if (getDependencies(bundleType, entry).indexOf('react') !== -1) { - // Optimization: rely on object-assign polyfill that is already a part - // of the React package instead of bundling it again. - shims['object-assign'] = path.resolve( - __dirname + '/shims/rollup/assign-umd.js' - ); - } - break; - case FB_DEV: - case FB_PROD: - // FB forks a few modules in www that are usually bundled. - // Instead of bundling them, they need to be kept as require()s in the - // final bundles so that they import www modules with the same names. - // Rollup doesn't make it very easy to rewrite and ignore such a require, - // so we resort to using a shim that re-exports the www module, and then - // treating shim's target destinations as external (see getDependencies). - forkedFBModules.forEach(srcPath => { - const fileName = path.parse(srcPath).name; - const shimPath = path.resolve( - __dirname + `/shims/rollup/${fileName}-www.js` - ); - shims[srcPath] = shimPath; - // - // Unfortunately the above doesn't work for relative imports, - // and Rollup isn't smart enough to understand they refer to the same file. - // We should come up with a real fix for this, but for now this will do. - // FIXME: this is gross. - shims['./' + fileName] = shimPath; - shims['../' + fileName] = shimPath; - // We don't have deeper relative requires between source files. - // - }); - break; - } - if (featureFlags) { - shims['shared/ReactFeatureFlags'] = require.resolve(featureFlags); - } - return shims; +function getImportSideEffects() { + return importSideEffects; } module.exports = { getImportSideEffects, getPeerGlobals, getDependencies, - getShims, + getForks, }; diff --git a/scripts/rollup/plugins/sizes-plugin.js b/scripts/rollup/plugins/sizes-plugin.js index c891d436b27e0..514795464d52b 100644 --- a/scripts/rollup/plugins/sizes-plugin.js +++ b/scripts/rollup/plugins/sizes-plugin.js @@ -1,3 +1,11 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +'use strict'; + const gzip = require('gzip-size'); module.exports = function sizes(options) { diff --git a/scripts/rollup/plugins/use-forks-plugin.js b/scripts/rollup/plugins/use-forks-plugin.js new file mode 100644 index 0000000000000..3ba56dd3a059e --- /dev/null +++ b/scripts/rollup/plugins/use-forks-plugin.js @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +'use strict'; + +const path = require('path'); + +let resolveCache = new Map(); +function useForks(forks) { + let resolvedForks = {}; + Object.keys(forks).forEach(srcModule => { + const targetModule = forks[srcModule]; + resolvedForks[require.resolve(srcModule)] = require.resolve(targetModule); + }); + return { + resolveId(importee, importer) { + if (!importer || !importee) { + return null; + } + let resolvedImportee = null; + let cacheKey = `${importer}:::${importee}`; + if (resolveCache.has(cacheKey)) { + // Avoid hitting file system if possible. + resolvedImportee = resolveCache.get(cacheKey); + } else { + try { + resolvedImportee = require.resolve(importee, { + paths: [path.dirname(importer)], + }); + } catch (err) { + // Not our fault, let Rollup fail later. + } + if (resolvedImportee) { + resolveCache.set(cacheKey, resolvedImportee); + } + } + if (resolvedImportee && resolvedForks.hasOwnProperty(resolvedImportee)) { + // We found a fork! + return resolvedForks[resolvedImportee]; + } + return null; + }, + }; +} + +module.exports = useForks; diff --git a/yarn.lock b/yarn.lock index 4618809712be8..3a9bfa57d67c4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4122,12 +4122,6 @@ rimraf@^2.5.4: dependencies: glob "^7.0.5" -rollup-plugin-alias@^1.2.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/rollup-plugin-alias/-/rollup-plugin-alias-1.3.1.tgz#a9152fec4b6a6510dae93989517ca7853c32a6fa" - dependencies: - slash "^1.0.0" - rollup-plugin-babel@^2.7.1: version "2.7.1" resolved "https://registry.yarnpkg.com/rollup-plugin-babel/-/rollup-plugin-babel-2.7.1.tgz#16528197b0f938a1536f44683c7a93d573182f57"