ESLint example
In this guide, you'll learn how to integrate ESLint into moon.
Begin by installing eslint
and any plugins in your root. We suggest using the same version across
the entire repository.
- Yarn
- Yarn (classic)
- npm
- pnpm
- Bun
yarn add --dev eslint eslint-config-moon
yarn add --dev eslint eslint-config-moon
# If using workspaces
yarn add --dev -W eslint eslint-config-moon
npm install --save-dev eslint eslint-config-moon
pnpm add --save-dev eslint eslint-config-moon
# If using workspaces
pnpm add --save-dev -w eslint eslint-config-moon
bun install --dev eslint eslint-config-moon
Setup
Since linting is a universal workflow, add a lint
task to
.moon/tasks/node.yml
with the following parameters.
tasks:
lint:
command:
- 'eslint'
# Support other extensions
- '--ext'
- '.js,.jsx,.ts,.tsx'
# Always fix and run extra checks
- '--fix'
- '--report-unused-disable-directives'
# Dont fail if a project has nothing to lint
- '--no-error-on-unmatched-pattern'
# Do fail if we encounter a fatal error
- '--exit-on-fatal-error'
# Only 1 ignore file is supported, so use the root
- '--ignore-path'
- '@in(4)'
# Run in current dir
- '.'
inputs:
# Source and test files
- 'src/**/*'
- 'tests/**/*'
# Other config files
- '*.config.*'
# Project configs, any format, any depth
- '**/.eslintrc.*'
# Root configs, any format
- '/.eslintignore'
- '/.eslintrc.*'
Projects can extend this task and provide additional parameters if need be, for example.
tasks:
lint:
args:
# Enable caching for this project
- '--cache'
TypeScript integration
If you're using the @typescript-eslint
packages, and want to
enable type-safety based lint rules, we suggest something similar to the official
monorepo configuration.
Create a tsconfig.eslint.json
in your repository root, extend your shared compiler options (we use
tsconfig.options.json
), and include all your project files.
{
"extends": "./tsconfig.options.json",
"compilerOptions": {
"emitDeclarationOnly": false,
"noEmit": true
},
"include": ["apps/**/*", "packages/**/*"]
}
Append the following inputs to your lint
task.
tasks:
lint:
# ...
inputs:
# TypeScript support
- 'types/**/*'
- 'tsconfig.json'
- '/tsconfig.eslint.json'
- '/tsconfig.options.json'
And lastly, add parserOptions
to your root-level config.
Configuration
Root-level
The root-level ESLint config is required, as ESLint traverses upwards from each file to find configurations, and this denotes the stopping point. It's also used to define rules for the entire repository.
module.exports = {
root: true, // Required!
extends: ['moon'],
rules: {
'no-console': 'error',
},
// TypeScript support
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.eslint.json',
tsconfigRootDir: __dirname,
},
};
The .eslintignore
file must also be defined at the root, as
only 1 ignore file
can exist in a repository. We ensure this ignore file is used by passing --ignore-path
above.
node_modules/
*.min.js
*.map
*.snap
Project-level
A project-level ESLint config can be utilized by creating a .eslintrc.<json|js|cjs|yml>
in the
project root. This is optional, but necessary when defining rules and ignore patterns unique to the
project.
module.exports = {
// Patterns to ignore (alongside the root .eslintignore)
ignorePatterns: ['build', 'lib'],
// Project specific rules
rules: {
'no-console': 'off',
},
};
The
extends
setting should not extend the root-level config, as ESLint will automatically merge configs while traversing upwards!
Sharing
To share configuration across projects, you have 3 options:
- Define settings in the root-level config. This only applies to the parent repository.
- Create and publish an
eslint-config
oreslint-plugin
npm package. This can be used in any repository. - A combination of 1 and 2.
For options 2 and 3, if you're utilizing package workspaces, create a local package with the following content.
module.exports = {
extends: ['airbnb'],
};
Within your root-level ESLint config, you can extend this package to inherit the settings.
module.exports = {
extends: 'eslint-config-company',
};
When using this approach, the package must be built and symlinked into
node_modules
before the linter will run correctly. Take this into account when going down this path!
FAQ
How to lint a single file or folder?
Unfortunately, this isn't currently possible, as the eslint
binary itself requires a file or
folder path to operate on, and in the task above we pass .
(current directory). If this was not
passed, then nothing would be linted.
This has the unintended side-effect of not being able to filter down lintable targets by passing arbitrary file paths. This is something we hope to resolve in the future.
To work around this limitation, you can create another lint task.
Should we use overrides
?
Projects should define their own rules using an ESLint config in their project root. However, if you want to avoid touching many ESLint configs (think migrations), then overrides in the root are a viable option. Otherwise, we highly encourage project-level configs.
module.exports = {
// ...
overrides: [
// Only apply to apps "foo" and "bar", but not others
{
files: ['apps/foo/**/*', 'apps/bar/**/*'],
rules: {
'no-magic-numbers': 'off',
},
},
],
};