npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@esrf/eslint-config

v2.0.1

Published

ESLint config

Downloads

1,800

Readme

Shared ESLint config @ ESRF

Dynamic linting configuration for JavaScript, TypeScript and Node projects, with support for React/JSX, Vitest, Storybook, Testing Library, and Cypress.

In addition to core ESLint rules, @esrf/eslint-config includes rules from the following ESLint plugins:

Written in ESLint's flat config format, available since ESLint v9, this shared configuration supports monorepos and can be easily loosened, tweaked or extended to adapt to the needs of any front-end project.

Prerequisites

The configuration makes the following assumptions:

  • All JS files are ES modules (i.e. package.json has "type": "module").
  • All JS/JSX/TS/TSX files inside the src folder target a browser environment, except for test files.
  • All JS/TS files outside the src folder, as well as test files inside the src folder, target a Node environment.

By "test files inside the src folder", we mean any file matching either src/**/__tests__/**/*.{js,jsx,ts,tsx} or src/**/*.test.{js,jsx,ts,tsx}.

CommonJS projects

If, for whatever reason you cannot set "type": "module" in package.json, you can still benefit from this linting configuration by switching to TypeScript, and/or by using the explicit .mjs or .cjs extensions for files outside the src folder.

We do not support MJS/CJS files inside the src folder at this time.

Getting started

Install the config, together with the required version of ESLint (cf. peerDependencies in package.json):

pnpm add --save-dev @esrf/eslint-config eslint@<x.y>

Create a file called eslint.config.js in the root of your project, with the following content:

import { createConfig, detectOpts } from '@esrf/eslint-config';
import { defineConfig, globalIgnores } from 'eslint/config';

const opts = detectOpts(import.meta.dirname);

const config = defineConfig([
  globalIgnores(['dist/', 'folder/to/ignore/']),
  ...createConfig(opts),
]);

export default config;

If your project's package.json doesn't have "type": "module" (or has "type": "commonjs"), use the MJS extension to enable ESM: eslint.config.mjs.

Adjust the list of ignored folders as needed. Don't forget folders that might be generated by your CI or your toolchain (e.g. pnpm store, test coverage report, etc.) Don't include node_modules or .git, which are already ignored by ESLint out of the box.

Add a linting script to your package.json. In a typical project with TypeScript, ESLint and Prettier, we recommend having four scripts as shown below to ensure linting, formatting and type-checking are performed in parallel:

"scripts": {
  "lint": "pnpm \"/^lint:/\"",
  "lint:eslint": "eslint --max-warnings=0",
  "lint:tsc": "tsc",
  "lint:prettier": "prettier . --cache --check"
},

Make sure to run the lint script above as part of your CI workflow.

The --max-warnings=0 option ensures that ESLint exits with a non-zero code if it finds warning-level violations. The warning level helps to make cosmetic, low-impact violations less intrusive during development, but those must still be fixed before committing.

Finally, in a TypeScript project, make sure tsconfig.json includes all TS and JS files in the codebase (including eslint.config.js itself).

{
  "compilerOptions": {
    "module": "esnext",
    "moduleResolution": "bundler",
    "target": "esnext",
    "lib": ["dom", "dom.iterable", "esnext"],
    "jsx": "react-jsx", // if React
    "allowJs": true, // include JS files when linting
    "skipLibCheck": true,
    "esModuleInterop": true,
    "isolatedModules": true,
    "resolveJsonModule": true,
    "noEmit": true, // allow running `tsc` for type-checking
    "incremental": true,
    "strict": true,
    "noImplicitOverride": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedSideEffectImports": true,
  },
  "include": ["*", "src"], // include root files like `eslint.config.js`
}
{
  "compilerOptions": {
    "module": "nodenext",
    "moduleResolution": "nodenext",
    "target": "esnext",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true, // include JS files when linting
    "skipLibCheck": true,
    "esModuleInterop": true,
    "isolatedModules": true,
    "resolveJsonModule": true,
    "noEmit": true, // allow running `tsc` for type-checking
    "incremental": true,
    "strict": true,
    "noImplicitOverride": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedSideEffectImports": true,
  },
  "include": ["*", "src"], // include root files like `eslint.config.js`
}

Monorepos

In monorepos, install and configure ESLint at the root, as well as in every project:

.
├── cypress/
│   └── tsconfig.json
├── node_modules/
├── packages/
│   ├── foo/
│   │   ├── node_modules/
│   |   ├── src/
│   │   ├── eslint.config.js
│   │   ├── package.json
│   │   └── tsconfig.json
│   └── bar/
│       ├── node_modules/
│       ├── src/
│       ├── eslint.config.js
│       ├── package.json
│       └── tsconfig.json
├── eslint.config.js
├── package.json
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
└── tsconfig.json

This allows each project to have a linting config tailored to its own needs (with or without React, with or without Vitest, etc.)

In the root eslint.config.js, ignore the packages folder to avoid linting the same files twice:

const config = defineConfig([
  globalIgnores(['packages/']),
  ...createConfig(opts),
]);

Here is one way of defining the linting scripts in the root package.json to ensure that the root folder is linted as well:

"scripts": {
  "lint": "pnpm \"/^lint:/\"",
  "lint:prettier": "prettier . --cache --check",
  "lint:eslint": "pnpm -r --parallel lint:eslint",
  "lint:tsc": "pnpm -r --parallel lint:tsc",
  "lint:root:eslint": "eslint --max-warnings=0",
  "lint:root:tsc": "tsc"
}
{
  "compilerOptions": {
    "module": "esnext",
    "moduleResolution": "bundler",
    "target": "esnext",
    "lib": ["dom", "dom.iterable", "esnext"],
    "jsx": "react-jsx",
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "isolatedModules": true,
    "resolveJsonModule": true,
    "noEmit": true,
    "incremental": true,
    "strict": true,
    "noImplicitOverride": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedSideEffectImports": true,
  },
  "include": ["*"], // only root files to avoid linting/type-checking project files twice
}
{
  "extends": "../../tsconfig.json", // extend root `tsconfig.json`
  "include": ["*", "src"], // project root files + source files
}

Migrating an existing codebase

If your project was already set up for linting with ESLint<=8, start with these steps:

  1. Upgrade ESLint to the version required by this config (cf. peerDependencies in package.json).
  2. Remove the legacy .eslintignore file (replaced with globalIgnores() in eslint.config.js).
  3. Remove any legacy configuration (eslintConfig property in package.json, .eslintrc.js file, etc.)

You're now ready to apply the new configuration to your codebase.

Run the lint script, pnpm lint:eslint, and check the output. Depending on the size of your codebase, you may see thousands of warnings and errors. Most will be rule violations, but some might also be "Unused eslint-disable directive" warnings, meaning that the new configuration no longer reports violations everywhere your previous config used to.

The most sane way to proceed from here is to start by turning off every single rule that reports a violation in eslint.config.js:

const config = defineConfig([
  globalIgnores(['dist/', 'folder/to/ignore/']),
  ...createConfig(opts),
  {
    rules: {
      'simple-import-sort/imports': 'off',
      'import/consistent-type-specifier-style': 'off',
      // ...
    },
  },
]);

Keep turning off rules until ESLint no longer reports any violations — only "Unused eslint-disable directive" warnings. You can now automatically remove all unused directives with pnpm lint:eslint --fix. Make sure to reformat all files afterwards with pnpm lint:prettier --write.

At this point, pnpm lint:eslint should pass, so it's a good time to commit the new linting set up and open a PR. Once merged, you can start to actually fix the new violations.

Proceed one rule at a time, ideally starting with the most impactful, auto-fixable rules, like import/consistent-type-specifier-style, import/no-duplicates, simple-import-sort/imports, etc.:

  1. Turn the rule back on by removing it from eslint.config.js.
  2. Run pnpm lint:eslint --fix.
  3. If the rule is not auto-fixable, fix the violations manually until pnpm lint:eslint passes.
  4. Commit, open a PR, request a review, and merge into the main branch once approved.

Rules with few violations can of course be fixed together in the same PR as long as the diff remains reviewable. When a fix is non-trivial, or when the rationale behind a rule might not be clear to the reviewer, make sure to comment and link to the documentation of the rule in question.

Usage guidelines

If you strongly disagree with a rule, or if it goes against agreed-upon practices in your project, or if it's really not worth fixing, either disable it entirely or configure it as you see fit, making sure to explain why in a comment:

const config = defineConfig([
  // ...
  {
    rules: {
      'react/prop-types': 'off', // legacy code, not worth fixing

      /* Default is "avoid", but there are lots of complicated `switch` statements,
       * notably in Redux reducers, which benefit from clear case blocks. */
      'unicorn/switch-case-braces': ['warn', 'always'],
    },
  },
  {
    /* Some rules apply only to specific files.
     * Make sure to use the same `files` array as in `src/index.js`. */
    files: ['**/*.{jsx,tsx}'],
    rules: {
      'jsx-a11y/control-has-associated-label': 'off',
    },
  },
]);

If you turn off a rule completely, beware that it will not be applied to new code. To disable a rule on existing code only, use eslint-disable directives instead (assuming the number of violations is within reason):

/* eslint-disable react/prop-types --
 * Long explanation why the rule is disabled. */

// eslint-disable-next-line react/no-multi-comp -- short explanation
export function MyComponent(props) {
  let foo; // eslint-disable-line no-unused-vars -- same-line syntax

  return (
    {/* eslint-disable-next-line jsx-a11y/anchor-is-valid -- JSX syntax */}
    <a onClick={(e) => e.stopPropagation()}>{props.value}</a>
  );
}

Of course, don't hesitate to open an issue in this repo if you think the config should be changed, if you'd like to better understand the rationale behind some of the rules and options, or if you're unsure how to fix a specific violation.

Troubleshooting

ESLint provides a brilliant config inspector to debug configuration issues, notably when ignoring/filtering files. You can run it with:

pnpm lint:eslint --inspect-config
pnpm --filter <project-in-monorepo> lint:eslint --inspect-config

API reference

detectOpts(projectDir)

This function looks at the dependencies installed in <projectDir>/package.json and whether a <projectDir>/tsconfig.json file exists. It returns an object that can then be passed to createConfig() in order generate an ESLint configuration tailored to your project.

Calling this function is entirely optional; the options object can be declared manually. It's also possible to override specific options as needed:

const opts = { react: true };
createConfig(opts);

const opts = detectOpts(import.meta.dirname);
createConfig({ ...opts, typescript: false });

All options accept boolean values, except for the typescript option, which accepts either false or an object with the a tsconfigRootDir property. By default, the project dir is used: { tsconfigRootDir: <projectDir> } but you can override it as follows:

import path from 'node:path';

createConfig({
  ...detectOpts(import.meta.dirname),
  typescript: {
    tsconfigRootDir: path.join(import.meta.dirname, '..'), // `tsconfig.json` in parent directory
  },
});

For the full list of options, please refer to the DEFAULT_OPTS object in src/index.js.

createConfig(opts)

This function generates a flat config array with the given options.

The returned array contains ESLint config objects — you can spread it, filter it, extend it, etc. before exporting it:

const config = createConfig(opts);
const unicornConfig = config.find(c => c.name.endsWith('unicorn'));

export default {
  ...config.filter(c => c !== unicornConfig),
  {
    ...unicornConfig,
    files: ['**/*.{ts,tsx}'] // apply unicorn rules to TS/TSX files only
  }
};