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;
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)