facebook / create-react-app

Set up a modern web app by running one command.

Home Page:https://create-react-app.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Support Lerna and/or Yarn Workspaces

rovansteen opened this issue Β· comments

Are there any plans to support Lerna? By default Lerna uses the packages directory to store the packages. Right now the content of that folder is not transpiled so it's not possible to use ES6 code in there. It would be nice to have the possibility to use Lerna to create a monorepo application with create-react-app.

I've tried using the src directory with Lerna, but that conflicts with not ignoring node_modules directory inside the src directory, so it gives a ton of lint errors when you do so.

I'm using a create-react-app created app within a lerna monorepo and am quite pleased with it. Steps:

~/someRepo/packages$ create-react-app app
~/someRepo/packages$ cd ..
~/someRepo$ lerna bootstrap
~/someRepo$ cd packages/app
~/someRepo/packages/app$ npm run start

πŸŽ‰

You can require your other packages by name within the create-react-app after adding them to app's package.json and running lerna bootstrap.

commented

This is pretty nice. Does Babel transpilation still work in this case though?

The individual packages under packages/ need to do a transpilation step. They'll be linked via lerna but the main entrypoints still need to be ES5.

I would prefer to not have to transpile all our packages when used in an overall app and yet I know that when we publish to npm we need them to be ES5-consumable.

For me the holy grail of Lerna support with CRA would look something like,

/web-clients
β”œβ”€β”€ package.json
└── packages
    β”œβ”€β”€ api
    β”‚   └── package.json
    β”œβ”€β”€ components
    β”‚   └── package.json
    β”œβ”€β”€ cra-app-1
    β”‚   └── package.json
    β”œβ”€β”€ cra-app-2
    β”‚   └── package.json
    └── something-else
        └── package.json

The idea here is that we have a monorepo for web clients that can contain multiple CRA-based apps as peers that have access to a few shared other packages (e.g. a lib of shared presentational components, maybe a lib with some api calling utils, anything else). The CRA apps should be able to require code from the other packages and Babel should know to transpile such code coming from within the monorepo automatically.

Is this possible with Lerna and CRA at the moment? Any related issues or info I can look at?

While I'm no longer using create-react-app (using next.js), this is how we do it in nteract for various components and the notebook-preview-demo (the next.js app): https://github.com/nteract/nteract/tree/master/packages

commented

Yarn added workspaces in 1.0 recently, don't know how it impacts this but thought it might be worth mentioning.

@gaearon I've got babel transpilation working alongside Lerna in a private fork of CRA.

Add something like this to the end of /config/paths:

module.exports.lernaRoot = path
  .resolve(module.exports.appPath, '../')
  .endsWith('packages')
  ? path.resolve(module.exports.appPath, '../../')
  : module.exports.appPath

module.exports.appLernaModules = []
module.exports.allLernaModules = fs.readdirSync(
  path.join(module.exports.lernaRoot, 'packages')
)

fs.readdirSync(module.exports.appNodeModules).forEach(folderName => {
  if (folderName === 'react-scripts') return
  const fullName = path.join(module.exports.appNodeModules, folderName)
  if (fs.lstatSync(fullName).isSymbolicLink()) {
    module.exports.appLernaModules.push(fs.realpathSync(fullName))
  }
})

Webpack configs:

// before
{
  include: paths.appSrc
}
// after
{
  include: paths.appLernaModules.concat(paths.appSrc)
}

Jest config:

// before
transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs)$'],

// after
transformIgnorePatterns: [
  // prettier-ignore
  '[/\\\\]node_modules[/\\\\](?!' + paths.allLernaModules.join('|') + ').*\\.(js|jsx|mjs)$'
]

Don't have the time to make a PR & add tests, comments, etc. but should be easy for someone else to do

Great!! @ashtonsix . I also have the lerna set up with CRA based on your code.

My folder structure is

lerna
  -- packages
      -- local package 1 (not published to npm registry)
      -- local package 2
      -- local package 3 
      -- local package ... 
      -- real app (created with CRA)
  -- lerna.json

For anyone else, the key for webpack in CRA app to transpile jsx is add lerna packages absolute path in include of babel-loader

BTW: I have it done without forking CRA, but used a mechanism to extend CRA webpack config, you may find more detail steps in #1328 (comment)

I've created an example monorepo and PR 3741 that adds support for it.

monorepo
  |--packages.json: workspaces: ["apps/*", "comps/*", "cra-comps/*"] <-- (yarn workspace)
  |--lerna.json: packages: ["apps/*", "comps/*", "cra-comps/*"] <-- (lerna w/o yarn workspace)
  |--apps
    |--cra-app1 <-- basic cra-app, doesn't use any monorepo comps
    |--cra-app2 <-- basic cra-app, doesn't use any monorepo comps
    |--cra-app3 <-- uses some monorepo comps
      |--package.json: dependencies: ["comp1": <vsn>, "comp2": <vsn>]
  |--comps
    |--comp3 <-- not a cra component?
      |--package.json: main: build/index.js
      |--build/index.js  <-- don't transpile?
      |--index.js
      |--index.test.js <-- don't test?
  |--cra-comps
    |--comp1 <-- standard-ish cra component with JSX
      |--package.json: main: comp1.js
      |--comp1.js
      |--comp1.test.js
    |--comp2 <-- cra component with dependency on other cra-comp in monorepo
      |--package.json: dependencies: ["comp1"]
      |--comp2.js: import comp1 from 'comp1'
      |--comp2.test.js

I assume that it is agreed that cra-comps should be transpiled just like app src (including JSX) since that seems to be the request from above comments, but some further questions about desired functionality:

  1. Which (if any) tests from cra-comps should run when running app tests? (only comps that the app specifies as dependencies?)
  2. Should files from cra-comps be linted when building app?
  3. Should a directory structure be enforced for cra-comps? (e.g. files need to be under /src)
  4. How to flag components that should not be treated as cra-comps? (or should cra-comps be by convention, e.g. well-known directory in monorepo?)

Is this the correct place to try to get the answers for those questions?

Without having tried to setup a monorepo with CRapp I'm having trouble visualising the problems, and thus, the workarounds. Here are some silly questions:

I've tried using the src directory with Lerna, but that conflicts with not ignoring node_modules directory inside the src directory, so it gives a ton of lint errors when you do so.

@rovansteen Is this due to the difficulties in globbing (include/exclude folders) in monorepo directory structure vs a regular package structure?

@bradfordlemley RE: Directory structure - Whats your thinking in enforcing a package folder structure (e.g. apps and comps)?

Background: My instinct would be to just keep the root level super thin, and let the user put whatever they want in packages/

 // <root>/package.sjon
"scripts": {
   "app1:start": "cd packages/app1/; yarn start",
   "app1:test": "cd packages/app1/; yarn test",
   "styleguide:build": "cd packages/styleguide/; yarn build",
   "colors:build": "cd packages/colors/; yarn build",
   "test": "lerna exec -- yarn test # Scales poorly!"
}

@AshCoolman PR 3741 is a working implementation of monorepo support in CRA. It doesn't enforce any directory structure. It does support the top-level scripts like you described above. It includes some monorepo usage info in the user guide.

That user guide section describes how monorepos would work for that particular PR approach, and might be a good basis for considering how you want monorepos to work.

It seems like there are a lot of folks who want a solution for shared components, but I'm not seeing much input for details of how they want monorepos to work. I encourage you to provide more input if you can.

@bradfordlemley thanks for very welcome work. I've tried the yarn workspaces described in README for 2.0.0-next.9754a231.

What seems strange is that when importing a component, comp1/index.js is indeed picked up but only if there is no comp1/build/index.js - if the latter exists, it is picked up instead and comp1/index.js ignored. This changes between runs of yarn start.

This is a pity as I was hoping to set up a repo where comp1/index.js just contains a re-export of comp1/src/index.js, and nwb is used to build components into comp1/build/index.js and publish them.

If it's just a matter of search priority, how about switching it around to prefer comp1/index.js if both are available?

@bebbi
This is probably happening because your comp1/package.json has a module or main entry, eg:

"module": "build/index.js"

It is possible to ignore that entry (and perhaps would be reasonable in this case), but it wouldn't pick up the files from /src without some extra sauce, basically because there's not a way for your package.json to specify its source location, so how would we know to get index.js from /src?

BTW, what's the problem with using build/index.js? ... is it that it doesn't automatically get rebuilt when comp1 sources change? something else? Have you tried adding a watcher to rebuild your component? Is that just too painful to coordinate throughout the monorepo?

Happy to try to make this work conveniently for your use case. Can you provide more info on your repo structure/usage/workflow (to avoid any incorrect assumptions about your use case)? Is it just a standard nwb react component that you want to use in a cra app in the same monorepo and also publish?

@bradfordlemley
Thanks - yes exactly, the use-case is developing an app in a "single codebase experience" and publish a few modules out of it. Modules are react or plain js.

My biggest need really is for a single, consistent CRA dev experience, ideally without duplicating the dev toolchain (error overlay, settings for lint, babel, webpack loaders, ..) I assume that's what many people are hoping for. I realize that some of this does require duplication as we need to use a non-CRA toolchain to enable lib output, and for many devs this probably means that within above use-case, there will be different behaviours within the same codebase (e.g. wp loaders) unless your wp configs keep pace with cra development.

So I'm mainly trying to understand for which aspects the new workspaces support further reduces that burden of duplication or non-uniform behaviour.

Re src, agreeing on a src convention for code is likely overkill for someone just interested in absolute path imports. But my comp/index.js has a export { default } from './src' which will do for now.

Yes, I can go with the lib (or es6) approach with watching. As cra builds are slow, I assume it'll probably be one line per app e.g. like below:

"start-app1": "concurrently --kill-others \"lerna run start --scope app1 --stream\" \"lerna run build:watch --ignore app1 --ignore app2 --parallel --stream\"

@bradfordlemley
Hm.. running yarn start in the cra app1and adding the lines

const a = {hello: 'there' }
const b = { ...a }`

to comp1/index.js triggers a Syntax error:

Failed to compile ../comp1/index.js
Syntax error: [...]/packages/comp1/index.js: Support for the experimental syntax 'objectRestSpread' isn't currently enabled (2:13)`
[...]
Add @babel/plugin-proposal-object-rest-spread (https://git.io/vb4Ss) to the 'plugins' section of your Babel config to enable transformation.

That doesn't happen when adding it inside the app1/index.js code.

Oh, this hasn't been released yet, it didn't make it into 2.0.0-next.9754a231 that you mentioned. I believe next alpha (soon?) should have it.

Update: available in react-scripts@2.0.0-next.47d2d941, see #3815 (comment).

commented

I think that it is really dangerous to use different pipelines for testing a component locally and publishing it.

I don’t think we should support using nwb for publishing but compiling with CRA pipeline in development. This doesn’t make sense to me. It will introduce subtle bugs.

My suggestion was that if we see a script called build in the component’s package.json, we should treat it as a third-party dependency, since presumably you use a different build mechanism. Another field we could use as a heuristic is private (presumably anything β€œpublic” must be compiled before publishing so we shouldn’t attempt that).

I know some people don’t like heuristics like this very much. I’m open to other options.

Not adding much to the future of this conversation, but I just hit this issue.
Ended up using https://github.com/dashed/react-app-rewire-babel-loader to add the additional packages in my workspaces that needed the compilation step.

Seems to be working (for now).

Hi, I am currently using CRA (1.x) in a mono-repo with shared components via nwb. Just FYI, maybe its interesting for the discussion and I do not know if this thing (testing in shared components) is mentioned how to do it.
I share the tests from react-scripts with the build-process in nwb for my shared packages:

// in package.json
"scripts": {
  "build": "nwb build-react-component",
  "test": "react-scripts test --env=jsdom"
}

Regarding the different heuristic-approaches: you could also check the "module"-field. I think this would be bether than checking for a build-script-entry, but worse than a check for a private-package.
private seems to be the cleanest solution, If there is no possibility to explicitly name the packages that have to be considered.

@Gaeron of course, very true.

Just a few practical experiences of using workspaces support in a monorepo a couple days. Happy if someone detects a flaw in the approach.

  • cra workspaces support is a great improvement. Easier node_modules handling, and no more npm linking to fix multiple react components.
  • The perfect CRA dev experience appears to stop at the boundary of publishable modules. The "compN depends on compM" simplification works as long as components are not published (public or private), i.e. need their own build chain. For code in publishable components, no CRA nicety and unified experience unfortunately.
  • Additionally, if publishable components depend on other components in the workspace, those others have to be built too because the nwb build chain refuses dependencies on "simple" components (ES6 index.js + minimal package.json). So I end up creating the whole build/watch ceremony for components whether they're publishable or not, and moving most things out of unspoilt CRA world.

Ceterum censeo a cra module build feature would rock.

(Re heuristics above, "module" sounds like the key most clearly correlated to publishable to me. "build" sounds slightly less clear as CRA has one too, and "private" could be used for other stuff, like avoiding accidental publishing before it's mature.

Has anyone considered the TypeScript case where you can use path mappings to point to packages? Since the packages are not under the apps you get the following error:

Module not found: You attempted to import /Users/agroza/Projects/bbp/sauron/packages/common/src which falls outside of the project src/ directory. Relative imports outside of src/ are not supported. You can either move it inside src/, or add a symlink to it from project's node_modules/.

What would be the solution for this case?

Can you give an example? This looks like you're just trying to import from outside src (which is not supported), nothing to do with typescript.

@bradfordlemley yes, of course. See cra-workspaces, it's the same example that also causes problems with jest (and a few other problems).

@rolandjitsu I think this yarn workspace support in react-scripts should support that use case (when it presumably gets ported into react-scripts-ts), without using tsconfig.json paths.
Your monorepo is roughly:

monorepo/
  app/
    src/
      App.js: import packageA from 'package-a'
  packages/
    package-a/
    package-b/

which is basically the same as examples discussed above.

There are still some details being discussed, like if/how to support src dirs under the shared packages, if shared packages should be publishable, how to detect shared packages that should not be built. Note, it doesn't allow package names to be remapped. Also, you can take a look at this cra-monorepo-examples which is trying to present the issues and open questions. It'd be great if you could comment here to help ensure it will meet your use case.

If you really want tsconfig.json paths support, probably best to follow up on the react-scripts-ts paths issue.

What is the current status of this ? Is there a simple solution for transpiling imported code in a simple use case:

project
  | ----- lerna.json
  | ----- package.json
  | ----- packages
               |-------- myapp (built using create-react-app myapp)
               |-------- shared (built using create-react-app shared)

myapp has shared dependecy and import a component (FormItemTitle) from shared:

> react-scripts start

Starting the development server...

Failed to compile.

../shared/src/atoms/FormItemTitle/FormItemTitle.js
Module parse failed: Unexpected token (27:18)
You may need an appropriate loader to handle this file type.
|
| class FormItemTitle extends Component {
|       static propTypes = {
|               title: PropTypes.string.isRequired
|       };

How to solve the transpilation issues here?

@renatonmendes It is supported in 2.0 alpha, see #3815 for install info. It only supports yarn workspaces (you should have "workspaces" in project/package.json).

If you have both of those and it's still not working, can you write back and add the contents of project/package.json, myapp/package.json, and shared/package.json, and also the import statement you're using in myapp?

well, IΒ΄ve managed to make it work according to the @ashtonix code from [above](Module build failed: Error: No ESLint configuration found.), with some changes:

$ create-react-app myapp
$ cd myapp
$ npm run eject

Then in config/paths.js:

module.exports.lernaRoot = path
  .resolve(resolveApp('.'), '../')
  .endsWith('packages')
  ? path.resolve(resolveApp('.'), '../../')
  : resolveApp('.')

module.exports.appLernaModules = []
module.exports.allLernaModules = fs.readdirSync(
  path.join(module.exports.lernaRoot, 'packages')
)

fs.readdirSync(module.exports.appNodeModules).forEach(folderName => {
  if (folderName === 'react-scripts') return
  const fullName = path.join(module.exports.appNodeModules, folderName)
  if (fs.lstatSync(fullName).isSymbolicLink()) {
    module.exports.appLernaModules.push(fs.realpathSync(fullName))
  }
})

For some reason the latest create-react-app does not export an appSrc, so this was changed to resolveApp('.')

Then same as original post:
At Webpack configs (config/webpack.config.dev.js, config/webpack.config.prod.js)


// before
{
  include: paths.appSrc
}
// after
{
  include: paths.appLernaModules.concat(paths.appSrc)
}

Jest config:

And in projectΒ΄s package.json in section "jest":

// before
transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs)$'],

// after
transformIgnorePatterns: [
  // prettier-ignore
  '[/\\\\]node_modules[/\\\\](?!' + paths.allLernaModules.join('|') + ').*\\.(js|jsx|mjs)$'
]

All fine, I got the code transpiled, but now IΒ΄m facing problems with EsLint when including the shared component:

$ npm run myapp
Starting the development server...

Failed to compile.

../shared/src/FormItemTitle/FormItemTitle.js
Module build failed: Error: No ESLint configuration found.

Any ideas of what is still missing ?

@bradfordlemley , should I install workspaces where ? In the shared project, in the myapp project that uses shared or in lerna package.json ?

BTW: IΒ΄m using Windows and npm. IΒ΄ve never used yarn, so will that work on my environment ?

Have a look at 2.0 User Guide's monorepo section, it shows an example of a workspaces entry in the top-level package.json, as well as the rest of the tree. (I just noticed that it incorrectly shows [] instead of {} for dependencies and devDependencies, should use {})

Thanks @bradfordlemley for hints. After a lot of pain, I could make my Lerna workspace to work:

$ lerna init

package.json
{
    "workspaces": ["*"],
    "private": true,
    "devDependencies": {
                "lerna": "^2.9.0"
        }
}

lerna.json
{
  "lerna": "2.9.0",
  "npmClient": "yarn",
  "useWorkspaces": true,
  "packages": [
    "packages/*"
  ],
  "version": "0.0.0"
}

My packages:

Auth package (packages/auth):

{
  "name": "@myorganization/auth",
  "version": "3.0.0",
  "author": "Myself",
  "license": "UNLICENSED",
  "dependencies": {
    "@myorganization/ux": ">=3.0.0",
    "react": "^16.2.0",
    "react-dom": "^16.2.0",
    "react-scripts": "2.0.0-next.47d2d941"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  }
}

UX package (packages/ux):

{
  "name": "@myorganization/ux",
  "version": "3.0.0",
  "author": "Myself",
  "license": "UNLICENSED",
  "dependencies": {
    "react": "^16.2.0",
    "react-dom": "^16.2.0",
    "react-scripts": "2.0.0-next.47d2d941"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  }
}

Boostrapped:
$ lerna bootstrap --hoist

I can run the ux library fine.

$ cd ux
$ yarn start

But when IΒ΄m trying to run my project that has the dependency, I got the following error:

$ cd auth
$ yarn start

yarn run v1.5.1
$ react-scripts start
Failed to compile.

../ux/src/atoms/ActivityItem/ActivityItem.js
Syntax error: D:\project\packages\ux\src\atoms\ActivityItem\ActivityItem.js: Support for the experimental syntax 'classProperties' isn't currently enabled (34:20):

  32 | class ActivityItem extends Component {
  33 |
> 34 |   static propTypes = {
     |                    ^
  35 |     showHeader: PropTypes.bool,
  36 |     timestamp: PropTypes.number,
  37 |     username: PropTypes.string,

Add @babel/plugin-proposal-class-properties (https://git.io/vb4SL) to the 'plugins' section of your Babel config to enable transformation.

This ActivityItem is from imported code:
import ActivityItem from '@myorganization/ux/src/atoms/ActivityItem/ActivityItem'

Seens that babel configuration is not being loaded correctly on imported module, as the plugin is installet at lerna root node_modules folder.

Should I create a .babelrc for each project in the monorepo? Will react-scripts start correctly configure babel for all projects, including the imported one?

Not sure if IΒ΄m doing something wrong or if this is an issue... Ways to solve that ?

EDIT:

I'm experiencing the same behaviour in cra-mongorepo-examples project. Check here

I'm frustrated. After 3 days still in same point - cannot import a component to create-react-app....

After not being successfull, IΒ΄ve tried to eject again the project in order to solve that on scripts.

As IΒ΄m now using monorepo, my current setup is:

project
  |-------- node_modules
  |-------- lerna.json
  |-------- package.json
  |-------- packages
                |----------- auth
                             |------- node_modules
                             |------- src
                             |------- packages.json
                |----------- ux
                             |------- node_modules
                             |------- src
                             |------- packages.json

lerna.json:

{
  "lerna": "2.9.0",
  "npmClient": "yarn",
  "useWorkspaces": true,
  "packages": [
    "packages/*"
  ],
  "version": "3.0.0"
}

Lerna root package.json:

{
	"name": "myorganization",
	"version": "3.0.0",
	"license": "UNLICENSED",
	"workspaces": ["packages/*"],
	"private": true,
	"scripts": {
		"clean": "lerna clean -f --yes && rm -rf node_modules",
		"reset": "yarn run clean && yarn",
		"auth": "cd packages/auth && yarn start"
	},
	"devDependencies": {
		"lerna": "^2.9.0"
	}
}

ux package.json: (ux was not ejected)

{
  "name": "@myorganization/ux",
  "version": "3.0.0",
  "author": "Me",
  "private": true,
  "license": "UNLICENSED",
  "dependencies": {
    "react": "^16.2.0",
    "react-dom": "^16.2.0",
    "react-scripts": "2.0.0-next.9754a231"
  },
  "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"
    ]
  },
  "devDependencies": {
    "eslint": "^4.19.0",
    "eslint-plugin-react": "^7.7.0"
  }
}

auth package.json:

{
  "name": "@myorganization/auth",
  "version": "3.0.0",
  "private": true,
  "author": "Me",
  "license": "UNLICENSED",
  "dependencies": {
    "@myorganization/ux": "^3.0.0",
    "@babel/core": "7.0.0-beta.38",
    "@babel/runtime": "7.0.0-beta.38",
    "autoprefixer": "7.2.5",
    "babel-core": "7.0.0-bridge.0",
    "babel-eslint": "8.2.1",
    "babel-jest": "22.1.0",
    "babel-loader": "8.0.0-beta.0",
    "babel-preset-react-app": "4.0.0-next.9754a231",
    "case-sensitive-paths-webpack-plugin": "2.1.1",
    "chalk": "2.3.0",
    "css-loader": "0.28.9",
    "dotenv": "4.0.0",
    "dotenv-expand": "4.0.1",
    "eslint-config-react-app": "3.0.0-next.9754a231",
    "eslint-loader": "1.9.0",
    "eslint-plugin-flowtype": "2.41.0",
    "eslint-plugin-import": "2.8.0",
    "eslint-plugin-jsx-a11y": "6.0.3",
    "extract-text-webpack-plugin": "3.0.2",
    "file-loader": "1.1.6",
    "fs-extra": "5.0.0",
    "html-webpack-plugin": "2.30.1",
    "identity-obj-proxy": "3.0.0",
    "jest": "22.1.2",
    "object-assign": "4.1.1",
    "postcss-flexbugs-fixes": "3.2.0",
    "postcss-loader": "2.0.10",
    "promise": "8.0.1",
    "raf": "3.4.0",
    "react": "^16.2.0",
    "react-dev-utils": "6.0.0-next.9754a231",
    "react-dom": "^16.2.0",
    "react-router-dom": "^4.2.2",
    "style-loader": "0.19.1",
    "svgr": "1.6.0",
    "sw-precache-webpack-plugin": "0.11.4",
    "thread-loader": "1.1.2",
    "uglifyjs-webpack-plugin": "1.1.6",
    "url-loader": "0.6.2",
    "webpack": "3.10.0",
    "webpack-dev-server": "2.11.0",
    "webpack-manifest-plugin": "1.3.2",
    "whatwg-fetch": "2.0.3"
  },
  "scripts": {
    "start": "node scripts/start.js",
    "build": "node scripts/build.js",
    "test": "node scripts/test.js --env=jsdom"
  },
  "browserslist": {
    "development": [
      "last 2 chrome versions",
      "last 2 firefox versions",
      "last 2 edge versions"
    ],
    "production": [
      ">1%",
      "last 4 versions",
      "Firefox ESR",
      "not ie < 11"
    ]
  },
  "jest": {
    "collectCoverageFrom": [
      "src/**/*.{js,jsx,mjs}"
    ],
    "setupFiles": [
      "<rootDir>/config/polyfills.js"
    ],
    "testMatch": [
      "<rootDir>/src/**/__tests__/**/*.{js,jsx,mjs}",
      "<rootDir>/src/**/?(*.)(spec|test).{js,jsx,mjs}"
    ],
    "testEnvironment": "node",
    "testURL": "http://localhost",
    "transform": {
      "^.+\\.(js|jsx|mjs)$": "<rootDir>/node_modules/babel-jest",
      "^.+\\.css$": "<rootDir>/config/jest/cssTransform.js",
      "^(?!.*\\.(js|jsx|mjs|css|json)$)": "<rootDir>/config/jest/fileTransform.js"
    },
    "transformIgnorePatterns": [
      "'[/\\\\]node_modules[/\\\\](?!' + paths.allLernaModules.join('|') + ').*\\.(js|jsx|mjs)$'",
      "^.+\\.module\\.css$"
    ],
    "moduleNameMapper": {
      "^react-native$": "react-native-web",
      "^.+\\.module\\.css$": "identity-obj-proxy"
    },
    "moduleFileExtensions": [
      "web.js",
      "mjs",
      "js",
      "json",
      "web.jsx",
      "jsx",
      "node"
    ]
  },
  "babel": {
    "presets": [
      "react-app"
    ]
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "devDependencies": {
    "eslint": "^4.19.0",
    "eslint-plugin-react": "^7.7.0"
  }
}

Then IΒ΄ve changed the config/paths to grab the referenced packages, adding the following code at the end of it:

module.exports.lernaRoot = path
  .resolve(resolveApp("."), "../")
  .endsWith("packages")
  ? path.resolve(resolveApp("."), "../../")
  : resolveApp(".");

module.exports.appLernaModules = [];

module.exports.appLernaNodeModules = path.join(
  module.exports.lernaRoot,
  "node_modules"
);

fs.readdirSync(module.exports.appLernaNodeModules).forEach(folderName => {
  if (folderName === "react-scripts") return;
  if (folderName.substr(0, 1) === ".") return;

  let fullName = path.join(module.exports.appLernaNodeModules, folderName);

  if (folderName.substr(0, 1) === "@" && fs.lstatSync(fullName).isDirectory()) {
    fs.readdirSync(fullName).forEach(subFolderName => {
      let subFullName = path.join(fullName, subFolderName);

      if (fs.lstatSync(subFullName).isSymbolicLink()) {
        let src = fs.realpathSync(subFullName);
        module.exports.appLernaModules.push(src);
      }
    });
  }

  if (fs.lstatSync(fullName).isSymbolicLink()) {
    module.exports.appLernaModules.push(fs.realpathSync(fullName));
  }
});

And changed the webpack.config.dev and webpack.config.prod to:

"use strict";

const autoprefixer = require("autoprefixer");
const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const CaseSensitivePathsPlugin = require("case-sensitive-paths-webpack-plugin");
const InterpolateHtmlPlugin = require("react-dev-utils/InterpolateHtmlPlugin");
const WatchMissingNodeModulesPlugin = require("react-dev-utils/WatchMissingNodeModulesPlugin");
const eslintFormatter = require("react-dev-utils/eslintFormatter");
const ModuleScopePlugin = require("react-dev-utils/ModuleScopePlugin");
const getClientEnvironment = require("./env");
const paths = require("./paths");

// Webpack uses `publicPath` to determine where the app is being served from.
// In development, we always serve from the root. This makes config easier.
const publicPath = "/";
// `publicUrl` is just like `publicPath`, but we will provide it to our app
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
// Omit trailing slash as %PUBLIC_PATH%/xyz looks better than %PUBLIC_PATH%xyz.
const publicUrl = "";
// Get environment variables to inject into our app.
const env = getClientEnvironment(publicUrl);

// Options for PostCSS as we reference these options twice
// Adds vendor prefixing based on your specified browser support in
// package.json
const postCSSLoaderOptions = {
  // Necessary for external CSS imports to work
  // https://github.com/facebookincubator/create-react-app/issues/2677
  ident: "postcss",
  plugins: () => [
    require("postcss-flexbugs-fixes"),
    autoprefixer({
      flexbox: "no-2009"
    })
  ]
};

console.log(paths.appLernaModules.concat(paths.appSrc));


// This is the development configuration.
// It is focused on developer experience and fast rebuilds.
// The production configuration is different and lives in a separate file.
module.exports = {
  // You may want 'eval' instead if you prefer to see the compiled output in DevTools.
  // See the discussion in https://github.com/facebookincubator/create-react-app/issues/343.
  devtool: "cheap-module-source-map",
  // These are the "entry points" to our application.
  // This means they will be the "root" imports that are included in JS bundle.
  // The first two entry points enable "hot" CSS and auto-refreshes for JS.
  entry: [
    // We ship a few polyfills by default:
    require.resolve("./polyfills"),
    // Include an alternative client for WebpackDevServer. A client's job is to
    // connect to WebpackDevServer by a socket and get notified about changes.
    // When you save a file, the client will either apply hot updates (in case
    // of CSS changes), or refresh the page (in case of JS changes). When you
    // make a syntax error, this client will display a syntax error overlay.
    // Note: instead of the default WebpackDevServer client, we use a custom one
    // to bring better experience for Create React App users. You can replace
    // the line below with these two lines if you prefer the stock client:
    // require.resolve('webpack-dev-server/client') + '?/',
    // require.resolve('webpack/hot/dev-server'),
    require.resolve("react-dev-utils/webpackHotDevClient"),
    // Finally, this is your app's code:
    paths.appIndexJs
    // We include the app code last so that if there is a runtime error during
    // initialization, it doesn't blow up the WebpackDevServer client, and
    // changing JS code would still trigger a refresh.
  ],
  output: {
    // Add /* filename */ comments to generated require()s in the output.
    pathinfo: true,
    // This does not produce a real file. It's just the virtual path that is
    // served by WebpackDevServer in development. This is the JS bundle
    // containing code from all our entry points, and the Webpack runtime.
    filename: "static/js/bundle.js",
    // There are also additional JS chunk files if you use code splitting.
    chunkFilename: "static/js/[name].chunk.js",
    // This is the URL that app is served from. We use "/" in development.
    publicPath: publicPath,
    // Point sourcemap entries to original disk location (format as URL on Windows)
    devtoolModuleFilenameTemplate: info =>
      path.resolve(info.absoluteResourcePath).replace(/\\/g, "/")
  },
  resolve: {
    // This allows you to set a fallback for where Webpack should look for modules.
    // We placed these paths second because we want `node_modules` to "win"
    // if there are any conflicts. This matches Node resolution mechanism.
    // https://github.com/facebookincubator/create-react-app/issues/253
    modules: ["node_modules", paths.appNodeModules].concat(
      // It is guaranteed to exist because we tweak it in `env.js`
      process.env.NODE_PATH.split(path.delimiter).filter(Boolean)
    ),
    // These are the reasonable defaults supported by the Node ecosystem.
    // We also include JSX as a common component filename extension to support
    // some tools, although we do not recommend using it, see:
    // https://github.com/facebookincubator/create-react-app/issues/290
    // `web` extension prefixes have been added for better support
    // for React Native Web.
    extensions: [".web.js", ".mjs", ".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/
      "react-native": "react-native-web"
    },
    plugins: [
      // Prevents users from importing files from outside of src/ (or node_modules/).
      // This often causes confusion because we only process files within src/ with babel.
      // 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])
    ]
  },
  module: {
    strictExportPresence: true,
    rules: [
      // Disable require.ensure as it's not a standard language feature.
      { parser: { requireEnsure: false } },

      // First, run the linter.
      // It's important to do this before Babel processes the JS.

      {
        test: /\.(js|jsx|mjs)$/,
        enforce: "pre",
        use: [
          {
            options: {
              formatter: eslintFormatter,
              eslintPath: require.resolve("eslint")
            },
            loader: require.resolve("eslint-loader")
          }
        ],
        include: paths.appLernaModules.concat(paths.appSrc)
      },
      {
        // "oneOf" will traverse all following loaders until one will
        // match the requirements. When no loader matches it will fall
        // back to the "file" loader at the end of the loader list.
        oneOf: [
          // "url" loader works like "file" loader except that it embeds assets
          // smaller than specified limit in bytes as data URLs to avoid requests.
          // A missing `test` is equivalent to a match.
          {
            test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
            loader: require.resolve("url-loader"),
            options: {
              limit: 10000,
              name: "static/media/[name].[hash:8].[ext]"
            }
          },
          // Process application JS with Babel.
          // The preset includes JSX, Flow, and some ESnext features.
          {
            test: /\.(js|jsx|mjs)$/,
            include: paths.appLernaModules.concat(paths.appSrc),
            use: [
              // This loader parallelizes code compilation, it is optional but
              // improves compile time on larger projects
              require.resolve("thread-loader"),
              {
                loader: require.resolve("babel-loader"),
                options: {
                  // 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.
                  cacheDirectory: true,
                  highlightCode: true
                }
              }
            ]
          },
          // Process any JS outside of the app with Babel.
          // Unlike the application JS, we only compile the standard ES features.
          {
            test: /\.js$/,
            use: [
              // This loader parallelizes code compilation, it is optional but
              // improves compile time on larger projects
              require.resolve("thread-loader"),
              {
                loader: require.resolve("babel-loader"),
                options: {
                  babelrc: false,
                  compact: false,
                  presets: [
                    require.resolve("babel-preset-react-app/dependencies")
                  ],
                  cacheDirectory: true,
                  highlightCode: true
                }
              }
            ]
          },
          // "postcss" loader applies autoprefixer to our CSS.
          // "css" loader resolves paths in CSS and adds assets as dependencies.
          // "style" loader turns CSS into JS modules that inject <style> tags.
          // In production, we use a plugin to extract that CSS to a file, but
          // in development "style" loader enables hot editing of CSS.
          // By default we support CSS Modules with the extension .module.css
          {
            test: /\.css$/,
            exclude: /\.module\.css$/,
            use: [
              require.resolve("style-loader"),
              {
                loader: require.resolve("css-loader"),
                options: {
                  importLoaders: 1
                }
              },
              {
                loader: require.resolve("postcss-loader"),
                options: postCSSLoaderOptions
              }
            ]
          },
          // Adds support for CSS Modules (https://github.com/css-modules/css-modules)
          // using the extension .module.css
          {
            test: /\.module\.css$/,
            use: [
              require.resolve("style-loader"),
              {
                loader: require.resolve("css-loader"),
                options: {
                  importLoaders: 1,
                  modules: true,
                  localIdentName: "[path]__[name]___[local]"
                }
              },
              {
                loader: require.resolve("postcss-loader"),
                options: postCSSLoaderOptions
              }
            ]
          },
          // Allows you to use two kinds of imports for SVG:
          // import logoUrl from './logo.svg'; gives you the URL.
          // import { ReactComponent as Logo } from './logo.svg'; gives you a component.
          {
            test: /\.svg$/,
            use: [
              {
                loader: require.resolve("babel-loader"),
                options: {
                  cacheDirectory: true
                }
              },
              require.resolve("svgr/webpack"),
              {
                loader: require.resolve("file-loader"),
                options: {
                  name: "static/media/[name].[hash:8].[ext]"
                }
              }
            ]
          },
          // "file" loader makes sure those assets get served by WebpackDevServer.
          // When you `import` an asset, you get its (virtual) filename.
          // In production, they would get copied to the `build` folder.
          // This loader doesn't use a "test" so it will catch all modules
          // that fall through the other loaders.
          {
            // Exclude `js` files to keep "css" loader working as it injects
            // its runtime that would otherwise be processed through "file" loader.
            // Also exclude `html` and `json` extensions so they get processed
            // by webpacks internal loaders.
            exclude: [/\.(js|jsx|mjs)$/, /\.html$/, /\.json$/],
            loader: require.resolve("file-loader"),
            options: {
              name: "static/media/[name].[hash:8].[ext]"
            }
          }
        ]
      }
      // ** STOP ** Are you adding a new loader?
      // Make sure to add the new loader(s) before the "file" loader.
    ]
  },
  plugins: [
    // Makes some environment variables available in index.html.
    // The public URL is available as %PUBLIC_URL% in index.html, e.g.:
    // <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
    // In development, this will be an empty string.
    new InterpolateHtmlPlugin(env.raw),
    // Generates an `index.html` file with the <script> injected.
    new HtmlWebpackPlugin({
      inject: true,
      template: paths.appHtml
    }),
    // Add module names to factory functions so they appear in browser profiler.
    new webpack.NamedModulesPlugin(),
    // Makes some environment variables available to the JS code, for example:
    // if (process.env.NODE_ENV === 'development') { ... }. See `./env.js`.
    new webpack.DefinePlugin(env.stringified),
    // This is necessary to emit hot updates (currently CSS only):
    new webpack.HotModuleReplacementPlugin(),
    // Watcher doesn't work well if you mistype casing in a path so we use
    // a plugin that prints an error when you attempt to do this.
    // See https://github.com/facebookincubator/create-react-app/issues/240
    new CaseSensitivePathsPlugin(),
    // If you require a missing module and then `npm install` it, you still have
    // to restart the development server for Webpack to discover it. This plugin
    // makes the discovery automatic so you don't have to restart.
    // See https://github.com/facebookincubator/create-react-app/issues/186
    new WatchMissingNodeModulesPlugin(paths.appNodeModules),
    // Moment.js is an extremely popular library that bundles large locale files
    // by default due to how Webpack interprets its code. This is a practical
    // solution that requires the user to opt into importing specific locales.
    // https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
    // You can remove this if you don't use Moment.js:
    new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
  ],
  // Some libraries import Node modules but don't use them in the browser.
  // Tell Webpack to provide empty mocks for them so importing them works.
  node: {
    dgram: "empty",
    fs: "empty",
    net: "empty",
    tls: "empty",
    child_process: "empty"
  },
  // Turn off performance hints during development because we don't do any
  // splitting or minification in interest of speed. These warnings become
  // cumbersome.
  performance: {
    hints: false
  }
};

Running, IΒ΄m getting the following result:


$ npm start

> @myorganization/auth@3.0.0 start D:\project\packages\auth
> node scripts/start.js

[ 'D:\\project\\packages\\auth',
  'D:\\project\\packages\\ux',
  'D:\\project\\packages\\auth\\src' ]
Starting the development server...

Failed to compile.

../ux/src/atoms/index.js
Module build failed: Error: No ESLint configuration found.

Commenting the eslint step in webpack.config.dev, IΒ΄m getting:

$ npm start

> @myorganization/auth@3.0.0 start D:\project\packages\auth
> node scripts/start.js

[ 'D:\\project\\packages\\auth',
  'D:\\project\\packages\\ux',
  'D:\\project\\packages\\auth\\src' ]
Starting the development server...

Failed to compile.

../ux/src/atoms/ActivityItem/ActivityItem.js
Syntax error: D:\project\packages\ux\src\atoms\ActivityItem\ActivityItem.js: Support for the experimental syntax 'classProperties' isn't currently enabled (34:20):

  32 | class ActivityItem extends Component {
  33 |
> 34 |   static propTypes = {
     |                    ^
  35 |     showHeader: PropTypes.bool,
  36 |     timestamp: PropTypes.number,
  37 |     username: PropTypes.string,

Add @babel/plugin-proposal-class-properties (https://git.io/vb4SL) to the 'plugins' section of your Babel config to enable transformation.

So, same problem. Not eslinting or transpiling imported code. Stuck in same place for some days and lots of work dependent on this.

Help very much appreciated to get out of this loop.

Well, after a succession of attempts and frustrations I got it working!!

The solution came from [this webpack forum issue].(webpack/webpack#6799 (comment))

Basically what is happening is that, for some reason, the babel configuration in the package.json is not working, so a slight change on webpack.config.dev.js is necessary on the babel section (havenΒ΄t evaluated webpack.config.prod.js yet):

              // 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.
              cacheDirectory: true,
              presets: [
                "react-app"
              ]

In my case I had to delete the babel section of the package.json also to make it work.
Of course you need also to point to new foders, like @ashtonsix posted up in the thread.

So @bradfordlemley, this sounds like something that could be changed in original CRA scripts to avoid this problem. What about this change ?

@renatonmendes , it looks like you were using an older alpha (react-scripts@2.0.0-next.9754a231) which did not have this support. (FTR, I believe you were using that older version because the newer alpha 2.0.0-next.47d2d941 recently became broken due an external dependency update, see #3815 (comment).)

A new alpha, react-scripts@2.0.0-next.b2fd8db8, was just published.

Could you give that a try?

I am not sure if this has been discussed somewhere else but nothing was mentionned in the docs. When creating a new app with create-react-app newapp, it directly installs all the node_modules. If same node modules where already installed on the root node_modules folder of the monorepo, they still get installed locally.

Would it be possible to just create files/folders with something like create-react-app newapp --no-install then manually install with lerna bootstrap --hoist to mutualise duplicate modules between apps?

Besides developping in a Vagrant VM on windows, I symlink node_modules to somewhere else, so they don't pollute my host, but with create-react-app I have no control.
If I initially create the newapp/node_modules folder symlinked to somewhere else, when creating the app with create-react-app newapp I get:

The directory newapp contains files that could conflict:
  node_modules
Either try using a new directory name, or remove the files listed above.

@trompx

If same node modules where already installed on the root node_modules folder of the monorepo, they still get installed locally

By "installed locally", I'm assuming you mean in the app's node_modules.

If you're using lerna w/ yarn workspaces, this should work with #3967, which should be in an upcoming CRA 2.0 release (also requires yarn >= 1.5.0). Yarn w/ workspaces automatically hoists to the top-level node_modules. (Without #3967, react-scripts will still get hoisted, but create-react-app will fail because it can't find the hoisted react-scripts.) I believe this will work even if your top-level node_modules is symlink'd elsewhere, but not sure.

It sounds like you're using lerna w/ npm, tho. I think something likelerna bootstrap --hoist after create-react-app would get lerna to hoist the modules that npm installed. I don't think create-react-app --no-install would quite work because create-react-app needs to install react-scripts first, then invokes react-scripts to create the app skeleton. Probably would be good to create a separate ticket for supporting lerna monorepos w/ npm to try to get that use case totally supported.

In either of these cases, you shouldn't hit any issue with "The directory newapp contains files that could conflict" safety check since those tools don't symlink the app's node_modules.

If you need to symlink the app's node_modules for some reason, it seems that you're working outside the standard monorepo workflow and tools, and you probably need some special features (like ignoring the safety check) to make it work and you should request those special features in a separate ticket.

Hey @bradfordlemley, thanks for the detailed answer.

By "installed locally", I'm assuming you mean in the app's node_modules.

Yes exactly.

I have all my devtools in a docker container. When working on a project, I mount the src folder inside the devtools container so I can use a bunch of tools on multiple projects, without polluting the host (in that case the Vagrant VM) with the npm/yarn/create-react-app etc.
So in the devtools container, I have both yarn and npm. Some projects need yarn, some others need npm.

When you say:

It sounds like you're using lerna w/ npm

I don't really understand. Most of the time, I install my dependencies with yarn but with create-react-app, I don't have control of what is used to install the packages.
I admit I'm a bit lost with lerna, which first used yarn it seems then migrated over npm5 (don't remember exactly which version made the switch).

So from what you're saying, with the 2.0 release, calling create-react-app app will automatically hoist to the top level node_modules? Will it detects automatically that the root folder is a monorepo due, either to the presence of workspaces in the package.json or the lerna.json file, or no matter what cra will always create the app/node_modules then we'll have to manually run lerna bootstrap --hoist?

Anyway, even if it automatically hoist to the top-level, what happens if the root package.json declares a package (installed by create-react-app) but with a different version (so other components, not cra apps, don't have to install it). In that case Lerna is supposed to install the version locally (in the app/node_modules), and I would have the same problem.

Windows has always had a bunch of problems with node modules long paths, so I ended up mounting my files in a Vagrant VM (then symlinked all node_modules folder outside of the mounted folders). I don't see another way to handle a monorepo on windows unfortunately, and am considering more and more switching to a linux distro.

@bradfordlemley I'm having some issues attempting to set this up. I'm not certain that I'm approaching this correctly. I've tried this multiple times, clearing the npm and yarn caches in between.

First, I created my root project directory, set up a package.json with workspaces: [ 'exercises/*' ] in it. I created the exercises directory, and tried to execute npx create-react-app@next --scripts-version=2.0.0-next.66cc7a90 conduit with the following result:

Aborting installation.
Unexpected error. Please report it as a bug:
{ Error: Cannot find module '/Users/nloding/code/talks/real-world-react/exercises/conduit/node_modules/react-scripts/package.json'
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:571:15)
    at Function.Module._load (internal/modules/cjs/loader.js:497:25)
    at Module.require (internal/modules/cjs/loader.js:626:17)
    at require (internal/modules/cjs/helpers.js:20:18)
    at checkNodeVersion (/Users/nloding/.npm/_npx/13539/lib/node_modules/create-react-app/createReactApp.js:526:23)
    at getPackageName.then.then.then.packageName (/Users/nloding/.npm/_npx/13539/lib/node_modules/create-react-app/createReactApp.js:331:7)
    at process._tickCallback (internal/process/next_tick.js:68:7) code: 'MODULE_NOT_FOUND' }

I get the same if I leave out the scripts and just do npx create-react-app@next conduit, and I even get that error with just npx create-react-app conduit. If I go to a directory outside my monorepo, all three of those commands work without error. The node_modules directory does appear to be getting hoisted correctly, as I see all those packages in the monorepo/node_modules directory, including react-scripts.

I'm not using lerna, I was looking to just use yarn workspaces. What is the correct approach to adding a CRA app to a monorepo?

(edit: spelling errors)

I've also tried adding "nohoist": ["**/react-scripts", "**/react-scripts/**"] to the root project.json, a workaround suggested in #3031, but that resulted in the same error.

Scratch that, I had the syntax in my package.json wrong. If I add nohoist as above, it does work. It would appear almost as if the PR in that thread isn't in the react-scripts alpha. Let me know if it's better to file this as a separate issue.

@nloding #3967 fixes #3031, but it hasn't been merged. The issue is that you can't run create-react-app inside a monorepo to create an app; however, apps already created will work, ie. react-scripts works fine inside a monorepo. You can either create the app outside of the monorepo and copy it in, or use --nohoist like you mentioned to work-around the problem. I'll ping the maintainers to try to get #3967 merged.

@bradfordlemley Deleted my last two comments, as they both proved to be my fault, nothing related to this. However, I did find a genuine bug - I swear, a real bug this time! - and created a separate issue for it. I am not sure if it is related to this PR or not. You can view it here:

#4511 Cannot resolve babel module when using react-scripts@next

Hi. Got a question regarding running tests in a monorepo.

First issue - CRA is complaining about incompatible Jest versions. I have 23.3.0 in the root node_modules folder (which is a dependency of 2 other apps in the monorepo), but CRA depends on 22.4.3. I thought that Yarn will take care of it and put the correct version in node_modules of the CRA app... Fortunately, I can skip this check using SKIP_PREFLIGHT_CHECK=true, but it doesn't really explain if it's just skipping this check for incompatible Jest versions or if I'm disabling some other important checks as well.

Second issue - it looks like running yarn test inside CRA app runs tests for all *.js files in the monorepo :/ Is there any way to prevent CRA from doing it?

I'm using create-react-app and react-scripts v2.0.0-next.3e165448.

I thought that Yarn will take care of it and put the correct version in node_modules of the CRA app

Yarn will figure it out and the correct version of jest will be somewhere, e.g. {monoroot}/node_modules/react-scripts/node_modules/jest, but the location depends on your monorepo. CRA's preflight check will flag issues and bail out, even though the installation is correct and will probably work. However, module resolution does get complicated in these situations and it's difficult to verify, so I think that preflight check bailing is the correct behavior. The best solution is probably to nohoist these tools in your workspace config, e.g. "nohoist": ["**/react-scripts", "**/jest"]. That should prevent the tools from getting hoisted and allow the CRA preflight check to do its job properly.

yarn test inside CRA app runs tests for all *.js files in the monorepo

#4570 (PR) or #4092 (RFC) would change the behavior to require packages to be opted in as "source packages", and CRA would only find tests for those opted-in packages. Those could help, but exactly which tests to include has been an open question, and more feedback is needed on the topic. As a workaround, you can add patterns to your test command to limit which tests get run, either via yarn test myapp mycomp or in your app's package.json test script itself.

I'm having plenty of issues with dependencies when testing and building other packages and apps in my monorepo after adding create-react-app@next (I even tried adding whole packages/apps to nohoist rule with nohoist: [<pacakge>/**], but it didn't really help), so I guess I'll just give up and create this new app in a separate repo...

@szimek just making sure, you have the latest alpha release of react-scripts also?

@nloding Yes.

We got a bit messy monorepo - we've got an SPA app (webpack 4, preact, babel 6, old postcss), Gatsby app (webpack 1, react 15, babel 6, super old postcss) and node app (webpack 3, react 15, babel 6, sass). I have no idea how it all works at the moment, because if I try to update any of the "core" dependencies (babel, postcss etc), one of the apps always breaks. Adding create-react-app to it, which uses webpack 4, react 16 and babel 7 is just too much for it ;) Same thing happened when I tried to update Gatsby app to v2, which also uses webpack 4, react 16 and babel 7 and latest postcss - Gatsby app worked fine, but one of the other app got broken :/ It's a bit hard to update anything at the moment :)

@szimek: I've had similar issues initially but managed to work with this in the root package.json
"nohoist": [ "**/babel**", "**/eslint**", "**/jest**", "**/webpack**" ]
you may consider hoisting **/@babel** as well for babel 7.
I vaguely remember trying nohoist: [<pacakge>/**] but like you said it didn't help much, but the above config worked for me.

@bugzpodder Thanks! I'll try it out, though it feels really hacky and prone to break in the future. In my case I'd have to add postcss to this list as well... It's just frustrating that updating a dependency in one project, suddenly breaks another one, because I have to not only test each app, but also build each app after every update to make sure they can be deployed and if a build fails, guess what needs to be added to the nohoist list.

BTW. I asked about it in another issue (yarnpkg/yarn#6088), but do you know what are the possible options for nohoist setting? What does e.g. **/babel** do exactly? Does it prevent all dependencies which name starts with babel in all workspaces from being hoisted? What about their dependencies - are they not hoisted as well, or would I have to add **/babel**/**?

I think **/babel** will cover dependencies of dependencies. ** would match aribitrarily deep directory structures, so this would match any path that contains "/babel"
I agree a lot of this is undesirable. Hopefully @babel 7 will be released this year and everyone would migrate to that. :)

With "nohoist": ["**/babel**"] I'm getting an error while building one of my apps (Gatsby):

Error: Entry module not found: Error: Cannot resolve module 'babel' in /Users/.../repo/apps/blog

because webpack is looking for babel-loader in ./apps/blog/node_modules and ./node_modules, but it's in ./apps/blog/node_modules/gatsby/node_modules 😭 It never ends...

I'm not really sure if these issues with dependencies in yarn workspaces are caused by some bugs in yarn, inconsistent versioning of (peer) dependencies or it's just the way dependency resolution works.

Anyway, this ticket is about create-react-app and not the nohoist setting, so I'll stop complaining about it ;)

@bradfordlemley If I'm not mistaken, the ModuleScopePlugin in the webpack config should point to paths.srcPaths instead of paths.appSrc since monorepo packages are linked in the root node_modules and I am unable to import any other package from within my CRA package. As soon as I change that path - everything works nicely.

@Pavel910

Monorepo source packages need to be imported with absolute paths, e.g.:
import MySharedModule from 'MySharedPackageName'
where MySharedPackageName is the name declared in the shared package's package.json.

If you try to import with relative paths, e.g.:
import MySharedModule from '../../comps/MySharedModule'
ModuleScopePlugin will (correctly) prevent it.

@bradfordlemley I double-checked my setup disabling all customizations, and it is working.
I guess I did something wrong with module-resolver plugin which created a relative path thus triggering ModuleScopePlugin. Thanks for the clarification.

Hi, correct me if this is not the right place for this question but I just wanted to clarify what the requirements are for getting transpilation working across linked packages in a lerna monorepo from a CRA package.

I have tried building a simple example with two packages (create-react-app@next) using bootstrap to symlink the shared components which seems to work but I run into:

Module parse failed: Unexpected token (8:6)
You may need an appropriate loader to handle this file type.

Is Yarn w/ workspaces a requirement for this to work or can I get away with just npm and lerna?
Is there a folder structure that needs to be adhered to in the package containing shared components to indicate which ones should be transpiled?
Is this actually implemented in the current CRA@next release?

@as-aaron I've had a play with this in the latest next and can confirm what you're trying to do works with Yarn workspaces following the instructions in the next README:

https://github.com/facebook/create-react-app/blob/next/packages/react-scripts/template/README.md#sharing-components-in-a-monorepo

Whether it works with a npm/Lerna only setup I'm not sure. The tone of the docs suggest only Yarn workspaces, but it isn't mentioned explicitly...

It appears that you don't need to be using yarn with workspaces specifically but if you add the workspaces key to the the root package.json CRA will compile those files.

I'm not sure if this would have any unintended consequences however.

To explicitly support the npm/Lerna only setup it would need to look for the packages key in lerna.json.

Unfortunately progress on support for yarn and lerna monorepos was reverted here

Our use case - we have a component library monorepo and would like to include an "example" web app as part of the same monorepo that uses CRA.

https://github.com/UKHomeOffice/govuk-react

Oh no - why was it reverted? ... time to move away from CRA then ...

@penx @nloding Afaiu CRA 2 will work up to a point as a peer within a monorepo, but after that reversion will no longer transpile JS from other monorepo packages itself. In the thread here #5024 the maintainers say:

We're going to revert monorepo support in favor of consuming library packages via nwb and provide excellent documentation on how to do so.

Not sure if that documentation has surfaced yet, or if a nwb based workflow would work in your case, but it's something I'm investigating and keeping an eye on. If it can support a workflow where a bunch of shared libraries and components can co-exist in a monorepo along with consuming CRA client apps, and nwb doesn't get in the way too much then I'm down with it.

that's sad, not having a possibility to transpile modules out of src limits usage of CRA

@jedrichards I have been using nwb but then the eslint warnings drive me bonkers and I haven't found a way to disable that in CRA. Have you?

Currently using a script to watch all packages that require transpilation using the babel cli and preset react-app, which is run in parallel with CRA start. Seems to work fine, would be interested to know if anyone is aware of any issues with this approach?

@as-aaron I'm using a similar solution, the problem I often face is:

  • babel-cli transpile my-package/src/* to my-package/lib/*
  • if I rename a component (ex: src/my-component to src/my-component/index.js) there's still a copy of the previous file in lib and this copy take precedence over my new file
  • i need to clean and restart the watcher, or manually remove the file + restart the app because of file resolution cache

This caused a lot of confusion in my dev team, and my project has A LOT of files (like 2k) and build step takes a lot of time using babel-cli (babel-loader can use a cache folder)

Another issue is that importing my-package file into my-app links to transpiled files, so "Go to definition" feature in IDE doesn't behave well

We used to use nwb but found it too opinionated (e.g. test framework, eslint config) and restrictive (e.g. using babel for custom build scripts).

There's argument to put up with the opinions and restrictions as they are mostly sensible and suitable for most use cases, but as there is no desire to create an 'eject' command for nwb, if you start using nwb and later find it too opinionated or restrictive for your needs, unpicking it could be tricky.

create-react-library looks promising, uses CRA and according to the README supports monorepos

@jimthedev @jedrichards I opened an issue at create-react-app about this. I came up with a quite acceptable setup for sharing components in a monorepo, that takes care of the ESLint error problem and bypasses the usage of nwb since it created too much overhead and wasn't performant enough for my use case. Also create-react-library - as pointed out by @penx - had problems with compiling performance and assets like svgs. It is easier to just transpile your code using babel and leave all the assets untouched. CRA will handle this. Check out my example repo here: https://github.com/FelixKuehl/cra-monorepo
Hope that helps you getting started.

commented

My company would also enjoy the benefits of importing + transpiling from other packages in a mono repo.

It can be fixed by actually transpiling dependencies "correctly":

          // Process any JS outside of the app with Babel.
          // Unlike the application JS, we only compile the standard ES features.
          {
            test: /\.js$/,
            exclude: /@babel(?:\/|\\{1,2})runtime/,
            loader: require.resolve('babel-loader'),
            options: {
              babelrc: false,
              configFile: false,
              compact: false,
              // presets: [
                // [
                //   require.resolve('babel-preset-react-app/dependencies'),
                //   { helpers: true },
                // ],
              // ],
              presets: [require.resolve('babel-preset-react-app')],

(in the react-scripts dev/prod webpack configs)

Hi,

A possible solution :

  1. babel watch imported modules

Add a watch script in the package.json (of the imported module) :

"watch": "rm -rf dist && babel --presets=@babel/preset-env,@babel/preset-react --watch src --out-dir dist --copy-files --ignore __tests__,spec.js,test.js,__snapshots__"
  1. Import the transpiled modules
import MyComponent form '@my/module/dist/component.js'
  1. Start the main and watch imported module

In the main project root directory

npm start

In the imported module

npm run watch

@hugomallet it's already what I do, except I'm using lerna to do it accross my packages

lerna run watch --parallel

and a script that runs both the watcher and the app

npm-run-all -p packages:watch app:start

It works fine, except for the issues I reported here : #1333 (comment)


I was expecting CRA to use the src files directly, applying the same transpiling rules to the app and local packages, and only transpile to an external folder on publishing. (maybe using a forked version of react-scripts build command, but for packages).

This was I can split the code and the dependencies, publish separate packages but enjoy a unified developer experience.

@alex-pex yes, using lerna is better. I think that we may build every imported package, but watch only on demand (npm run watch inside packages currently edited).

I was expecting CRA to use the src files directly, applying the same transpiling rules to the app and local packages, and only transpile to an external folder on publishing. (maybe using a forked version of react-scripts build command, but for packages).

I think the problem is how do you detect a local package (that requires transpilation) from another (that is directly usable) ? Performance will degrade if every imported module is transpiled by default.

@hugomallet

I think the problem is how do you detect a local package (that requires transpilation) from another (that is directly usable) ? Performance will degrade if every imported module is transpiled by default.

yarn workspaces provides a tree of local packages, based on the pattern provided in the root package.json. Maybe babel-loader should transpile src + node_modules if the symlink points to one of workspace location (= local package).

@alex-pex you suggest that every imported file from local package (symlink) should be transpiled ?
It sounds ok for this use case.

It does not allow to transpile a package from a private registry, but is it necessary ?

Did someone already suggested a property in package.json (or somewhere else but published on npm to allow recursion) that lists all modules that should be transpiled ?

I mention a problem I have with newest create react app, and that is that CRA is very strict if there is any similar packages with other versions in the monorepo. That it needs.
Then the build command wont work. Because it complains with a really long message.

I dont know what is the best way to tackle that, but some examples are eslint, jest.

Is there an approach to tackle this without adding the flag SKIP... in your .env file?

@vongohren Yes there is an approach. Have consistent versions of react-scripts in your monrepo and avoid dependency version conflicts.
Use nohoist if you have to use different versions of eslint, jest, webpack, etc.
E.g.:

"nohoist": [
      "**/eslint**"
    ]

For conflicting eslin-* plugins. I had to use the above nohoist config to make my setup work.

@FelixKuehl thanks for the response! So steps are

  1. Keep consistent react scripts packages
  2. Try to use the same versions when possible
  3. Use nohoist of you need to use different versions

Questions

  1. Where does nohoist go? Is it lerna.json? package.json since we use yarn worskpace?
  2. What if react scrips are strict to 5.6.0, should we also use 5.6.0, since ^5.6.0 does not work. What is the approach to follow when react-scripts update their packages?

Hi,

Is there any way to load config from parent directory ? recursive dotenv lookup ?

What about non-JS module ? If a shared component use CSS module or SVG, how we can get the exact same configuration between a real-world app (my-react-app-) and shared component (comp-) ?

I'm not confident to use others tools in conjuction with "create-react-app", they cause an overhead. If you miss something in configuration, you can produce different behavior / browser support / polyfill duplication across "my-react-app-" and "comp-".

commented

@alistair-hmh can we just use the workspace in package.json, and then every package defined in workspace list, need a compilation

You may get away with Lerna and npm but if you are adding new tooling might as well use yarn?

https://yarnpkg.com/lang/en/docs/workspaces/ is the correct page. You can google around for other docs.

Yarn workspaces essentially automatically symlinks your workspaces into node_modules so you can import them. Since its a symlink, any updates in that workspace should get picked up when you are developing your app.

For automatic transpilation, you'd rely on babel --watch, this will transpile your src/ into dist/ whenever there is a change and then webpackdevserver will pickup these changes and reload your app.

You can see my setup here of how this is done. You can play around with it and see how it works out for you. https://github.com/bugzpodder/yarn-workspace-cra

commented

We want that our shared packages, those who defined in our workspace and yarn symlinks into node_modules, to be written in es6 and create-react-app will take care of compiling it.
webpack does not compile node_modules, so that's why @bugzpodder get the error about the unexpected token.
so there is 2 way to fix it:
option 1: All the shared package will be renamed with a predefined prefix for example: @shaerd/
then create-react-app should add a simple role in webpack.js:
{
test: /.jsx?$/,
include: [path.resolve(__dirname, '../node_modules/@shaerd')],
use: {
loader: 'babel-loader',
},
},
so for each workspace package, we want to compile we just giving it a name with that prefix
for example @shaerd/utils, @shaerd/logger and so on.

option 2:
create-react-app will parse the packge.json and retrieve the workspaces list.
from this list we can find all the packges that are in those folders.
create-react-app will just add a rule for each package in webpack.js

option 1 is very easy to implement, and solves our problem but we need to add a predefined prefix in the name of the packges
option 2 is harder to implement, but we will not have this limtations about naming the shared packages

@bugzpodder and @F1LT3R looking at both the repos you have posted I have the same problem as you guys do - you have to transpile everything under packages/. The good news with using a Yarn workspace is doing a yarn start in app/abc will pick up changes to the transpiled code. The solution that CRA2 had in the alpha releases supporting Yarn workspaces worked perfect for what we needed. All I did was yarn start in the app folder and lerna and Babel wasn't needed for code under packages/. It just worked and was a simple solution but for some reason the CRA team decided to pull it from the release and I haven't heard anything new on if it's back on the roadmap.

@alistair-hmh - I uploaded my POC repo that shows how easy it was to use CRA2 alpha with a Yarn workspace. A package is just a folder with a dumb package.json and no Babel, etc. When you run the app it picks up changes in the packages just like they were under the apps folder. Very simple and worked for our needs. ctrl-click in the IDE went directly to the source code and not the transpiled version in dist. Lerna is in there just for convenience commands but isn't really needed.

https://github.com/MikeSuiter/cra2-alpha-yarn-workspace

At this point with CRA2 removing the above functionality I think I'm going to go with this solution:

  • Keep using a Yarn workspace
  • Use these scripts in the workspace root:
    "start": "lerna run --parallel start",
    "test": "lerna run --parallel test",
    "build": "lerna --stream --concurrency 1 run build"
  • Which makes is kind of ugly because you have to put dist in the package import from app import App from 'wp-core/dist/containers/App';.

@MikeSuiter set main field in package.json to make regular imports work: import App from 'wp-core/containers/App
https://github.com/bugzpodder/yarn-workspace-cra/blob/master/common/package.json#L4

@bugzpodder We already have an existing app that is pretty big and looking to move it to workspaces so we can share code with new apps we need to develop. Since we already import using import EditorTab from 'containers/Process/EditorTab'; it would be nice to keep it that way. Also, we have several components with the same name so importing deep is safer. This is a sample of our structure.

packages
  containers
    Process
      EditorTab
        index.js
      HomeTab
        index.js
    Job
      EditorTab
        index.js
      HomeTab
        index.js
    Script
      EditorTab
        index.js
      HomeTab
        index.js

I took your yarn-workspace-cra app and tried a few things. Move your common/src/index.js to common/src/Test/index.js and make a new common/src/index.js that contains this:

export { default as Test } from './Test';

Now try these three ways to import in admin/src/App.js. In my code the way I want to do it (2nd line) fails.

import { Test } from 'common'; // Works
import Test from 'common/Test'; // Fails
import Test from 'common/dist/Test'; // Works

Another thing I haven't been able to figure out is how to get Chrome to use the source code instead of the transplied code. Put a debugger in Test.render() and Chrome will show the transpiled code in the debugger.

@alistair-hmh Yeah maybe I should think about using just an index.js and only exporting/importing from that. Since our current code is a big CRA all under src, we currently have only full folder-style imports. Also we don't plan to publish anything under packages - it is in a monorepo.

In our real code we are using @company/package.

I did make a cra2-ga branch of the repo I made above if you want to look at it. This is my POC for what we're doing in our real app.

https://github.com/MikeSuiter/cra2-alpha-yarn-workspace/tree/cra2-ga

PS - I think you can get rid of packages in lerna.json if you have them defined in package.json (yarn workspace). That will save some duplication.

@alistair-hmh I just figured out how to get IntelliJ IDEA to go to the src folder instead of dist when doing a ctrl-click

  "main": "dist/index.js",
  "jsnext:main": "src/index.js",

I didn't know if this might help you with what you posted above:

#1333 (comment)

@alistair-hmh Do you have it where you only have to build you CRA app and it automatically builds the packages? If you are do you have a repo that demos it?

#1333 (comment)

Option 1 is not applicable as we use the scope to publish private packages

#1333 (comment)

Some docs about jsnext:main and module : https://github.com/rollup/rollup/wiki/pkg.module

pkg.module will point to a module that has ES2015 module syntax but otherwise only syntax features that the target environments support.

It shouldn't be used to point at src/index if src/index need to be transpiled


Packages like microbundle use a "source" field

https://github.com/developit/microbundle#specifying-builds-in-packagejson

I don't understand what I'm missing with my repo setup, but unfortunately I can't share much because it's all private/proprietary code. I cloned @FelixKuehl's cra-monorepo repository to start, but I cannot reference any packages, it just says it cannot resolve the module.

Here's the rough setup:

- packages
  |--> ui (name: @myapp/ui)
- web
  |--> client (CRA2 app, dependency on @myapp/ui)

No matter what I've done - build, watch, start, etc., the client throws an error that it cannot resolve @myapp/ui.

@alistair-hmh I finally made progress, but I don't like it. Not sure what step I'm missing. I have to import from the src folder. import { Component } from '@myapp/ui/src/Component' even though in my src/index.js file I'm exporting Component. I should note that @myapp/ui is a CRA2 app as well, that is using styleguidist to document the components.

@alistair-hmh @MikeSuiter I think I'm officially dumping CRA due to all this nonsense. It's a nice tool for some workflows, but not for mine. It's time for me to start my own, and I'm using this as my starting point: https://github.com/bradfordlemley/create-react-app

When the monorepo/workspace changes were pulled from the CRA2 release, the guy who did the original PR created a fork that he's maintained. I'm just going to eject from that and build from there.

I linked to it in my comment.

FWIW, I would recommend trying to stay on the path that the CRA maintainers are laying out, if possible.

It seems like @bugzpodder 's example monorepo is the current recommended approach. I don't think that approach will be able to offer quite the same dev-friendly experience as CRA-integrated monorepo support as what was in CRA 2 alphas, but let's try it and see.

It would be helpful to have a more official best-practice monorepo example (including how to test the whole monorepo) to help folks get going and be something that the community can contribute improvements toward.

From #5024:

We're going to revert monorepo support in favor of consuming library packages via nwb and provide excellent documentation on how to do so.

@bradfordlemley I'll give that example a shot, but my concern is what @alistair-hmh mentioned above - delay for transpiling and hotloading that didn't exist in the CRA alpha

Should be simple enough to wrap that pkg config and workspace paths resolver into a CRA fork - and create a POC monorepo. If anyone is interested in seeing it working in a re-usable way like that, let me know.

@alistair-hmh I would love to see a CRA fork and POC monorepo. One gripe I have with the Babel way in packages is I haven't been able to figure out a way to debug src in packages - only the transpiled code.

BTW @alistair-hmh I'll update and cleanup my original cra2-alpha-yarn-workspace that currently works with CRA2 alpha if you want to use that as a test or starting point for your POC monorepo.

@alistair-hmh I updated my repo that currently works with CRA2 alpha and it has an app that uses dependencies from packages and also a standalone app. There is a core package that has a dependency on a shared package. Everything seems to be working great!

Some notes:

  • I moved most of the dependencies to the root package.json which is nice so you can update in one place instead of having to in each app/package
  • package.json in the packages are minimal
  • Running tests on the app also runs tests in packages
  • Each app and package needs its own lint script
  • I made a mix of functional and class components
  • Debugging in the packages shows the source code in Chrome
  • app-shared/index.js uses the react-hot-loader so the entire page doesn't reload on changes
  • The root package.json has several convenience scripts

Take a look at it and let me know if you want me add anything else added to it. Hopefully it will give you a good starting point for testing your CRA2 fork. When you do try to add your CRA2 to it you'll need to update the browserslist as it has changed since the alpha.

  "browserslist": [
    "> 0.2%",
    "not dead",
    "not ie <= 11",
    "not op_mini all"
  ]

https://github.com/MikeSuiter/cra2-alpha-yarn-workspace