Skip to content

Commit 22d9169

Browse files
Initial monorepo support, facebook#3741.
1 parent 3e9ba54 commit 22d9169

30 files changed

+475
-51
lines changed

.travis.yml

Lines changed: 2 additions & 0 deletions
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

Lines changed: 2 additions & 0 deletions
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'
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
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');
1112

1213
module.exports = babelJest.createTransformer({
1314
presets: [require.resolve('babel-preset-react-app')],
15+
// @remove-on-eject-begin
1416
babelrc: false,
1517
configFile: false,
18+
// @remove-on-eject-end
1619
});

packages/react-scripts/config/paths.js

Lines changed: 48 additions & 11 deletions
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
@@ -92,6 +94,8 @@ module.exports = {
9294
servedPath: getServedPath(resolveApp('package.json')),
9395
};
9496

97+
let checkForMonorepo = true;
98+
9599
// @remove-on-eject-begin
96100
const resolveOwn = relativePath => path.resolve(__dirname, '..', relativePath);
97101

@@ -119,17 +123,13 @@ module.exports = {
119123
ownTypeDeclarations: resolveOwn('lib/react-app.d.ts'),
120124
};
121125

122-
const ownPackageJson = require('../package.json');
123-
const reactScriptsPath = resolveApp(`node_modules/${ownPackageJson.name}`);
124-
const reactScriptsLinked =
125-
fs.existsSync(reactScriptsPath) &&
126-
fs.lstatSync(reactScriptsPath).isSymbolicLink();
127-
128-
// config before publish: we're in ./packages/react-scripts/config/
129-
if (
130-
!reactScriptsLinked &&
131-
__dirname.indexOf(path.join('packages', 'react-scripts', 'config')) !== -1
132-
) {
126+
// detect if template should be used, ie. when cwd is react-scripts itself
127+
const useTemplate =
128+
appDirectory === fs.realpathSync(path.join(__dirname, '..'));
129+
130+
checkForMonorepo = !useTemplate;
131+
132+
if (useTemplate) {
133133
module.exports = {
134134
dotenv: resolveOwn('template/.env'),
135135
appPath: resolveApp('.'),
@@ -156,3 +156,40 @@ if (
156156
// @remove-on-eject-end
157157

158158
module.exports.moduleFileExtensions = moduleFileExtensions;
159+
160+
module.exports.srcPaths = [module.exports.appSrc];
161+
162+
const findPkgs = (rootPath, globPatterns) => {
163+
const globOpts = {
164+
cwd: rootPath,
165+
strict: true,
166+
absolute: true,
167+
};
168+
return globPatterns
169+
.reduce(
170+
(pkgs, pattern) =>
171+
pkgs.concat(globby.sync(path.join(pattern, 'package.json'), globOpts)),
172+
[]
173+
)
174+
.map(f => path.dirname(path.normalize(f)));
175+
};
176+
177+
const getMonorepoPkgPaths = () => {
178+
const monoPkgPath = findPkg.sync(path.resolve(appDirectory, '..'));
179+
if (monoPkgPath) {
180+
// get monorepo config from yarn workspace
181+
const pkgPatterns = require(monoPkgPath).workspaces;
182+
const pkgPaths = findPkgs(path.dirname(monoPkgPath), pkgPatterns);
183+
// only include monorepo pkgs if app itself is included in monorepo
184+
if (pkgPaths.indexOf(appDirectory) !== -1) {
185+
return pkgPaths.filter(f => fs.realpathSync(f) !== appDirectory);
186+
}
187+
}
188+
return [];
189+
};
190+
191+
if (checkForMonorepo) {
192+
// if app is in a monorepo (lerna or yarn workspace), treat other packages in
193+
// the monorepo as if they are app source
194+
Array.prototype.push.apply(module.exports.srcPaths, getMonorepoPkgPaths());
195+
}

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

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,11 @@ module.exports = {
153153
// https://github.com/facebook/create-react-app/issues/290
154154
// `web` extension prefixes have been added for better support
155155
// for React Native Web.
156-
extensions: paths.moduleFileExtensions
157-
.map(ext => `.${ext}`)
158-
.filter(ext => useTypeScript || !ext.includes('ts')),
156+
extensions: paths.jsExts.concat(
157+
paths.moduleFileExtensions
158+
.map(ext => `.${ext}`)
159+
.filter(ext => useTypeScript || !ext.includes('ts'))
160+
),
159161
alias: {
160162
// Support React Native Web
161163
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
@@ -196,19 +198,20 @@ module.exports = {
196198
options: {
197199
formatter: require.resolve('react-dev-utils/eslintFormatter'),
198200
eslintPath: require.resolve('eslint'),
199-
// @remove-on-eject-begin
200201
baseConfig: {
201202
extends: [require.resolve('eslint-config-react-app')],
202203
settings: { react: { version: '999.999.999' } },
203204
},
205+
// @remove-on-eject-begin
204206
ignore: false,
205207
useEslintrc: false,
206208
// @remove-on-eject-end
207209
},
208210
loader: require.resolve('eslint-loader'),
209211
},
210212
],
211-
include: paths.appSrc,
213+
include: paths.srcPaths,
214+
exclude: [/[/\\\\]node_modules[/\\\\]/],
212215
},
213216
{
214217
// "oneOf" will traverse all following loaders until one will
@@ -230,7 +233,8 @@ module.exports = {
230233
// The preset includes JSX, Flow, and some ESnext features.
231234
{
232235
test: /\.(js|mjs|jsx|ts|tsx)$/,
233-
include: paths.appSrc,
236+
include: paths.srcPaths,
237+
exclude: [/[/\\\\]node_modules[/\\\\]/],
234238
loader: require.resolve('babel-loader'),
235239
options: {
236240
customize: require.resolve(

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -270,21 +270,22 @@ module.exports = {
270270
options: {
271271
formatter: require.resolve('react-dev-utils/eslintFormatter'),
272272
eslintPath: require.resolve('eslint'),
273-
// @remove-on-eject-begin
274273
// TODO: consider separate config for production,
275274
// e.g. to enable no-console and no-debugger only in production.
276275
baseConfig: {
277276
extends: [require.resolve('eslint-config-react-app')],
278277
settings: { react: { version: '999.999.999' } },
279278
},
279+
// @remove-on-eject-begin
280280
ignore: false,
281281
useEslintrc: false,
282282
// @remove-on-eject-end
283283
},
284284
loader: require.resolve('eslint-loader'),
285285
},
286286
],
287-
include: paths.appSrc,
287+
include: paths.srcPaths,
288+
exclude: [/[/\\\\]node_modules[/\\\\]/],
288289
},
289290
{
290291
// "oneOf" will traverse all following loaders until one will
@@ -305,8 +306,8 @@ module.exports = {
305306
// The preset includes JSX, Flow, TypeScript and some ESnext features.
306307
{
307308
test: /\.(js|mjs|jsx|ts|tsx)$/,
308-
include: paths.appSrc,
309-
309+
include: paths.srcPaths,
310+
exclude: [/[/\\\\]node_modules[/\\\\]/],
310311
loader: require.resolve('babel-loader'),
311312
options: {
312313
customize: require.resolve(
Lines changed: 5 additions & 0 deletions
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;
Lines changed: 8 additions & 0 deletions
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+
});
Lines changed: 10 additions & 0 deletions
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+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import React from 'react';
2+
3+
import Comp1 from 'comp1';
4+
5+
const Comp2 = () => (
6+
<div>
7+
Comp2, nested Comp1: <Comp1 />
8+
</div>
9+
);
10+
11+
export default Comp2;

0 commit comments

Comments
 (0)