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?
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