@samitouri / QOS-React / commits / 8cbc16f0fa

Unify the way we fork modules (#11711)

* Unify the way we fork modules * Replace rollup-plugin-alias with our own plugin This does exactly what we need and doesn't suffer from https://github.com/rollup/rollup-plugin-alias/issues/34. * Move the new plugin to its own file * Rename variable for consistency I settled on calling them "forks" since we already have a different concept of "shims". * Move fork config into its own file

Dan Abramov committed Nov 30, 2017 at 12:11 UTC 8cbc16f0faedafe4f7424b286af52dafd7a79587
16 files changed +174 -112
.flowconfig
-3
@@ -8,9 +8,6 @@
8 <PROJECT_ROOT>/scripts/rollup/shims/facebook-www/.*
9 <PROJECT_ROOT>/scripts/rollup/shims/react-native/.*
10
11 -# Note: intentionally *don't* ignore /scripts/rollup/shims/rollup/
12 -# because it is part of the build and isn't external.
13 -
11 <PROJECT_ROOT>/.*/node_modules/y18n/.*
12 <PROJECT_ROOT>/node_modules/chrome-devtools-frontend/.*
13 <PROJECT_ROOT>/node_modules/devtools-timeline-model/.*
package.json
-1
@@ -80,7 +80,6 @@
80 "prop-types": "^15.6.0",
81 "rimraf": "^2.6.1",
82 "rollup": "^0.51.7",
83 - "rollup-plugin-alias": "^1.2.1",
83 "rollup-plugin-babel": "^2.7.1",
84 "rollup-plugin-closure-compiler-js": "^1.0.4",
85 "rollup-plugin-commonjs": "^8.2.6",
packages/react-cs-renderer/src/__tests__/ReactNativeCS-test.internal.js
+1 -1
@@ -13,7 +13,7 @@ var React;
13 var ReactNativeCS;
14
15 jest.mock('shared/ReactFeatureFlags', () =>
16 - require('../ReactNativeCSFeatureFlags'),
16 + require('shared/forks/ReactFeatureFlags.native-cs'),
17 );
18
19 describe('ReactNativeCS', () => {
packages/react/src/forks/ReactCurrentOwner.www.js renamed
packages/shared/forks/ReactFeatureFlags.native-cs.js renamed
+1 -1
@@ -10,7 +10,7 @@
10 import invariant from 'fbjs/lib/invariant';
11
12 import typeof * as FeatureFlagsType from 'shared/ReactFeatureFlags';
13 -import typeof * as CSFeatureFlagsType from './ReactNativeCSFeatureFlags';
13 +import typeof * as CSFeatureFlagsType from './ReactFeatureFlags.native-cs';
14
15 export const debugRenderPhaseSideEffects = false;
16 export const enableAsyncSubtreeAPI = true;
packages/shared/forks/ReactFeatureFlags.native.js renamed
+1 -1
@@ -10,7 +10,7 @@
10 import invariant from 'fbjs/lib/invariant';
11
12 import typeof * as FeatureFlagsType from 'shared/ReactFeatureFlags';
13 -import typeof * as FeatureFlagsShimType from './ReactNativeFeatureFlags';
13 +import typeof * as FeatureFlagsShimType from './ReactFeatureFlags.native';
14
15 // Re-export dynamic flags from the fbsource version.
16 export const {debugRenderPhaseSideEffects} = require('ReactFeatureFlags');
packages/shared/forks/ReactFeatureFlags.www.js renamed
+1 -1
@@ -8,7 +8,7 @@
8 */
9
10 import typeof * as FeatureFlagsType from 'shared/ReactFeatureFlags';
11 -import typeof * as FeatureFlagsShimType from './ReactFeatureFlags-www';
11 +import typeof * as FeatureFlagsShimType from './ReactFeatureFlags.www';
12
13 // Re-export dynamic flags from the www version.
14 export const {
packages/shared/forks/lowPriorityWarning.www.js renamed
packages/shared/forks/object-assign.umd.js renamed
scripts/rollup/build.js
+19 -22
@@ -4,20 +4,19 @@ const rollup = require('rollup').rollup;
4 const babel = require('rollup-plugin-babel');
5 const closure = require('rollup-plugin-closure-compiler-js');
6 const commonjs = require('rollup-plugin-commonjs');
7 -const alias = require('rollup-plugin-alias');
7 const prettier = require('rollup-plugin-prettier');
8 const replace = require('rollup-plugin-replace');
9 const stripBanner = require('rollup-plugin-strip-banner');
10 const chalk = require('chalk');
12 -const join = require('path').join;
13 -const resolve = require('path').resolve;
14 -const resolvePlugin = require('rollup-plugin-node-resolve');
11 +const path = require('path');
12 +const resolve = require('rollup-plugin-node-resolve');
13 const fs = require('fs');
14 const rimraf = require('rimraf');
15 const argv = require('minimist')(process.argv.slice(2));
16 const Modules = require('./modules');
17 const Bundles = require('./bundles');
18 const sizes = require('./plugins/sizes-plugin');
19 +const useForks = require('./plugins/use-forks-plugin');
20 const Stats = require('./stats');
21 const extractErrorCodes = require('../error-codes/extract-errors');
22 const syncReactDom = require('./sync').syncReactDom;
@@ -88,7 +87,7 @@ function getBabelConfig(updateBabelOptions, bundleType, filename) {
87 return Object.assign({}, options, {
88 plugins: options.plugins.concat([
89 // Use object-assign polyfill in open source
91 - resolve('./scripts/babel/transform-object-assign-require'),
90 + path.resolve('./scripts/babel/transform-object-assign-require'),
91 // Minify invariant messages
92 require('../error-codes/replace-invariant-error-codes'),
93 // Wrap warning() calls in a __DEV__ check so they are stripped from production.
@@ -113,11 +112,11 @@ function handleRollupWarnings(warning) {
112 );
113 }
114 const importSideEffects = Modules.getImportSideEffects();
116 - const path = match[1];
117 - if (typeof importSideEffects[path] !== 'boolean') {
115 + const externalModule = match[1];
116 + if (typeof importSideEffects[externalModule] !== 'boolean') {
117 throw new Error(
118 'An external module "' +
120 - path +
119 + externalModule +
120 '" is used in a DEV-only code path ' +
121 'but we do not know if it is safe to omit an unused require() to it in production. ' +
122 'Please add it to the `importSideEffects` list in `scripts/rollup/modules.js`.'
@@ -218,11 +217,10 @@ function getPlugins(
217 bundleType,
218 globalName,
219 moduleType,
221 - modulesToStub,
222 - featureFlags
220 + modulesToStub
221 ) {
222 const findAndRecordErrorCodes = extractErrorCodes(errorCodeOpts);
225 - const shims = Modules.getShims(bundleType, entry, featureFlags);
223 + const forks = Modules.getForks(bundleType, entry);
224 const isProduction = isProductionBundleType(bundleType);
225 const isInGlobalScope = bundleType === UMD_DEV || bundleType === UMD_PROD;
226 const isFBBundle = bundleType === FB_DEV || bundleType === FB_PROD;
@@ -236,10 +234,10 @@ function getPlugins(
234 return source;
235 },
236 },
239 - // Shim some modules for www custom behavior and optimizations.
240 - alias(shims),
237 + // Shim any modules that need forking in this environment.
238 + useForks(forks),
239 // Use Node resolution mechanism.
242 - resolvePlugin({
240 + resolve({
241 skip: externals,
242 }),
243 // Remove license headers from individual modules
@@ -384,8 +382,7 @@ async function createBundle(bundle, bundleType) {
382 bundleType,
383 bundle.global,
384 bundle.moduleType,
387 - bundle.modulesToStub,
388 - bundle.featureFlags
385 + bundle.modulesToStub
386 ),
387 // We can't use getters in www.
388 legacy: bundleType === FB_DEV || bundleType === FB_PROD,
@@ -439,9 +436,9 @@ rimraf('build', async () => {
436 // create a new build directory
437 fs.mkdirSync('build');
438 // create the packages folder for NODE+UMD bundles
442 - fs.mkdirSync(join('build', 'packages'));
439 + fs.mkdirSync(path.join('build', 'packages'));
440 // create the dist folder for UMD bundles
444 - fs.mkdirSync(join('build', 'dist'));
441 + fs.mkdirSync(path.join('build', 'dist'));
442
443 await Packaging.createFacebookWWWBuild();
444 await Packaging.createReactNativeBuild();
@@ -462,11 +459,11 @@ rimraf('build', async () => {
459 }
460
461 if (syncFbsource) {
465 - await syncReactNative(join('build', 'react-native'), syncFbsource);
466 - await syncReactNativeRT(join('build', 'react-rt'), syncFbsource);
467 - await syncReactNativeCS(join('build', 'react-cs'), syncFbsource);
462 + await syncReactNative(path.join('build', 'react-native'), syncFbsource);
463 + await syncReactNativeRT(path.join('build', 'react-rt'), syncFbsource);
464 + await syncReactNativeCS(path.join('build', 'react-cs'), syncFbsource);
465 } else if (syncWww) {
469 - await syncReactDom(join('build', 'facebook-www'), syncWww);
466 + await syncReactDom(path.join('build', 'facebook-www'), syncWww);
467 }
468
469 console.log(Stats.printResults());
scripts/rollup/bundles.js
-2
@@ -129,7 +129,6 @@ const bundles = [
129 'deepFreezeAndThrowOnMutationInDev',
130 'flattenStyle',
131 ],
132 - featureFlags: 'react-native-renderer/src/ReactNativeFeatureFlags',
132 },
133
134 /******* React Native RT *******/
@@ -156,7 +155,6 @@ const bundles = [
155 entry: 'react-cs-renderer',
156 global: 'ReactCSRenderer',
157 externals: ['CSStatefulComponent'],
159 - featureFlags: 'react-cs-renderer/src/ReactNativeCSFeatureFlags',
158 },
159
160 /******* React Test Renderer *******/
scripts/rollup/forks.js new
+71
@@ -0,0 +1,71 @@
1 +'use strict';
2 +
3 +const bundleTypes = require('./bundles').bundleTypes;
4 +
5 +const UMD_DEV = bundleTypes.UMD_DEV;
6 +const UMD_PROD = bundleTypes.UMD_PROD;
7 +const FB_DEV = bundleTypes.FB_DEV;
8 +const FB_PROD = bundleTypes.FB_PROD;
9 +
10 +// If you need to replace a file with another file for a specific environment,
11 +// add it to this list with the logic for choosing the right replacement.
12 +const forks = Object.freeze({
13 + // Optimization: for UMDs, use object-assign polyfill that is already a part
14 + // of the React package instead of bundling it again.
15 + 'object-assign': (bundleType, entry, dependencies) => {
16 + if (bundleType !== UMD_DEV && bundleType !== UMD_PROD) {
17 + // It's only relevant for UMD bundles since that's where the duplication
18 + // happens. Other bundles just require('object-assign') anyway.
19 + return null;
20 + }
21 + if (dependencies.indexOf('react') === -1) {
22 + // We can only apply the optimizations to bundle that depend on React
23 + // because we read assign() from an object exposed on React internals.
24 + return null;
25 + }
26 + // We can use the fork!
27 + return 'shared/forks/object-assign.umd.js';
28 + },
29 +
30 + // We have a few forks for different environments.
31 + 'shared/ReactFeatureFlags': (bundleType, entry) => {
32 + switch (entry) {
33 + case 'react-native-renderer':
34 + return 'shared/forks/ReactFeatureFlags.native.js';
35 + case 'react-cs-renderer':
36 + return 'shared/forks/ReactFeatureFlags.native-cs.js';
37 + default:
38 + switch (bundleType) {
39 + case FB_DEV:
40 + case FB_PROD:
41 + return 'shared/forks/ReactFeatureFlags.www.js';
42 + }
43 + }
44 + return null;
45 + },
46 +
47 + // This logic is forked on www to blacklist warnings.
48 + 'shared/lowPriorityWarning': (bundleType, entry) => {
49 + switch (bundleType) {
50 + case FB_DEV:
51 + case FB_PROD:
52 + return 'shared/forks/lowPriorityWarning.www.js';
53 + default:
54 + return null;
55 + }
56 + },
57 +
58 + // In FB bundles, we preserve an inline require to ReactCurrentOwner.
59 + // See the explanation in FB version of ReactCurrentOwner in www:
60 + 'react/src/ReactCurrentOwner': (bundleType, entry) => {
61 + switch (bundleType) {
62 + case FB_DEV:
63 + case FB_PROD:
64 + return 'react/src/forks/ReactCurrentOwner.www.js';
65 + default:
66 + return null;
67 + }
68 + },
69 +});
70 +
71 +module.exports = forks;
scripts/rollup/modules.js
+23 -74
@@ -1,31 +1,11 @@
1 'use strict';
2
3 const path = require('path');
4 +const forks = require('./forks');
5 const bundleTypes = require('./bundles').bundleTypes;
6
7 const UMD_DEV = bundleTypes.UMD_DEV;
8 const UMD_PROD = bundleTypes.UMD_PROD;
8 -const FB_DEV = bundleTypes.FB_DEV;
9 -const FB_PROD = bundleTypes.FB_PROD;
10 -
11 -// Bundles exporting globals that other modules rely on.
12 -const knownGlobals = Object.freeze({
13 - react: 'React',
14 - 'react-dom': 'ReactDOM',
15 -});
16 -
17 -// Redirect some modules to Haste forks in www.
18 -// Assuming their names in www are the same, and also match
19 -// imported names in corresponding ./shims/rollup/*-www.js shims.
20 -const forkedFBModules = Object.freeze([
21 - // At FB, we don't know them statically:
22 - 'shared/ReactFeatureFlags',
23 - // This logic is also forked internally.
24 - 'shared/lowPriorityWarning',
25 - // In FB bundles, we preserve an inline require to ReactCurrentOwner.
26 - // See the explanation in FB version of ReactCurrentOwner in www:
27 - 'react/src/ReactCurrentOwner',
28 -]);
9
10 // For any external that is used in a DEV-only condition, explicitly
11 // specify whether it has side effects during import or not. This lets
@@ -41,6 +21,12 @@ const importSideEffects = Object.freeze({
21 deepFreezeAndThrowOnMutationInDev: HAS_NO_SIDE_EFFECTS_ON_IMPORT,
22 });
23
24 +// Bundles exporting globals that other modules rely on.
25 +const knownGlobals = Object.freeze({
26 + react: 'React',
27 + 'react-dom': 'ReactDOM',
28 +});
29 +
30 // Given ['react'] in bundle externals, returns { 'react': 'React' }.
31 function getPeerGlobals(externals, moduleType) {
32 const peerGlobals = {};
@@ -62,72 +48,35 @@ function getDependencies(bundleType, entry) {
48 path.dirname(require.resolve(entry))
49 ) + '/package.json');
50 // Both deps and peerDeps are assumed as accessible.
65 - let deps = Array.from(
51 + return Array.from(
52 new Set([
53 ...Object.keys(packageJson.dependencies || {}),
54 ...Object.keys(packageJson.peerDependencies || {}),
55 ])
56 );
71 - // In www, forked modules are also require-able.
72 - if (bundleType === FB_DEV || bundleType === FB_PROD) {
73 - deps = [...deps, ...forkedFBModules.map(name => path.basename(name))];
74 - }
75 - return deps;
57 }
58
78 -function getImportSideEffects() {
79 - return importSideEffects;
59 +// Hijacks some modules for optimization and integration reasons.
60 +function getForks(bundleType, entry) {
61 + const forksForBundle = {};
62 + Object.keys(forks).forEach(srcModule => {
63 + const dependencies = getDependencies(bundleType, entry);
64 + const targetModule = forks[srcModule](bundleType, entry, dependencies);
65 + if (targetModule === null) {
66 + return;
67 + }
68 + forksForBundle[srcModule] = targetModule;
69 + });
70 + return forksForBundle;
71 }
72
82 -// Hijacks some modules for optimization and integration reasons.
83 -function getShims(bundleType, entry, featureFlags) {
84 - const shims = {};
85 - switch (bundleType) {
86 - case UMD_DEV:
87 - case UMD_PROD:
88 - if (getDependencies(bundleType, entry).indexOf('react') !== -1) {
89 - // Optimization: rely on object-assign polyfill that is already a part
90 - // of the React package instead of bundling it again.
91 - shims['object-assign'] = path.resolve(
92 - __dirname + '/shims/rollup/assign-umd.js'
93 - );
94 - }
95 - break;
96 - case FB_DEV:
97 - case FB_PROD:
98 - // FB forks a few modules in www that are usually bundled.
99 - // Instead of bundling them, they need to be kept as require()s in the
100 - // final bundles so that they import www modules with the same names.
101 - // Rollup doesn't make it very easy to rewrite and ignore such a require,
102 - // so we resort to using a shim that re-exports the www module, and then
103 - // treating shim's target destinations as external (see getDependencies).
104 - forkedFBModules.forEach(srcPath => {
105 - const fileName = path.parse(srcPath).name;
106 - const shimPath = path.resolve(
107 - __dirname + `/shims/rollup/${fileName}-www.js`
108 - );
109 - shims[srcPath] = shimPath;
110 - // <hack>
111 - // Unfortunately the above doesn't work for relative imports,
112 - // and Rollup isn't smart enough to understand they refer to the same file.
113 - // We should come up with a real fix for this, but for now this will do.
114 - // FIXME: this is gross.
115 - shims['./' + fileName] = shimPath;
116 - shims['../' + fileName] = shimPath;
117 - // We don't have deeper relative requires between source files.
118 - // </hack>
119 - });
120 - break;
121 - }
122 - if (featureFlags) {
123 - shims['shared/ReactFeatureFlags'] = require.resolve(featureFlags);
124 - }
125 - return shims;
73 +function getImportSideEffects() {
74 + return importSideEffects;
75 }
76
77 module.exports = {
78 getImportSideEffects,
79 getPeerGlobals,
80 getDependencies,
132 - getShims,
81 + getForks,
82 };
scripts/rollup/plugins/sizes-plugin.js
+8
@@ -1,3 +1,11 @@
1 +/**
2 + * Copyright (c) 2013-present, Facebook, Inc.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + */
7 +'use strict';
8 +
9 const gzip = require('gzip-size');
10
11 module.exports = function sizes(options) {
scripts/rollup/plugins/use-forks-plugin.js new
+49
@@ -0,0 +1,49 @@
1 +/**
2 + * Copyright (c) 2013-present, Facebook, Inc.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + */
7 +'use strict';
8 +
9 +const path = require('path');
10 +
11 +let resolveCache = new Map();
12 +function useForks(forks) {
13 + let resolvedForks = {};
14 + Object.keys(forks).forEach(srcModule => {
15 + const targetModule = forks[srcModule];
16 + resolvedForks[require.resolve(srcModule)] = require.resolve(targetModule);
17 + });
18 + return {
19 + resolveId(importee, importer) {
20 + if (!importer || !importee) {
21 + return null;
22 + }
23 + let resolvedImportee = null;
24 + let cacheKey = `${importer}:::${importee}`;
25 + if (resolveCache.has(cacheKey)) {
26 + // Avoid hitting file system if possible.
27 + resolvedImportee = resolveCache.get(cacheKey);
28 + } else {
29 + try {
30 + resolvedImportee = require.resolve(importee, {
31 + paths: [path.dirname(importer)],
32 + });
33 + } catch (err) {
34 + // Not our fault, let Rollup fail later.
35 + }
36 + if (resolvedImportee) {
37 + resolveCache.set(cacheKey, resolvedImportee);
38 + }
39 + }
40 + if (resolvedImportee && resolvedForks.hasOwnProperty(resolvedImportee)) {
41 + // We found a fork!
42 + return resolvedForks[resolvedImportee];
43 + }
44 + return null;
45 + },
46 + };
47 +}
48 +
49 +module.exports = useForks;
yarn.lock
-6
@@ -4122,12 +4122,6 @@ rimraf@^2.5.4:
4122 dependencies:
4123 glob "^7.0.5"
4124
4125 -rollup-plugin-alias@^1.2.1:
4126 - version "1.3.1"
4127 - resolved "https://registry.yarnpkg.com/rollup-plugin-alias/-/rollup-plugin-alias-1.3.1.tgz#a9152fec4b6a6510dae93989517ca7853c32a6fa"
4128 - dependencies:
4129 - slash "^1.0.0"
4130 -
4125 rollup-plugin-babel@^2.7.1:
4126 version "2.7.1"
4127 resolved "https://registry.yarnpkg.com/rollup-plugin-babel/-/rollup-plugin-babel-2.7.1.tgz#16528197b0f938a1536f44683c7a93d573182f57"