Search code examples
reactjstypescripteslinttsconfig

ESLint "Unable to resolve path to module" in project with shared modules


I am getting ESLint errors when attempting to import modules from a shared project. The errors happen on every import from the shared/ project. The exact error is the common ESLint import error: Unable to resolve path to module 'shared/hooks/api'

When I disable ESLint, all my projects run and build fine. VSCode also can auto-complete the imports for me correctly, so I know at least it is partially setup correctly (I think). I can cmd+click on the imports and it navigates me to the correct shared/ project file. All of this makes me think that everything is "resolving" OK, just ESLint can't figure it out.

An example:

// mobile/screens/Home.tsx

// Error: Unable to resolve path to module 'shared/hooks/api'
import { someHook } from 'shared/hooks/api'

My project is setup like this:

frontend/
├─ mobile/
│  ├─ ...
│  ├─ screens/
│  │  ├─ Home.tsx
│  ├─ eslintrc.yml
│  ├─ tsconfig.json
├─ shared/
│  ├─ ...
│  ├─ hooks/
│  │  ├─ api.ts
│  ├─ eslintrc.yml
│  ├─ tsconfig.json
├─ web/
│  ├─ ...
│  ├─ eslintrc.yml
│  ├─ tsconfig.json

My tsconfig.json files look like:


// mobile/tsconfig.json
{
  "compilerOptions": {
    "jsx": "react-native",
    "rootDirs": ["./src", "../shared/src/*"],
    "paths": {
      "shared/*": ["../shared/src/*"],
      "screens/*": ["./src/screens/*"]
    }
  },
  "extends": "expo/tsconfig.base",
  "baseUrl": "src",
  "include": [
    "src",
    "./babel.config.js",
    "**/*.tsx",
    "**/*.ts",
    "../shared/**/*.ts",
    "../shared/**/*.tsx"
  ]
}

// shared/tsconfig.json
{
  "compilerOptions": {
    ...,
    "baseUrl": "src",
    "paths": {
      "hooks/*": ["hooks/*"]
    }
  },
  "include": ["src"]
}

And relevant ESLint configurations:

# mobile/eslintrc.yml

env:
  browser: true
  es2021: true

parser: '@typescript-eslint/parser'

parserOptions:
  ecmaVersion: latest
  sourceType: module
  project: ['./tsconfig.json']

extends:
  - eslint:recommended
  - plugin:react/recommended
  - plugin:react/jsx-runtime
  - plugin:react-hooks/recommended
  - plugin:@typescript-eslint/recommended
  - plugin:@typescript-eslint/recommended-requiring-type-checking
  - plugin:import/recommended
  - plugin:jsdoc/recommended
  - prettier

plugins:
  - react
  - '@typescript-eslint'
  - 'no-relative-import-paths'
...

settings:
  react:
    version: detect
  import/resolver:
    node:
      paths: ['src', '../shared/src']
      extensions: ['.js', '.jsx', '.ts', '.tsx']
# shared/eslintrc.yml

env:
  browser: true
  es2021: true

parser: '@typescript-eslint/parser'

parserOptions:
  ecmaVersion: latest
  sourceType: module
  project: ['tsconfig.json']

extends:
  - eslint:recommended
  - plugin:react/recommended
  - plugin:react/jsx-runtime
  - plugin:react-hooks/recommended
  - plugin:@typescript-eslint/recommended
  - plugin:@typescript-eslint/recommended-requiring-type-checking
  - plugin:import/recommended
  - plugin:jsdoc/recommended
  - prettier

plugins:
  - react
  - '@typescript-eslint'
  - 'no-relative-import-paths'

... 

settings:
  react:
    version: detect
  import/resolver:
    node:
      paths: ['src']
      extensions: ['.js', '.jsx', '.ts', '.tsx']
// VSCode Workspace settings
{ 
  "eslint.workingDirectories": ["./shared", "./mobile", "./web"],
}

The configurations for the web/ project are pretty similar to the mobile/ project, except that the tooling is for a ReactJS project instead of react-native.

How can I setup the eslintrc.yml and tsconfig.json files to work correctly with this project structure?


Solution

  • You are using baseUrl in your tsconfig.json which lets you simplify your imports:

    import  {someHook} from "shared/hooks/api";
    

    This is a TypeScript feature! By default eslint (and most bundlers/runtimes) do not read your tsconfig.json, nor do they respect the baseUrl, paths or other settings that impact TypeScript's module resolution. It's quite a footgun.

    You'll need to install and configure the appropriate plugin to tell ESLint to respect your tsconfig: https://www.npmjs.com/package/eslint-import-resolver-typescript