Add support for yarn and lerna monorepos. (#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.
This commit is contained in:
bradfordlemley
2018-02-01 13:58:18 -07:00
committed by Dan Abramov
parent 5348d6eecf
commit b43ad04b88
33 changed files with 534 additions and 51 deletions

View File

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

View File

@@ -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
@@ -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);
@@ -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();
// detect if template should be used, ie. when cwd is react-scripts itself
const useTemplate =
appDirectory === fs.realpathSync(path.join(__dirname, '..'));
// config before publish: we're in ./packages/react-scripts/config/
if (
!reactScriptsLinked &&
__dirname.indexOf(path.join('packages', 'react-scripts', 'config')) !== -1
) {
checkForMonorepo = !useTemplate;
if (useTemplate) {
module.exports = {
dotenv: resolveOwn('template/.env'),
appPath: resolveApp('.'),
@@ -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());
}

View File

@@ -145,10 +145,10 @@ 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
@@ -156,7 +156,8 @@ module.exports = {
loader: require.resolve('eslint-loader'),
},
],
include: paths.appSrc,
include: paths.srcPaths,
exclude: [/[/\\\\]node_modules[/\\\\]/],
},
{
// "oneOf" will traverse all following loaders until one will
@@ -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
@@ -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.
@@ -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,
},
},

View File

@@ -152,12 +152,12 @@ 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
@@ -165,7 +165,8 @@ module.exports = {
loader: require.resolve('eslint-loader'),
},
],
include: paths.appSrc,
include: paths.srcPaths,
exclude: [/[/\\\\]node_modules[/\\\\]/],
},
{
// "oneOf" will traverse all following loaders until one will
@@ -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
@@ -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,
},
@@ -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,
},
},

View File

@@ -0,0 +1,3 @@
{
"presets": ["react-app"]
}

View File

@@ -0,0 +1,5 @@
import React from 'react';
const Comp1 = () => <div>Comp1</div>;
export default Comp1;

View File

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

View File

@@ -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"
}
}

View File

@@ -0,0 +1,11 @@
import React from 'react';
import Comp1 from 'comp1';
const Comp2 = () => (
<div>
Comp2, nested Comp1: <Comp1 />
</div>
);
export default Comp2;

View File

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

View File

@@ -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"
}

View File

@@ -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*

View File

@@ -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.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

View File

@@ -0,0 +1,15 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": "./index.html",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@@ -0,0 +1,28 @@
.App {
text-align: center;
}
.App-logo {
animation: App-logo-spin infinite 20s linear;
height: 80px;
}
.App-header {
background-color: #222;
height: 150px;
padding: 20px;
color: white;
}
.App-title {
font-size: 1.5em;
}
.App-intro {
font-size: large;
}
@keyframes App-logo-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}

View File

@@ -0,0 +1,24 @@
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import Comp2 from 'comp2';
class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">YarnWS-CraApp1</h1>
</header>
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
<Comp2 />
</div>
);
}
}
export default App;

View File

@@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
ReactDOM.unmountComponentAtNode(div);
});

View File

@@ -0,0 +1,5 @@
body {
margin: 0;
padding: 0;
font-family: sans-serif;
}

View File

@@ -0,0 +1,6 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));

View File

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
<g fill="#61DAFB">
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
<circle cx="420.9" cy="296.5" r="45.7"/>
<path d="M520.5 78.1z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,4 @@
{
"private": true,
"workspaces": ["packages/*"]
}

View File

@@ -43,7 +43,9 @@
"eslint-plugin-react": "7.5.1",
"extract-text-webpack-plugin": "3.0.2",
"file-loader": "1.1.6",
"find-pkg": "1.0.0",
"fs-extra": "5.0.0",
"globby": "7.1.1",
"html-webpack-plugin": "2.30.1",
"identity-obj-proxy": "3.0.0",
"jest": "22.1.2",

View File

@@ -109,7 +109,7 @@ inquirer
const jestConfig = createJestConfig(
filePath => path.posix.join('<rootDir>', filePath),
null,
true
paths.srcPaths
);
console.log();
@@ -205,18 +205,6 @@ inquirer
console.log(` Adding ${cyan('Jest')} configuration`);
appPackage.jest = jestConfig;
// Add Babel config
console.log(` Adding ${cyan('Babel')} preset`);
appPackage.babel = {
presets: ['react-app'],
};
// Add ESlint config
console.log(` Adding ${cyan('ESLint')} configuration`);
appPackage.eslintConfig = {
extends: 'react-app',
};
fs.writeFileSync(
path.join(appPath, 'package.json'),
JSON.stringify(appPackage, null, 2) + os.EOL

View File

@@ -53,7 +53,7 @@ argv.push(
createJestConfig(
relativePath => path.resolve(__dirname, '..', relativePath),
path.resolve(paths.appSrc, '..'),
false
paths.srcPaths
)
)
);

View File

@@ -8,16 +8,19 @@
'use strict';
const fs = require('fs');
const path = require('path');
const chalk = require('chalk');
const paths = require('../../config/paths');
module.exports = (resolve, rootDir, isEjecting) => {
module.exports = (resolve, rootDir, srcRoots) => {
// Use this instead of `paths.testsSetup` to avoid putting
// an absolute filename into configuration after ejecting.
const setupTestsFile = fs.existsSync(paths.testsSetup)
? '<rootDir>/src/setupTests.js'
: undefined;
const toRelRootDir = f => '<rootDir>/' + path.relative(rootDir || '', f);
// TODO: I don't know if it's safe or not to just use / as path separator
// in Jest configs. We need help from somebody with Windows to determine this.
const config = {
@@ -25,15 +28,15 @@ module.exports = (resolve, rootDir, isEjecting) => {
setupFiles: [resolve('config/polyfills.js')],
setupTestFrameworkScriptFile: setupTestsFile,
testMatch: [
'<rootDir>/src/**/__tests__/**/*.{js,jsx,mjs}',
'<rootDir>/src/**/?(*.)(spec|test).{js,jsx,mjs}',
'**/__tests__/**/*.{js,jsx,mjs}',
'**/?(*.)(spec|test).{js,jsx,mjs}',
],
// where to search for files/tests
roots: srcRoots.map(toRelRootDir),
testEnvironment: 'node',
testURL: 'http://localhost',
transform: {
'^.+\\.(js|jsx|mjs)$': isEjecting
? '<rootDir>/node_modules/babel-jest'
: resolve('config/jest/babelTransform.js'),
'^.+\\.(js|jsx|mjs)$': resolve('config/jest/babelTransform.js'),
'^.+\\.css$': resolve('config/jest/cssTransform.js'),
'^(?!.*\\.(js|jsx|mjs|css|json)$)': resolve(
'config/jest/fileTransform.js'
@@ -60,6 +63,7 @@ module.exports = (resolve, rootDir, isEjecting) => {
if (rootDir) {
config.rootDir = rootDir;
}
const overrides = Object.assign({}, require(paths.appPackageJson).jest);
const supportedKeys = [
'collectCoverageFrom',

View File

@@ -73,6 +73,7 @@ You can find the most recent version of this guide [here](https://github.com/fac
- [Developing Components in Isolation](#developing-components-in-isolation)
- [Getting Started with Storybook](#getting-started-with-storybook)
- [Getting Started with Styleguidist](#getting-started-with-styleguidist)
- [Sharing Components in a Monorepo](#sharing-components-in-a-monorepo)
- [Publishing Components to npm](#publishing-components-to-npm)
- [Making a Progressive Web App](#making-a-progressive-web-app)
- [Opting Out of Caching](#opting-out-of-caching)
@@ -1819,6 +1820,66 @@ Learn more about React Styleguidist:
* [GitHub Repo](https://github.com/styleguidist/react-styleguidist)
* [Documentation](https://react-styleguidist.js.org/docs/getting-started.html)
## Sharing Components in a Monorepo
A typical monorepo folder structure looks like this:
```
monorepo/
app1/
app2/
comp1/
comp2/
```
The monorepo allows components to be separated from the app, providing:
* a level of encapsulation for components
* sharing of components
### How to Set Up a Monorepo
Below expands on the monorepo structure above, adding the package.json files required to configure the monorepo for [yarn workspaces](https://yarnpkg.com/en/docs/workspaces).
```
monorepo/
package.json:
"workspaces": ["*"],
"private": true
app1/
package.json:
"dependencies": ["@myorg/comp1": ">=0.0.0", "react": "^16.2.0"],
"devDependencies": ["react-scripts": "2.0.0"]
src/
app.js: import comp1 from '@myorg/comp1';
app2/
package.json:
"dependencies": ["@myorg/comp1": ">=0.0.0", "react": "^16.2.0"],
"devDependencies": ["react-scripts": "2.0.0"]
src/
app.js: import comp1 from '@myorg/comp1';
comp1/
package.json:
"name": "@myorg/comp1",
"version": "0.1.0"
index.js
comp2/
package.json:
"name": "@myorg/comp2",
"version": "0.1.0",
"dependencies": ["@myorg/comp1": ">=0.0.0"],
"devDependencies": ["react": "^16.2.0"]
index.js: import comp1 from '@myorg/comp1'
```
* Monorepo tools work on a package level, the same level as an npm package.
* The "workspaces" in the top-level package.json is an array of glob patterns specifying where shared packages are located in the monorepo.
* The scoping prefixes, e.g. @myorg/, are not required, but are recommended, allowing you to differentiate your packages from others of the same name. See [scoped packages ](https://docs.npmjs.com/misc/scope) for more info.
* Using a package in the monorepo is accomplished in the same manner as a published npm package, by specifying the shared package as dependency.
* In order to pick up the monorepo version of a package, the specified dependency version must semantically match the package version in the monorepo. See [semver](https://docs.npmjs.com/misc/semver) for info on semantic version matching.
### CRA Apps in a Monorepo
* CRA apps in a monorepo are just a standard CRA app, they use the same react-script scripts.
* However, when you use react-scripts for an app in a monorepo, all packages in the monorepo are treated as app sources -- they are watched, linted, transpiled, and tested in the same way as if they were part of the app itself.
* Without this functionality, each package would need its own build/test/etc functionality and it would be challenging to link all of these together.
### Lerna and Publishing
[Lerna](https://github.com/lerna/lerna) is a popular tool for managing monorepos. Lerna can be configured to use yarn workspaces, so it will work with the monorepo structure above. It's important to note that while lerna helps publish various packages in a monorepo, react-scripts does nothing to help publish a component to npm. A component which uses JSX or ES6+ features would need to be built by another tool before it can be published to npm. See [publishing components to npm](#publishing-components-to-npm) for more info.
## Publishing Components to npm
Create React App doesn't provide any built-in functionality to publish a component to npm. If you're ready to extract a component from your project so other people can use it, we recommend moving it to a separate directory outside of your project and then using a tool like [nwb](https://github.com/insin/nwb#react-components-and-libraries) to prepare it for publishing.