Skip to content

Commit 36eb585

Browse files
Support Lerna monorepos
1 parent 7690af5 commit 36eb585

File tree

4 files changed

+146
-6
lines changed

4 files changed

+146
-6
lines changed
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// @remove-on-eject-begin
2+
/**
3+
* Copyright (c) 2015-present, Facebook, Inc.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
// @remove-on-eject-end
9+
'use strict';
10+
11+
const path = require('path');
12+
const fs = require('fs');
13+
14+
const globbySync = require('globby').sync;
15+
const loadJsonFileSync = require('load-json-file').sync;
16+
const ValidationError = require('@lerna/validation-error');
17+
const Package = require('@lerna/package');
18+
const PackageGraph = require('@lerna/package-graph');
19+
const Project = require('@lerna/project');
20+
21+
function flattenResults(results) {
22+
return results.reduce((acc, result) => acc.concat(result), []);
23+
}
24+
25+
// Sync version of Lerna's makeFileFinder
26+
// Heavily inspired by https://github.com/lerna/lerna/blob/62843b04e3a5a03012ceabe465519b39a09fbcc1/core/project/lib/make-file-finder.js
27+
function makeFileFinderSync(rootPath, packageConfigs) {
28+
const globOpts = {
29+
cwd: rootPath,
30+
absolute: true,
31+
followSymlinkedDirectories: false,
32+
// POSIX results always need to be normalized
33+
transform: fp => path.normalize(fp),
34+
};
35+
36+
if (packageConfigs.some(cfg => cfg.indexOf('**') > -1)) {
37+
if (packageConfigs.some(cfg => cfg.indexOf('node_modules') > -1)) {
38+
throw new ValidationError(
39+
'EPKGCONFIG',
40+
'An explicit node_modules package path does not allow globstars (**)'
41+
);
42+
}
43+
44+
globOpts.ignore = [
45+
// allow globs like "packages/**",
46+
// but avoid picking up node_modules/**/package.json
47+
'**/node_modules/**',
48+
];
49+
}
50+
51+
return (fileName, fileMapper, customGlobOpts) => {
52+
const options = Object.assign({}, customGlobOpts, globOpts);
53+
const packages = packageConfigs.sort().map(globPath => {
54+
const results = globbySync(path.join(globPath, fileName), options).sort();
55+
56+
if (fileMapper) {
57+
return fileMapper(results);
58+
}
59+
60+
return results;
61+
});
62+
63+
// always flatten the results
64+
return flattenResults(packages);
65+
};
66+
}
67+
68+
function getPackagesSync(project) {
69+
const mapper = packageConfigPath => {
70+
const packageJson = loadJsonFileSync(packageConfigPath);
71+
return new Package(
72+
packageJson,
73+
path.dirname(packageConfigPath),
74+
project.rootPath
75+
);
76+
};
77+
78+
const finder = makeFileFinderSync(project.rootPath, project.packageConfigs);
79+
80+
return finder('package.json', filePaths => filePaths.map(mapper));
81+
}
82+
83+
module.exports.getAllLocalDependencies = function getAllLocalDependencies(
84+
appName
85+
) {
86+
const project = new Project(process.cwd());
87+
const packages = getPackagesSync(project);
88+
const packageGraph = new PackageGraph(
89+
packages,
90+
'allDependencies',
91+
'forceLocal'
92+
);
93+
const currentNode = packageGraph.get(appName);
94+
if (!currentNode) {
95+
return undefined;
96+
}
97+
98+
const dependencies = new Set(currentNode.localDependencies.keys());
99+
const dependenciesToExplore = new Set(dependencies);
100+
dependenciesToExplore.delete(appName);
101+
102+
while (dependenciesToExplore.size > 0) {
103+
const packageName = dependenciesToExplore.values().next().value;
104+
dependenciesToExplore.delete(packageName);
105+
const node = packageGraph.get(packageName);
106+
const newDependencies = new Set(node.localDependencies.keys());
107+
newDependencies.delete(appName);
108+
109+
for (const d of newDependencies.values()) {
110+
if (!dependencies.has(d)) {
111+
dependenciesToExplore.add(d);
112+
dependencies.add(d);
113+
}
114+
}
115+
}
116+
117+
const additionalSrcPaths = Array.from(dependencies).map(dependencyName => {
118+
const resolvedPath = fs.realpathSync(
119+
packageGraph.get(dependencyName).location
120+
);
121+
return path.resolve(resolvedPath, 'src');
122+
});
123+
return additionalSrcPaths;
124+
};

packages/react-scripts/config/paths.js

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,23 @@
1111
const path = require('path');
1212
const fs = require('fs');
1313
const url = require('url');
14+
const { getAllLocalDependencies } = require('./lerna');
1415

1516
// Make sure any symlinks in the project folder are resolved:
1617
// https://github.com/facebook/create-react-app/issues/637
1718
const appDirectory = fs.realpathSync(process.cwd());
1819
const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
1920

21+
const appName = require(resolveApp('package.json')).name;
22+
23+
const getAppSrc = appSrc => {
24+
const localDependencies = getAllLocalDependencies(appName);
25+
return {
26+
appSrc,
27+
fullAppSrcs: localDependencies ? localDependencies.concat(appSrc) : appSrc,
28+
};
29+
};
30+
2031
const envPublicUrl = process.env.PUBLIC_URL;
2132

2233
function ensureSlash(inputPath, needsSlash) {
@@ -82,7 +93,7 @@ module.exports = {
8293
appHtml: resolveApp('public/index.html'),
8394
appIndexJs: resolveModule(resolveApp, 'src/index'),
8495
appPackageJson: resolveApp('package.json'),
85-
appSrc: resolveApp('src'),
96+
...getAppSrc(resolveApp('src')),
8697
appTsConfig: resolveApp('tsconfig.json'),
8798
appJsConfig: resolveApp('jsconfig.json'),
8899
yarnLockFile: resolveApp('yarn.lock'),
@@ -105,7 +116,7 @@ module.exports = {
105116
appHtml: resolveApp('public/index.html'),
106117
appIndexJs: resolveModule(resolveApp, 'src/index'),
107118
appPackageJson: resolveApp('package.json'),
108-
appSrc: resolveApp('src'),
119+
...getAppSrc(resolveApp('src')),
109120
appTsConfig: resolveApp('tsconfig.json'),
110121
appJsConfig: resolveApp('jsconfig.json'),
111122
yarnLockFile: resolveApp('yarn.lock'),
@@ -140,7 +151,7 @@ if (
140151
appHtml: resolveOwn('template/public/index.html'),
141152
appIndexJs: resolveModule(resolveOwn, 'template/src/index'),
142153
appPackageJson: resolveOwn('package.json'),
143-
appSrc: resolveOwn('template/src'),
154+
...getAppSrc(resolveOwn('template/src')),
144155
appTsConfig: resolveOwn('template/tsconfig.json'),
145156
appJsConfig: resolveOwn('template/jsconfig.json'),
146157
yarnLockFile: resolveOwn('template/yarn.lock'),

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ module.exports = function(webpackEnv) {
327327
// To fix this, we prevent you from importing files out of src/ -- if you'd like to,
328328
// please link the files into your node_modules/ and let module-resolution kick in.
329329
// Make sure your source files are compiled, as they will not be processed in any way.
330-
new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
330+
new ModuleScopePlugin(paths.fullAppSrcs, [paths.appPackageJson]),
331331
],
332332
},
333333
resolveLoader: {
@@ -383,7 +383,7 @@ module.exports = function(webpackEnv) {
383383
loader: require.resolve('eslint-loader'),
384384
},
385385
],
386-
include: paths.appSrc,
386+
include: paths.fullAppSrcs,
387387
},
388388
{
389389
// "oneOf" will traverse all following loaders until one will
@@ -405,7 +405,7 @@ module.exports = function(webpackEnv) {
405405
// The preset includes JSX, Flow, TypeScript, and some ESnext features.
406406
{
407407
test: /\.(js|mjs|jsx|ts|tsx)$/,
408-
include: paths.appSrc,
408+
include: paths.fullAppSrcs,
409409
loader: require.resolve('babel-loader'),
410410
options: {
411411
customize: require.resolve(

packages/react-scripts/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@
2929
"types": "./lib/react-app.d.ts",
3030
"dependencies": {
3131
"@babel/core": "7.6.0",
32+
"@lerna/package": "^3.13.0",
33+
"@lerna/project": "^3.13.1",
34+
"@lerna/package-graph": "^3.13.0",
35+
"@lerna/validation-error": "^3.13.0",
3236
"@svgr/webpack": "4.3.2",
3337
"@typescript-eslint/eslint-plugin": "^2.2.0",
3438
"@typescript-eslint/parser": "^2.2.0",
@@ -52,6 +56,7 @@
5256
"eslint-plugin-react-hooks": "^1.6.1",
5357
"file-loader": "3.0.1",
5458
"fs-extra": "7.0.1",
59+
"globby": "^8.0.1",
5560
"html-webpack-plugin": "4.0.0-beta.5",
5661
"identity-obj-proxy": "3.0.0",
5762
"jest": "24.9.0",

0 commit comments

Comments
 (0)