Monorepos: Lerna, TypeScript, CRA and Storybook combined
Jonathan Schneider

Jonathan Schneider @shnydercom

About: Interested in adaptable UX/UI, tools for linking (local) graph data and using Linked Data

Location:
Barcelona
Joined:
Sep 23, 2019

Monorepos: Lerna, TypeScript, CRA and Storybook combined

Publish Date: Oct 31 '19
144 32

Let’s be lazy:

repository on github
That’s the code for the starter repository.
Also made this repository a template repository
image of a template button

This post details why, how to prevent errors and how to do it yourself. It is useful if you want to set up a monorepo for an existing codebase, or if you run into errors when extending your monorepo.

Updated to use react-scripts v4.0.2!

With this update, the template contains:

  • the latest React@^17.0.1 and storybook
  • some example stories and components in the UI library part
  • those components can use css and scss, and CSS gets built into the output folder, along with type definitions
  • modifying the UI library triggers a storybook hot reload, building the UI library triggers a CRA hot reload

So, for the not-so-lazy:

If you've been using ReactJS in more than one project or are building multiple Apps, you've probably come across lerna already. Since setting up webpack can be tricky, the choice is usually to use create-React-app as long as possible. So we're going to look at how this works with a centralised TypeScript config that we'll also use for our ui component library, which we'll put in a separate repository. We’ll use yarn since we’ll make use of yarn workspaces as well.

yarn init

a private package as the root of our monorepo. Why private? Because private packages don’t get published to npm, our root is only there for organizing everything, and lastly defining yarn workspaces only works in a private package.

Lerna logo

Introducing: Lerna

First of all, you’ll need to install lerna, and while you can do that globally, I recommend installing it in your monorepo unless you (and the contributors to your monorepo) want to author lots of monorepos with lerna and it’s part of your standard toolset.

yarn add lerna -D

Now we have lerna, which gives us organization tools for monorepos. For example initialization:

yarn lerna init

This will create a lerna.json file and a packages folder. Since we’ll use yarn workspaces, we need to define yarn as our npmClient and set useWorkspaces to true. Our lerna.json will end up looking like this:

{
  "packages": [
    "packages/*"
  ],
  "version": "0.0.0",
  "npmClient": "yarn",
  "useWorkspaces": true
}
Enter fullscreen mode Exit fullscreen mode

And that is all the configuration we need for lerna.

Since we’re using yarn workspaces, we need to modify our package.json, by adding:

"workspaces": [
    "packages/*"
  ],
Enter fullscreen mode Exit fullscreen mode

Note: your packages-folder doesn’t need to have that name. You could also have your ui-lib, apps and server code in different subfolders. For using workspaces and lerna together, you should however define them in both lerna.json and package.json.

Storybookjs logo

Project Setup: UI component library package

Initializing sub-packages in monorepos is pretty similar to normal repos, with one thing to note when setting the name. You just change into the directory:

cd packages && mkdir my-monorepo-ui-lib && cd my-monorepo-ui-lib

And initialize a package:

yarn init

But with the name @my-org/my-monorepo-ui-lib. This is using a feature called npm organization scope and requires you to set up an organization with npmjs.com if you want to publish as the @my-org organization.

This is not mandatory, but it shows a source for bugs when we’re developing monorepos:

  • The package name isn’t always the same as the directory name
  • Configuration files and script parameters sometimes need a package name, sometimes a directory name
  • You can use this syntax even if you never intend to publish

Quick and dirty package installation

We want to build reusable react components in our UI library, but later our create-react-app package will determine which version of react we will use. That’s why react and react-dom can only be a peerDependency in the UI library. Storybook is our way to quickly try out our react components, so we’ll add that as a devDependency:

yarn add react react-dom -P
yarn add @storybook/react babel-loader -D

This is how we’ve always been doing it, right? Turns out, now there’s a node_modules folder in our ui-lib package, with react, react-dom and @storybook inside. But we wanted to have our packages at the root, so lerna will help us do that from the root package:

cd ../..
yarn lerna bootstrap

Now there’s a node_modules folder at the root, containing react, react-dom and @storybook. The node_modules folder inside our ui-lib package is still there, it contains a .bin-folder with storybook’s command line (bash/cmd) scripts for starting and building. All tools executing command line scripts such as storybook, tsc and create-react-app are not necessarily aware that they’re run in a monorepo, they execute commands on the operating system and are usually built for “normal” npm repos.

Troubleshooting bash and cmd scripts: storybook, tsc, react-scripts

Inside ui-lib, if we try to run

yarn start-storybook

it will execute the script but tell us that we have no storybook configuration file yet:

Create a storybook config file in "./.storybook/config.{ext}

We get the same error if we add it as a script in ui-lib’s package.json (naturally):

  "scripts": {
    "story": "start-storybook" 
  },
Enter fullscreen mode Exit fullscreen mode

Let’s fix that error by creating the file packages/my-monorepo-ui-lib/.storybook/config.js

import { configure } from '@storybook/react'

const req = require.context('../src', true, /\.story\.(ts|tsx)$/)

configure(() => {
  req.keys().forEach(filename => req(filename))
}, module);
Enter fullscreen mode Exit fullscreen mode

and packages/my-monorepo-ui-lib/src folder, that can be empty for now. Inside our ui-lib, running

yarn start-storybook

and

yarn story

works fine now, although it’s empty.

The difference becomes clear once we go to the root and try to run command line scripts from there:

cd ../..
yarn start-storybook

and we have the same error as before. The reason is that the node_modules-folder at the root also contains the command line script, and tries to look for a storybook config relative to the root package. Lerna will help us here as well, at the root we can call

yarn lerna run story --stream

That command will run ‘story’ relative to all packages in parallel, and ‘stream’ the script output to the console. This only works for so-called ‘lifecycle scripts’, i.e. scripts defined in one of the sub-packages' package.json, so the following command will not work:

yarn lerna run start-storybook

This is also the reason you’ll see scripts defined such as

"tsc": "tsc",

but it’s generally better to choose a different name to avoid confusion, especially because a lot of people install tsc and other tools globally.

initial App preview

Project Setup: CRA App

Take caution when using CRA for new packages in combination with yarn workspaces:

cd packages
create-react-app my-monorepo-cra-app

This will throw an error, since CRA copies files out of the node_modules folder where it’s installed in (here: packages/my-monorepo-cra-app/node_modules), while yarn workspaces make sure everything gets installed in the root-node_modules-folder. So in the root package.json delete

  "workspaces": [
    "packages/*"
  ],
Enter fullscreen mode Exit fullscreen mode

and add it back in after you’ve run CRA. Then in the root folder run

yarn lerna bootstrap

and your dependencies will neatly be moved to the root-node_modules. Running

yarn lerna run start --stream

will start your CRA-App, the JavasScript version of it.

Typescript logo

Adding Typescript

Monorepos can help centralize configuration, so we’ll create a general tsconfig.json at the root of our monorepo. It would be great if we could use that in every subproject, but CRA needs to make some assumptions about its TypeScript setup, so it adds/overwrites the values inside tsconfig. That’s also good news, since it doesn’t just overwrite the file - and we can extend from another tsconfig. In our library project on the other hand we are more free, we can change the webpack there if we have to.

How to structure your typescript-configurations

This decision depends on how many packages and what types of typescript-packages you want in your monorepo:

  • One CRA App, one UI library: Go for
    • one tsconfig.json at the root with cosmetic settings like removeComments; settings that don’t conflict with CRA and which aren’t library-specific, like library export
    • one extending from that, autogenerated in your CRA package
    • Lastly one for your library that sets “outDir”:”lib” and configures declaration export. This needs to correspond with the settings in the lib’s package.json:
  "main": "./lib/index.js",
  "types": "./lib/index.d.ts",
Enter fullscreen mode Exit fullscreen mode
  • Many CRA Apps: Same structure as the one above. The reason is, that right now using CRA means that you’ll have to recompile your library to make changes in your CRA App. When running react-scripts start though, the node_modules-folder is also being watched, so you can run tsc in your library in watch mode after starting CRA
  • Many libraries: Create an additional tsconfig.lib.json at the root, where you generalize your export settings. If one of your libraries depends on another one of your libraries, have a look at typescripts path-mapping and project references features

Apart from typescript, create-react-app supports css, scss and json-imports out of the box with just a little bit of configuration. We’ll add a typings.d.ts-file at the root for those types, so those file types are importable by default:

declare module "*.json" {
  const value: any;
  export default value;
}
declare module '*.scss' {
  const content: any;
  export default content;
}
declare module '*.css' {
  interface IClassNames {
    [className: string]: string
  }
  const classNames: IClassNames;
  export = classNames;
}
Enter fullscreen mode Exit fullscreen mode

This is the minimal tsconfig.json we could work with:

{
  "exclude": ["node_modules"],
  "files":  ["./typings.d.ts"],
  "compilerOptions": {
    "jsx": "react",
    "esModuleInterop": true,
    "skipLibCheck": true
  }
}
Enter fullscreen mode Exit fullscreen mode

We want to use typescript in all our packages, which is done by the lerna add command:

yarn lerna add typescript -D

We include skipLibCheck as well, because we want tsc to run fast.

UI-library with storybook and typescript

When structuring our UI library, it’s good to follow a consistent pattern. The goal is to just run ‘tsc’ and have working Javascript, no webpack needed if we can avoid it by clear structure.

It’s especially important to:

  • Separate concerns by usage (utils in one folder, React components in another)
  • Prevent cyclic imports/exports (utils exported before react components - if you use factories don’t put them in utils, export them after react components)
  • Make it easy for the next person to extend the library (group your react component with its story and its unit test)

So your folder structure may end up looking like this:

index.ts in every folder, *.spec.ts and *.story.ts in component folders

Any file named index.ts is either a leaf in the file tree and exports unit-tested code or is a branch and exports its subfolders. Unit-tests and stories are not exported and their files can be excluded from the compiled code via configuration. Here’s an example of what the files may look like:

export * from utils; export * from './myuihelper'

However, we do need webpack for one thing: Storybook’s configuration for typescript. And since we’re at it, we can add support for scss and some file types as well.

cd packages/my-monorepo-ui-lib
yarn add @babel/core @types/storybook__react awesome-typescript-loader babel-loader node-sass sass-loader source-map-loader style-loader -D

Bootstrapping is not needed because we’re using yarn workspaces, and our packages can be found at the root’s node_modules folder.

Directly adding it inside the package is a workaround for an error in lerna add in combination with organization scopes:

lerna WARN No packages found where @babel/core can be added

The cleaner option would be to use lerna add with the --scope parameter, however this has been incompatible with how we set the organisation scope. The command would be:

yarn lerna add @babel/core @types/storybook__react awesome-typescript-loader babel-loader node-sass sass-loader source-map-loader style-loader --scope=@my-org/my-monorepo-ui-lib -D

Are you wondering, what the --scope-parameter is all about?
Here, --scope is the installation scope parameter, @my-org the npmjs-organization scope. So all those packages will be added to our UI library package.

file webpack.config.js inside packages/my-monorepo-ui-lib/.storybook

Our UI lib’s webpack config is comparatively short:

const path = require('path');
module.exports = {
  module: {
    rules: [{
        test: /\.scss$/,
        loaders: ["style-loader", "css-loader", "sass-loader"],
        include: path.resolve(__dirname, '../')
      },
      {
        test: /\.css/,
        loaders: ["style-loader", "css-loader"],
        include: path.resolve(__dirname, '../')
      },
      {
        enforce: 'pre',
        test: /\.js$/,
        loader: "source-map-loader",
        exclude: [
          /node_modules\//
        ]
      },
      {
        test: /\.tsx?$/,
        include: path.resolve(__dirname, '../src'),
        loader: 'awesome-typescript-loader',
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf|svg)$/,
        loader: "file-loader"
      }
    ]
  },
  resolve: {
    extensions: [".tsx", ".ts", ".js"]
  }
};
Enter fullscreen mode Exit fullscreen mode

And we could use a minimal tsconfig.json that just extends from our root tsconfig.json, and puts the output in the lib-folder:

{
  "include": [
    "src"
  ],
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "outDir": "lib",
    "declaration": true
  }
}
Enter fullscreen mode Exit fullscreen mode

This allows us to compile typescript-files and run storybook, but we want to do more! (to do less later on...)

For our library project, we need to emit declaration files (the files ending in *.d.ts). Otherwise we’ll receive errors such as:

Could not find a declaration file for module '@my-org/my-monorepo-ui-lib'. '.../lerna-typescript-cra-uilib-starter/packages/my-monorepo-ui-lib/lib/index.js' implicitly has an 'any' type.
my-monorepo-cra-app: Try npm install @types/my-org__my-monorepo-ui-lib if it exists or add a new declaration (.d.ts) file containing declare module '@my-org/my-monorepo-ui-lib'; TS7016

For clarification: Webpack isn’t used in our build-process, tsc is. The Webpack we’re configuring is used by storybook.

Typescript with CRA

The limits of centralizing our typescript configuration is determined by create-react-app’s use of typescript. At the time of writing this article, switching a CRA App from Javascript to Typescript is done by changing the index.js file to index.tsx and adding all the needed dependencies. Check CRA’s documentation for changes: https://create-react-app.dev/docs/adding-typescript

Inside our CRA-package, we run

yarn add typescript @types/node @types/react @types/react-dom @types/jest -D

then we copy our minimal tsconfig.json from the ui-lib over to the CRA App package. If we run

yarn start

Now, CRA’s compilerOptions will be added to our tsconfig.json.

Loading a component from our UI library

Now it’s time to load our UI library into our CRA App, it will be installed by running:

yarn lerna add @my-org/my-monorepo-ui-lib

But as you might have noticed, we haven’t done much build setup for the library yet. Why didn’t we do that earlier? The reason is pretty simple: CRA, lerna and Storybook are evolving, and so is typescript, npm and even Javascript. And with ES6 modules, we have a powerful new feature built into the language, replacing earlier module management solutions. The only problem is that it’s not 100% adopted, but as we want to be a good library provider, we offer a fallback. So let’s export our library to ES6 modules - and an “older” module management system. Otherwise we’ll run into errors such as:

Unexpected token “export”

If you want to deep-dive into that topic, this blog about nodejs modules and npm is a good start.

Npm as our package management solution has also been around since before ES6 and typescript’s rise, so we can set different entry points for our library project inside package.json:

  • “main” is the oldest one, it’ll point to our pre-ES6 export (“./lib/index.js”)
  • “types” is the place where our type declarations can be found ("./lib/index.d.ts")
  • “module” is the entrypoint for our ES6 modules ("./lib-esm/index.js")

Our project is written in typescript from the start, so we’re bundling the declarations with our package. If you’ve seen yourself importing @types-packages, this is because those projects are written in Javascript at the core, and type definitions have been added later on.

So we set a tsconfig.esm.json up to export as an ES6 module:

{
  "include": [
    "src"
  ],
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "outDir": "lib-esm",
    "module": "esnext",
    "target": "esnext",
    "moduleResolution": "node",
    "lib": ["dom", "esnext"],
    "declaration": false
  }
}
Enter fullscreen mode Exit fullscreen mode

This does the following:

  • Our modules will go into the lib-esm-folder, which we specified as our module-entrypoint in package.json.
  • Our module resolution strategy is “node”. If we don’t set it we’ll get an error such as:

src/index.ts:1:15 - error TS2307: Cannot find module './utils'.
1 export * from './utils';

This way, our library has one export for the latest Javascript features and one that is downwards compatible, so our library can have a bigger range of consumers. Note that for our own final App, CRA uses babel under the hood for compatibility in different browsers.

We’re already emitting our declarations in the lib-folder, so we won’t emit them another time here.

Finally, we’ll add a library-build-script in our library package.json:

"libbuild": "tsc && tsc --build tsconfig.esm.json"
Enter fullscreen mode Exit fullscreen mode

And we’re ready to add our library package to our CRA package. We can set a wildcard for the package version so that it’s always going to be the latest version.

"dependencies": {
    "@my-org/my-monorepo-ui-lib": "*",
Enter fullscreen mode Exit fullscreen mode

In our CRA App we can now add the component from the library, fully type-checked:

<MyNewComponent text= in a react render function"/>

And because monorepos should make our lifes easier, we’ll add scripts in our root-package.json to start storybook, and execute the library build before starting our CRA app:

  "scripts": {
    "story": "lerna run story --stream",
    "prestart": "lerna run libbuild --stream",
    "start": "lerna run start --stream"
  }
Enter fullscreen mode Exit fullscreen mode

This will hopefully prevent the most common errors you can run into with this monorepo-setup. If you have additional tips, feel free to add them in the comments!

Comments 32 total

  • Anil Kumar Pandey
    Anil Kumar PandeyNov 1, 2019

    Hi @shnyder, first of all very very thanks for writing this, it is very clean, understandable and helpful.

    I ran the script through github and it works perfectly fine, but now I want build my component using the React Material UI (material-ui.com/), so what is the best way to add this dependency?

    Should I directly run the yarn add @material-ui/core in the root of the project? How it will add the dependency to storybook and cra app?

    • Jonathan Schneider
      Jonathan Schneider Nov 1, 2019

      Both, you can also use yarn lerna add @material-ui/core --scope=@my-org/my-monorepo-ui-lib at the root. In my experience it works well for single packages, if you want to add many it often doesn't.

      Then you can import material ui Components in your own component inside the UI lib, e.g.

      import {Button} from '@material-ui/core';
      

      and run yarn story for adjusting it in storybook. Because it's a sub-dependency of the UI lib package, you'll have it included when you run CRA with yarn start as well.

      • Anil Kumar Pandey
        Anil Kumar PandeyNov 1, 2019

        Very Very thank you for the quick reply, I tried it and it is working fine.

        Can you please share how do I create the builds. Dev (minify with js map files) and Prod (complete minify) ?

        Please suggest me something to achieve this.

  • Anil Kumar Pandey
    Anil Kumar PandeyNov 2, 2019

    Hi @shnyder,

    The component CSS files are not getting copied from my-monorepo-ui-lib to my-monorepo-cra-app. So I am getting this error.

    ../my-monorepo-ui-lib/lib-esm/components/card/card.js
    Module not found: Can't resolve './card.css

    Thanks,
    Anil

    • Jonathan Schneider
      Jonathan Schneider Nov 4, 2019

      Since you're using Material UI, I recommend you have a look at how it does styling with JSS, their approach to theming, etc: material-ui.com/styles/basics/

      The different approaches to bundling css for libraries would be a good topic for another blog post, I deliberately chose not to cover that here, to focus on monorepo-structure more. In the end, CRA will split your css in chunks when building. So the question is how your library users will consume that. If your CRA App is the only consumer, I'd recommend importing css only in the *.story-files, keeping it out of your components. In your CRA App, add an scss file that imports all the css you need, e.g. @import "../../ui-lib/src/components/yourfile.css. This is more a practical approach than a clean approach, but it separates concerns and will thus continue to work, even if there's a version update to create-react-app.

      If you want to publish your library, see which CSS-with-javascript combination you're using (JSS, CSS Modules, ...) and check for the recommended build process.

  • krishnaSpotnana
    krishnaSpotnanaNov 22, 2019

    We could have used lerna without yarn workspaces as well here? Why they are being used together here? Please provide insights on same.

    • Jonathan Schneider
      Jonathan Schneider Nov 26, 2019

      We're combining the benefits of the two: yarn workspaces does the package/dependency management on the level of the package manager, which you'd otherwise do with lerna's --hoist flag. You can execute some commands monorepo-wide with yarn workspaces as well. If you want to do that, here's a good article that does just that.

      However, lerna is a toolset that provides higher-level capabilities e.g. for diffing your packages' versions in different ways, so it reduces the difficulty of managing a monorepo. Here are some more details, and how performance is affected.

      • krishnaSpotnana
        krishnaSpotnanaApr 27, 2020

        Thanks for this great article. Why do we have lib-esm and lib folders. What happens If I remove lib folder? Just curious.

        • Jonathan Schneider
          Jonathan Schneider Apr 27, 2020

          we're building against two target versions of javascript, one with ECMA Script Modules ('esm'), the other with a (one of the) module system that existed before. When people start using your library, the different entrypoints to your lib that are defined in package.json make sure that you don't break compatibility. Check out the media coverage of the 'is-promise' library if you want to see what impact a change to the module system can have ;)

      • smriti-spotnana
        smriti-spotnanaApr 27, 2020

        Doesn't learna boostrap do the hoisting for us, I was thinking? - bcoz I think it would shift the dependencies from individual package to root? but, it also does npm install in all packages, I think.

  • Anil Kumar Pandey
    Anil Kumar PandeyDec 5, 2019

    Hi @shnyder,

    I am trying to do the CI/CD pipelining, so added this script into gitlab but it is getting failed. Please let me know if there any issue in script.

    stages:  
       - build  
       - deploy
    
    build prod:  
       image: node:10.15.0-stretch  
       stage: build  
       script:
          - yarn global add lerna jest
          - yarn global add react
          - yarn global add typescript
          - lerna exec -- npm install
          - lerna bootstrap
          - yarn build
       artifacts:    
          paths:          
             - build/    
          expire_in: 10 days
    deploy prod:  
       image: python:latest  
       stage: deploy 
       script:    
          - pip install awscli    
          - aws s3 sync ./build s3://BUCKET_NAME --acl public-read 
    
    Enter fullscreen mode Exit fullscreen mode
    • Andres Guerrero
      Andres GuerreroDec 12, 2019

      Hi @anil

      Do you achieve to do the CI/CD pipelining? I'm using gitlab too and i would be helpfull if you explain me what you did..

      Thanks in advanced!

    • Jonathan Schneider
      Jonathan Schneider Dec 22, 2019

      Hi @anil ,

      what's the output of your gitlab build log?

      In this post I tried to focus on the basic monorepo setup, to get a clean local dev environment with as many "defaults" as possible (so as little config as possible). It would be great to connect this post to a default config for a (gitlab) CI/CD-pipeline, so what's your experience with it so far?

  • FlatPenguin
    FlatPenguinFeb 11, 2020

    Hi @shnyder,

    This is a very good article and helping me a lot in understanding how to setup a Lerna, CRA, Typescript, and Storybook monorepo.

    But one question I have, how would we incorporate pre-styled components into our Storybook? I would like to use Sass to give all of my shared components a default styling that could then be overridden by the client application if desired. So I'm assuming we would need to use Webpack at this point to build our lib directories instead of using tsc directly?

    Sorry, thanks for your patience and time. I'm very new to front-end development and still trying to piece all the different technologies together.

    • Jonathan Schneider
      Jonathan Schneider Mar 19, 2020

      Hi @FlatPenguin,

      sorry for the late reply. If you're new to front-end development and don't want to use one of the css-in-js solutions have a look at the BEM-Notation for naming styles. It helps you structure your styles early on, even if you don't follow it 100%. With node-sass you can actually compile the sass into css and ship it with the rest of your library, so no need to ship scss

  • Patrik Keinonen
    Patrik KeinonenMar 18, 2020

    Hi @shnyder

    I'd like to leverage intellisense with TypeScript and VS Code. With the current, it's building, but VS Code does not find the reference to the shared components.

    • Jonathan Schneider
      Jonathan Schneider Mar 19, 2020

      did you build your most recent code as a library? When you run "yarn start" in the CRA-project it will look into your node_modules folder for a library build. There should be a symlink to your ui-lib-project folder, but the package.json of the UI-lib only tells the "outside world" where to find the main javascript and typescript-files that you've built, not the content of its /src-folder:
      "main": "./lib/index.js",
      "module": "./lib-esm/index.js",
      "types": "./lib/index.d.ts",

  • smriti-spotnana
    smriti-spotnanaApr 27, 2020

    What happens if i remove main from the package.json and just keep lib-esm and provide it's access via module? and then import this package in another package via import (and not require())? Will this fail in older browsers? hence, I must keep both lib and lib-esm? - Also, I read on the internet that main is used when we import this package using require() and module is used when we import it using ES6 import? is this correct? is this the only relevance of main and module? I don't think so though, but couldn't find more on it. Thank you!

    • Jonathan Schneider
      Jonathan Schneider Apr 27, 2020

      yes, javascript has evolved over the years and since version 6 supports modules out of the box. NPM has started as a package manager for node.js, so there were module systems before ES6 that solved the needs of the backend more or the frontend. If you're using typescript I would go for import-statements instead of require(). You're saving yourself some time if you start building with typescript, let babel take care of downwards compatibility (Babel is what's used in create-react-app under the hood) and learn the details of the javascript ecosystem later on =)

      • smriti-spotnana
        smriti-spotnanaApr 27, 2020

        so, I have package1 and CRA. CRA imports package1. and package1's package.json just have module: "lib-esm/index.js" and no main. and CRA is in typescript. So, when babel (which react uses internally) will build my code, will it also provide downward compatibility for the es6 code I had imported = package1 = available as ES6 and not ES3 bcoz I am only exposing "lib-esm/index.js"? - so, I can safely do this in my monorepo right? w/o worrying about browser compatibility issues when running my CRA app?

        • Jonathan Schneider
          Jonathan Schneider May 1, 2020

          good question. I haven't tested your particular case yet, but I'd recommend having both module and main if your app is build against esm and before: The problem here is that it depends on which code babel runs on. If you're running a full build, then dependencies are baked (and minified etc) into the different chunks for your app. If you're just running in development, it might pull in modules, and you can run into problems for example when an es6-class (which is native to the browser) is extended like a non-native class (because js didn't always have classes). Somewhere I read a statement from the CRA team saying that they try to create builds for the largest common denominator of configurations (Browser/OS) out there - so that means ES6 modules and classes, because both are pretty standard now

  • smriti-spotnana
    smriti-spotnanaApr 27, 2020

    isn't yarn lerna run test equivalent to yarn workspaces run test? - when to use what? and why should we use yarn workspaces at all if lerna is able to do all that for us?

  • smriti-spotnana
    smriti-spotnanaApr 28, 2020

    What if I just keep lib and don't expose/create lib-esm at all? - bcoz CRA is anyway getting transpiled by babel? hence, can do away with lib-esm?

    Thank you

  • Pedro Paulo Almeida
    Pedro Paulo AlmeidaMay 7, 2020

    Hello. Thank you so much for this very good article.

    After install / bootstrap the dependencies using lerna bootstrap and run lerna run start --parallel on the root folder, the CRA package complains like this:

    
    There might be a problem with the project dependency tree.
    It is likely not a bug in Create React App, but something you need to fix locally.
    
    The react-scripts package provided by Create React App requires a dependency:
    
      "babel-loader": "8.0.6"
    
    Don't try to install it manually: your package manager does it automatically.
    However, a different version of babel-loader was detected higher up in the tree:
    etc..
    
    Enter fullscreen mode Exit fullscreen mode

    This is an old and known common error when starting with CRA: we shouldn't add babel-loader AND react-scripts at the same time. But, yeah, I know that babel-loader is there because of my-monorepo-ui-lib package. So, I'm kinda stuck in this error... do you have any suggestion?

    Thank you so much for your time.

    • Jonathan Schneider
      Jonathan Schneider Feb 15, 2021

      Hi Pedro,
      I know it's a bit late, but with the update to react-scripts 4.x and storybook, this error should be gone. It came from a version-mismatch of the babel-loader, because CRA comes with one version, but the older storybook needed another one to function. Storybook has really shortened the list of dependencies it needs to run, and babel-loader is still in the dependency tree, but not explicitly.

      • Joe Shelby
        Joe ShelbyAug 14, 2021

        and today it is back - this time complaining that CRA supports only 8.1 while the one installed was 8.2.2.

        • Jonathan Schneider
          Jonathan Schneider Aug 14, 2021

          Hi Joe, thanks for pointing that out. I just fixed it by adding a

            "resolutions": {
              "babel-loader": "8.1.0"
            },
          
          Enter fullscreen mode Exit fullscreen mode

          to the main package.json. That feature is not so old, and the version needs to be adjusted to the one in the CRA error message. That's unfortunately the situation for now, both storybook and CRA have open issues for that topic. I'm happy to accept Pull Requests or Issues once the version needs to be bumped again =) But I hope the underlying dependency-mismatch gets fixed

  • Martin McNeela
    Martin McNeelaJun 8, 2020

    Hi @shnyder this is a great read and really helped me get my head around monrepo's.

    I have followed all the steps and have a working project. However I have two small issues I hope you can help with:

    1) --stream (or any other flag) doesn't work with "yarn lerna run start --stream", any ideas why this would be?
    2) I want to be able to change my components in the ui-lib and have the cra app update with them (currently I have to restart the server), is this possible? how would I begin to make that work?

  • Negin Basiri
    Negin BasiriOct 28, 2020

    Hi, I don't need ui-lib I only have CRA-app. I am getting this error
    qterra-app: $ react-scripts start
    qterra-app: /Users/nbasiri/projects/mono-qterra/node_modules/react-scripts/scripts/utils/verifyTypeScriptSetup.js:231
    qterra-app: appTsConfig.compilerOptions[option] = suggested;
    qterra-app: ^
    qterra-app: TypeError: Cannot add property noFallthroughCasesInSwitch, object is not extensible
    qterra-app: at verifyTypeScriptSetup (/Users/nbasiri/projects/mono-qterra/node_modules/react-scripts/scripts/utils/verifyTypeScriptSetup.js:231:45)
    qterra-app: at Object. (/Users/nbasiri/projects/mono-qterra/node_modules/react-scripts/scripts/start.js:31:1)
    qterra-app: at Module._compile (internal/modules/cjs/loader.js:1133:30)
    qterra-app: at Object.Module._extensions..js (internal/modules/cjs/loader.js:1153:10)
    qterra-app: at Module.load (internal/modules/cjs/loader.js:977:32)
    qterra-app: at Function.Module._load (internal/modules/cjs/loader.js:877:14)
    qterra-app: at Function.executeUserEntryPoint as runMain
    qterra-app: at internal/main/run_main_module.js:18:47
    qterra-app: error Command failed with exit code 1.

    Can you please help how to fix this error?

  • Jonathan Schneider
    Jonathan Schneider Feb 15, 2021

    Hi everyone,
    with the update to react-scripts and storybook some things are simpler in the template. I've included basic scss/css compilation as part of the library build process. CRA supports both out of the box, so it's better if the monorepo-template does so as well.

    I'm surprised there is still so much interest, so I want to ask - what's your biggest pain point with monorepos? Which development tasks are still hard for you in the frontend?

    ...thinking about which article/template to write next

  • Joe Shelby
    Joe ShelbyAug 14, 2021

    Fixes for latest versions (triggered by version and dependency conflicts).

    1) change react-scripts for both packages to 4.0.3
    2) add "babel-loader": "8.1.0" to the root package.json file (cra complains when 8.2.2 is found in the root, but storybook will install its override and use it)
    3) add "@storybook/addon-docs": "^6.1.17", to the storybook package package.json file

    with those, the root commands started working for me.

    • Jonathan Schneider
      Jonathan Schneider Aug 14, 2021

      I believe that the main culprit is "@storybook/addon-docs": "^6.1.17". If the next version increases the babel-loader version again, you'd have to bump that then. The "resolutions" field actually changes the dependency tree resolution, but it is just in the RFC phase now. I was actually hoping to make it work with other package managers, and I'm happy for any input in that direction

Add comment