DEV Community

drmikecrowe
drmikecrowe

Posted on • Originally published at pinnsg.com on

Shareable ESLint/Prettier Configs for Multi-Project Synergy

Header Image

Recently, the eslint-config-prettier v8 upgrade broke my ESLint configuration, and I realized I needed a centralized way of managing my ESLint configuration across projects.

This is the outline for how I will solve common configuration across projects going forward. Here are the key features:

  • Layer your ESLint rules based on topics: ESLint + Prettier, then TypeScript, then React/Vue.
  • Use Lerna to publish scoped packages to npmjs.
  • Some helper tools to upgrade your code.

Disclaimer: This is not my original work, but leveraged from other’s work, most notably:

  • The ESLint configuration started with ntnyq configs
  • The TypeScript idea came from unlikelystudio settings

Layered ESLint

The benefit of this organizational structure is layering your ESLint rules. Some rules apply for TypeScript projects. Some for TypeScript/React projects. What if you add Prettier to the mix?

A picture is worth a 1000 words:

Layered Flow

Each rule layers parent rules into it’s rules. For example, our company configuration contains:

eslint-config-prettier-typescript-react:

  extends: [
    '@pinnsg/typescript-react',
    '@pinnsg/prettier-react',
    '@pinnsg/prettier-typescript',
    ...

Enter fullscreen mode Exit fullscreen mode

which in turn eslint-config-prettier-typescript:

  extends: [
    '@pinnsg/typescript',
    '@pinnsg/prettier',
    ...

Enter fullscreen mode Exit fullscreen mode

etc.

Naturally, when you publish your configs, they will reference your scope, not mine 😄.

To package.json or not to package.json

A colleague recently embarked on a move to migrate all their dot-config files into package.json. Initially I was resistant to this, as I felt like it was adding lots of bloat to package.json. However, now that I’ve worked with this for a bit, I’m starting to see the advantage of a single configuration file (as much as possible).

However, the level of support for package.json config depends upon the base library being used. You will see these scenarios:

  1. Full package.json support, including referencing your shared configuration via the package name.
  2. package.json support without support for package names
  3. Generic json/js/rc file support

Every package is different, so you will have to check the documentation individually to determine the level of support. If the package uses cosmiconfig, you are golden (#1 above). Other configuration packages are not as complete, and offer less flexibility.

Some config libraries, such as rc-config-loader (used by npm-check-updates) support configuration in package.json, but not referencing shared configuration. If this is the case, you must use an external .js configuration file and import your configuration. Here’s how the npm-check-updates config must use .ncurc.js such as:

module.exports = require("@pinnsg/config-npm-check-updates");

Enter fullscreen mode Exit fullscreen mode

Instructions

  • Clone the template repo
  • Update the individual rules per your standards
  • Choose your NPM scope.
    • If you use your npm username, you can it as your scope (my personal configs are published to @drmikecrowe for my use)
    • If you want a different scope, you must login to npmjs.com and add an organization to your account. For instance, our @pinnsg scope is our organizational scope
  • Globally search/replace all occurrences of my scope (@drmikecrowe) and replace with your scope
  • Globally search/replace all occurrences of my username (drmikecrowe) and replace with your username (these are 2 steps in case your scope/username are different)
  • Rename packages/mrm-preset-drmikecrowe to match your scope (without the @)
  • Login to NPM using npm login
  • Publish your packages using lerna publish

NOTE: As you do your initial publish, or after updating, sometimes things go bump in the night during your publish and packages are tagged/pushed but not published. To re-publish you can simply run:

lerna exec -- "npm publish || exit 0"

Enter fullscreen mode Exit fullscreen mode

to publish all packages (you’re receive errors for the packages that succeeded)

MRM

mrm is a fantastic tool for updating projects. Their tagline is:

Codemods for your project config files

I’m a huge fan of this project. It allows me to script intelligent updates to my configs based on set criteria. For example, in the configs preset (explained below), it does:

  const parts = []
  if (hasPrettier) parts.push('prettier')
  if (hasTypescript) parts.push('typescript')
  if (hasVue) parts.push('vue')
  else if (hasReact) parts.push('react')

  const base = parts.length ? '-' + parts.join('-') : ''
  const full = `${configScope}/eslint-config${base}`
  const eslintPreset = `${configScope}/${base.slice(1)}`

Enter fullscreen mode Exit fullscreen mode

So, by package.json inspection, it determines which preset you most likely want, and updates the config to match that preset. Very cool.

MRM Presets

A Preset is a way for you to customize MRM behavior. Included is a custom preset for your own use where you can put your upgrades as you use MRM. In this preset I have 2 tasks:

  • configs: Migrate configs of a project to this structure
  • typescript: Migrate your tsconfig.json to this structure (yes, this should probably be part of the configs preset, but it was easier to simply tweak the existing MRM typescript task)

To use these, follow these steps:

  • Publish your preset per instructions above
  • Install your preset globally with npm i -g mrm mrm-preset-YOURSCOPE (without the @ — i.e. mrm-preset-drmikecrowe)
  • Change into a project you want to upgrade
  • Make sure you have committed all your changes and your git tree is clean
  • Run mrm eslint
  • If you use Prettier, run mrm prettier
    • (these two command setup eslint/prettier in a standard way — the next step really needs .eslintrc.json instead of a .js file)
  • Run mrm --preset YOURSCOPE config

Once that finishes, you can evaluate the proposed changes and see if you like the results. If they are satisfactory, commit them and enjoy the new config. If they are not, do a

git reset --hard HEAD

Enter fullscreen mode Exit fullscreen mode

and update your preset at

packages/mrm-preset-drmikecrowe/configs/index.js

Enter fullscreen mode Exit fullscreen mode

as needed to modify the configs as you see fit.

Enjoy!

Top comments (1)

Collapse
 
andrewdias profile image
Andrew Dias

Thanks for the excellent post. I tried to follow this pattern with a base config, a typescript config, and a react config. Here is my problem: in my TS config I override a rule from my base config; if I then try create a TS-react config that extends both (in that order), then the overridden rule in in the TS config is wiped out by the React config. I assume that since they both extend the base config, the base config in the React config overrides that in the TS config. Apologies if that is not clear. I don't know if it's just the way I implemented it, or if it is a weakness in the pattern.