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"