From 36eb5853caf2716efbcf77cc5d31901558f5bdbf Mon Sep 17 00:00:00 2001 From: Fazouane Marouane Date: Tue, 20 Aug 2019 19:37:33 +0100 Subject: [PATCH 1/2] Support Lerna monorepos --- packages/react-scripts/config/lerna.js | 124 ++++++++++++++++++ packages/react-scripts/config/paths.js | 17 ++- .../react-scripts/config/webpack.config.js | 6 +- packages/react-scripts/package.json | 5 + 4 files changed, 146 insertions(+), 6 deletions(-) create mode 100644 packages/react-scripts/config/lerna.js diff --git a/packages/react-scripts/config/lerna.js b/packages/react-scripts/config/lerna.js new file mode 100644 index 00000000000..dc335647dee --- /dev/null +++ b/packages/react-scripts/config/lerna.js @@ -0,0 +1,124 @@ +// @remove-on-eject-begin +/** + * Copyright (c) 2015-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 path = require('path'); +const fs = require('fs'); + +const globbySync = require('globby').sync; +const loadJsonFileSync = require('load-json-file').sync; +const ValidationError = require('@lerna/validation-error'); +const Package = require('@lerna/package'); +const PackageGraph = require('@lerna/package-graph'); +const Project = require('@lerna/project'); + +function flattenResults(results) { + return results.reduce((acc, result) => acc.concat(result), []); +} + +// Sync version of Lerna's makeFileFinder +// Heavily inspired by https://github.com/lerna/lerna/blob/62843b04e3a5a03012ceabe465519b39a09fbcc1/core/project/lib/make-file-finder.js +function makeFileFinderSync(rootPath, packageConfigs) { + const globOpts = { + cwd: rootPath, + absolute: true, + followSymlinkedDirectories: false, + // POSIX results always need to be normalized + transform: fp => path.normalize(fp), + }; + + if (packageConfigs.some(cfg => cfg.indexOf('**') > -1)) { + if (packageConfigs.some(cfg => cfg.indexOf('node_modules') > -1)) { + throw new ValidationError( + 'EPKGCONFIG', + 'An explicit node_modules package path does not allow globstars (**)' + ); + } + + globOpts.ignore = [ + // allow globs like "packages/**", + // but avoid picking up node_modules/**/package.json + '**/node_modules/**', + ]; + } + + return (fileName, fileMapper, customGlobOpts) => { + const options = Object.assign({}, customGlobOpts, globOpts); + const packages = packageConfigs.sort().map(globPath => { + const results = globbySync(path.join(globPath, fileName), options).sort(); + + if (fileMapper) { + return fileMapper(results); + } + + return results; + }); + + // always flatten the results + return flattenResults(packages); + }; +} + +function getPackagesSync(project) { + const mapper = packageConfigPath => { + const packageJson = loadJsonFileSync(packageConfigPath); + return new Package( + packageJson, + path.dirname(packageConfigPath), + project.rootPath + ); + }; + + const finder = makeFileFinderSync(project.rootPath, project.packageConfigs); + + return finder('package.json', filePaths => filePaths.map(mapper)); +} + +module.exports.getAllLocalDependencies = function getAllLocalDependencies( + appName +) { + const project = new Project(process.cwd()); + const packages = getPackagesSync(project); + const packageGraph = new PackageGraph( + packages, + 'allDependencies', + 'forceLocal' + ); + const currentNode = packageGraph.get(appName); + if (!currentNode) { + return undefined; + } + + const dependencies = new Set(currentNode.localDependencies.keys()); + const dependenciesToExplore = new Set(dependencies); + dependenciesToExplore.delete(appName); + + while (dependenciesToExplore.size > 0) { + const packageName = dependenciesToExplore.values().next().value; + dependenciesToExplore.delete(packageName); + const node = packageGraph.get(packageName); + const newDependencies = new Set(node.localDependencies.keys()); + newDependencies.delete(appName); + + for (const d of newDependencies.values()) { + if (!dependencies.has(d)) { + dependenciesToExplore.add(d); + dependencies.add(d); + } + } + } + + const additionalSrcPaths = Array.from(dependencies).map(dependencyName => { + const resolvedPath = fs.realpathSync( + packageGraph.get(dependencyName).location + ); + return path.resolve(resolvedPath, 'src'); + }); + return additionalSrcPaths; +}; diff --git a/packages/react-scripts/config/paths.js b/packages/react-scripts/config/paths.js index e5a3e0b5374..c4f3a6d63cf 100644 --- a/packages/react-scripts/config/paths.js +++ b/packages/react-scripts/config/paths.js @@ -11,12 +11,23 @@ const path = require('path'); const fs = require('fs'); const url = require('url'); +const { getAllLocalDependencies } = require('./lerna'); // Make sure any symlinks in the project folder are resolved: // https://github.com/facebook/create-react-app/issues/637 const appDirectory = fs.realpathSync(process.cwd()); const resolveApp = relativePath => path.resolve(appDirectory, relativePath); +const appName = require(resolveApp('package.json')).name; + +const getAppSrc = appSrc => { + const localDependencies = getAllLocalDependencies(appName); + return { + appSrc, + fullAppSrcs: localDependencies ? localDependencies.concat(appSrc) : appSrc, + }; +}; + const envPublicUrl = process.env.PUBLIC_URL; function ensureSlash(inputPath, needsSlash) { @@ -82,7 +93,7 @@ module.exports = { appHtml: resolveApp('public/index.html'), appIndexJs: resolveModule(resolveApp, 'src/index'), appPackageJson: resolveApp('package.json'), - appSrc: resolveApp('src'), + ...getAppSrc(resolveApp('src')), appTsConfig: resolveApp('tsconfig.json'), appJsConfig: resolveApp('jsconfig.json'), yarnLockFile: resolveApp('yarn.lock'), @@ -105,7 +116,7 @@ module.exports = { appHtml: resolveApp('public/index.html'), appIndexJs: resolveModule(resolveApp, 'src/index'), appPackageJson: resolveApp('package.json'), - appSrc: resolveApp('src'), + ...getAppSrc(resolveApp('src')), appTsConfig: resolveApp('tsconfig.json'), appJsConfig: resolveApp('jsconfig.json'), yarnLockFile: resolveApp('yarn.lock'), @@ -140,7 +151,7 @@ if ( appHtml: resolveOwn('template/public/index.html'), appIndexJs: resolveModule(resolveOwn, 'template/src/index'), appPackageJson: resolveOwn('package.json'), - appSrc: resolveOwn('template/src'), + ...getAppSrc(resolveOwn('template/src')), appTsConfig: resolveOwn('template/tsconfig.json'), appJsConfig: resolveOwn('template/jsconfig.json'), yarnLockFile: resolveOwn('template/yarn.lock'), diff --git a/packages/react-scripts/config/webpack.config.js b/packages/react-scripts/config/webpack.config.js index 074bb0d6d5f..268e61625ba 100644 --- a/packages/react-scripts/config/webpack.config.js +++ b/packages/react-scripts/config/webpack.config.js @@ -327,7 +327,7 @@ module.exports = function(webpackEnv) { // To fix this, we prevent you from importing files out of src/ -- if you'd like to, // please link the files into your node_modules/ and let module-resolution kick in. // Make sure your source files are compiled, as they will not be processed in any way. - new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]), + new ModuleScopePlugin(paths.fullAppSrcs, [paths.appPackageJson]), ], }, resolveLoader: { @@ -383,7 +383,7 @@ module.exports = function(webpackEnv) { loader: require.resolve('eslint-loader'), }, ], - include: paths.appSrc, + include: paths.fullAppSrcs, }, { // "oneOf" will traverse all following loaders until one will @@ -405,7 +405,7 @@ module.exports = function(webpackEnv) { // The preset includes JSX, Flow, TypeScript, and some ESnext features. { test: /\.(js|mjs|jsx|ts|tsx)$/, - include: paths.appSrc, + include: paths.fullAppSrcs, loader: require.resolve('babel-loader'), options: { customize: require.resolve( diff --git a/packages/react-scripts/package.json b/packages/react-scripts/package.json index 9d92e63c390..52f89e72ca1 100644 --- a/packages/react-scripts/package.json +++ b/packages/react-scripts/package.json @@ -29,6 +29,10 @@ "types": "./lib/react-app.d.ts", "dependencies": { "@babel/core": "7.6.0", + "@lerna/package": "^3.13.0", + "@lerna/project": "^3.13.1", + "@lerna/package-graph": "^3.13.0", + "@lerna/validation-error": "^3.13.0", "@svgr/webpack": "4.3.2", "@typescript-eslint/eslint-plugin": "^2.2.0", "@typescript-eslint/parser": "^2.2.0", @@ -52,6 +56,7 @@ "eslint-plugin-react-hooks": "^1.6.1", "file-loader": "3.0.1", "fs-extra": "7.0.1", + "globby": "^8.0.1", "html-webpack-plugin": "4.0.0-beta.5", "identity-obj-proxy": "3.0.0", "jest": "24.9.0", From 45a01240cfd91fd2082af7d0d2d8e8beb3610dbd Mon Sep 17 00:00:00 2001 From: Fazouane Marouane Date: Thu, 19 Sep 2019 00:08:30 +0200 Subject: [PATCH 2/2] update dependencies --- packages/react-scripts/package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/react-scripts/package.json b/packages/react-scripts/package.json index 52f89e72ca1..8afc16ddfea 100644 --- a/packages/react-scripts/package.json +++ b/packages/react-scripts/package.json @@ -29,9 +29,9 @@ "types": "./lib/react-app.d.ts", "dependencies": { "@babel/core": "7.6.0", - "@lerna/package": "^3.13.0", - "@lerna/project": "^3.13.1", - "@lerna/package-graph": "^3.13.0", + "@lerna/package": "^3.16.0", + "@lerna/project": "^3.16.0", + "@lerna/package-graph": "^3.16.0", "@lerna/validation-error": "^3.13.0", "@svgr/webpack": "4.3.2", "@typescript-eslint/eslint-plugin": "^2.2.0", @@ -56,7 +56,7 @@ "eslint-plugin-react-hooks": "^1.6.1", "file-loader": "3.0.1", "fs-extra": "7.0.1", - "globby": "^8.0.1", + "globby": "^9.2.0", "html-webpack-plugin": "4.0.0-beta.5", "identity-obj-proxy": "3.0.0", "jest": "24.9.0",