Create-react-app: Support Lerna and/or Yarn Workspaces

Created on 1 Jan 2017  Β·  192Comments  Β·  Source: facebook/create-react-app

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.

new feature

Most helpful comment

What would be nice at this point if someone from the CRA team would post what the status of supporting monorepos is. Do they still plan on going the nwb route with documentation and when can we expect to see it?

There are a lot of people interested in using CRA with monorepos so code can be shared among apps and several of them have posted what they've tried to do. One thing that is common with all the attempts to support monorepos with CRA is they work but not with the experience people want. What we currently have working is a decent solution but then there are annoyances like not being able to debug source code (debugging transpiled code is ugly).

All 192 comments

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

:tada:

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.

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

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

  -- 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 https://github.com/facebookincubator/create-react-app/issues/1328#issuecomment-338447062

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

  |--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 [email protected], see https://github.com/facebook/create-react-app/issues/3815#issuecomment-363631534.

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:

  | ----- 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 https://github.com/facebook/create-react-app/issues/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, 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:

  |-------- 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/[email protected] 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/[email protected] 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].(https://github.com/webpack/webpack/issues/6799#issuecomment-374523938)

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 ([email protected]) 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 https://github.com/facebook/create-react-app/issues/3815#issuecomment-373834505.)

A new alpha, [email protected], 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 (https://github.com/yarnpkg/yarn/issues/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 :sob: 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 https://github.com/facebook/create-react-app/issues/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.

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 : https://github.com/facebook/create-react-app/issues/1333#issuecomment-426919496


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

@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

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.

  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:

https://github.com/facebook/create-react-app/issues/1333#issuecomment-438116227

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

https://github.com/facebook/create-react-app/issues/1333#issuecomment-438003523

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

https://github.com/facebook/create-react-app/issues/1333#issuecomment-438687886

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 https://github.com/facebook/create-react-app/issues/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

@bugzpodder or @bradfordlemley or @alistair-hmh - trying the suggestion of using bugzpodder's repo (https://github.com/bugzpodder/yarn-workspace-cra) instead of Bradford's CRA fork. Going to give it a solid go ... except I have one quirk in my setup and curious if anyone of you have a suggestion, using that repos setup.

One of my packages is a CRA app itself, using Styleguidist to document all my lovely components. In the example repo, the entrypoint is listed as dist/index.js and the start command builds it and sets a watcher. That's different than how CRA's default start command works.

Development works (for now) setting the entrypoint on this library to src/index.js - but I'm not sure that's going to give me the right results in the end, especially when I go to build it.

Any thoughts?

@nloding styleguidist allows your own webpack config in styleguidst.config.js

module.exports = {
  webpackConfig: {
    module: {
      rules: [
        {
          test: /\.jsx?$/,
          exclude: /node_modules/,
          loader: require.resolve("babel-loader"),
        },
        ....
  },
 resolve: {
      alias: {
        "@foo/components": path.resolve(__dirname, "src"),
      },
    },

Yes, to test you'll need to do yarn link react-scripts or yarn create-react-app: https://github.com/facebook/create-react-app/blob/master/package.json#L9

@alistair-hmh very simple setup. Also seems incredibly easy to accomplish with react-scripts-rewired. How do you toggle between src/index.js and dist/index.js though?

@alistair-hmh perhaps I'm confused on your development: true and production: true settings in the package.json. When I push my master branch up for my CI/CD pipeline to build and deploy, how does it deploy the production bundles?

Why would you build with workspaces off if you're using workspaces?

@alistair-hmh I cloned your CRA fork and POC and get an error when trying to run the POC.

Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.

PS D:\Workspaces\React\cra-workspaces-support-1333> yarn install
yarn install v1.12.3
[1/4] Resolving packages...
[2/4] Fetching packages...
info [email protected]: The platform "win32" is incompatible with this module.
info "[email protected]" is an optional dependency and failed compatibility check. Excluding it from installation.
[3/4] Linking dependencies...
warning "workspace-aggregator-0ccba57c-2f4a-42b1-ae94-ddcf51bced8f > @project/comp-one > [email protected]" has unmet peer dependency "eslint@>= 4.12.1".
warning "workspace-aggregator-0ccba57c-2f4a-42b1-ae94-ddcf51bced8f > @project/comp-one > [email protected]" has unmet peer dependency "webpack@>=2".
[4/4] Building fresh packages...
success Saved lockfile.
Done in 33.86s.
PS D:\Workspaces\React\cra-workspaces-support-1333> cd .\packages\apps\app-foo\
PS D:\Workspaces\React\cra-workspaces-support-1333\packages\apps\app-foo> yarn link react-scripts
yarn link v1.12.3
success Using linked package for "react-scripts".
Done in 0.13s.
PS D:\Workspaces\React\cra-workspaces-support-1333\packages\apps\app-foo> yarn start
yarn run v1.12.3
$ react-scripts start
Starting the development server...
Failed to compile.

./src/App.js
Module not found: Can't resolve '@project/comp-two' in 'D:\Workspaces\React\cra-workspaces-support-1333\packages\apps\app-foo\src'

I decided to try my CRA2 POC with your CRA2 fork which is a little more complicated than your POC and also has a PureComponent class which fails. Our current CRA 2.1.1 app uses static propTypes and doesn't get this error.

Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.

PS D:\Workspaces\React\cra2-alpha-yarn-workspace> cd .\apps\app-shared\
PS D:\Workspaces\React\cra2-alpha-yarn-workspace\apps\app-shared> yarn link react-scripts
yarn link v1.12.3
success Using linked package for "react-scripts".
Done in 0.12s.
PS D:\Workspaces\React\cra2-alpha-yarn-workspace\apps\app-shared> yarn start
yarn run v1.12.3
$ react-scripts start
Starting the development server...
Failed to compile.

D:/Workspaces/React/cra2-alpha-yarn-workspace/packages/core/components/Header/index.js
SyntaxError: D:\Workspaces\React\cra2-alpha-yarn-workspace\packages\core\components\Header\index.js: Support for the experimental syntax 'classProperties' isn't curre
ntly enabled (8:20):

   6 | 
   7 | class Header extends PureComponent {
>  8 |   static propTypes = {
     |                    ^
   9 |     title: PropTypes.string.isRequired
  10 |   };
  11 | 

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

I just updated my POC to 2.1.1 if you want to try it with your CRA2 fork and see if you get same error.

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

BTW - if you prefer I post stuff specific to your CRA2 fork in that repo let me know.

@alistair-hmh I guess I personally don't see a case where you're working in the monorepo and using the dist build just because another team has pulled in that component via a registry (bit, npm, whatever). As long as when I do yarn build in my monorepo, everything is built using the production configs ... what's the difference?

@alistair-hmh Two of us here, one on Mac and one on Windows, have followed your instructions on the POC app and both of us get this:

Failed to compile.

./src/App.js
Module not found: Can't resolve '@project/comp-two' in 'D:\Workspaces\React\cra-workspaces-support-1333\packages\apps\app-foo\src'

I'm also getting the same error on my CRA2 POC.

A lot of people are subscribed to this issue; perhaps you could discuss issues with your fork or POC, in an issue on your repo?

I second @ljharb - and I'd also say this issue should be abandoned here. The CRA maintainers have said they are favoring the approach of using nwb are working on documentation for that. Anything else, I would guess, is going to exist purely as a fork or a package that overwrites configs (a la react-scripts-rewired).

I have @alistair-hmh's code working with react-scripts-rewired and it seems pretty smooth so far. I have to rewire some CRA code anyway (my app needs to output a second entry point), so this is convenient for me. Feel free to @ me in another thread!

@alistair-hmh I'd suggest reading through the comment thread that was already linked - there's discussion there regarding CRA, why it dropped monorepos, and why they mentioned nwb; afaik that's all the "documentation" there is at this point: https://github.com/facebook/create-react-app/issues/5024

What would be nice at this point if someone from the CRA team would post what the status of supporting monorepos is. Do they still plan on going the nwb route with documentation and when can we expect to see it?

There are a lot of people interested in using CRA with monorepos so code can be shared among apps and several of them have posted what they've tried to do. One thing that is common with all the attempts to support monorepos with CRA is they work but not with the experience people want. What we currently have working is a decent solution but then there are annoyances like not being able to debug source code (debugging transpiled code is ugly).

Issues like these ones https://github.com/Microsoft/TypeScript/issues/12996 and https://github.com/webpack/webpack/issues/4991 lead to double build (one on preparation phase before we launch CRA and one when all watchers kick in) since CRA now forces to build/watch libraries instead of direct usage. Only babel currently has --skip-initial-build option.

Hello πŸ‘‹

FYI, I've worked yesterday on a PR to support Lerna (v2 and v3) mono repos. I'd be glad to have some feedback or comments on it. https://github.com/facebook/create-react-app/pull/6599

One way of testing it would be to copy the react-scripts directory inside your own monorepo, just check that you're depending on a compatible version (lerna FTW!).

Thanks,
Marouane

Hey, I wrote a blog post about sharing components between cra projects. hope you'll find it useful:
https://medium.com/capriza-engineering/sharing-source-code-and-libraries-in-react-bd30926df312

What would be nice at this point if someone from the CRA team would post what the status of supporting monorepos is. Do they still plan on going the nwb route with documentation and when can we expect to see it?

There are a lot of people interested in using CRA with monorepos so code can be shared among apps and several of them have posted what they've tried to do. One thing that is common with all the attempts to support monorepos with CRA is they work but not with the experience people want.

So... any update from CRA team?

I've spent the last few days having my 1st go at using a monorepo (Lerna + Yarn workspaces), containing a CRA Storybook UI library, and several CRA consumer apps. But I can't import any of my JSX lib components.

I don't want to eject CRA.
I don't want to use (an unsupported) fork of CRA.

I like CRA and would prefer a solution that makes it easy to consume Storybook components across a number of sibling apps. ;)

@basher - I understand the desire to be able to directly import components from another workspace.

However, this can be done the same way any other component lib author does it. Run your component workspace through a babel compilation process and output modules that are compatible with what create-react-app can import (e.g. no on jsx, yes on es modules, etc...).

@duhseekoh

Yes, that works. Sort of.

I'm now using babel to transpile my UI Lib JSX, and I updated my UI Lib "package.json" with "main": "dist/index.js".

However, my component JSX files also import Sass partials.

This breaks my consuming app - e.g.
"Module not found: Can't resolve './Button.scss'".

I'll probably have to look for another solution with non-ejected CRA that does NOT involve Lerna & Yarn Workspaces, which is a shame as the setup/workflow looked ideal for me and the team.

@basher you need to compile SASS too... from CRA perspective your common component is the same as any other NPM package. Alternatively you may import SASS in something like main.sass of the app and not import them from component's JS.

@duhseekoh in addition to above mentioned problems having a compiled library requires it to be built BEFORE the watch/start, and the more components you have, the more such build procedures are created, so this approach does not scale well. Also it's kinda overkill to compile libs that are not used anywhere outside the project, e.g. not published to NPM.

However, my component JSX files also import Sass partials.
This breaks my consuming app - e.g.
"Module not found: Can't resolve './Button.scss'".

Here's what a sample component looks like when I transpile it from my component lib:

image


As long as you tell babel to --copy-files then the scss file will travel with it and since CRA can import scss files from a package, it works as expected.

@duhseekoh in addition to above mentioned problems having a compiled library requires it to be built >BEFORE the watch/start, and the more components you have, the more such build procedures are >created, so this approach does not scale well.

If you don't compile you're entire component lib to one file, then you can take advantage of babel --watch and it will only transpile files that change from save to save.


Also it's kinda overkill to compile libs that are not used > anywhere outside the project, e.g. not published to NPM.

Seems like a murky path to go down to say "we're going to take full advantage of monorepo tools like yarn workspaces and lerna, but at the same time, all of our packages inside are going to be compatible with how CRA imports files". What if you have multiple CRA workspaces in your monorepo and they are on different versions? There may be differences in what language features they support. Now your component lib has to be aware of those differences. It's easier to simply follow the standard and output a consumer agnostic build.

Honestly though, I agree that the CRA team could do something here. Maybe a small addition to react-scripts which packages up the babel config needed to output CRA compatible components, and some associated documentation.

What if you have multiple CRA workspaces in your monorepo and they are on different versions?

@duhseekoh I guess in this case the compile-first approach is right. But in all other cases when all apps are CRA-only we can benefit from non-compiled solution a lot. In my case it was CRA and CRNA endpoints. Compilation of a lib for both of them is clearly an overhead... saddest part is that it used to work in 2.x betas and stopped working in release...

@duhseekoh - Thanks, I'm now successfully copying Sass files, so my consumer app imports UI library components with appropriate styling! :)

For anyone else who has setup a monorepo with Lerna + Yarn Workspaces (following the official docs), containing a CRA Storybook UI library, and 1 or more CRA consuming apps, this is how I got it to work:

Monorepo root package.json key parts:

{
    "name": "monorepo-name",
    "version": "0.1.0",
    "private": true,
    "workspaces": [
        "packages/*"
    ],
    "devDependencies": {
                "lerna": "^3.13.1",
        /* plus... eslint, stylelint, prettier, lint-staged, husky... */
    },
    "scripts": {
        "precommit": "lint-staged",
        "storybook": "cd packages/OmniUI-Lib && start-storybook -p 9009 -s public",
        "build-storybook": "lerna exec --scope library-name -- babel src -d dist --copy-files --ignore \"src/stories/\"",
        "watch-storybook": "lerna exec --scope library-name -- babel src -d dist --watch --copy-files --ignore \"src/stories/\"",
        "start-app1": "cd packages/app1 && react-scripts start"
    }
}

Storybook/CRA UI library package.json key parts:

{
  "name": "library-name",
  "version": "0.1.0",
  "private": true,
  "main": "dist/index.js",
  "dependencies": {
    /* CRA dependencies... */
  },
  "devDependencies": {
    /* Storybook dependencies... */
    "babel-cli": "^6.26.0",
    "babel-loader": "8.0.4",
    "babel-preset-env": "^1.6.1",
    "babel-preset-react": "^6.24.1",
    "babel-preset-stage-1": "^6.24.1",
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "storybook": "start-storybook -p 9009 -s public",
    /* "build-storybook": "build-storybook -s public" */
  }
}

CRA consumer app1 package.json:

{
  "name": "app1",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "react": "^16.8.4",
    "react-dom": "^16.8.4",
    "react-scripts": "2.1.8"
  },
  "devDependencies": {
    "babel-loader": "8.0.5",
    "library-name": "0.1.9"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  }
}

NOTES:

  • devDependencies in library and consumer app both list different versions of babel-loader. See this Storybook bug.
  • Additionally, there's a known issue with CRA V2 and Storybook V5 not playing well together. So consumer app needs a .env file containing SKIP_PREFLIGHT_CHECK=true so that you can start it.
  • Library devDependencies also contain babel-cli and babel-presets.
  • Library package needs a .babelrc file containing { "presets": [ "env", "stage-1", "react" ] } in order to be able to run the build and watch commands.
  • Babel 7 has undergone some massive changes, so you need to apply an upgrade to the devDependencies and .babelrc to enable starting Storybook and viewing in a browser, and being able to build/watch the library. See Babel upgrade docs.
  • Storybook library has been published to a private NPM repo. I'm using lerna update wizard to manage versioning of the library in my consumer apps.

This has been a bit of a learning curve...
I'm glad I've got JSX library component (with Sass dependencies) importing from 1 CRA Storybook library app into another CRA consumer app.

UPDATE:
I can start both my CRA consumer app and Storybook/CRA app in monorepo.
I can also build/export library.
See NOTES above, especially the corrections/additions about Babel 7.

At the very least let's load the sourcemaps from transpiled, sibling components as proposed here:

Using this: https://github.com/webpack-contrib/source-map-loader

FWIW, we're currently using πŸ‰ Lerna + βš›οΈ CRA + 🐈 Yarn Workspaces for in a commercial project with:

  • 1x Lerna Monorepo
  • 5x CRA Apps in packages/apps
  • 8x Component Libraries in packages/libs
  • 4x Storybooks that roll up the 8 component libraries
  • 9x Developers using it full time.

For this we are using React Workspaces, which uses npm package @react-workspaces/react-scripts to wire everything together.

Fun facts:

  • Apps do not build locally. Builds happen in Jenkins. No transpiled code lives on developer machines.
  • All code gets the CRA developer experience.
  • @react-workspaces/react-scripts builds the dependency tree for whichever app you are running and only adds those paths to the babel loader, meaning that you only lint and load source that is included in that app.
  • Due to tree-shaking, you can have any number of apps build in CI, and the output will only include the source for the components they use.

Try it out:

  1. Clone the cra-workspaces-playground repo
  2. yarn install
  3. cd packages/apps/app-one
  4. yarn start

(Clicking on this screenshot will take you there)

Screen Shot 2019-04-26 at 12 33 50 AM

Disclaimer: I made this. (With a little help from my friends.) ❀️

@F1LT3R That sounds neat! Would love to switch to something like this, but struggling with the single vs multiple toolchains for these needs:

  • Ability to publish selected packages to their own github repo (using git subtree et al) for extended or open collab, where they need a "standalone" developer experience
  • Ability to publish selected packages from packages/lib to npm
  • Some apis live in the same monorepo (different lib versions & toolchain, serverless-webpack), currently using nohoist directive.

Have you got any thoughts around these?

@F1LT3R Looks like an interesting setup you've got there. 8 component libs and 4 storybooks?

Having had _some_ success setting up a Lerna monorepo we've now abandoned the idea.

Conceptually, monorepo made sense. In practise, there were too many "hoops" to jump through to enable us to still use CRA and storybook, and maintain a good (simple) developer experience.

The "killer" for me was not being able to easily manage different versions of dependencies (e.g. storybook lib components across a range of apps) with standard Lerna commands. I started using lerna update wizard, but even then had some issues.

@basher - I think what made it work for us, was using CRA all the way down. Every component is a CRA app. Every storybook is a CRA app. There's one shared config for every component, which happens to be whatever version of CRA we're using. Same lint config as the CRA app, same jest config as the CRA app, and so on. If you can happily lock yourself into x version of CRA, then you can use whatever version of Storybook, etc that supports that version of CRA. This reduced our cross-configuration hoops enough to make it worth it.

@bebbi ...

Have you got any thoughts around these?

  • Ability to publish selected packages to their own github repo (using git subtree et al) for extended or open collab, where they need a "standalone" developer experience

We're using submodules for media like audio/videos. You could extend it to do something like that. We figured we would publish the monorepo shell and populate it with specific packages for giving to clients for maintenance, overflow work, etc. Then we just use their codebase as a submodule.

  • Ability to publish selected packages from packages/lib to npm

Just npm publish w/ Lerna? lerna run --parallel publish and have publish script in each modules package.json.

  • Some apis live in the same monorepo (different lib versions & toolchain, serverless-webpack), currently using nohoist directive.

We use nohoist to make sure babel doesn't tread on itself in a few places. I think we have 4 hoists.

Just npm publish w/ Lerna? lerna run --parallel publish and have publish script in each modules package.json.

I'm trying to avoid a separate build chain for stand-alone/publish. I think the risk of the same code being compiled with different build chains that possibly not support the same babel/webpack features was one of the reasons source packages didn't move forward.

So for mixing apps and publishable libs in monorepos, I suppose the only solution avoiding this still is two different sets of ES code, and only importing built libs into their CRA.

@bebbi feel free to discuss in https://github.com/react-workspaces/cra-workspaces-playground issues if you're interested in working on a solution together.

For those looking for a workaround to get working sourcemaps in a monorepo you can checkout my minimal reproduction:
https://github.com/minheq/monorepo-cra-source-map

By overriding webpack configuration, it will generate correct sourcemap for local packages, effectively giving you the ability to debug packages in VSCode from CRA and (haven't tested) correct sourcemap for sentry or bugsnag.

Would appreciate if someone can comment on the repo regarding any potential issues

We have recently setup our components library as a monorepo using lerna. We setup CRA on top of it for development and preview. I would agree that it may not be an ideal setup as yet, but we are still working towards making it better.

Here is how the structure looks like:

lerna.json
package.json
public/
src/
└── index.js
    www/
    └── layout.js 
    config/
    └── build.js
    |   babel-rc.js
    |   enzyme.js
    |
    packages/
    β”œβ”€β”€ component1
    |   └── package.json
    |       src/
    |       dist/
    └── component3
        └── package.json
            src/
            dist/

PS I wanted to ask if there are any official docs or RFC about the potential support of lerna yet.

Never mind, just saw this thread #5024 which explains a bit about current status of monorepo support.

Is there anything on this on the roadmap? It's interesting that this is not supported, yet create-react-app itself uses lerna πŸ€”

What is the obstacle, or reasoning behind a lack of support for including paths outside of src? webpack, yarn, lerna, typescript these all provide tooling necessary.

For those that are looking for setting up Lerna, CRA + Storybook with using TypeScript - I finally found a great "this is how to set up correctly" article that is extremely well written.

https://dev.to/shnydercom/monorepos-lerna-typescript-cra-and-storybook-combined-4hli

@imjakechapman The problem with his solution is you have to compile your library packages with each change then restart your CRA app. What people want is a CRA experience for monorepos where you can do a yarn start in apps/my-app and make changes in packages/my-library and it automatically compiles and refreshes the page. We currently have 5 apps that live in apps/* and they are dumb with just an App.js. All of the code for these apps lives in packages/* so it can be shared.

@MikeSuiter agreed, but I'd like to mention there are more use-cases: Some people develop libs in monorepos that are used in apps but also published on a registry. For such libs, you can argue that it's best to import their built code in CRA, because if you import their source code, there's a risk of different behavior of source code import vs code built with a (separate) toolchain.

For us, using nwb, watching the libs source code and triggering their build on code changes works. It's slow though - and the problem is that there are differences imposed on source code depending on whether the code is app or lib code.

Some time ago I've created the article https://medium.com/disdj/monorepo-scripts-strategies-naming-conventions-691c64b51acb where I share how to build libs and apps in a monorepo.

Just wondering instead of trying to tackle this issue from within CRA, has anyone attempted to figure this out from the Lerna side of things? For example, make packages/ a symlink to src/, or use "packages": ["src/*"] in lerna.json?

Would appreciate if someone can comment on the repo regarding any potential issues

This looks promising, @minheq! Will report back.

Just wondering instead of trying to tackle this issue from within CRA, has anyone attempted to figure this out from the Lerna side of things? For example, make packages/ a symlink to src/, or use "packages": ["src/*"] in lerna.json?

I've created a simple CRA setup with Lerna support, you can see the code here https://github.com/stepankuzmin/create-react-app-lerna

After working with lerna and monorepos for a while and needing support for typescript project references, this is the best solution I've come up with.. https://github.com/cipriancaba/ts-monorepo-project-references-example Will add react-native soon and a shared ui components package

@stepankuzmin Are you seeing any downside with such a setup so far?

@stepankuzmin Are you seeing any downside with such a setup so far?

The example doesn't cover the requirements of most in this thread, which seem to be multiple CRA, shared JSX, shared TS/JS, storybook, jest, eslint

After working with lerna and monorepos for a while and needing support for typescript project references, this is the best solution I've come up with.. cipriancaba/ts-monorepo-project-references-example Will add react-native soon and a shared ui components package

This is the best I've seen so far.

I'm adding some complexity, testing, etc. to your example project and submitting a PR.

After working with lerna and monorepos for a while and needing support for typescript project references, this is the best solution I've come up with.. https://github.com/cipriancaba/ts-monorepo-project-references-example Will add react-native soon and a shared ui components package

@cipriancaba nice example. We came up with pretty much the same a while ago and it has been working well for us so far. My take away from your example are the start commands.

One question though: The project reference to shared in react-app doesn't have any effect for now, right? CRA doesn't support project references as far as I know, does it? This is why the start:react command needs to start react-app as well as shared in parallel.

The project reference to shared in react-app doesn't have any effect for now, right? CRA doesn't support project references as far as I know, does it? This is why the start:react command needs to start react-app as well as shared in parallel.

I believe so. It might be possible to include some of the work in https://github.com/facebook/create-react-app/issues/1333#issuecomment-505718147 to improve CRA integration.

How is your project dealing with regular node processes? https://github.com/cipriancaba/ts-monorepo-project-references-example/pull/1#issuecomment-565672938 @cipriancaba @alfechner

@stepankuzmin Are you seeing any downside with such a setup so far?

The example doesn't cover the requirements of most in this thread, which seem to be multiple CRA, shared JSX, shared TS/JS, storybook, jest, eslint

@abrkn I honestly failed to grasp the benefits of insisting on having multiple CRA apps instead of just using one as an overarching build pipeline wrapper app, having gone through every single comment in this thread, two times already, and still didn't really get it. Is it about the CI/CD process? Or compatibility with legacy apps not within this architecture? I feel like if I am starting everything anew - say a project with a component library, a storybook dev server, and a product app - a single CRA setup with all three Lerna packages sitting inside ./src/packages will do, no?

@gsklee I have a CRA app with storybook which is a design system/component library, a server with some SSR (using next.js) and the CRA client.

packages/
   client
   design-system
   server

If I want the server to also use the design system, I don't want it to live inside the src of the client. I do want a seperate application/package to be able to require the design system from client and server.

@CodingDive yeah, so my question was, disregarding the Next.js part, couldn't we just do:

[CRA_ROOT]/
    src/
        packages/
            component_lib/
            client/
            storybook/

And that they can all share one single set of lint configs & build pipeline from the root CRA setup?

In the most simple case that would work but what happens if any other app
wants to use the design system? After all, you mount your client with
ReactDOM.render in the index.ts/js. The design system wants to export
components so you'd have to ensure via the package.json that the entry
point of the client is not accidentally executed. Even then, the package
importing your design system might run risk to load the whole client code.

Full lerna support must be supported in every possible way. Especially,
TypeScript support and support for babel macros was important to me. I
actually made this work in my app after a lot of trial and error by using
TypeScript projects, yarn workspaces & lerna integration.

That being said, it took me way too long to configure, I get issues on
every other typescript eslint or CRA version bump and had to manually
nohoist a lot of packages and resolve resolutions via yarn. If this
worked out of the box, it'd make my setup so much easier. πŸ˜…

On Mon, Dec 16, 2019, 01:17 G. Kay Lee notifications@github.com wrote:

@CodingDive https://github.com/CodingDive yeah, so my question was,
disregarding the Next.js part, couldn't we just do:

[CRA_ROOT]/
src/
packages/
component_lib/
client/
storybook/

β€”
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/facebook/create-react-app/issues/1333?email_source=notifications&email_token=AG4BSR2VIJOCOYEHF6MUNTTQY3CI5A5CNFSM4C3DCS72YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEG5GC2Y#issuecomment-565862763,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AG4BSR7YBB4UFPGV3IYDWALQY3CI5ANCNFSM4C3DCS7Q
.

@abrkn we've got a Express application and use nodemon with ts-node. As with CRA we start it in parallel. When we setup the structure ts-node didn't support project references. Maybe that changes or changed at some point.

@gsklee we have multiple CRA's and server side apps in one Lerna repository. They leverage shared modules and not only UI Components.

I have an update on the React-Workspaces solution:

Since I last posted, the company I had been working for had reached the following stats using a React Workspaces Monorepo:

  • πŸ‰ 1x Lerna + 🐈 Yarn Workspaces Monorepo
  • βš›οΈ 7x CRA App's (each with their own Jenkins job)
  • πŸ”¬ 100x Components
  • 🏫 5x Component Libraries
  • πŸ“– 5x Storybooks - rollup the component libraries
  • πŸ‘· 18x Engineers - actively contributing
  • πŸ‘₯ 4x Teams

Here are the slides we used to present our findings at the Boston React Meetup: https://slides.com/alistairmacdonald-f1lt3r/react-workspaces/#/

According to NPM, React-Workspaces usage seems to be stable/growing. There are many more people using it than I would ever have expected (2708 downloads per week at peek).

Google Chrome 2019-12-27 at 00 27 54@2x

Support is being added for the following features:

  • TypeScript
  • React App Rewired
  • Ant Design

These features are ready for PR review if anyone wants to take a look and comment there:
https://github.com/react-workspaces/react-workspaces-playground/pull/21

I feel like the solution we have in React-Workspaces is good. It is simple, unobtrusive and does not require any crazy configuration. Try the React-Workspaces-Playground repo and see what you think.

There several things that need to be addressed that it would be good to get help with:

  1. Adding build scripts to React Workspaces for component libraries - perhaps on a git post-merge hook.
  2. Fixing Yarn Workspaces' ability to install local deps without trying to look online. (This would allow a lot of flexibility for monorepos in CI).
  3. Folding Yarn Workspaces support back into Create React App - if enough people think it's a good idea.

Thoughts?

Anyone have any leads on how to get this running with @ashtonsix's solution here, but also using Typescript? Looks like I get webpack loading the files, but it doesn't recognize them as TS, saying

../common/state/user/actions.ts
SyntaxError: /Users/brandon/Code/apps/packages/common/state/user/actions.ts: Unexpected token, expected "," (4:66)

  2 | import { UpdateUserParams } from './reducer';
  3 | 
> 4 | export const login = createAction('USER/LOGIN')<{ username: string; password: string }>();
    |                                                                   ^
  5 | 

Feel like there's one more config option I need to edit to get it compiling TS files in /common

Got it. To get TS to work, you need to explicitly add presets: [['@babel/preset-typescript', { allExtensions: true, isTSX: true }]] to your loader in addition to @ashtonsix's solution:

            // Process application JS with Babel.
            // The preset includes JSX, Flow, TypeScript, and some ESnext features.
            {
              test: /\.(js|mjs|jsx|ts|tsx)$/,
              include: [paths.appSrc, ...paths.appLernaModules],
              loader: require.resolve('babel-loader'),
              options: {
                customize: require.resolve('babel-preset-react-app/webpack-overrides'),
                presets: [['@babel/preset-typescript', { allExtensions: true, isTSX: true }]], // ADD THIS
                plugins: [
                  [
                    require.resolve('babel-plugin-named-asset-import'),
                    {
                      loaderMap: {
                        svg: {
                          ReactComponent: '@svgr/webpack?-svgo,+titleProp,+ref![path]',
                        },
                      },
                    },
                  ],
                ],
                // 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,
                // See #6846 for context on why cacheCompression is disabled
                cacheCompression: false,
                compact: isEnvProduction,
              },
            },

Although I still can't get jest to work, says

SyntaxError: Cannot use import statement outside a module

      2 |
      3 | import { RootState, RootAction } from 'state/types';
    > 4 | import user from 'common/state/user';

Will follow up once I figure that out...

Hey try this
https://github.com/elv1n/cra-workspaces
I used it from a lot of time and it still works with the latest cra releases.
It works with Typescript with zero configuration. I'm surprised that is not popular because its is the easiest way to integrate yarn workspaces!

None of these solutions worked for me. Still getting dependency tree errors

Hey try this
https://github.com/elv1n/cra-workspaces
I used it from a lot of time and it still works with the last cra releases.
It works with Typescript with zero configuration. I'm surprised that is not popular because its is the easiest way to integrate yarn workspaces!

Still get an issue building with conflicting jest versions

There might be a problem with the project dependency tree.
@project-name/dashboard: It is likely not a bug in Create React App, but something you need to fix locally.
@project-name/dashboard: The react-scripts package provided by Create React App requires a dependency:
@project-name/dashboard:   "jest": "24.9.0"

Its really annoying that CRA even cares about a dev dependency like jest when building.

I ended up just adding SKIP_PREFLIGHT_CHECK as the error suggested

@dan-cooke Try putting this in your root package.json and deleting all node_modules folders and yarn.lock. Then run yarn and see what happens. This worked for me using https://github.com/react-workspaces/create-react-app.

  "workspaces": {
    "packages": [
      "apps/*",
      "packages/*"
    ],
    "nohoist": [
      "**/webpack-dev-server",
      "**/babel-loader",
      "**/babel-jest"
    ]
  },
  "devDependencies": {
    "babel-jest": "24.9.0",
    ...
  }

That's great. I was searching for an option to not pull the packages to the
root but I couldn't get the correct terminology πŸ˜…

Thank you!

On Tue, 28 Jan 2020, 14:46 Mike Suiter, notifications@github.com wrote:

@dan-cooke https://github.com/dan-cooke Try putting this in your root
package.json and deleting all node_modules folders and yarn.lock. Then
run yarn and see what happens. This worked for me using
https://github.com/react-workspaces/create-react-app.

"workspaces": {
"packages": [
"apps/",
"packages/
"
],
"nohoist": [
"/webpack-dev-server",
"
/babel-loader",
"**/babel-jest"
]
},
"devDependencies": {
"babel-jest": "24.9.0",
...
}

β€”
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/facebook/create-react-app/issues/1333?email_source=notifications&email_token=AFOCQ55RS32MYQ4VKY2YVALRABALZA5CNFSM4C3DCS72YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEKDRVXA#issuecomment-579279580,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AFOCQ55QHFVPRZGRK7GL6QTRABALZANCNFSM4C3DCS7Q
.

Did anyone of the CRA team already comment if this is on the roadmap or if we should rely on a fork by the community?

What is the expectation here? That create-react-app expands into component library creation? It seems like that's often the real desire throughout this issue.

create-react-app has no issues running in a monorepo. if you need to import components from another package that you maintain in that monorepo, then you need to do what every other package built for consumption does, which is compile it to remove non standard things like jsx and typescript.

you wouldn't expect that create-react-app or webpack is able to use a package from npm that hasn't gone through any transpilation, would you? it's the same case for your own workspace packages.

if you want to build a component library or any type of js-like package that a create-react-app project can consume, then it's:

  1. create new package in monorepo
  2. add a tsconfig to it
  3. configure that tsconfig to process jsx and whatever other features you may want
  4. add a build script to that package
  5. add that package as a dependency from your CRA package

No need for a fork. No need for the CRA team to expand CRA into a component library builder or to start doing heavy transpilation on node_modules.

or if you don't want to use typescript, use babel. you can even get all of the same babel configuration that react-scripts uses, because they publish it to npm. it even has a section for 'Usage outside of Create React App'. https://www.npmjs.com/package/babel-preset-react-app#usage-outside-of-create-react-app

While your solution works, it's not a great experience from a developer perspective. I know because I've spend days getting Prettier, Eslint, TypeScript, yarn workspaces and lerna to work seamlessly together. As indicated by the popularity of this issue and the amount of forks and pull requests that try to address it, a lot of people seem to not like the extra transpilation step to use a component from their lerna package.

Problem points include

  • source maps
  • exporting SVGs as styled components from one package and importing it in another
  • knowing which package is causing conflicts across two CRA repositories so that we can nohoist them when using yarn workspaces. I assume this will be even more of a problem with berry (yarn2) and PnP

In my setup, I can't export components that use babel-macros when the consumer of the package uses babel-macros as well. E.g when using the css prop from styled-components/macro for inline-styling components of my design system, I'm getting errors when trying to import and use those components within the client package which also uses inline-styling via babel-macros.

Even if the CRA team decides to not address any of the issues, it'd be good to have them weigh in and let us know. We could then add a section on how to set up lerna with the babel/TypeScript transpilation step to the official documentation.

@duhseekoh how to make it compatible for example with es6 components? We target es5 to make it work, but es5 makes tree-shaking very hard.

take a look at --module from tsc. setting it to es6 in your component packages should preserve things like import/export in the compiled code, thus enabling tree shaking.
https://www.typescriptlang.org/docs/handbook/compiler-options.html

i believe CRAs babel preset does this already if you're using that instead of tsc to do your component package build.

@duhseekoh I do know how to set target for tsc. CRA fail to consume es6 dependencies/dependencies of dependencies.

Hmm. What types of errors do you get?

I _think_ webpack might be looking for your package to have the module key set if you don't already do that. e.g. "module": "./lib/es/index.js",

We are using CRA with Lerna and TypeScript.

I order to import packages from outside "src", we use craco.

We have two apps that both use shared components, but each app has its own colour theme,
so we also made a plugin to expose Sass aliases, in order for common components to be able to use the correct colour theme from the app that is consuming the component.

craco.config.js

const cracoIncludeLernaPackagePlugin = require('./craco-include-lerna-package-plugin');
const cracoSassAliasPlugin = require('./craco-sass-alias-webpack-plugin');

module.exports = {
  plugins: [
    {
      plugin: cracoIncludeLernaPackagePlugin,
      options: {
        packages: [
          'PackageName1/src', // Contains raw TS code
          'PackageName2/src', // Contains raw TS code
      },
    },
    {
      plugin: cracoSassAliasPlugin,
      options: {
        aliases: {
          'COMMON_COLOURS': 'src/assets/styles/colours.scss',
        },
      }
    }
  ],
};

craco-sass-alias-webpack-plugin.js:

const path = require('path');

module.exports = {
  overrideWebpackConfig: ({ webpackConfig, pluginOptions }) => {

    const sassAliases = Object.entries(pluginOptions.aliases).reduce((acc, [key, value]) => {
      acc[key] = path.resolve(__dirname, value);
      return acc;
    }, {});

    const resolve = {
      ...webpackConfig.resolve,
      alias: {
        ...webpackConfig.resolve.alias,
        ...sassAliases,
      }
    };

    return Object.assign({}, webpackConfig, {
      resolve
    });
  },
};

craco-include-lerna-package-plugin.js:

const path = require('path');

module.exports = {
  overrideWebpackConfig: ({ webpackConfig, pluginOptions }) => {
    const packages = pluginOptions.packages || [];
    webpackConfig.module.rules.forEach(r => {
      if (r.oneOf) {
        r.oneOf.forEach(o => {
          if (
            typeof o.include === 'string' &&
            typeof o.loader === 'string' &&
            o.loader.endsWith(path.join('node_modules', 'babel-loader', 'lib', 'index.js'))
          ) {
            console.log('Adding Lerna packages to babel-loader include:');
            o.include = packages.reduce(
              (acc, package) => {
                const packagePath = path.resolve(__dirname, `../${package}`);
                console.log(`  * Added ${packagePath}`);
                acc.push(packagePath);
                return acc;
              },
              [o.include]
            );
          }
        });
      }
    });

    return webpackConfig;
  },
};

Is there any official word on when this feature will be available, or some progress in going on?

My two cents for those who still need to transpile a package linked from monorepo.

This is how we get it done: Lerna + TS + overrided CRA webpack with customize-cra:

our lerna look like this:

β”œβ”€β”€ packages
β”‚   β”œβ”€β”€ common       // shared code
β”‚   β”‚   └── package.json
β”‚   β”œβ”€β”€ react-app-1
β”‚   β”‚   β”‚   └── node_modules/@monorepo/common   // symlink to package/common
β”‚   β”‚   └── package.json
β”‚   └── react-app-2
β”‚       β”‚   └── node_modules/@monorepo/common   // symlink to package/common
β”‚       └── package.json
β”œβ”€β”€ package.json
└── lerna.json

config-overrides.js for each react-app-*

const { override, babelInclude } = require('customize-cra')
const path = require('path')

module.exports = override(
  babelInclude([
    path.resolve('src'),        // make sure you link your own source
    path.resolve('../common'),  // your shared module to transpile
  ]),
)

replace all react-scripts from package.json with react-app-rewired

@anthanh Do you have an example repo you can share? I've been trying out your suggestion but I'm getting errors related to parsing arrow functions in TS.

@alexnitta here you are https://github.com/anthanh/lerna-react-ts-transpile-modules

You can run the app and edit the import @monorepo/common/module1-2 or the implementation packages/common/module1/index.ts and the changes will be updated.

@anthanh wow, thanks for the speedy reply and example repo. I've got it working locally, so there's some other aspect of how my project is constructed that is complicating matters. I'll try working from your example as a starting point and see if I can identify the issue.

@anthanh It looks good, but the react component doesn't seem to work?

@lucasleelz I just have updated the PoC with a component example. I hope you find it useful.

If you use Yarn Workspaces, you can go even further with the solution of @anthanh by using get-yarn-workspaces to avoid manually specifying the paths of your packages.

config-overrides.js:

const getYarnWorkspaces = require('get-yarn-workspaces');
const { override, babelInclude } = require('customize-cra');

module.exports = override(
  babelInclude(getYarnWorkspaces())
);

IMO, this is by far the simplest solution for my needs.

If you use Yarn Workspaces, you can go even further with the solution of @anthanh by using get-yarn-workspaces to avoid manually specifying the paths of your packages.

config-overrides.js:

const getYarnWorkspaces = require('get-yarn-workspaces');
const { override, babelInclude } = require('customize-cra');

module.exports = override(
  babelInclude(getYarnWorkspaces())
);

IMO, this is by far the simplest solution for my needs.

Sadly, not easy as it looks.

For me, is not applying the same babel config for shared projects.

What is the expectation here? That create-react-app expands into component library creation? It seems like that's often the real desire throughout this issue.

create-react-app has no issues running in a monorepo. if you need to import components from another package that you maintain in that monorepo, then you need to do what every other package built for consumption does, which is compile it to remove non standard things like jsx and typescript.

you wouldn't expect that create-react-app or webpack is able to use a package from npm that hasn't gone through any transpilation, would you? it's the same case for your own workspace packages.

if you want to build a component library or any type of js-like package that a create-react-app project can consume, then it's:

  1. create new package in monorepo
  2. add a tsconfig to it
  3. configure that tsconfig to process jsx and whatever other features you may want
  4. add a build script to that package
  5. add that package as a dependency from your CRA package

No need for a fork. No need for the CRA team to expand CRA into a component library builder or to start doing heavy transpilation on node_modules.

or if you don't want to use typescript, use babel. you can even get all of the same babel configuration that react-scripts uses, because they publish it to npm. it even has a section for 'Usage outside of Create React App'. https://www.npmjs.com/package/babel-preset-react-app#usage-outside-of-create-react-app

In a real life workflow this is bad. You lost things like hot-reload. We use NWB to transpile our shared components, but everytime we save some shared component, nwb deletes the entire lib folder to re-create them, so CRA in the app project get lost, because all files was deleted at same time. Sometimes CRA can recover, sometimes not, so we have to restart CRA again. Is a hell.

Unfortunate that NWB blows everything away for a single component change.

Using babel --watch for transpilation incrementally makes file changes. CRA picks up that change from that one file in your component lib and works the same as if the file were in your app src.

Seems like if NWB is going to bill itself as a component library build framework, it shouldn't be out of the question for them to support a similar type of incremental transpilation that babel does to work with the ever popular CRA.

Unfortunate that NWB blows everything away for a single component change.

Using babel --watch for transpilation incrementally makes file changes. CRA picks up that change from that one file in your component lib and works the same as if the file were in your app src.

Seems like if NWB is going to bill itself as a component library build framework, it shouldn't be out of the question for them to support a similar type of incremental transpilation that babel does to work with the ever popular CRA.

Thank you for your fast response.

I removed nwb from my workflow and replaced with babel --watch for now.

Sadly this still adds an unnecessary transpile step due to this lack of workspaces support from CRA

Its really annoying that CRA even cares about a dev dependency like jest when building.

I ended up just adding SKIP_PREFLIGHT_CHECK as the error suggested

Fundamentally, I have this frustration with NPM in general. To build an app or library, I only need a small handful of libraries: babel, webpack, rimraf, cross-env, etc. But these all get lumped into "devDependencies", and so to build something I also end up installing cypress and jest and storybook and enzyme and mocha and chai....

My two cents for those who still need to transpile a package linked from monorepo.

This is how we get it done: Lerna + TS + overrided CRA webpack with customize-cra:

our lerna look like this:

β”œβ”€β”€ packages
β”‚   β”œβ”€β”€ common       // shared code
β”‚   β”‚   └── package.json
β”‚   β”œβ”€β”€ react-app-1
β”‚   β”‚   β”‚   └── node_modules/@monorepo/common   // symlink to package/common
β”‚   β”‚   └── package.json
β”‚   └── react-app-2
β”‚       β”‚   └── node_modules/@monorepo/common   // symlink to package/common
β”‚       └── package.json
β”œβ”€β”€ package.json
└── lerna.json

config-overrides.js for each react-app-*

const { override, babelInclude } = require('customize-cra')
const path = require('path')

module.exports = override(
  babelInclude([
    path.resolve('src'),        // make sure you link your own source
    path.resolve('../common'),  // your shared module to transpile
  ]),
)

replace all react-scripts from package.json with react-app-rewired

I suggest 1 more thing: change include section in tsconfig.json:

{
  "compilerOptions": {
  ...
  },
  "include": [
    "src",
    "../common"
  ]
}

@sluramod +1 do the same to include common folders both in config-overrides.js and tsconfig.json

I'm also getting error

../shared/lib/icons/coffee.d.ts 2:0
Module parse failed: The keyword 'interface' is reserved (2:0)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
| import React, { CSSProperties } from 'react';
> interface Props {
|     width: CSSProperties['width'];
|     height: CSSProperties['height'];

This is shared typescript package(shared) which pointed to build folder(lib) with definitions files and the warning comes for definition files. Seems skipLibCheck is ignored due to symlink nature of Lerna and yarn Workspace.

I have same kind of setup for another up and it doesn't gives errors but for this it gives errors.

My first attempt to integrate CRA with lerna was based on https://juliangaramendy.dev/monorepo-demo/

That was pretty successful and orthodox: components are built separately (before the CRA app), and then are then available as dependencies of the CRA app like any other dependency in node_modules would be. Unfortunately I found a problem with it, which was that when when I debugged the CRA app, the debugger couldn't set breakpoints in nor properly step into the TypeScript in component packages. That might (I'm not sure) be because WebPack is transpiling the node_modules which makes their source maps out of date.

So I looked for another way to do this and found it here:

The steps are:

The are various ways to implement this change to webpack.config.js (many of which are suggested in comments earlier in this thread):

Anyway I did it using react-app-rewired as described in
https://github.com/minheq/monorepo-cra-source-map

I needed to do something else (one more thing) as well: because the TypeScript source in the component packages are in src subdirectories, I had to change the import statements in the app to import from client\src instead of from client. Ideally I should have been able to, instead, fix this with baseUrl and paths in the tsconfig.json -- however these are not supported by CRA.

Given this new method (i.e. editing webpack.config.js) it's sufficient to run yarn build in the ui-react project -- its build will now also build all its dependencies. It's no longer necessary to build or pre-build the dependencies explicitly.

My example of making this change is at https://github.com/cwellsx/view/commit/8d046d9d4e76fef46d50994864d9da6e9c365c1d

My first attempt to integrate CRA with lerna was based on https://juliangaramendy.dev/monorepo-demo/

That was pretty successful and orthodox: components are built separately (before the CRA app), and then are then available as dependencies of the CRA app like any other dependency in node_modules would be. Unfortunately I found a problem with it, which was that when when I debugged the CRA app, the debugger couldn't set breakpoints in nor properly step into the TypeScript in component packages. That might (I'm not sure) be because WebPack is transpiling the node_modules which makes their source maps out of date.

There's another issue here #8071 discussing it along with a pull request, if you would like to check it out and upvote it.

@alexnitta here you are https://github.com/anthanh/lerna-react-ts-transpile-modules

You can run the app and edit the import @monorepo/common/module1-2 or the implementation packages/common/module1/index.ts and the changes will be updated.

This worked well for me. Saved my tail.

@cwellsx I'm doing the same thing to import shared packages into my CRA package, and this works beautifully. But one thing I'm struggling with is getting absolute import paths working within my shared package.

I can use absolute imports in my CRA app by setting "baseUrl": "./" in tsconfig.json. This allows me to write import statements like this within the CRA package: import { Foo } from 'src/components/Foo'.

I'd also like to do this within my shared package, but updating baseUrl in tsconfig.json within the shared package obviously doesn't work since babel doesn't read that file.

Is there another way to get absolute import paths working within shared packages?

Successful setup:

  • All source code acts like it is part of the main project:
  • hot loading works great with changes anywhere
  • vscode acts like all the source code is all part of the same codebase: navigation, refactoring, etc. works as intended, (even auto imports works most of the time)

Using same setup as:

https://github.com/anthanh/lerna-react-ts-transpile-modules/tree/master/packages/webapp

  • modify package.json

    • change scripts to use react-app-rewired

    • add react-app-rewired and customize-cra devDependencies

  • add config-overrides.js

    • add packages paths to babel config

Also:

  • Yarn workspaces v2
  • tsconfig paths (note, we are using a module prefix @lib/ to make it easy to distinguish package usage)
{
    "compilerOptions": {
        "paths": {
            "@lib/utils/*": [
                "packages/utils/*"
            ],
            "@lib/utils-react/*": [
                "packages/utils-react/*"
            ],
        }
    }
}
  • typescript packages: Don't use src folder in your packages, just put the source files at the root. This allows the import without the 'src' everywhere. It's not that bad really, it just means there is an extra package.json file sitting in the middle of the source files.
  • Bonus: We are actually also using a git submodule with nested yarn workspaces since most teams work in their own repos with out current setup - but this allows us to share code easily, while still having our own independent repos

@ricklove thanks. do you mind putting all that in a repo? I think it would help a lot of people.

@ricklove thanks, i was just able to make my lerna monorepo work following those same steps!
Also, thanks to your description i also found this great tutorial from 2019 that teachs, in more detail, how to make all that:
https://medium.com/@Notif.me/extract-your-react-components-as-dependencies-in-a-lerna-cra-project-without-ejecting-cf76236ffe82

Anyway, it would be great if create-react-app could make it easier to add those customizations without requiring those external packages.

πŸ˜„

would be fantastic to have this feature. I followed the same tutorial that @raphaelpc followed and it does the job, but it doesn't feel right to change the start script to "start": "react-app-rewired start". Would be really keen to work on this, but it would be my first contribution to a big open source project πŸ™ˆ.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

fson picture fson  Β·  3Comments

wereHamster picture wereHamster  Β·  3Comments

fson picture fson  Β·  3Comments

alleroux picture alleroux  Β·  3Comments

dualcnhq picture dualcnhq  Β·  3Comments