node.jseslintpre-commit-hookpre-commit.com

How to get new-style ESLint config working with pre-commit?


I've got a project with a .pre-commit-config.yaml that features, among other things, the official ESLint hook. It works perfectly when using the old-style .eslintrc.json config, but replacing it with the new-style eslint.config.js causes it to fail.

The hook config looks like this:

  - repo: https://github.com/pre-commit/mirrors-eslint
    rev: v8.51.0
    hooks:
    - id: eslint
      types: [javascript]
      additional_dependencies: [
        'globals@13.21.0',
        'eslint@8.51.0',
        '@eslint/js',
        'eslint-config-prettier@9.0.0'
      ]

Running pre-commit install-hooks, ESLint itself and the listed dependencies are seemingly installed without errors (no ~/.cache/pre-commit/pre-commit.log gets created). I don't have ESLint globally installed.

This is the old-style .eslintc.json config with which everything works:

{
    "env": {
      "es2022": true,
      "browser": true
    },
    "extends": [
      "eslint:recommended",
      "prettier"
    ],
    "ignorePatterns": [
      "project/static/htmx/**"
    ],
    "parserOptions": {
      "sourceType": "module"
    },
    "rules": {}
}

However, using this eslint.config.js fails, even though it seems to be perfectly valid when comparing to new style config examples in the ESLint documentation:

import globals from "globals";
import js from "@eslint/js";
import eslintConfigPrettier from "eslint-config-prettier";

export default [
  js.configs.recommended,
  eslintConfigPrettier,
  {
    languageOptions: {
      globals: {
        ...globals.browser,
      },
      sourceType: "module",
      ecmaVersion: 2022,
    },
  },
  {
    ignores: ["project/static/htmx/**"],
  },
];

This is the error when running pre-commit run --all-files:

eslint...................................................................Failed
- hook id: eslint
- exit code: 2

Oops! Something went wrong! :(

ESLint: 8.51.0

Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'globals' imported from /Users/jkl/Development/foobar/eslint.config.js
Did you mean to import globals/index.js?
    at new NodeError (node:internal/errors:406:5)
    at packageResolve (node:internal/modules/esm/resolve:789:9)
    at moduleResolve (node:internal/modules/esm/resolve:838:20)
    at defaultResolve (node:internal/modules/esm/resolve:1043:11)
    at ModuleLoader.defaultResolve (node:internal/modules/esm/loader:383:12)
    at ModuleLoader.resolve (node:internal/modules/esm/loader:352:25)
    at ModuleLoader.getModuleJob (node:internal/modules/esm/loader:228:38)
    at ModuleWrap.<anonymous> (node:internal/modules/esm/module_job:85:39)
    at link (node:internal/modules/esm/module_job:84:36)

ERR_MODULE_NOT_FOUND is equally raised for the other two imports if I move them to the first line, so it's not a globals issue.

Despite me installing all the necessary dependencies for those three imports in eslint.config.js to work in the documented way, they cannot be found. So something's off, and the fact that the hook works fine with the old-style config, without changing .pre-commit-config.yaml at all, makes me think that either a) I'm missing some crucial bit of configuration, or b) the ESLint hook just plain doesn't support the new-style config for some reason, or c) ESLint itself has some sort of incompatibility with the combination of the new config and the way pre-commit installs it.

I'm not a NodeJS user in daily life, nor familiar with the pre-commit internal workings. I'm happy to debug further and post more info to help solve this, with some assistance on what to look for next.


Solution

  • Disclaimer: I'm not a Node/Javascript expert, so take with care.

    Pre-commit configures NODE_PATH to point to the node_modules directory where the additional dependencies are installed (similar to a virtual environment in Python). To the best of my knowledge, NODE_PATH is recognised by CommonJS require, but not by ESModule import. To quote Node's docs:

    NODE_PATH is not part of resolving import specifiers. Please use symlinks if this behavior is desired.

    I found that converting from ESModules (eslint.config.mjs) to CommonJS (eslint.config.cjs) resolved the issue for me.