You're Invited:Meet the Socket Team at BlackHat and DEF CON in Las Vegas, Aug 4-6.RSVP
Socket
Book a DemoInstallSign in
Socket

@esrf/eslint-config

Package Overview
Dependencies
Maintainers
3
Versions
18
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@esrf/eslint-config

ESLint config

1.2.3
latest
npmnpm
Version published
Weekly downloads
321
-28.82%
Maintainers
3
Weekly downloads
 
Created
Source

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 ESLint:

pnpm install @esrf/eslint-config eslint

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).

Recommended tsconfig.json for front-end project
{
  "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`
}
Recommended tsconfig.json for Node project
{
  "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"
}
Recommended root tsconfig.json
{
  "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
}
Recommended tsconfig.json in each project
{
  "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:

  • Upgrade ESLint to the version required by this config (cf. peerDependencies in package.json).
  • Remove the legacy .eslintignore file (replaced with globalIgnores() in eslint.config.js).
  • 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.:

  • Turn the rule back on by removing it from eslint.config.js.
  • Run pnpm lint:eslint --fix.
  • If the rule is not auto-fixable, fix the violations manually until pnpm lint:eslint passes.
  • 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
  }
};

FAQs

Package last updated on 12 Jun 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts