Skip to content

Commit b0750fd

Browse files
Add support for yarn and lerna monorepos. (facebook#3741)
* Support for multiple source paths via package.json srcPaths entry. Initial support for yarn workspace. Support lerna workspace, fix for when to use template files. Remove support for specifying srcPaths in package.json. Re-enable transpilation caching. * Clean up, use file matching (similar to original) in webpack configs instead of matching function. * Remove package lock files. * Fix for eject. Note: monorepos won't work after eject. Can be fixed easily with JEST 22.0.?+ which has file pattern matches against realpaths. * Filter tests to run only tests in monorepo components included by the app. (Not sure this is desireable, might be cool to be able to easily run all tests in monorepo from one app.) * Fix conditions for when to use template. * Fix eject. * Remove code that is not needed w/ Jest 22. * Include all cra-comp tests in monorepo instead of trying to include only tests that are dependencies of app. (tests can be easily filtered via jest cli if desired, e.g. 'npm test -- myapp comp1') * Pin find-pkg version. * Hopefully fix jest test file matching on windows by removing first slash. * E2E tests for monorepo. * Run monorepo tests in CI. * Fix and test post-eject build. * Fix e2e test. * Fix test suite names in appveyor. * Include individual package dirs as srcPaths instead of top-level monorepo root. Fixes build/start after eject. * Fix running tests after eject. * Clean up test workspace, add some verifcations. * Cleanup. * Try to fix hang when running test on appveyor. * Don't write babel or lint config to package.json when ejecting. * Incorporate review comments. * Simply monorepo pkg finder * Only include monorepo pkgs if app itself is included in monorepo * Check for specific tests in e2e * Fixes for windows. * Fix for kitchensink mocha tests compiling. * Add lerna monorepo test. * Fix lerna bootstrap on windows. * Incorporate more review comments: * remove support for lerna w/o yarn workspace * add react and react-dom as devDeps to comp1 and comp2 * Add monorepo info to user guide.
1 parent bff8b6c commit b0750fd

33 files changed

+609
-77
lines changed

.travis.yml

+2
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@ script:
1919
- 'if [ $TEST_SUITE = "kitchensink-eject" ]; then tasks/e2e-kitchensink-eject.sh; fi'
2020
- 'if [ $TEST_SUITE = "old-node" ]; then tasks/e2e-old-node.sh; fi'
2121
- 'if [ $TEST_SUITE = "behavior" ]; then tasks/e2e-behavior.sh; fi'
22+
- 'if [ $TEST_SUITE = "monorepos" ]; then tasks/e2e-monorepos.sh; fi'
2223
env:
2324
matrix:
2425
- TEST_SUITE=simple
2526
- TEST_SUITE=installs
2627
- TEST_SUITE=kitchensink
2728
- TEST_SUITE=kitchensink-eject
2829
- TEST_SUITE=behavior
30+
- TEST_SUITE=monorepos
2931
matrix:
3032
include:
3133
- os: osx

appveyor.yml

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ environment:
2020
test_suite: 'kitchensink'
2121
- nodejs_version: 8
2222
test_suite: 'kitchensink-eject'
23+
- nodejs_version: 8
24+
test_suite: "monorepos"
2325
cache:
2426
- '%APPDATA%\npm-cache -> appveyor.cleanup-cache.txt'
2527
- '%LOCALAPPDATA%\Yarn\Cache -> appveyor.cleanup-cache.txt'

packages/react-error-overlay/package.json

+6-7
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,13 @@
3030
"lib/index.js"
3131
],
3232
"devDependencies": {
33-
"@babel/code-frame": "7.0.0-beta.46",
34-
"@babel/core": "7.0.0-beta.46",
35-
"@babel/runtime": "7.0.0-beta.46",
36-
"anser": "1.4.6",
33+
"@babel/code-frame": "7.0.0",
34+
"@babel/core": "7.1.0",
35+
"anser": "1.4.7",
3736
"babel-core": "^7.0.0-bridge.0",
38-
"babel-eslint": "^8.2.2",
39-
"babel-jest": "^22.4.3",
40-
"babel-loader": "^8.0.0-beta.0",
37+
"babel-eslint": "9.0.0",
38+
"babel-jest": "23.6.0",
39+
"babel-loader": "8.0.4",
4140
"@bradfordlemley/babel-preset-react-app": "^5.0.3",
4241
"chalk": "^2.3.2",
4342
"chokidar": "^2.0.2",

packages/react-scripts/config/jest/babelTransform.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
// @remove-file-on-eject
1+
// @remove-on-eject-begin
22
/**
33
* Copyright (c) 2014-present, Facebook, Inc.
44
*
55
* This source code is licensed under the MIT license found in the
66
* LICENSE file in the root directory of this source tree.
77
*/
8+
// @remove-on-eject-end
89
'use strict';
910

1011
const babelJest = require('babel-jest');
@@ -14,4 +15,5 @@ module.exports = babelJest.createTransformer({
1415
// @remove-on-eject-begin
1516
babelrc: false,
1617
configFile: false,
18+
// @remove-on-eject-end
1719
});

packages/react-scripts/config/paths.js

+48-11
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
const path = require('path');
1212
const fs = require('fs');
1313
const url = require('url');
14+
const findPkg = require('find-pkg');
15+
const globby = require('globby');
1416

1517
// Make sure any symlinks in the project folder are resolved:
1618
// https://github.com/facebook/create-react-app/issues/637
@@ -99,6 +101,8 @@ module.exports = {
99101
servedPath: getServedPath(resolveApp('package.json')),
100102
};
101103

104+
let checkForMonorepo = true;
105+
102106
// @remove-on-eject-begin
103107
const resolveOwn = relativePath => path.resolve(__dirname, '..', relativePath);
104108

@@ -124,17 +128,13 @@ module.exports = {
124128
ownNodeModules: resolveOwn('node_modules'), // This is empty on npm 3
125129
};
126130

127-
const ownPackageJson = require('../package.json');
128-
const reactScriptsPath = resolveApp(`node_modules/${ownPackageJson.name}`);
129-
const reactScriptsLinked =
130-
fs.existsSync(reactScriptsPath) &&
131-
fs.lstatSync(reactScriptsPath).isSymbolicLink();
132-
133-
// config before publish: we're in ./packages/react-scripts/config/
134-
if (
135-
!reactScriptsLinked &&
136-
__dirname.indexOf(path.join('packages', 'react-scripts', 'config')) !== -1
137-
) {
131+
// detect if template should be used, ie. when cwd is react-scripts itself
132+
const useTemplate =
133+
appDirectory === fs.realpathSync(path.join(__dirname, '..'));
134+
135+
checkForMonorepo = !useTemplate;
136+
137+
if (useTemplate) {
138138
module.exports = {
139139
dotenv: resolveOwn('template/.env'),
140140
appPath: resolveApp('.'),
@@ -157,3 +157,40 @@ if (
157157
};
158158
}
159159
// @remove-on-eject-end
160+
161+
module.exports.srcPaths = [module.exports.appSrc];
162+
163+
const findPkgs = (rootPath, globPatterns) => {
164+
const globOpts = {
165+
cwd: rootPath,
166+
strict: true,
167+
absolute: true,
168+
};
169+
return globPatterns
170+
.reduce(
171+
(pkgs, pattern) =>
172+
pkgs.concat(globby.sync(path.join(pattern, 'package.json'), globOpts)),
173+
[]
174+
)
175+
.map(f => path.dirname(path.normalize(f)));
176+
};
177+
178+
const getMonorepoPkgPaths = () => {
179+
const monoPkgPath = findPkg.sync(path.resolve(appDirectory, '..'));
180+
if (monoPkgPath) {
181+
// get monorepo config from yarn workspace
182+
const pkgPatterns = require(monoPkgPath).workspaces;
183+
const pkgPaths = findPkgs(path.dirname(monoPkgPath), pkgPatterns);
184+
// only include monorepo pkgs if app itself is included in monorepo
185+
if (pkgPaths.indexOf(appDirectory) !== -1) {
186+
return pkgPaths.filter(f => fs.realpathSync(f) !== appDirectory);
187+
}
188+
}
189+
return [];
190+
};
191+
192+
if (checkForMonorepo) {
193+
// if app is in a monorepo (lerna or yarn workspace), treat other packages in
194+
// the monorepo as if they are app source
195+
Array.prototype.push.apply(module.exports.srcPaths, getMonorepoPkgPaths());
196+
}

packages/react-scripts/config/webpack.config.dev.js

+29-10
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ const getCSSModuleLocalIdent = require('@bradfordlemley/react-dev-utils/getCSSMo
2020
const getClientEnvironment = require('./env');
2121
const paths = require('./paths');
2222
const ManifestPlugin = require('webpack-manifest-plugin');
23-
const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin');
23+
const ModuleNotFoundPlugin = require('@bradfordlemley/react-dev-utils/ModuleNotFoundPlugin');
2424
// @remove-on-eject-begin
25-
const getCacheIdentifier = require('react-dev-utils/getCacheIdentifier');
25+
const getCacheIdentifier = require('@bradfordlemley/react-dev-utils/getCacheIdentifier');
2626
// @remove-on-eject-end
2727

2828
// Webpack uses `publicPath` to determine where the app is being served from.
@@ -146,7 +146,14 @@ module.exports = {
146146
// https://github.com/facebook/create-react-app/issues/290
147147
// `web` extension prefixes have been added for better support
148148
// for React Native Web.
149-
extensions: paths.jsExts.concat(['.mjs', '.web.js', '.js', '.json', '.web.jsx', '.jsx']),
149+
extensions: paths.jsExts.concat([
150+
'.mjs',
151+
'.web.js',
152+
'.js',
153+
'.json',
154+
'.web.jsx',
155+
'.jsx',
156+
]),
150157
alias: {
151158
// Support React Native Web
152159
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
@@ -186,11 +193,15 @@ module.exports = {
186193
use: [
187194
{
188195
options: {
189-
formatter: require.resolve('react-dev-utils/eslintFormatter'),
196+
formatter: require.resolve(
197+
'@bradfordlemley/react-dev-utils/eslintFormatter'
198+
),
190199
eslintPath: require.resolve('eslint'),
191200
// @remove-on-eject-begin
192201
baseConfig: {
193-
extends: [require.resolve('@bradfordlemley/eslint-config-react-app')],
202+
extends: [
203+
require.resolve('@bradfordlemley/eslint-config-react-app'),
204+
],
194205
settings: { react: { version: '999.999.999' } },
195206
},
196207
ignore: false,
@@ -200,7 +211,8 @@ module.exports = {
200211
loader: require.resolve('eslint-loader'),
201212
},
202213
],
203-
include: paths.appSrc,
214+
include: paths.srcPaths,
215+
exclude: [/[/\\\\]node_modules[/\\\\]/],
204216
},
205217
{
206218
// "oneOf" will traverse all following loaders until one will
@@ -222,7 +234,8 @@ module.exports = {
222234
// The preset includes JSX, Flow, and some ESnext features.
223235
{
224236
test: /\.(js|mjs|jsx)$/,
225-
include: paths.appSrc,
237+
include: paths.srcPaths,
238+
exclude: [/[/\\\\]node_modules[/\\\\]/],
226239
loader: require.resolve('babel-loader'),
227240
options: {
228241
customize: require.resolve(
@@ -231,7 +244,9 @@ module.exports = {
231244
// @remove-on-eject-begin
232245
babelrc: false,
233246
configFile: false,
234-
presets: [require.resolve('@bradfordlemley/babel-preset-react-app')],
247+
presets: [
248+
require.resolve('@bradfordlemley/babel-preset-react-app'),
249+
],
235250
// Make sure we have a unique cache identifier, erring on the
236251
// side of caution.
237252
// We remove this when the user ejects because the default
@@ -246,7 +261,9 @@ module.exports = {
246261
// @remove-on-eject-end
247262
plugins: [
248263
[
249-
require.resolve('babel-plugin-named-asset-import'),
264+
require.resolve(
265+
'@bradfordlemley/babel-plugin-named-asset-import'
266+
),
250267
{
251268
loaderMap: {
252269
svg: {
@@ -276,7 +293,9 @@ module.exports = {
276293
compact: false,
277294
presets: [
278295
[
279-
require.resolve('@bradfordlemley/babel-preset-react-app/dependencies'),
296+
require.resolve(
297+
'@bradfordlemley/babel-preset-react-app/dependencies'
298+
),
280299
{ helpers: true },
281300
],
282301
],

packages/react-scripts/config/webpack.config.prod.js

+31-12
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ const ModuleScopePlugin = require('@bradfordlemley/react-dev-utils/ModuleScopePl
2424
const getCSSModuleLocalIdent = require('@bradfordlemley/react-dev-utils/getCSSModuleLocalIdent');
2525
const paths = require('./paths');
2626
const getClientEnvironment = require('./env');
27-
const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin');
27+
const ModuleNotFoundPlugin = require('@bradfordlemley/react-dev-utils/ModuleNotFoundPlugin');
2828
// @remove-on-eject-begin
29-
const getCacheIdentifier = require('react-dev-utils/getCacheIdentifier');
29+
const getCacheIdentifier = require('@bradfordlemley/react-dev-utils/getCacheIdentifier');
3030
// @remove-on-eject-end
3131

3232
// Webpack uses `publicPath` to determine where the app is being served from.
@@ -221,7 +221,14 @@ module.exports = {
221221
// https://github.com/facebook/create-react-app/issues/290
222222
// `web` extension prefixes have been added for better support
223223
// for React Native Web.
224-
extensions: paths.jsExts.concat(['.mjs', '.web.js', '.js', '.json', '.web.jsx', '.jsx']),
224+
extensions: paths.jsExts.concat([
225+
'.mjs',
226+
'.web.js',
227+
'.js',
228+
'.json',
229+
'.web.jsx',
230+
'.jsx',
231+
]),
225232
alias: {
226233
// Support React Native Web
227234
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
@@ -261,13 +268,17 @@ module.exports = {
261268
use: [
262269
{
263270
options: {
264-
formatter: require.resolve('@bradfordlemley/react-dev-utils/eslintFormatter'),
271+
formatter: require.resolve(
272+
'@bradfordlemley/react-dev-utils/eslintFormatter'
273+
),
265274
eslintPath: require.resolve('eslint'),
266275
// @remove-on-eject-begin
267276
// TODO: consider separate config for production,
268277
// e.g. to enable no-console and no-debugger only in production.
269278
baseConfig: {
270-
extends: [require.resolve('@bradfordlemley/eslint-config-react-app')],
279+
extends: [
280+
require.resolve('@bradfordlemley/eslint-config-react-app'),
281+
],
271282
settings: { react: { version: '999.999.999' } },
272283
},
273284
ignore: false,
@@ -277,7 +288,8 @@ module.exports = {
277288
loader: require.resolve('eslint-loader'),
278289
},
279290
],
280-
include: paths.appSrc,
291+
include: paths.srcPaths,
292+
exclude: [/[/\\\\]node_modules[/\\\\]/],
281293
},
282294
{
283295
// "oneOf" will traverse all following loaders until one will
@@ -298,8 +310,8 @@ module.exports = {
298310
// The preset includes JSX, Flow, and some ESnext features.
299311
{
300312
test: /\.(js|mjs|jsx)$/,
301-
include: paths.appSrc,
302-
313+
include: paths.srcPaths,
314+
exclude: [/[/\\\\]node_modules[/\\\\]/],
303315
loader: require.resolve('babel-loader'),
304316
options: {
305317
customize: require.resolve(
@@ -308,7 +320,9 @@ module.exports = {
308320
// @remove-on-eject-begin
309321
babelrc: false,
310322
configFile: false,
311-
presets: [require.resolve('@bradfordlemley/babel-preset-react-app')],
323+
presets: [
324+
require.resolve('@bradfordlemley/babel-preset-react-app'),
325+
],
312326
// Make sure we have a unique cache identifier, erring on the
313327
// side of caution.
314328
// We remove this when the user ejects because the default
@@ -323,7 +337,9 @@ module.exports = {
323337
// @remove-on-eject-end
324338
plugins: [
325339
[
326-
require.resolve('@bradfordlemley/babel-plugin-named-asset-import'),
340+
require.resolve(
341+
'@bradfordlemley/babel-plugin-named-asset-import'
342+
),
327343
{
328344
loaderMap: {
329345
svg: {
@@ -351,7 +367,9 @@ module.exports = {
351367
compact: false,
352368
presets: [
353369
[
354-
require.resolve('@bradfordlemley/babel-preset-react-app/dependencies'),
370+
require.resolve(
371+
'@bradfordlemley/babel-preset-react-app/dependencies'
372+
),
355373
{ helpers: true },
356374
],
357375
],
@@ -479,7 +497,8 @@ module.exports = {
479497
}),
480498
// Inlines the webpack runtime script. This script is too small to warrant
481499
// a network request.
482-
shouldInlineRuntimeChunk && new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime~.+[.]js/]),
500+
shouldInlineRuntimeChunk &&
501+
new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime~.+[.]js/]),
483502
// Makes some environment variables available in index.html.
484503
// The public URL is available as %PUBLIC_URL% in index.html, e.g.:
485504
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"presets": ["react-app"]
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import React from 'react';
2+
3+
const Comp1 = () => <div>Comp1</div>;
4+
5+
export default Comp1;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import React from 'react';
2+
import ReactDOM from 'react-dom';
3+
import Comp1 from '.';
4+
5+
it('renders Comp1 without crashing', () => {
6+
const div = document.createElement('div');
7+
ReactDOM.render(<Comp1 />, div);
8+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "comp1",
3+
"version": "1.0.0",
4+
"main": "index.js",
5+
"license": "MIT",
6+
"devDependencies": {
7+
"react": "^16.2.0",
8+
"react-dom": "^16.2.0"
9+
}
10+
}

0 commit comments

Comments
 (0)