From b0750fdccb8eb22cf7b098768818a7e9b345f461 Mon Sep 17 00:00:00 2001 From: bradfordlemley Date: Thu, 1 Feb 2018 13:58:18 -0700 Subject: [PATCH] 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. --- .travis.yml | 2 + appveyor.yml | 2 + packages/react-error-overlay/package.json | 13 +- .../config/jest/babelTransform.js | 4 +- packages/react-scripts/config/paths.js | 59 ++++++-- .../config/webpack.config.dev.js | 39 +++-- .../config/webpack.config.prod.js | 43 ++++-- .../fixtures/kitchensink/.babelrc | 3 + .../monorepos/packages/comp1/index.js | 5 + .../monorepos/packages/comp1/index.test.js | 8 ++ .../monorepos/packages/comp1/package.json | 10 ++ .../monorepos/packages/comp2/index.js | 11 ++ .../monorepos/packages/comp2/index.test.js | 8 ++ .../monorepos/packages/comp2/package.json | 13 ++ .../monorepos/packages/cra-app1/.gitignore | 21 +++ .../monorepos/packages/cra-app1/package.json | 32 +++++ .../packages/cra-app1/public/favicon.ico | Bin 0 -> 3870 bytes .../packages/cra-app1/public/index.html | 40 ++++++ .../packages/cra-app1/public/manifest.json | 15 ++ .../monorepos/packages/cra-app1/src/App.css | 32 +++++ .../monorepos/packages/cra-app1/src/App.js | 24 ++++ .../packages/cra-app1/src/App.test.js | 9 ++ .../monorepos/packages/cra-app1/src/index.css | 5 + .../monorepos/packages/cra-app1/src/index.js | 6 + .../monorepos/packages/cra-app1/src/logo.svg | 7 + .../fixtures/monorepos/yarn-ws/package.json | 4 + packages/react-scripts/package.json | 9 +- packages/react-scripts/scripts/eject.js | 14 +- packages/react-scripts/scripts/test.js | 2 +- .../scripts/utils/createJestConfig.js | 15 +- packages/react-scripts/template/README.md | 91 ++++++++++-- tasks/e2e-monorepos.sh | 135 ++++++++++++++++++ tasks/local-test.sh | 5 +- 33 files changed, 609 insertions(+), 77 deletions(-) create mode 100644 packages/react-scripts/fixtures/kitchensink/.babelrc create mode 100644 packages/react-scripts/fixtures/monorepos/packages/comp1/index.js create mode 100644 packages/react-scripts/fixtures/monorepos/packages/comp1/index.test.js create mode 100644 packages/react-scripts/fixtures/monorepos/packages/comp1/package.json create mode 100644 packages/react-scripts/fixtures/monorepos/packages/comp2/index.js create mode 100644 packages/react-scripts/fixtures/monorepos/packages/comp2/index.test.js create mode 100644 packages/react-scripts/fixtures/monorepos/packages/comp2/package.json create mode 100644 packages/react-scripts/fixtures/monorepos/packages/cra-app1/.gitignore create mode 100644 packages/react-scripts/fixtures/monorepos/packages/cra-app1/package.json create mode 100644 packages/react-scripts/fixtures/monorepos/packages/cra-app1/public/favicon.ico create mode 100644 packages/react-scripts/fixtures/monorepos/packages/cra-app1/public/index.html create mode 100644 packages/react-scripts/fixtures/monorepos/packages/cra-app1/public/manifest.json create mode 100644 packages/react-scripts/fixtures/monorepos/packages/cra-app1/src/App.css create mode 100644 packages/react-scripts/fixtures/monorepos/packages/cra-app1/src/App.js create mode 100644 packages/react-scripts/fixtures/monorepos/packages/cra-app1/src/App.test.js create mode 100644 packages/react-scripts/fixtures/monorepos/packages/cra-app1/src/index.css create mode 100644 packages/react-scripts/fixtures/monorepos/packages/cra-app1/src/index.js create mode 100644 packages/react-scripts/fixtures/monorepos/packages/cra-app1/src/logo.svg create mode 100644 packages/react-scripts/fixtures/monorepos/yarn-ws/package.json create mode 100755 tasks/e2e-monorepos.sh diff --git a/.travis.yml b/.travis.yml index 846a8143750..4fa8a29a9cc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,7 @@ script: - 'if [ $TEST_SUITE = "kitchensink-eject" ]; then tasks/e2e-kitchensink-eject.sh; fi' - 'if [ $TEST_SUITE = "old-node" ]; then tasks/e2e-old-node.sh; fi' - 'if [ $TEST_SUITE = "behavior" ]; then tasks/e2e-behavior.sh; fi' + - 'if [ $TEST_SUITE = "monorepos" ]; then tasks/e2e-monorepos.sh; fi' env: matrix: - TEST_SUITE=simple @@ -26,6 +27,7 @@ env: - TEST_SUITE=kitchensink - TEST_SUITE=kitchensink-eject - TEST_SUITE=behavior + - TEST_SUITE=monorepos matrix: include: - os: osx diff --git a/appveyor.yml b/appveyor.yml index 5f5143167d6..63b2514fc6d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -20,6 +20,8 @@ environment: test_suite: 'kitchensink' - nodejs_version: 8 test_suite: 'kitchensink-eject' + - nodejs_version: 8 + test_suite: "monorepos" cache: - '%APPDATA%\npm-cache -> appveyor.cleanup-cache.txt' - '%LOCALAPPDATA%\Yarn\Cache -> appveyor.cleanup-cache.txt' diff --git a/packages/react-error-overlay/package.json b/packages/react-error-overlay/package.json index 8983b91ec2a..62376a99049 100644 --- a/packages/react-error-overlay/package.json +++ b/packages/react-error-overlay/package.json @@ -30,14 +30,13 @@ "lib/index.js" ], "devDependencies": { - "@babel/code-frame": "7.0.0-beta.46", - "@babel/core": "7.0.0-beta.46", - "@babel/runtime": "7.0.0-beta.46", - "anser": "1.4.6", + "@babel/code-frame": "7.0.0", + "@babel/core": "7.1.0", + "anser": "1.4.7", "babel-core": "^7.0.0-bridge.0", - "babel-eslint": "^8.2.2", - "babel-jest": "^22.4.3", - "babel-loader": "^8.0.0-beta.0", + "babel-eslint": "9.0.0", + "babel-jest": "23.6.0", + "babel-loader": "8.0.4", "@bradfordlemley/babel-preset-react-app": "^5.0.3", "chalk": "^2.3.2", "chokidar": "^2.0.2", diff --git a/packages/react-scripts/config/jest/babelTransform.js b/packages/react-scripts/config/jest/babelTransform.js index f8d8c4020e3..44df15b15bc 100644 --- a/packages/react-scripts/config/jest/babelTransform.js +++ b/packages/react-scripts/config/jest/babelTransform.js @@ -1,10 +1,11 @@ -// @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'); @@ -14,4 +15,5 @@ module.exports = babelJest.createTransformer({ // @remove-on-eject-begin babelrc: false, configFile: false, + // @remove-on-eject-end }); diff --git a/packages/react-scripts/config/paths.js b/packages/react-scripts/config/paths.js index f7929c33279..23205d2ac83 100644 --- a/packages/react-scripts/config/paths.js +++ b/packages/react-scripts/config/paths.js @@ -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 @@ -99,6 +101,8 @@ module.exports = { servedPath: getServedPath(resolveApp('package.json')), }; +let checkForMonorepo = true; + // @remove-on-eject-begin const resolveOwn = relativePath => path.resolve(__dirname, '..', relativePath); @@ -124,17 +128,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('.'), @@ -157,3 +157,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()); +} diff --git a/packages/react-scripts/config/webpack.config.dev.js b/packages/react-scripts/config/webpack.config.dev.js index c23fab63cb2..6a681b2f27e 100644 --- a/packages/react-scripts/config/webpack.config.dev.js +++ b/packages/react-scripts/config/webpack.config.dev.js @@ -20,9 +20,9 @@ const getCSSModuleLocalIdent = require('@bradfordlemley/react-dev-utils/getCSSMo const getClientEnvironment = require('./env'); const paths = require('./paths'); const ManifestPlugin = require('webpack-manifest-plugin'); -const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin'); +const ModuleNotFoundPlugin = require('@bradfordlemley/react-dev-utils/ModuleNotFoundPlugin'); // @remove-on-eject-begin -const getCacheIdentifier = require('react-dev-utils/getCacheIdentifier'); +const getCacheIdentifier = require('@bradfordlemley/react-dev-utils/getCacheIdentifier'); // @remove-on-eject-end // Webpack uses `publicPath` to determine where the app is being served from. @@ -146,7 +146,14 @@ module.exports = { // https://github.com/facebook/create-react-app/issues/290 // `web` extension prefixes have been added for better support // for React Native Web. - extensions: paths.jsExts.concat(['.mjs', '.web.js', '.js', '.json', '.web.jsx', '.jsx']), + extensions: paths.jsExts.concat([ + '.mjs', + '.web.js', + '.js', + '.json', + '.web.jsx', + '.jsx', + ]), alias: { // Support React Native Web // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/ @@ -186,11 +193,15 @@ module.exports = { use: [ { options: { - formatter: require.resolve('react-dev-utils/eslintFormatter'), + formatter: require.resolve( + '@bradfordlemley/react-dev-utils/eslintFormatter' + ), eslintPath: require.resolve('eslint'), // @remove-on-eject-begin baseConfig: { - extends: [require.resolve('@bradfordlemley/eslint-config-react-app')], + extends: [ + require.resolve('@bradfordlemley/eslint-config-react-app'), + ], settings: { react: { version: '999.999.999' } }, }, ignore: false, @@ -200,7 +211,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 @@ -222,7 +234,8 @@ module.exports = { // The preset includes JSX, Flow, and some ESnext features. { test: /\.(js|mjs|jsx)$/, - include: paths.appSrc, + include: paths.srcPaths, + exclude: [/[/\\\\]node_modules[/\\\\]/], loader: require.resolve('babel-loader'), options: { customize: require.resolve( @@ -231,7 +244,9 @@ module.exports = { // @remove-on-eject-begin babelrc: false, configFile: false, - presets: [require.resolve('@bradfordlemley/babel-preset-react-app')], + presets: [ + require.resolve('@bradfordlemley/babel-preset-react-app'), + ], // Make sure we have a unique cache identifier, erring on the // side of caution. // We remove this when the user ejects because the default @@ -246,7 +261,9 @@ module.exports = { // @remove-on-eject-end plugins: [ [ - require.resolve('babel-plugin-named-asset-import'), + require.resolve( + '@bradfordlemley/babel-plugin-named-asset-import' + ), { loaderMap: { svg: { @@ -276,7 +293,9 @@ module.exports = { compact: false, presets: [ [ - require.resolve('@bradfordlemley/babel-preset-react-app/dependencies'), + require.resolve( + '@bradfordlemley/babel-preset-react-app/dependencies' + ), { helpers: true }, ], ], diff --git a/packages/react-scripts/config/webpack.config.prod.js b/packages/react-scripts/config/webpack.config.prod.js index b2a301038ca..e4407a10633 100644 --- a/packages/react-scripts/config/webpack.config.prod.js +++ b/packages/react-scripts/config/webpack.config.prod.js @@ -24,9 +24,9 @@ const ModuleScopePlugin = require('@bradfordlemley/react-dev-utils/ModuleScopePl const getCSSModuleLocalIdent = require('@bradfordlemley/react-dev-utils/getCSSModuleLocalIdent'); const paths = require('./paths'); const getClientEnvironment = require('./env'); -const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin'); +const ModuleNotFoundPlugin = require('@bradfordlemley/react-dev-utils/ModuleNotFoundPlugin'); // @remove-on-eject-begin -const getCacheIdentifier = require('react-dev-utils/getCacheIdentifier'); +const getCacheIdentifier = require('@bradfordlemley/react-dev-utils/getCacheIdentifier'); // @remove-on-eject-end // Webpack uses `publicPath` to determine where the app is being served from. @@ -221,7 +221,14 @@ module.exports = { // https://github.com/facebook/create-react-app/issues/290 // `web` extension prefixes have been added for better support // for React Native Web. - extensions: paths.jsExts.concat(['.mjs', '.web.js', '.js', '.json', '.web.jsx', '.jsx']), + extensions: paths.jsExts.concat([ + '.mjs', + '.web.js', + '.js', + '.json', + '.web.jsx', + '.jsx', + ]), alias: { // Support React Native Web // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/ @@ -261,13 +268,17 @@ module.exports = { use: [ { options: { - formatter: require.resolve('@bradfordlemley/react-dev-utils/eslintFormatter'), + formatter: require.resolve( + '@bradfordlemley/react-dev-utils/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('@bradfordlemley/eslint-config-react-app')], + extends: [ + require.resolve('@bradfordlemley/eslint-config-react-app'), + ], settings: { react: { version: '999.999.999' } }, }, ignore: false, @@ -277,7 +288,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 @@ -298,8 +310,8 @@ module.exports = { // The preset includes JSX, Flow, and some ESnext features. { test: /\.(js|mjs|jsx)$/, - include: paths.appSrc, - + include: paths.srcPaths, + exclude: [/[/\\\\]node_modules[/\\\\]/], loader: require.resolve('babel-loader'), options: { customize: require.resolve( @@ -308,7 +320,9 @@ module.exports = { // @remove-on-eject-begin babelrc: false, configFile: false, - presets: [require.resolve('@bradfordlemley/babel-preset-react-app')], + presets: [ + require.resolve('@bradfordlemley/babel-preset-react-app'), + ], // Make sure we have a unique cache identifier, erring on the // side of caution. // We remove this when the user ejects because the default @@ -323,7 +337,9 @@ module.exports = { // @remove-on-eject-end plugins: [ [ - require.resolve('@bradfordlemley/babel-plugin-named-asset-import'), + require.resolve( + '@bradfordlemley/babel-plugin-named-asset-import' + ), { loaderMap: { svg: { @@ -351,7 +367,9 @@ module.exports = { compact: false, presets: [ [ - require.resolve('@bradfordlemley/babel-preset-react-app/dependencies'), + require.resolve( + '@bradfordlemley/babel-preset-react-app/dependencies' + ), { helpers: true }, ], ], @@ -479,7 +497,8 @@ module.exports = { }), // Inlines the webpack runtime script. This script is too small to warrant // a network request. - shouldInlineRuntimeChunk && new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime~.+[.]js/]), + shouldInlineRuntimeChunk && + new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime~.+[.]js/]), // Makes some environment variables available in index.html. // The public URL is available as %PUBLIC_URL% in index.html, e.g.: // diff --git a/packages/react-scripts/fixtures/kitchensink/.babelrc b/packages/react-scripts/fixtures/kitchensink/.babelrc new file mode 100644 index 00000000000..c14b2828d16 --- /dev/null +++ b/packages/react-scripts/fixtures/kitchensink/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["react-app"] +} diff --git a/packages/react-scripts/fixtures/monorepos/packages/comp1/index.js b/packages/react-scripts/fixtures/monorepos/packages/comp1/index.js new file mode 100644 index 00000000000..f3cb7964952 --- /dev/null +++ b/packages/react-scripts/fixtures/monorepos/packages/comp1/index.js @@ -0,0 +1,5 @@ +import React from 'react'; + +const Comp1 = () =>
Comp1
; + +export default Comp1; diff --git a/packages/react-scripts/fixtures/monorepos/packages/comp1/index.test.js b/packages/react-scripts/fixtures/monorepos/packages/comp1/index.test.js new file mode 100644 index 00000000000..25160484031 --- /dev/null +++ b/packages/react-scripts/fixtures/monorepos/packages/comp1/index.test.js @@ -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(, div); +}); diff --git a/packages/react-scripts/fixtures/monorepos/packages/comp1/package.json b/packages/react-scripts/fixtures/monorepos/packages/comp1/package.json new file mode 100644 index 00000000000..baa4c31dc71 --- /dev/null +++ b/packages/react-scripts/fixtures/monorepos/packages/comp1/package.json @@ -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" + } +} diff --git a/packages/react-scripts/fixtures/monorepos/packages/comp2/index.js b/packages/react-scripts/fixtures/monorepos/packages/comp2/index.js new file mode 100644 index 00000000000..6dcc69dacf1 --- /dev/null +++ b/packages/react-scripts/fixtures/monorepos/packages/comp2/index.js @@ -0,0 +1,11 @@ +import React from 'react'; + +import Comp1 from 'comp1'; + +const Comp2 = () => ( +
+ Comp2, nested Comp1: +
+); + +export default Comp2; diff --git a/packages/react-scripts/fixtures/monorepos/packages/comp2/index.test.js b/packages/react-scripts/fixtures/monorepos/packages/comp2/index.test.js new file mode 100644 index 00000000000..6d022e87d54 --- /dev/null +++ b/packages/react-scripts/fixtures/monorepos/packages/comp2/index.test.js @@ -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(, div); +}); diff --git a/packages/react-scripts/fixtures/monorepos/packages/comp2/package.json b/packages/react-scripts/fixtures/monorepos/packages/comp2/package.json new file mode 100644 index 00000000000..329b14077ba --- /dev/null +++ b/packages/react-scripts/fixtures/monorepos/packages/comp2/package.json @@ -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" +} diff --git a/packages/react-scripts/fixtures/monorepos/packages/cra-app1/.gitignore b/packages/react-scripts/fixtures/monorepos/packages/cra-app1/.gitignore new file mode 100644 index 00000000000..d30f40ef442 --- /dev/null +++ b/packages/react-scripts/fixtures/monorepos/packages/cra-app1/.gitignore @@ -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* diff --git a/packages/react-scripts/fixtures/monorepos/packages/cra-app1/package.json b/packages/react-scripts/fixtures/monorepos/packages/cra-app1/package.json new file mode 100644 index 00000000000..218edb79d6b --- /dev/null +++ b/packages/react-scripts/fixtures/monorepos/packages/cra-app1/package.json @@ -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" + ] + } +} diff --git a/packages/react-scripts/fixtures/monorepos/packages/cra-app1/public/favicon.ico b/packages/react-scripts/fixtures/monorepos/packages/cra-app1/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a11777cc471a4344702741ab1c8a588998b1311a GIT binary patch literal 3870 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 baj7?x%VD!5Cxk2YbYLNVeiXvvpMCWYo=by@ literal 0 HcmV?d00001 diff --git a/packages/react-scripts/fixtures/monorepos/packages/cra-app1/public/index.html b/packages/react-scripts/fixtures/monorepos/packages/cra-app1/public/index.html new file mode 100644 index 00000000000..ed0ebafa1b7 --- /dev/null +++ b/packages/react-scripts/fixtures/monorepos/packages/cra-app1/public/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + + React App + + + +
+ + + diff --git a/packages/react-scripts/fixtures/monorepos/packages/cra-app1/public/manifest.json b/packages/react-scripts/fixtures/monorepos/packages/cra-app1/public/manifest.json new file mode 100644 index 00000000000..ef19ec243e7 --- /dev/null +++ b/packages/react-scripts/fixtures/monorepos/packages/cra-app1/public/manifest.json @@ -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" +} diff --git a/packages/react-scripts/fixtures/monorepos/packages/cra-app1/src/App.css b/packages/react-scripts/fixtures/monorepos/packages/cra-app1/src/App.css new file mode 100644 index 00000000000..31be39dcc49 --- /dev/null +++ b/packages/react-scripts/fixtures/monorepos/packages/cra-app1/src/App.css @@ -0,0 +1,32 @@ +.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); + } +} diff --git a/packages/react-scripts/fixtures/monorepos/packages/cra-app1/src/App.js b/packages/react-scripts/fixtures/monorepos/packages/cra-app1/src/App.js new file mode 100644 index 00000000000..ab9f3f95793 --- /dev/null +++ b/packages/react-scripts/fixtures/monorepos/packages/cra-app1/src/App.js @@ -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 ( +
+
+ logo +

YarnWS-CraApp1

+
+

+ To get started, edit src/App.js and save to reload. +

+ +
+ ); + } +} + +export default App; diff --git a/packages/react-scripts/fixtures/monorepos/packages/cra-app1/src/App.test.js b/packages/react-scripts/fixtures/monorepos/packages/cra-app1/src/App.test.js new file mode 100644 index 00000000000..a754b201bf9 --- /dev/null +++ b/packages/react-scripts/fixtures/monorepos/packages/cra-app1/src/App.test.js @@ -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(, div); + ReactDOM.unmountComponentAtNode(div); +}); diff --git a/packages/react-scripts/fixtures/monorepos/packages/cra-app1/src/index.css b/packages/react-scripts/fixtures/monorepos/packages/cra-app1/src/index.css new file mode 100644 index 00000000000..b4cc7250b98 --- /dev/null +++ b/packages/react-scripts/fixtures/monorepos/packages/cra-app1/src/index.css @@ -0,0 +1,5 @@ +body { + margin: 0; + padding: 0; + font-family: sans-serif; +} diff --git a/packages/react-scripts/fixtures/monorepos/packages/cra-app1/src/index.js b/packages/react-scripts/fixtures/monorepos/packages/cra-app1/src/index.js new file mode 100644 index 00000000000..395b74997b2 --- /dev/null +++ b/packages/react-scripts/fixtures/monorepos/packages/cra-app1/src/index.js @@ -0,0 +1,6 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import './index.css'; +import App from './App'; + +ReactDOM.render(, document.getElementById('root')); diff --git a/packages/react-scripts/fixtures/monorepos/packages/cra-app1/src/logo.svg b/packages/react-scripts/fixtures/monorepos/packages/cra-app1/src/logo.svg new file mode 100644 index 00000000000..6b60c1042f5 --- /dev/null +++ b/packages/react-scripts/fixtures/monorepos/packages/cra-app1/src/logo.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/react-scripts/fixtures/monorepos/yarn-ws/package.json b/packages/react-scripts/fixtures/monorepos/yarn-ws/package.json new file mode 100644 index 00000000000..aad9ea832aa --- /dev/null +++ b/packages/react-scripts/fixtures/monorepos/yarn-ws/package.json @@ -0,0 +1,4 @@ +{ + "private": true, + "workspaces": ["packages/*"] +} diff --git a/packages/react-scripts/package.json b/packages/react-scripts/package.json index ee8f27fcba7..5b9ad00f34a 100644 --- a/packages/react-scripts/package.json +++ b/packages/react-scripts/package.json @@ -22,13 +22,15 @@ }, "dependencies": { "@babel/core": "7.1.0", + "@bradfordlemley/babel-plugin-named-asset-import": "^0.2.2", + "@bradfordlemley/babel-preset-react-app": "^5.0.3", + "@bradfordlemley/eslint-config-react-app": "^3.0.3", + "@bradfordlemley/react-dev-utils": "^6.0.4", "@svgr/webpack": "2.4.1", "babel-core": "7.0.0-bridge.0", "babel-eslint": "9.0.0", "babel-jest": "23.6.0", "babel-loader": "8.0.4", - "@bradfordlemley/babel-plugin-named-asset-import": "^0.2.2", - "@bradfordlemley/babel-preset-react-app": "^5.0.3", "bfj": "6.1.1", "case-sensitive-paths-webpack-plugin": "2.1.2", "chalk": "2.4.1", @@ -36,13 +38,13 @@ "dotenv": "6.0.0", "dotenv-expand": "4.2.0", "eslint": "5.6.0", - "@bradfordlemley/eslint-config-react-app": "^3.0.3", "eslint-loader": "2.1.1", "eslint-plugin-flowtype": "2.50.1", "eslint-plugin-import": "2.14.0", "eslint-plugin-jsx-a11y": "6.1.2", "eslint-plugin-react": "7.11.1", "file-loader": "2.0.0", + "find-pkg": "2.0.0", "fs-extra": "7.0.0", "html-webpack-plugin": "4.0.0-alpha.2", "identity-obj-proxy": "3.0.0", @@ -57,7 +59,6 @@ "postcss-preset-env": "6.0.6", "postcss-safe-parser": "4.0.1", "react-app-polyfill": "^0.1.3", - "@bradfordlemley/react-dev-utils": "^6.0.4", "resolve": "1.8.1", "sass-loader": "7.1.0", "style-loader": "0.23.0", diff --git a/packages/react-scripts/scripts/eject.js b/packages/react-scripts/scripts/eject.js index d383b4d17dc..df3011346df 100644 --- a/packages/react-scripts/scripts/eject.js +++ b/packages/react-scripts/scripts/eject.js @@ -109,7 +109,7 @@ inquirer const jestConfig = createJestConfig( filePath => path.posix.join('', 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 diff --git a/packages/react-scripts/scripts/test.js b/packages/react-scripts/scripts/test.js index 731924f4a3d..e8440c83993 100644 --- a/packages/react-scripts/scripts/test.js +++ b/packages/react-scripts/scripts/test.js @@ -74,7 +74,7 @@ argv.push( createJestConfig( relativePath => path.resolve(__dirname, '..', relativePath), path.resolve(paths.appSrc, '..'), - false + paths.srcPaths ) ) ); diff --git a/packages/react-scripts/scripts/utils/createJestConfig.js b/packages/react-scripts/scripts/utils/createJestConfig.js index 6a6b84958dd..c0b64b5527c 100644 --- a/packages/react-scripts/scripts/utils/createJestConfig.js +++ b/packages/react-scripts/scripts/utils/createJestConfig.js @@ -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) ? '/src/setupTests.js' : undefined; + const toRelRootDir = f => '/' + 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 = { @@ -38,15 +41,15 @@ module.exports = (resolve, rootDir, isEjecting) => { setupTestFrameworkScriptFile: setupTestsFile, testMatch: [ - '/src/**/__tests__/**/*.{js,jsx}', - '/src/**/?(*.)(spec|test).{js,jsx}', + '/src/**/__tests__/**/*.{js,jsx,mjs}', + '/src/**/?(*.)(spec|test).{js,jsx,mjs}', ], + // where to search for files/tests + roots: srcRoots.map(toRelRootDir), testEnvironment: 'jsdom', testURL: 'http://localhost', transform: { - '^.+\\.(js|jsx)$': isEjecting - ? '/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|css|json)$)': resolve('config/jest/fileTransform.js'), }, diff --git a/packages/react-scripts/template/README.md b/packages/react-scripts/template/README.md index c591bbf0d00..830dc25b6e4 100644 --- a/packages/react-scripts/template/README.md +++ b/packages/react-scripts/template/README.md @@ -75,6 +75,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) - [Why Opt-in?](#why-opt-in) @@ -1846,6 +1847,74 @@ 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. @@ -2447,19 +2516,19 @@ Note that in order to support routers that use HTML5 `pushState` API, you may wa You can adjust various development and production settings by setting environment variables in your shell or with [.env](#adding-development-environment-variables-in-env). -| Variable | Development | Production | Usage | +| Variable | Development | Production | Usage | | :------------------: | :--------------------: | :----------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| BROWSER | :white_check_mark: | :x: | By default, Create React App will open the default system browser, favoring Chrome on macOS. Specify a [browser](https://github.com/sindresorhus/opn#app) to override this behavior, or set it to `none` to disable it completely. If you need to customize the way the browser is launched, you can specify a node script instead. Any arguments passed to `npm start` will also be passed to this script, and the url where your app is served will be the last argument. Your script's file name must have the `.js` extension. | -| HOST | :white_check_mark: | :x: | By default, the development web server binds to `localhost`. You may use this variable to specify a different host. | -| PORT | :white_check_mark: | :x: | By default, the development web server will attempt to listen on port 3000 or prompt you to attempt the next available port. You may use this variable to specify a different port. | -| HTTPS | :white_check_mark: | :x: | When set to `true`, Create React App will run the development server in `https` mode. | -| PUBLIC_URL | :x: | :white_check_mark: | Create React App assumes your application is hosted at the serving web server's root or a subpath as specified in [`package.json` (`homepage`)](#building-for-relative-paths). Normally, Create React App ignores the hostname. You may use this variable to force assets to be referenced verbatim to the url you provide (hostname included). This may be particularly useful when using a CDN to host your application. | -| CI | :large_orange_diamond: | :white_check_mark: | When set to `true`, Create React App treats warnings as failures in the build. It also makes the test runner non-watching. Most CIs set this flag by default. | -| REACT_EDITOR | :white_check_mark: | :x: | When an app crashes in development, you will see an error overlay with clickable stack trace. When you click on it, Create React App will try to determine the editor you are using based on currently running processes, and open the relevant source file. You can [send a pull request to detect your editor of choice](https://github.com/facebook/create-react-app/issues/2636). Setting this environment variable overrides the automatic detection. If you do it, make sure your systems [PATH]() environment variable points to your editor’s bin folder. You can also set it to `none` to disable it completely. | +| BROWSER | :white_check_mark: | :x: | By default, Create React App will open the default system browser, favoring Chrome on macOS. Specify a [browser](https://github.com/sindresorhus/opn#app) to override this behavior, or set it to `none` to disable it completely. If you need to customize the way the browser is launched, you can specify a node script instead. Any arguments passed to `npm start` will also be passed to this script, and the url where your app is served will be the last argument. Your script's file name must have the `.js` extension. | +| HOST | :white_check_mark: | :x: | By default, the development web server binds to `localhost`. You may use this variable to specify a different host. | +| PORT | :white_check_mark: | :x: | By default, the development web server will attempt to listen on port 3000 or prompt you to attempt the next available port. You may use this variable to specify a different port. | +| HTTPS | :white_check_mark: | :x: | When set to `true`, Create React App will run the development server in `https` mode. | +| PUBLIC_URL | :x: | :white_check_mark: | Create React App assumes your application is hosted at the serving web server's root or a subpath as specified in [`package.json` (`homepage`)](#building-for-relative-paths). Normally, Create React App ignores the hostname. You may use this variable to force assets to be referenced verbatim to the url you provide (hostname included). This may be particularly useful when using a CDN to host your application. | +| CI | :large_orange_diamond: | :white_check_mark: | When set to `true`, Create React App treats warnings as failures in the build. It also makes the test runner non-watching. Most CIs set this flag by default. | +| REACT_EDITOR | :white_check_mark: | :x: | When an app crashes in development, you will see an error overlay with clickable stack trace. When you click on it, Create React App will try to determine the editor you are using based on currently running processes, and open the relevant source file. You can [send a pull request to detect your editor of choice](https://github.com/facebook/create-react-app/issues/2636). Setting this environment variable overrides the automatic detection. If you do it, make sure your systems [PATH]() environment variable points to your editor’s bin folder. You can also set it to `none` to disable it completely. | | CHOKIDAR_USEPOLLING | :white_check_mark: | :x: | When set to `true`, the watcher runs in polling mode, as necessary inside a VM. Use this option if `npm start` isn't detecting changes. | -| GENERATE_SOURCEMAP | :x: | :white_check_mark: | When set to `false`, source maps are not generated for a production build. This solves OOM issues on some smaller machines. | -| NODE_PATH | :white_check_mark: | :white_check_mark: | Same as [`NODE_PATH` in Node.js](https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders), but only relative folders are allowed. Can be handy for emulating a monorepo setup by setting `NODE_PATH=src`. | -| INLINE_RUNTIME_CHUNK | :x: | :white_check_mark: | By default, Create React App will embed the runtime script into `index.html` during the production build. When set to `false`, the script will not be embedded and will be imported as usual. This is normally required when dealing with CSP. | +| GENERATE_SOURCEMAP | :x: | :white_check_mark: | When set to `false`, source maps are not generated for a production build. This solves OOM issues on some smaller machines. | +| NODE_PATH | :white_check_mark: | :white_check_mark: | Same as [`NODE_PATH` in Node.js](https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders), but only relative folders are allowed. Can be handy for emulating a monorepo setup by setting `NODE_PATH=src`. | +| INLINE_RUNTIME_CHUNK | :x: | :white_check_mark: | By default, Create React App will embed the runtime script into `index.html` during the production build. When set to `false`, the script will not be embedded and will be imported as usual. This is normally required when dealing with CSP. | ## Troubleshooting diff --git a/tasks/e2e-monorepos.sh b/tasks/e2e-monorepos.sh new file mode 100755 index 00000000000..635badcf219 --- /dev/null +++ b/tasks/e2e-monorepos.sh @@ -0,0 +1,135 @@ +#!/bin/bash +# 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. + +# ****************************************************************************** +# This is an end-to-end test intended to run on CI. +# You can also run it locally but it's slow. +# ****************************************************************************** + +# Start in tasks/ even if run from root directory +cd "$(dirname "$0")" + +# App temporary location +# http://unix.stackexchange.com/a/84980 +temp_app_path=`mktemp -d 2>/dev/null || mktemp -d -t 'temp_app_path'` +custom_registry_url=http://localhost:4873 +original_npm_registry_url=`npm get registry` +original_yarn_registry_url=`yarn config get registry` + +function cleanup { + echo 'Cleaning up.' + cd "$root_path" + # Uncomment when snapshot testing is enabled by default: + # rm ./packages/react-scripts/template/src/__snapshots__/App.test.js.snap + rm -rf "$temp_app_path" + npm set registry "$original_npm_registry_url" + yarn config set registry "$original_yarn_registry_url" +} + +# Error messages are redirected to stderr +function handle_error { + echo "$(basename $0): ERROR! An error was encountered executing line $1." 1>&2; + cleanup + echo 'Exiting with error.' 1>&2; + exit 1 +} + +function handle_exit { + cleanup + echo 'Exiting without error.' 1>&2; + exit +} + +# Check for the existence of one or more files. +function exists { + for f in $*; do + test -e "$f" + done +} + +# Exit the script with a helpful error message when any error is encountered +trap 'set +x; handle_error $LINENO $BASH_COMMAND' ERR + +# Cleanup before exit on any termination signal +trap 'set +x; handle_exit' SIGQUIT SIGTERM SIGINT SIGKILL SIGHUP + +# Echo every command being executed +set -x + +# Go to root +cd .. +root_path=$PWD + +if hash npm 2>/dev/null +then + npm i -g npm@latest + npm cache clean || npm cache verify +fi + +# Bootstrap create-react-app monorepo +yarn + +# Start local registry +tmp_registry_log=`mktemp` +nohup npx verdaccio@2.7.2 &>$tmp_registry_log & +# Wait for `verdaccio` to boot +grep -q 'http address' <(tail -f $tmp_registry_log) + +# Set registry to local registry +npm set registry "$custom_registry_url" +yarn config set registry "$custom_registry_url" + +# Login so we can publish packages +npx npm-cli-login@0.0.10 -u user -p password -e user@example.com -r "$custom_registry_url" --quotes + +git clean -df +./tasks/publish.sh --yes --force-publish=* --skip-git --cd-version=prerelease --exact --npm-tag=latest + +function verifyTest { + CI=true yarn test --watch=no --json --outputFile testoutput.json || return 1 + cat testoutput.json + # on windows, output contains double backslashes for path separator + grep -E -q "src([\\]{1,2}|/)App.test.js" testoutput.json || return 1 + grep -E -q "comp1([\\]{1,2}|/)index.test.js" testoutput.json || return 1 + grep -E -q "comp2([\\]{1,2}|/)index.test.js" testoutput.json || return 1 +} + +function verifyBuild { + yarn build || return 1 + grep -F -R --exclude=*.map "YarnWS-CraApp" build/ -q || return 1 +} + +# ****************************************************************************** +# Test yarn workspace monorepo +# ****************************************************************************** +# Set up yarn workspace monorepo +pushd "$temp_app_path" +cp -r "$root_path/packages/react-scripts/fixtures/monorepos/yarn-ws" . +cd "yarn-ws" +cp -r "$root_path/packages/react-scripts/fixtures/monorepos/packages" . +yarn + +# Test cra-app1 +cd packages/cra-app1 +verifyBuild +yarn start --smoke-test +verifyTest + +# Test eject +echo yes | npm run eject +verifyBuild +yarn start --smoke-test +verifyTest + +# ****************************************************************************** +# Test create-react-app inside workspace +# ****************************************************************************** +# npx create-react-app --internal-testing-template="$root_path"/packages/react-scripts/fixtures/yarn-ws/ws/cra-app1 cra-app2 +# -- above needs https://github.com/facebookincubator/create-react-app/pull/3435 to user create-react-app +popd + +# Cleanup +cleanup diff --git a/tasks/local-test.sh b/tasks/local-test.sh index 43a74ae0ec5..124e210872b 100755 --- a/tasks/local-test.sh +++ b/tasks/local-test.sh @@ -49,7 +49,7 @@ while [ "$1" != "" ]; do shift done -test_command="./tasks/e2e-simple.sh && ./tasks/e2e-kitchensink.sh && ./tasks/e2e-kitchensink-eject.sh && ./tasks/e2e-installs.sh" +test_command="./tasks/e2e-simple.sh && ./tasks/e2e-kitchensink.sh && ./tasks/e2e-kitchensink-eject.sh && ./tasks/e2e-installs.sh && ./tasks/e2e-monorepos.sh" case ${test_suite} in "all") ;; @@ -65,6 +65,9 @@ case ${test_suite} in "installs") test_command="./tasks/e2e-installs.sh" ;; + "monorepos") + test_command="./tasks/e2e-monorepos.sh" + ;; *) ;; esac