Sebastian Weber Frontend developer from Germany. Fell in love with CSS over 20 years ago. My fire for web development still blazes. Currently my focus is on React.

Reduce maintenance effort with shared ESLint and Prettier configs

6 min read 1787

Reduce maintenance effort with shared ESLint and Prettier configs

If you work on multiple projects, you might end up using the same ESLint and Prettier settings in each of them. Sure, using the same handy ESLint plugins and configurations is good for consistency, but you have to copy and paste your dependencies from your package.json, .eslintrc.js, and .prettierrc over and over again:

With each new project, this approach can increasingly contribute to a maintenance problem. If you want (or need) to change your rule set regularly, you have to touch all projects and make manually sure that they do not diverge from each other.

This is not a DRY approach, which essentially means that every piece of information should have a single source of truth. Duplicated code (or other information) is considered an anti-pattern and should be refactored to a unique representation.

When it comes to reducing the amount you have to copy with ESLint and Prettier configs, bundling them in your own custom npm package saves a lot of time and effort. With this approach, you only have to make changes in a single place, publish a new version, and update the dependency version in your projects. In addition, you can add or override rules, or add configurations that are project-specific.

In this post, we’ll talk about how to bundle your ESLint and Prettier configs for easier use across projects through the following sections:

Overview of the ESLint and Prettier setup

To follow along, take a look the following GitHub projects:

The ESLint package is, on the one hand, the foundation for this article, as it contains my preferred ESLint configurations that I’ve used in all my React projects for a long time. This configuration deactivates all the formatting rules of ESLint and makes sure that Prettier is used for code beautifying.

Monorepo setup for shared libraries

I aim to provide two npm packages to source out ESLint and Prettier configurations into individual npm packages. Therefore, I’m using a monorepo project to facilitate individual publishing of the packages, but it is by no means required to use a monorepo setup. This article does not explain how npm workspaces for monorepos work, but if you’re interested in that, you can follow along my in-detail guide.

The folder structure of the npm project looks like this:

.
├── packages/
│   ├── eslint-config/
│   │   ├── eslint-config.js
│   │   └── package.json
│   └── prettier-config/
│       ├── package.json
│       └── prettier-config.json    
├── .gitignore
└── package.json

The root package.json defines the location of the workspaces eslint-config and prettier-config.



{
  "name": "Shared ESLint and Prettier config",
  "version": "1.0.0",
  "workspaces": [
    "packages/*"
  ],
  "scripts": {
    "publish-eslint-config": "npm publish --workspace @doppelmutzi/eslint-config-react",
    "publish-prettier-config": "npm publish --workspace @doppelmutzi/prettier-config"
  }
}

With the help of the scripts publish-eslint-config and publish-prettier-config, you can publish each package to its respective public npm registry.

Speaking of packages, let’s dive into the implementation of the package @doppelmutzi/eslint-config-react next.

Shared ESLint configuration (@doppelmutzi/eslint-config-react)

This section deals with the workspace @doppelmutzi/eslint-config-react, which exposes our ESLint configuration.

The package.json

Let’s take a look at our eslint-config/package.json in the provided GitHub repo.

{
    "name": "@doppelmutzi/eslint-config-react",
    "version": "1.0.0",
    "main": "./eslint-config.js",    
    "peerDependencies": {
        "@babel/eslint-parser": "7.16.5",
        "eslint": "8.5.0",
        "eslint-config-prettier": "8.3.0",
        "eslint-config-standard": "16.0.3",
        "eslint-import-resolver-node": "0.3.6",
        "eslint-plugin-import": "2.25.3",
        "eslint-plugin-node": "11.1.0",
        "eslint-plugin-prettier": "4.0.0",
        "eslint-plugin-promise": "6.0.0",
        "eslint-plugin-react": "7.28.0",
        "eslint-plugin-react-hooks": "4.3.0",
        "prettier": "2.5.1"
    },
    "scripts": {
        "publish-manual": "npm publish"
    },
    "publishConfig": {
        "access": "public"
    },
    // ...    
}

The most important part is the main property, which references the file holding our ESLint configuration. We’ll take a look at it in a minute; the version property needs to be set before you publish a new version.

The peerDependencies property defines the packages that you’ll need install in your project to use our shared library. I will go into more detail in the React example project that makes use of this library.

The publishConfig property is specific to npm and enables us to publish this project to the public npm registry. The publish-manual script just uses the inbuilt npm CLI command to publish this package. At the end of this section, we’ll invoke this script to publish a version of this package.


More great articles from LogRocket:


The eslint-config.jsfile

eslint-config.js holds the ESLint configuration that we want to reuse in our projects. We can name it however we like, but it has to be referenced in the main field of our package.json.

To make it concrete, this file holds the content you’d normally put into the ESLint configuration file (.eslintrc, .eslintrc.js, etc.) in your project.

// .eslintrc.js
module.exports = {
  // ...
  extends: [
    "plugin:react/recommended",
    "standard",
    "plugin:prettier/recommended",
  ],
  parser: "@babel/eslint-parser",
  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
    ecmaVersion: 12,
    sourceType: "module",
  },
  plugins: ["react"],
};

As I described in the intro, this is my preferred React setup.

Publish to npm

Let’s publish this to the public npm registry. In the packages/eslint-config/ folder, we can either execute $ npm run publish-manual or $ npm publish directly. Additionally, with this npm workspaces setup, we can also use the script publish-eslint-config from the root folder ($ npm run publish-eslint-config) to push it to the registry.

{
  // root package.json
  "description": "Shared ESLint and Prettier config",
  "workspaces": [
    "packages/*"
  ],
  "scripts": {
    "publish-eslint-config": "npm publish --workspace @doppelmutzi/eslint-config-react",
    "publish-prettier-config": "npm publish --workspace @doppelmutzi/prettier-config"
  }
}
Execution and output of the npm publish command
Execution and output of the npm publish command

Make sure that you’ve adjusted the version number in packages/eslint-config/package.json to the new version; otherwise you’ll get a 403 error. If publishing works, you will find your new version on npm.

Version gets published to npmjs
Version gets published to npmjs

Shared Prettier configuration (@doppelmutzi/prettier-config)

In this section, I’ll explain how to outsource your custom Prettier configuration into a separate package, as I’ve done with @doppelmutzi/prettier-config in my sample GitHub repo.

The package.json

Let’s take a look at our current prettier-config/package.json.

{
    "name": "@doppelmutzi/prettier-config",
    "version": "1.0.0",
    "main": "./prettier-config.json",
    "peerDependencies": {
        "prettier": "2.5.1"
    },
    "publishConfig": {
        "access": "public"
    },
    "scripts": {
        "publish-manual": "npm publish"
    },
    // ...
}

Again, the most interesting part is the main field, which points to the extracted Prettier configuration.

The other fields have the same meaning as we gave in the ESLint section; the only peer dependency is Prettier itself. This will become relevant in the next section when we will include this library.

The prettier-config.json file

prettier-config.json constitutes the configuration file. We’ll use this to override Prettier’s default behavior.

In our example, we define Prettier to use single quotes (singleQuote) and to avoid parentheses around a sole arrow function parameter (arrowParens).

{
    "singleQuote": true,
    "arrowParens": "avoid"
}

Publish to npm

This is analogous to publishing the @doppelmutzi/eslint-config-react package. After adjusting the version field, we just invoke $ npm run publish-prettier-config in the root folder.

Using shared libraries in React projects

The React example project I built shows how to use our libraries. In this section, I’ll explain step by step how to use it in your own React projects.

Install custom ESLint and Prettier packages

To introduce these libraries into your React project, you have to add them as dependencies first.

# add eslint-config-react as dev dependency
$ npm i -D @doppelmutzi/eslint-config-react
# add prettier-config as dev dependency
$ npm i -D @doppelmutzi/prettier-config

Install peer dependencies

After this, we have to install the peer dependencies we defined in the package.json files of each library. We can leverage the npm package install-peerdeps for this.

# install eslint-config-react's peer dependencies as dev peerDependencies
$ npx install-peerdeps --dev @doppelmutzi/eslint-config-react
# install prettier-config's peer dependencies as dev dependencies
$ npx install-peerdeps --dev @doppelmutzi/prettier-config

As an alternative, you can copy the libraries defined in peerDependencies fields into the devDependencies section of your package.json and invoke $ npm install.

In the end, your package.json will look like this:

{
  // ...
  "devDependencies": {
    "@babel/eslint-parser": "7.16.5",
    "eslint": "8.5.0",
    "eslint-config-prettier": "8.3.0",
    "eslint-config-standard": "16.0.3",
    "eslint-import-resolver-node": "0.3.6",
    "eslint-plugin-import": "2.25.3",
    "eslint-plugin-node": "11.1.0",
    "eslint-plugin-prettier": "4.0.0",
    "eslint-plugin-promise": "6.0.0",
    "eslint-plugin-react": "7.28.0",
    "eslint-plugin-react-hooks": "4.3.0",
    "prettier": "2.5.1",
    // your other dependencies
  }
}

Set up configuration files

The last step is to set up .eslintrc.js and .prettierrc, our config files. To use the ESLint configuration in @doppelmutzi/eslint-config-react, you need the following configuration:

module.exports = {
  extends: ['@doppelmutzi/eslint-config-react'],
};

You still have the option to override or extend this setup with your project-specific configuration.
For the Prettier setup, you just have to put the following content into .prettierrc.

"@doppelmutzi/prettier-config"

Configure VS Code and IntelliJ

The last step is to configure your IDE correctly. My preferred setup is to auto-fix ESLint issues on save; therefore, I need to provide a configuration to enable save actions as a user or workspace configuration.

For a workspace configuration, you have to open the workspace settings, switch to the JSON editor, and paste the following into it.

  "editor.codeActionsOnSave": {
    "source.fixAll": true,
    "source.fixAll.eslint": true
  },
  "editor.formatOnSave": true,
  "javascript.format.enable": false,
  "[javascript]": {
    "editor.formatOnSave": false
  },
  "[javascriptreact]": {
    "editor.formatOnSave": false
  },
  // ...

VS Code generates a .vscode/settings.json file in the project’s root folder. This settings.json file from the companion project also includes the required configuration to enable the ESLint workflow with a Yarn Berry PnP project.

To find out more about ESLint and Yarn Berry, you can read my previous LogRocket article on package managers.

To configure IntelliJ, you have to go to the Code Quality Tools section of the Preferences menu. Select Automatic ESLint configuration to let IntelliJ pick up the ESLint package from node_modules folder. In addition, you need to check Run eslint --fix on save.

IntelliJ's code quality tools preferences to activate ESLint
IntelliJ’s Code Quality Tools preferences to activate ESLint

Conclusion

This article explains how to make ESLint and Prettier configurations available in a separate package. With this in place, you’ll have a unique place to house your code that support reusability and constitutes a single point of truth. This DRY approach reduces maintenance effort because changes only have to be made once in order to be reflected across apps.

If you want to find out more about my approach to using ESLint and Prettier in my own React projects, feel free to read some articles I’ve published on my personal blog on this topic:

 

Are you adding new JS libraries to improve performance or build new features? What if they’re doing the opposite?

There’s no doubt that frontends are getting more complex. As you add new JavaScript libraries and other dependencies to your app, you’ll need more visibility to ensure your users don’t run into unknown issues.

LogRocket is a frontend application monitoring solution that lets you replay JavaScript errors as if they happened in your own browser so you can react to bugs more effectively.

https://logrocket.com/signup/

LogRocket works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app’s performance, reporting metrics like client CPU load, client memory usage, and more.

Build confidently — .

Sebastian Weber Frontend developer from Germany. Fell in love with CSS over 20 years ago. My fire for web development still blazes. Currently my focus is on React.

Leave a Reply