I try to help

Monorepo - React, Storybook and ESBuild

Monorepo

Approximate reading time: 3 minute(s)

This article is the third in a three-part series about monorepos.

If you want to follow along you can clone the repository.

React and a component library

We've done a lot of work on our little monorepo. Let's add React and create a component library.

Let's first create our ui-components package. If you cloned the repo you can easily do this by cloning the pkg-template folder and rename accordingly.

Now let's add React to our library. Run the following command inside the packages/ui-components folder.

yarn add react
yarn add -D @types/react

We should also add the following in the base tsconfig.json.

     "skipLibCheck": true,

+    "jsx": "react",
     "esModuleInterop": true,

Let's add a dummy component:

import React from 'react';
import { roll } from '@monorepo/diceroll';

export interface MonorepoButtonProps {
  label: string;
  roll?: true;
}

export const MonorepoButton = (props: MonorepoButtonProps): JSX.Element => {
  return <button>{props.label} {props.roll ? roll('1d20') : ''}</button>
}

ESLint redux

We'll need to make some additions to our ESLint config. Run this command at root level.

yarn add -D -W eslint-plugin-react

   extends: [
     'eslint:recommended',
+    'plugin:react/recommended',
     'plugin:@typescript-eslint/recommended',
     'prettier',
+    'prettier/react',
     'prettier/@typescript-eslint',
   ],

Storybook

Storybook is a useful tool for creating UI components in isolation. It's a great tool which can aid in development. I won't go into detail about it, but their documentation is good and even have a tutorial site.

I would love to say that adding Storybook with our custom wiring is hard. But it isn't, it works out of the box simply running:

npx sb init

The caveat is you still want to run a watch command if you use other packages in your components, like diceroll.

You can now run Storybook by running yarn run storybook. You should end up with something like this:

Storybook

I'll leave doing the same for the ui package as an exercise to the reader.

Extra: ESBuild

I've been reading about ESBuild for a while now and the benchmarks speak for themselves. Let's see if we can integrate it.

Important note: At the time of writing, you won't be able to use React 17+ JSX Transform. The one mentioned in a note above. This is a ESBuild limitation.

Run the following in the ui package folder and let's make changes to our Webpack config.

yarn add -D esbuild-loader
// packages/ui/webpack.config.js
+    module: {
+      rules: [
+        {
+          test: /\.tsx?$/,
+          loader: 'esbuild-loader',
+          options: {
+            loader: 'tsx',
+            target: 'es2015',
+            tsconfigRaw: require('./tsconfig.json')
+          }
+        },
+      ]
+    },
+
+    optimization: {
+      minimize: env.production ? true : false,
+      minimizer: [
+        new ESBuildMinifyPlugin({
+          target: 'es2015'
+        })
+      ]
+    },
+
     plugins: [
       new HtmlWebpackPlugin({
         template: './src/index.html',
       }),
     ],
+
+    resolve: {
+      extensions: [ '.tsx', '.ts', '.js' ],
+    },

For me, I went from 2.3s to 900ms for the Webpack part of the build. And we can even remove the compile script for an even bigger speed increase.

Keep in mind, however, that ESBuild does not typecheck so you should modify the git hooks to check for such scenarios.

Personal thoughts

This experience was a fun one. There is still room for improvement, though. One caveat you may have encountered is the need to have a watch script for a package to keep its depdendencies up to date. I don't think this is too much of a problem, but may feel cumbersome when you also want to run ui's start script.

I think I might start using ESBuild more often, the speed improvement is great. If you're looking for other "fast" tools you may want to look at:

The code is not perfect by any means, and you'll probably make a lot of changes to better suit your project and your team's needs. But it should give you a quick head start with not a lot of wiring and with minimal dependencies.

Examples of changes one might want to look into:

  • Change Storybook's webpack config to use ESBuild instead of Babel
  • Use ts-loader instead of relying on watch scripts
  • Take a deeper dive in configuring Webpack