Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Lerna and/or Yarn Workspaces #1333

Open
rovansteen opened this issue Jan 1, 2017 · 195 comments
Open

Support Lerna and/or Yarn Workspaces #1333

rovansteen opened this issue Jan 1, 2017 · 195 comments

Comments

@rovansteen
Copy link
Contributor

@rovansteen rovansteen commented Jan 1, 2017

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.

@rgbkrk
Copy link

@rgbkrk rgbkrk commented Feb 11, 2017

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

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

🎉

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

@gaearon
Copy link
Member

@gaearon gaearon commented Feb 11, 2017

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

@rgbkrk
Copy link

@rgbkrk rgbkrk commented Feb 11, 2017

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.

@rgbkrk
Copy link

@rgbkrk rgbkrk commented Feb 11, 2017

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.

@jedrichards
Copy link

@jedrichards jedrichards commented Sep 7, 2017

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?

@rgbkrk
Copy link

@rgbkrk rgbkrk commented Sep 7, 2017

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

@tbillington
Copy link

@tbillington tbillington commented Sep 8, 2017

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

@ashtonsix
Copy link
Contributor

@ashtonsix ashtonsix commented Nov 29, 2017

@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

@shendepu
Copy link

@shendepu shendepu commented Dec 26, 2017

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

My folder structure is

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

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

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

@bradfordlemley
Copy link
Contributor

@bradfordlemley bradfordlemley commented Jan 11, 2018

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

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

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

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

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

@leandrocrs
Copy link

@leandrocrs leandrocrs commented Apr 22, 2020

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.

@leandrocrs
Copy link

@leandrocrs leandrocrs commented Apr 22, 2020

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.

@duhseekoh
Copy link

@duhseekoh duhseekoh commented Apr 22, 2020

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.

@leandrocrs
Copy link

@leandrocrs leandrocrs commented Apr 22, 2020

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

@JeremyGrieshop
Copy link

@JeremyGrieshop JeremyGrieshop commented Apr 23, 2020

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

@sluramod
Copy link

@sluramod sluramod commented May 1, 2020

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"
  ]
}
@ambroseus
Copy link

@ambroseus ambroseus commented May 1, 2020

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

@iamchathu
Copy link

@iamchathu iamchathu commented Jun 2, 2020

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.

@cwellsx
Copy link

@cwellsx cwellsx commented Jun 8, 2020

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 cwellsx/view@8d046d9

@JeremyGrieshop
Copy link

@JeremyGrieshop JeremyGrieshop commented Jun 8, 2020

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.

@geoyws
Copy link

@geoyws geoyws commented Jun 10, 2020

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

@calvinwyoung
Copy link

@calvinwyoung calvinwyoung commented Jun 23, 2020

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

@ricklove
Copy link

@ricklove ricklove commented Jul 10, 2020

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
@farah
Copy link

@farah farah commented Jul 11, 2020

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

@raphaelpc
Copy link

@raphaelpc raphaelpc commented Jul 13, 2020

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

😄

@jamiehaywood
Copy link

@jamiehaywood jamiehaywood commented Sep 12, 2020

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

@Jekins
Copy link

@Jekins Jekins commented Mar 16, 2021

This is my solution:

Add this to the end of /config/paths:

module.exports.appLernaModules = []
const folderLernaPackageName = '@my-monorepo' // this is the name of your packages folder in package.json. For example: name: '@my-monorepo/utils'
const lernaRoot = path.resolve(module.exports.appPath, '../../')
const lernaPackages = path.join(lernaRoot, 'packages') // this is the name of your packages folder in monorepo

const regexpFolderLernaPackage = new RegExp(`^${folderLernaPackageName}\\/`, 'g')
const lernaPackagesFromDependencies = Object.keys(
  require(resolveApp('package.json')).dependencies
)
  .filter((packageName) => packageName.startsWith(folderLernaPackageName))
  .map((packageName) => packageName.replace(regexpFolderLernaPackage, ''))

fs.readdirSync(lernaPackages).forEach((folderName) => {
  const isDependencyNeed = lernaPackagesFromDependencies.includes(folderName)

  if (isDependencyNeed) {
    module.exports.appLernaModules.push(path.join(lernaPackages, folderName))
  }
})

Replace this in /config/webpack.config.js:

// before
{
  include: paths.appSrc
}

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

Thanks for the idea @ashtonsix

@baerrach
Copy link

@baerrach baerrach commented Mar 16, 2021

After

yarn eject

I stopped reading.

@ricklove
Copy link

@ricklove ricklove commented Mar 26, 2021

That feeling you get when you have the same problem a year later and find an answer written by your past self. Thanks past @ricklove ! Note: your memory is not great.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
2.0
  
Needs Decision
Linked pull requests

Successfully merging a pull request may close this issue.

None yet