Make and Publish Your ESLint Config with pnpm in a Monorepo

28/04/2022 • 4 min read

In this article, we will create an ESLint config using a monorepo structure. Then, use the packages inside our repository with workspaces of pnpm as dependencies. And finally, publish those packages to npm repository.

Repository: github.com/BerkeKaragoz/eslint-config

eslint-config-berkekaragoz NPM page

ESLint

The way that you use presets in ESLint is by extending the packages in your config file.

If you want to use eslint-config-berkekaragoz and add my preferred settings on top of that, I would have to do this:

# .eslintrc.yml
extends:
   - berkekaragoz
But where does the actual name come from?

Short answer, NPM package name.

There are certain naming recommendations for ESLint configs. Either start with eslint-config- or @SCOPE/eslint-config.

So in the example above, you can either extend with berkekaragoz or eslint-config-berkekaragoz strings.

These will resolve to the config exported from index.js. If you want to export multiple configs, lets say react, you would export the settings from react.js. That will be usable by extending the berkekaragoz/react.

Making Shareable ESLint config

There are 3 types of configuration types in ESLint:

  • JSON
  • YAML
  • JavaScript Object

To make your shareable config, you should export it as a JavaScript object.

A simple one would look like this:

// index.js
module.exports = {
   rules: { eqeqeq: "error" }, // do not allow '=='
}

You can see the list of ESLint rules here.

Our Monorepo

You can export all of your config in one package. But since we want to export multiple packages, we are making a monorepo structure. This way the versions of the configs are clearer. And, this is a good sample to learn publishing. The project is simple, but you still have to do the same publishing steps such as a complex project.

I make the monorepo package name as a scoped module: @berkekaragoz/eslint-config.

This structure is going to be:

packages/ # all have index.js and package.json + additional exports
    all/ # could be just consisting from 'extends' statements
    formatting/
    suggestions/
    react/
    typescript/
package.json

All of those packages name except all is directly suffixed to eslint-config-berkekaragoz-.

The directory named all's package name is: eslint-config-berkekaragoz.

Assuming the ESLint configs has been made, it is time to configure package.json's.

eslint rules meme

Workspaces

We have 5 packages in our sample. Well, actually 6 including our monorepo, but thats not going to be published.

Now, in the @berkekaragoz/eslint-config's package.json, we are going to set up the workspaces.

But why? For example, we want to use formatting package inside all package, so it is a dependency. Our packages are in the same repository, but we are not going to export all of our monorepo 5 times. That means, we have to use NPM dependencies.

We could specify the file paths to the package.json's to use those dependencies. But there would be more work when publishing since relative paths shouldn't be used.

This is where workspaces come handy:

{
   "dependencies": {
      "eslint-config-berkekaragoz-formatting": "workspace:*"
   }
}

This is a pnpm feature. After you create a workspace, it uses the package inside the monorepo. And when you publish the package, it automatically replaces the versions with npm repository versions.

So lets setup the workspace by configuring the monorepo (@berkekaragoz/eslint-config) package.json:

{
   "workspaces": ["packages/*"]
}

And use pnpm i. If you never used pnpm, you will notice that it installs very fast. It leverages symlinks to avoid duplication.

Publishing

Publishing is very simple at this point. You have to be registered to npmjs.com, or the repository you are going to publish to.

Run pnpm publish in the directory of your package to publish it. Or you can use pnpm -r publish in the root directory of the monorepo.

My package: npmjs.com/package/eslint-config-berkekaragoz

Dealing with Problems

When I was publishing my own packages, I ran into some problems with workspace. The publish command, did not replace workspace:* versions with the proper ones while publishing. This was on version 7.0.0-rc.8.

You can examine packages by using the pnpm view command, same as npm. So did I.

Expected behavior: pnpm view eslint-config-berkekaragoz@0.1.8: image

Actual behavior: pnpm view eslint-config-berkekaragoz@0.1.7: image

I joined pnpm's Discord server: r.pnpm.io/chat. Turns out there already was an issue related to that. And, they patched this problem in less than 24 hours in version 7.0.0-rc.9.

Testing

Before publishing, especially if it affects production grade software, you should test the package out. You should use the pack command to get a tarball, and use that as a dependency first. Then you can publish that tarball.

Funny thing is, I actually did that. I packed with pnpm and published with npm (because I knew about the problem). But I left the tarball where it is generated, inside the package. When I published it specifiying the path of the tarball, it made a new tarball and published it. Also not replacing the workspace:*.

You can run into these problems, but you should know the ways to deal with them.

End

Thanks for reading. This one thought me more than I imagined, hope it was the same for you. I would love to hear your feedback. You can even leave it as an issue in the monorepo that we used as a sample.

"Make and Publish Your ESLint Config with pnpm in a Monorepo", 28/04/2022, 06:50:00

#code-standards, #guide, #javascript, #npm, #web-development