Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
September 9, 2022 10:31 am GMT

Introducing esbuild To Your Monorepo

In this post I will introduce esbuild as the bundling solution for my monorepo. I will bundle 2 packages with the same bundling configuration and create a custom esbuild plugin to assist with that.

I have noticed that although the build process for packages under my pedalboard Monorepo creates different artifacts (CJS, ESM and types - you can read about it in more details in my Hybrid NPM package through TypeScript Compiler (TSC) post) it does not create a single bundle for the entire package.

If I take the components package for example, it has a dist/esm directory which has the ESM code in it, scattered in different files and not as a single minified bundle I can deploy somewhere and then fetch in a single request.

For that you need a JS bundler.

There are many bundlers out there, the most notorious one being Webpack, and Im not willing to go into the rabbit hole of comparing them in this post (god knows the web is packed with these comparisons... web packed sorry )
I want to go with a solution which seems to be a bit more efficient, fast and elegant IMO. Im going with esbuild.

In this post I will attempt the following -

  • Get the components package to be bundled as part of its build process and create the bundle under dist/main/index.js
  • Share the esbuild configuration so that I can reuse it in another package under the monorepo - the hooks package
  • Give a esbuild custom plugins

Lets go!

The code Im referring to can be found under my pedalboard monorepo on GitHub.

Im going into the components package and installing esbuild as the first step -

yarn add -D esbuild

Now that I have it installed I will create a new NPM script called bundle and in that script I will call the esbuild CLI command with the relevant parameters to bundle my code.
But where is the code I would like to bundle?

Well, the code I would like to bundle resides under the dist/esm directory which gets created and populated when I run the build script for this package. What creates it is TSC and you can read more about it here.

So, in the dist/esm I have the entry point file and the rest of the required modules along with some source map files. It looks like this:

Image description

Given the above my bundle script will look something like this:

"bundle": "esbuild dist/esm/index.js --bundle --minify --sourcemap --outfile=dist/main/index.js"

I would like dist/esm/index.js to be the entry point, bundle it, minify it and have some source map file to it before putting it in dist/main/index.js.
Lets run it and see what happens:

yarn bundle

Oops, Im getting this error:

 [ERROR] Could not resolve "./index.css"    dist/esm/src/Pagination/index.js:4:7:      4  import './index.css';

And rightfully so.
My dist/esm directory does not hold the CSS files for the component.
TSC cannot come to the rescue here since its job is transpiling TS only, so the 2 remaining options are either we copy the files ourselves, in some script, which can be hard to maintain since we will need to resolve the paths, or we can create a plugin for esbuild which detects any .css file and alters the path to be relative to the packages src directory.

Hmm sounds ambitious for a peaceful Friday but lets try it and learn something about esbuild custom plugins on the way -

esbuild custom plugin

What I would like to do is create a plugin which filters any .css extension in imports and redirect its path to the src folder of the package.
In order to achieve that I can no longer use the CLI to execute the bundler, but rather use a node script which will trigger esbuild.
I created such a script called esbuild.config.js (just to be aligned with the rest of the config files, though it is more of a script than a configuration) and in it I define my plugin and trigger the esbuild.

My plugin implementation is really naive. The resolveDir is the absolute path of the imported file and I simply remove the dist/esm from it and it then redirects to the package src instead:

const path = require('path');let cssPlugin = {   name: 'css',   setup(build) {       // Redirect all paths starting with "images/" to "./public/images/"       build.onResolve({filter: /.\.css$/}, (args) => {           const path1 = args.resolveDir.replace('/dist/esm', '');           return {path: path.join(path1, args.path)};       });   },};require('esbuild')   .build({       entryPoints: ['dist/esm/index.js'],       bundle: true,       minify: true,       sourcemap: true,       outfile: 'dist/main/index.js',       plugins: [cssPlugin],   })   .catch(() => process.exit(1));

And my bundle script look like this now:

"bundle": "node ./esbuild.config.js",

Lets run the bundler again yes! We have 4 files under the dist/main directory:

Image description

So we have the package as a bundle. Now lets make it run at the end of the build script (since it depends on the dist/esm files) -

"build": "tsc --project tsconfig.esm.json & tsc --project tsconfig.cjs.json && yarn bundle",

While both TSC run in parallel, the last command awaits for them to complete (notice the && there).
Nice :)

But Im a greedy ba5tard...

This is all well, but I would like my hooks package to also enjoy this bundling, along with the plugin, but I have no intention of duplicating the code. Can I reuse it?

First of all I will bring my esbuild.config.js file to the projects root. Running yarn bundle now obviously fails since it cannot find the file, so Im changing the path -

"bundle": "node ../../esbuild.config.js",

I can now add the same scripts to my hooks package and we;re done, but lets refactor it a bit.
First let me extract the plugin to its own file called esbuild.css.plugin.js -

const path = require('path');module.exports = {   name: 'css',   setup(build) {       // Redirect all paths starting with "images/" to "./public/images/"       build.onResolve({filter: /.\.css$/}, (args) => {           const path1 = args.resolveDir.replace('/dist/esm', '');           return {path: path.join(path1, args.path)};       });   },};

And now I will import it in my esbuild.config.js file, so it looks like this:

const cssPlugin = require('./esbuild.css.plugin');require('esbuild')   .build({       entryPoints: ['dist/esm/index.js'],       bundle: true,       minify: true,       sourcemap: true,       outfile: 'dist/main/index.js',       plugins: [cssPlugin],   })   .catch(() => process.exit(1));

Wrapping Up

We can take it from here further and allow that each package will have its own esbuild.config.js where it will extend the root one and allow to override it, but you know what thats a nice challenge for you to try, right? ;)

As always, if you know of better means to achieve this or have questions, please leave your comments on the comments section below so we can all learn from it.

The code can be found on the pedalboard GitHub repo.

Hey! If you liked what you've just read check out @mattibarzeev on Twitter


Original Link: https://dev.to/mbarzeev/introducing-esbuild-to-your-monorepo-1fa6

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To