Skip to content

Commit

Permalink
Add support for yarn and lerna monorepos. (facebook#3741)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
bradfordlemley authored and gaearon committed Feb 1, 2018
1 parent 5348d6e commit b43ad04
Show file tree
Hide file tree
Showing 33 changed files with 535 additions and 52 deletions.
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ script:
- 'if [ $TEST_SUITE = "installs" ]; then tasks/e2e-installs.sh; fi'
- 'if [ $TEST_SUITE = "kitchensink" ]; then tasks/e2e-kitchensink.sh; fi'
- 'if [ $TEST_SUITE = "old-node" ]; then tasks/e2e-old-node.sh; fi'
- 'if [ $TEST_SUITE = "monorepos" ]; then tasks/e2e-monorepos.sh; fi'
env:
matrix:
- TEST_SUITE=simple
- TEST_SUITE=installs
- TEST_SUITE=kitchensink
- TEST_SUITE=monorepos
matrix:
include:
- node_js: 0.10
Expand Down
5 changes: 4 additions & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@ environment:
test_suite: "installs"
- nodejs_version: 8
test_suite: "kitchensink"
- nodejs_version: 8
test_suite: "monorepos"
- nodejs_version: 6
test_suite: "simple"
- nodejs_version: 6
test_suite: "installs"
- nodejs_version: 6
test_suite: "kitchensink"

- nodejs_version: 6
test_suite: "monorepos"
cache:
- node_modules -> appveyor.cleanup-cache.txt
- packages\react-scripts\node_modules -> appveyor.cleanup-cache.txt
Expand Down
5 changes: 4 additions & 1 deletion packages/react-scripts/config/jest/babelTransform.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
// @remove-file-on-eject
// @remove-on-eject-begin
/**
* Copyright (c) 2014-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// @remove-on-eject-end
'use strict';

const babelJest = require('babel-jest');

module.exports = babelJest.createTransformer({
presets: [require.resolve('babel-preset-react-app')],
// @remove-on-eject-begin
babelrc: false,
// @remove-on-eject-end
});
59 changes: 48 additions & 11 deletions packages/react-scripts/config/paths.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
const path = require('path');
const fs = require('fs');
const url = require('url');
const findPkg = require('find-pkg');
const globby = require('globby');

// Make sure any symlinks in the project folder are resolved:
// https://github.com/facebook/create-react-app/issues/637
Expand Down Expand Up @@ -63,6 +65,8 @@ module.exports = {
servedPath: getServedPath(resolveApp('package.json')),
};

let checkForMonorepo = true;

// @remove-on-eject-begin
const resolveOwn = relativePath => path.resolve(__dirname, '..', relativePath);

Expand All @@ -86,17 +90,13 @@ module.exports = {
ownNodeModules: resolveOwn('node_modules'), // This is empty on npm 3
};

const ownPackageJson = require('../package.json');
const reactScriptsPath = resolveApp(`node_modules/${ownPackageJson.name}`);
const reactScriptsLinked =
fs.existsSync(reactScriptsPath) &&
fs.lstatSync(reactScriptsPath).isSymbolicLink();

// config before publish: we're in ./packages/react-scripts/config/
if (
!reactScriptsLinked &&
__dirname.indexOf(path.join('packages', 'react-scripts', 'config')) !== -1
) {
// detect if template should be used, ie. when cwd is react-scripts itself
const useTemplate =
appDirectory === fs.realpathSync(path.join(__dirname, '..'));

checkForMonorepo = !useTemplate;

if (useTemplate) {
module.exports = {
dotenv: resolveOwn('template/.env'),
appPath: resolveApp('.'),
Expand All @@ -117,3 +117,40 @@ if (
};
}
// @remove-on-eject-end

module.exports.srcPaths = [module.exports.appSrc];

const findPkgs = (rootPath, globPatterns) => {
const globOpts = {
cwd: rootPath,
strict: true,
absolute: true,
};
return globPatterns
.reduce(
(pkgs, pattern) =>
pkgs.concat(globby.sync(path.join(pattern, 'package.json'), globOpts)),
[]
)
.map(f => path.dirname(path.normalize(f)));
};

const getMonorepoPkgPaths = () => {
const monoPkgPath = findPkg.sync(path.resolve(appDirectory, '..'));
if (monoPkgPath) {
// get monorepo config from yarn workspace
const pkgPatterns = require(monoPkgPath).workspaces;
const pkgPaths = findPkgs(path.dirname(monoPkgPath), pkgPatterns);
// only include monorepo pkgs if app itself is included in monorepo
if (pkgPaths.indexOf(appDirectory) !== -1) {
return pkgPaths.filter(f => fs.realpathSync(f) !== appDirectory);
}
}
return [];
};

if (checkForMonorepo) {
// if app is in a monorepo (lerna or yarn workspace), treat other packages in
// the monorepo as if they are app source
Array.prototype.push.apply(module.exports.srcPaths, getMonorepoPkgPaths());
}
12 changes: 7 additions & 5 deletions packages/react-scripts/config/webpack.config.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,18 +145,19 @@ module.exports = {
options: {
formatter: eslintFormatter,
eslintPath: require.resolve('eslint'),
// @remove-on-eject-begin
baseConfig: {
extends: [require.resolve('eslint-config-react-app')],
},
// @remove-on-eject-begin
ignore: false,
useEslintrc: false,
// @remove-on-eject-end
},
loader: require.resolve('eslint-loader'),
},
],
include: paths.appSrc,
include: paths.srcPaths,
exclude: [/[/\\\\]node_modules[/\\\\]/],
},
{
// "oneOf" will traverse all following loaders until one will
Expand All @@ -178,7 +179,8 @@ module.exports = {
// The preset includes JSX, Flow, and some ESnext features.
{
test: /\.(js|jsx|mjs)$/,
include: paths.appSrc,
include: paths.srcPaths,
exclude: [/[/\\\\]node_modules[/\\\\]/],
use: [
// This loader parallelizes code compilation, it is optional but
// improves compile time on larger projects
Expand All @@ -188,8 +190,8 @@ module.exports = {
options: {
// @remove-on-eject-begin
babelrc: false,
presets: [require.resolve('babel-preset-react-app')],
// @remove-on-eject-end
presets: [require.resolve('babel-preset-react-app')],
// This is a feature of `babel-loader` for webpack (not Babel itself).
// It enables caching results in ./node_modules/.cache/babel-loader/
// directory for faster rebuilds.
Expand Down Expand Up @@ -275,8 +277,8 @@ module.exports = {
options: {
// @remove-on-eject-begin
babelrc: false,
presets: [require.resolve('babel-preset-react-app')],
// @remove-on-eject-end
presets: [require.resolve('babel-preset-react-app')],
cacheDirectory: true,
},
},
Expand Down
12 changes: 7 additions & 5 deletions packages/react-scripts/config/webpack.config.prod.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,20 +152,21 @@ module.exports = {
options: {
formatter: eslintFormatter,
eslintPath: require.resolve('eslint'),
// @remove-on-eject-begin
// TODO: consider separate config for production,
// e.g. to enable no-console and no-debugger only in production.
baseConfig: {
extends: [require.resolve('eslint-config-react-app')],
},
// @remove-on-eject-begin
ignore: false,
useEslintrc: false,
// @remove-on-eject-end
},
loader: require.resolve('eslint-loader'),
},
],
include: paths.appSrc,
include: paths.srcPaths,
exclude: [/[/\\\\]node_modules[/\\\\]/],
},
{
// "oneOf" will traverse all following loaders until one will
Expand All @@ -186,7 +187,8 @@ module.exports = {
// The preset includes JSX, Flow, and some ESnext features.
{
test: /\.(js|jsx|mjs)$/,
include: paths.appSrc,
include: paths.srcPaths,
exclude: [/[/\\\\]node_modules[/\\\\]/],
use: [
// This loader parallelizes code compilation, it is optional but
// improves compile time on larger projects
Expand All @@ -196,8 +198,8 @@ module.exports = {
options: {
// @remove-on-eject-begin
babelrc: false,
presets: [require.resolve('babel-preset-react-app')],
// @remove-on-eject-end
presets: [require.resolve('babel-preset-react-app')],
compact: true,
highlightCode: true,
},
Expand Down Expand Up @@ -317,8 +319,8 @@ module.exports = {
options: {
// @remove-on-eject-begin
babelrc: false,
presets: [require.resolve('babel-preset-react-app')],
// @remove-on-eject-end
presets: [require.resolve('babel-preset-react-app')],
cacheDirectory: true,
},
},
Expand Down
3 changes: 3 additions & 0 deletions packages/react-scripts/fixtures/kitchensink/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"presets": ["react-app"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import React from 'react';

const Comp1 = () => <div>Comp1</div>;

export default Comp1;
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React from 'react';
import ReactDOM from 'react-dom';
import Comp1 from '.';

it('renders Comp1 without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<Comp1 />, div);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "comp1",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"devDependencies": {
"react": "^16.2.0",
"react-dom": "^16.2.0"
}
}
11 changes: 11 additions & 0 deletions packages/react-scripts/fixtures/monorepos/packages/comp2/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';

import Comp1 from 'comp1';

const Comp2 = () => (
<div>
Comp2, nested Comp1: <Comp1 />
</div>
);

export default Comp2;
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React from 'react';
import ReactDOM from 'react-dom';
import Comp2 from '.';

it('renders Comp2 without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<Comp2 />, div);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "comp2",
"dependencies": {
"comp1": "^1.0.0"
},
"devDependencies": {
"react": "^16.2.0",
"react-dom": "^16.2.0"
},
"version": "1.0.0",
"main": "index.js",
"license": "MIT"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.

# dependencies
/node_modules

# testing
/coverage

# production
/build

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "cra-app1",
"version": "0.1.0",
"private": true,
"dependencies": {
"comp2": "^1.0.0",
"react": "^16.2.0",
"react-dom": "^16.2.0"
},
"devDependencies": {
"react-scripts": "latest"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
},
"browserslist": {
"development": [
"last 2 chrome versions",
"last 2 firefox versions",
"last 2 edge versions"
],
"production": [
">1%",
"last 4 versions",
"Firefox ESR",
"not ie < 11"
]
}
}
Binary file not shown.
Loading

0 comments on commit b43ad04

Please sign in to comment.