Skip to content

Commit 69754f3

Browse files
Initial monorepo support, facebook#3741.
1 parent 6e14dbb commit 69754f3

32 files changed

+541
-50
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
@@ -64,6 +66,8 @@ module.exports = {
6466
servedPath: getServedPath(resolveApp('package.json')),
6567
};
6668

69+
let checkForMonorepo = true;
70+
6771
// @remove-on-eject-begin
6872
const resolveOwn = relativePath => path.resolve(__dirname, '..', relativePath);
6973

@@ -88,17 +92,13 @@ module.exports = {
8892
ownNodeModules: resolveOwn('node_modules'), // This is empty on npm 3
8993
};
9094

91-
const ownPackageJson = require('../package.json');
92-
const reactScriptsPath = resolveApp(`node_modules/${ownPackageJson.name}`);
93-
const reactScriptsLinked =
94-
fs.existsSync(reactScriptsPath) &&
95-
fs.lstatSync(reactScriptsPath).isSymbolicLink();
96-
97-
// config before publish: we're in ./packages/react-scripts/config/
98-
if (
99-
!reactScriptsLinked &&
100-
__dirname.indexOf(path.join('packages', 'react-scripts', 'config')) !== -1
101-
) {
95+
// detect if template should be used, ie. when cwd is react-scripts itself
96+
const useTemplate =
97+
appDirectory === fs.realpathSync(path.join(__dirname, '..'));
98+
99+
checkForMonorepo = !useTemplate;
100+
101+
if (useTemplate) {
102102
module.exports = {
103103
dotenv: resolveOwn('template/.env'),
104104
appPath: resolveApp('.'),
@@ -120,3 +120,40 @@ if (
120120
};
121121
}
122122
// @remove-on-eject-end
123+
124+
module.exports.srcPaths = [module.exports.appSrc];
125+
126+
const findPkgs = (rootPath, globPatterns) => {
127+
const globOpts = {
128+
cwd: rootPath,
129+
strict: true,
130+
absolute: true,
131+
};
132+
return globPatterns
133+
.reduce(
134+
(pkgs, pattern) =>
135+
pkgs.concat(globby.sync(path.join(pattern, 'package.json'), globOpts)),
136+
[]
137+
)
138+
.map(f => path.dirname(path.normalize(f)));
139+
};
140+
141+
const getMonorepoPkgPaths = () => {
142+
const monoPkgPath = findPkg.sync(path.resolve(appDirectory, '..'));
143+
if (monoPkgPath) {
144+
// get monorepo config from yarn workspace
145+
const pkgPatterns = require(monoPkgPath).workspaces;
146+
const pkgPaths = findPkgs(path.dirname(monoPkgPath), pkgPatterns);
147+
// only include monorepo pkgs if app itself is included in monorepo
148+
if (pkgPaths.indexOf(appDirectory) !== -1) {
149+
return pkgPaths.filter(f => fs.realpathSync(f) !== appDirectory);
150+
}
151+
}
152+
return [];
153+
};
154+
155+
if (checkForMonorepo) {
156+
// if app is in a monorepo (lerna or yarn workspace), treat other packages in
157+
// the monorepo as if they are app source
158+
Array.prototype.push.apply(module.exports.srcPaths, getMonorepoPkgPaths());
159+
}

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,19 +185,20 @@ module.exports = {
185185
options: {
186186
formatter: require.resolve('react-dev-utils/eslintFormatter'),
187187
eslintPath: require.resolve('eslint'),
188-
// @remove-on-eject-begin
189188
baseConfig: {
190189
extends: [require.resolve('eslint-config-react-app')],
191190
settings: { react: { version: '999.999.999' } },
192191
},
192+
// @remove-on-eject-begin
193193
ignore: false,
194194
useEslintrc: false,
195195
// @remove-on-eject-end
196196
},
197197
loader: require.resolve('eslint-loader'),
198198
},
199199
],
200-
include: paths.appSrc,
200+
include: paths.srcPaths,
201+
exclude: [/[/\\\\]node_modules[/\\\\]/],
201202
},
202203
{
203204
// "oneOf" will traverse all following loaders until one will
@@ -219,7 +220,8 @@ module.exports = {
219220
// The preset includes JSX, Flow, and some ESnext features.
220221
{
221222
test: /\.(js|mjs|jsx)$/,
222-
include: paths.appSrc,
223+
include: paths.srcPaths,
224+
exclude: [/[/\\\\]node_modules[/\\\\]/],
223225
loader: require.resolve('babel-loader'),
224226
options: {
225227
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
@@ -256,21 +256,22 @@ module.exports = {
256256
options: {
257257
formatter: require.resolve('react-dev-utils/eslintFormatter'),
258258
eslintPath: require.resolve('eslint'),
259-
// @remove-on-eject-begin
260259
// TODO: consider separate config for production,
261260
// e.g. to enable no-console and no-debugger only in production.
262261
baseConfig: {
263262
extends: [require.resolve('eslint-config-react-app')],
264263
settings: { react: { version: '999.999.999' } },
265264
},
265+
// @remove-on-eject-begin
266266
ignore: false,
267267
useEslintrc: false,
268268
// @remove-on-eject-end
269269
},
270270
loader: require.resolve('eslint-loader'),
271271
},
272272
],
273-
include: paths.appSrc,
273+
include: paths.srcPaths,
274+
exclude: [/[/\\\\]node_modules[/\\\\]/],
274275
},
275276
{
276277
// "oneOf" will traverse all following loaders until one will
@@ -291,8 +292,8 @@ module.exports = {
291292
// The preset includes JSX, Flow, and some ESnext features.
292293
{
293294
test: /\.(js|mjs|jsx)$/,
294-
include: paths.appSrc,
295-
295+
include: paths.srcPaths,
296+
exclude: [/[/\\\\]node_modules[/\\\\]/],
296297
loader: require.resolve('babel-loader'),
297298
options: {
298299
customize: require.resolve(
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"presets": ["react-app"]
3+
}
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;
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 Comp2 from '.';
4+
5+
it('renders Comp2 without crashing', () => {
6+
const div = document.createElement('div');
7+
ReactDOM.render(<Comp2 />, div);
8+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"name": "comp2",
3+
"dependencies": {
4+
"comp1": "^1.0.0"
5+
},
6+
"devDependencies": {
7+
"react": "^16.2.0",
8+
"react-dom": "^16.2.0"
9+
},
10+
"version": "1.0.0",
11+
"main": "index.js",
12+
"license": "MIT"
13+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# See https://help.github.com/ignore-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
6+
# testing
7+
/coverage
8+
9+
# production
10+
/build
11+
12+
# misc
13+
.DS_Store
14+
.env.local
15+
.env.development.local
16+
.env.test.local
17+
.env.production.local
18+
19+
npm-debug.log*
20+
yarn-debug.log*
21+
yarn-error.log*
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"name": "cra-app1",
3+
"version": "0.1.0",
4+
"private": true,
5+
"dependencies": {
6+
"comp2": "^1.0.0",
7+
"react": "^16.2.0",
8+
"react-dom": "^16.2.0"
9+
},
10+
"devDependencies": {
11+
"react-scripts": "latest"
12+
},
13+
"scripts": {
14+
"start": "react-scripts start",
15+
"build": "react-scripts build",
16+
"test": "react-scripts test --env=jsdom",
17+
"eject": "react-scripts eject"
18+
},
19+
"browserslist": {
20+
"development": [
21+
"last 2 chrome versions",
22+
"last 2 firefox versions",
23+
"last 2 edge versions"
24+
],
25+
"production": [
26+
">1%",
27+
"last 4 versions",
28+
"Firefox ESR",
29+
"not ie < 11"
30+
]
31+
}
32+
}
Binary file not shown.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
6+
<meta name="theme-color" content="#000000">
7+
<!--
8+
manifest.json provides metadata used when your web app is added to the
9+
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
10+
-->
11+
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
12+
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
13+
<!--
14+
Notice the use of %PUBLIC_URL% in the tags above.
15+
It will be replaced with the URL of the `public` folder during the build.
16+
Only files inside the `public` folder can be referenced from the HTML.
17+
18+
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
19+
work correctly both with client-side routing and a non-root public URL.
20+
Learn how to configure a non-root public URL by running `npm run build`.
21+
-->
22+
<title>React App</title>
23+
</head>
24+
<body>
25+
<noscript>
26+
You need to enable JavaScript to run this app.
27+
</noscript>
28+
<div id="root"></div>
29+
<!--
30+
This HTML file is a template.
31+
If you open it directly in the browser, you will see an empty page.
32+
33+
You can add webfonts, meta tags, or analytics to this file.
34+
The build step will place the bundled scripts into the <body> tag.
35+
36+
To begin the development, run `npm start` or `yarn start`.
37+
To create a production bundle, use `npm run build` or `yarn build`.
38+
-->
39+
</body>
40+
</html>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"short_name": "React App",
3+
"name": "Create React App Sample",
4+
"icons": [
5+
{
6+
"src": "favicon.ico",
7+
"sizes": "64x64 32x32 24x24 16x16",
8+
"type": "image/x-icon"
9+
}
10+
],
11+
"start_url": "./index.html",
12+
"display": "standalone",
13+
"theme_color": "#000000",
14+
"background_color": "#ffffff"
15+
}

0 commit comments

Comments
 (0)