We recently migrated to a monorepo and we are still trying to set everything up. We use typescript and eslint with prettier. The structure is the following:
root
- common
- folder_a
- file_a.ts
- build_scripts
- script_a.js
- project_a
- components
- component_a
- Component.tsx
- tsconfig.json
- .eslintrc.json
- node_modules
- package.json
- .prettierignore
- .prettierrc.yml
- .eslintignore
- .eslintrc.base.json
- tsconfig.base.json
- node_modules
- package.json
- Makefile
- webpack.config.js
- .pre-commit-config.yaml
The common
folder contains code that is used across multiple projects and isn't considered a project of its own (no tsconfig.json). All sub-projects that contain their own tsconfig.json and .eslintrc.json file, are extending the base ones, as you can see below:
tsconfig.base.json
{
"compilerOptions": {
"lib": ["ES2017", "DOM"],
"target": "ES6",
"module": "commonjs",
"baseUrl": ".",
"sourceMap": true,
"alwaysStrict": true,
"noUnusedParameters": true,
"noUnusedLocals": true,
"noEmitOnError": true,
"paths": {
"@common/*": ["./common/*"]
}
},
// include all projects that don't have their own
// tsconfig.json file AND need to be compiled/linted
// => very important because typescript-eslint will
// otherwise load ALL files in memory for type-checking
// and consequently eslint will crash with an OOM error
"include": ["common/**/*"]
}
.eslintrc.base.json (rules omitted for brevity)
{
"env": {
"es2020": true,
"node": true
},
"extends": [
"plugin:destructuring/recommended",
"airbnb-base",
"prettier",
"plugin:prettier/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.base.json",
"ecmaVersion": 11,
"sourceType": "module"
},
"plugins": [
"prettier",
"@typescript-eslint",
"destructuring"
],
"rules": {}
}
.eslintignore
*/**/build
Makefile
**/*.js
**/*.json
.pre-commit-config.yaml (taken from the example in https://github.com/pre-commit/pre-commit/issues/466#issuecomment-274282684)
- repo: local
hooks:
- id: lint_fix
name: lint_fix_project_a
entry: node_modules/.bin/eslint --report-unused-disable-directives --fix -c project_a/.eslintrc.json --ext .ts,.tsx
files: ^project_a/
types: [file]
language: node
package.json
{
"name": "web",
"version": "1.0.0",
"private": true,
"dependencies": {},
"devDependencies": {
"@babel/core": "7.2.2",,
"@typescript-eslint/eslint-plugin": "4.15.1",
"@typescript-eslint/parser": "4.15.1",
"eslint": "7.20.0",
"eslint-config-airbnb": "18.2.1",
"eslint-config-airbnb-base": "14.2.0",
"eslint-config-prettier": "6.10.1",
"eslint-config-standard": "14.1.1",
"eslint-import-resolver-typescript": "2.0.0",
"eslint-plugin-destructuring": "2.2.0",
"eslint-plugin-import": "2.22.1",
"eslint-plugin-node": "11.1.0",
"eslint-plugin-prettier": "3.1.3",
"eslint-plugin-promise": "4.2.1",
"eslint-plugin-standard": "4.0.1",
"eslint-webpack-plugin": "2.5.4",
"eslint-plugin-jsx-a11y": "6.4.1",
"eslint-plugin-react": "7.22.0",
"eslint-plugin-react-hooks": "4.2.0",
"express": "4.0.0",
"prettier": "2.1.2",
"typescript": "3.9.7"
}
}
project_a/tsconfig.json
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
"lib": ["ES2017", "DOM", "DOM.Iterable"],
"jsx": "react",
"baseUrl": ".",
"paths": {
"@common/*": ["../common/*"],
"@components/*": ["./components/*"]
}
},
// overwrite the base "includes"
"include": ["**/*"],
"exclude": [
"**/node_modules/*",
"**/build/*"
]
}
project_a/.eslintrc.json (rules omitted for brevity)
{
"extends": [
"plugin:react/recommended",
"plugin:destructuring/recommended",
"airbnb",
"airbnb/hooks",
"prettier",
"plugin:prettier/recommended",
"prettier/react",
"../.eslintrc.base.json"
],
"env": {
"browser": true,
"commonjs": true,
"node": false
},
"parserOptions": {
"project": "./tsconfig.json",
"ecmaFeatures": {
"jsx": true
}
},
"plugins": [
"prettier",
"react",
"@typescript-eslint",
"destructuring"
],
"rules": {}
}
Bundling project_a
with webpack
, using ts-loader
, eslint-webpack-plugin
and project_a/tsconfig.json
works perfectly fine. No linting errors reported. Same for VSCode, the vscode-eslint extension doesn't report anything.
Also running the following inside project_a
is successful without any linting errors.
../node_modules/.bin/eslint . --ext .ts,.tsx --fix -c .eslintrc.json
However, when changing a file in project_a
and trying to stage and commit, the following error is thrown:
/home/web/project_a/components/component_a/Component.tsx
0:0 error Parsing error: "parserOptions.project" has been set for @typescript-eslint/parser. The file does not match your project config: project_a/components/component_a/Component.tsx.
The file must be included in at least one of the projects provided
It makes no sense to me, why this error would only be thrown in that specific use-case, when linting via the pre-commit hook. Also, it's very peculiar, since project_a/tsconfig.json
include
s all files in that folder (except node_modules
and build
artifacts).
"include": ["**/*"]
It seems to me it might be related to us having added
"include": ["common/**/*"]
in ./tsconfig.json
. That was added (as per the comment), because having a file from common
open in VSCode and trying to save some changes, would make eslint crash, since according to various sources, the @typescript-eslint/parser
would include all files in memory to gather type information for linting.
All related Github issues just mention that the file in question is just not included, as per the error itslef, but I don't see how this is happening in my scenario.
It turns out that @typescript-eslint/parser
resolves the provided tsconfig.json
relative to the current working directory, which I missed while reading the docs. Thus, since the pre-commit hook is always run from the root directory, it was using tsconfig.base.json
instead of the one in project_a
.
In order to fix this, the following steps need to be taken (according to https://github.com/typescript-eslint/typescript-eslint/issues/251#issuecomment-567365174):
project_a/.eslintrc.json
to project_a/.eslintrc.js
parserOptions.tsconfigRootDir
like the following:module.exports = {
parserOptions: {
project: './tsconfig.json'
tsconfigRootDir: __dirname // important option
}
}