UPDATE: I have since tried Turborepo and Nx. And both of those are probably better than this solution.
The information in this article is still valid, but probably not the best advice I can give.
This article is the second in a three-part series about monorepos.
You can clone the repository.
This part is a fair bit shorter than the first one and focuses on installing and configuring ESLint and Jest. We already have a functioning monorepo. It's simple, but it gets the job done. Now we'll be adding linting and testing. You test your code right? Good!
ESLint
Let's install ESLint. We'll install this at the root level.
Why do you install packages at the root level?
I install them at root level so all use the same version. Use your own judgement here. To me it makes a lot of sense for my entire project to use the same version of typescript
, jest
, eslint
and even lodash
. To you it may not, in that case, feel free to install in each package.
yarn add -D -W eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-config-prettier eslint-plugin-prettier prettier
Now let's create our rules. This is merely an example, and you are more than welcome to change and adapt these rules to your project and team.
// .eslintrc.js
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
plugins: [
'@typescript-eslint',
],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
'prettier/@typescript-eslint',
],
};
Add an ignore file so ESLint doesn't waste time on files
// .eslintignore
node_modules
dist
build
coverage
.eslintrc.js
husky.config.js
jest.config.js
jest.config.base.js
lint-staged.config.js
webpack.config.js
Add a lint
script at root level.
// package.json
"scripts": {
"build": "lerna run build --stream",
"watch": "lerna run watch --parallel",
+ "lint": "eslint ./packages --ext .ts,.tsx",
"clean": "rm -rf .build && lerna run clean --parallel"
If you require separate rulesets in a package, remember that ESLint configs conform to a hierarchy.
Jest
This is all fine and dandy, but I want to test some of this code!
yarn add -D -W jest ts-jest
We'll have a base file for some common configuration options. Tweak these to taste.
// jest.config.base.js
module.exports = {
preset: 'ts-jest',
roots: ['<rootDir>/src'],
testEnvironment: 'node',
collectCoverage: true,
coveragePathIgnorePatterns: [
'<rootDir>/build/',
'<rootDir>/dist/',
'<rootDir>/node_modules/',
],
coverageDirectory: '<rootDir>/coverage/',
verbose: true
};
// jest.config.js
const base = require('./jest.config.base');
module.exports = {
...base,
roots: ['<rootDir>'],
projects: [
'<rootDir>/packages/ui',
'<rootDir>/packages/api',
'<rootDir>/packages/diceroll'
],
};
We'll add a file like the one below in each package, we can also have per-package overrides in that file.
// packages/ui/jest.config.js
const base = require('../../jest.config.base');
const packageJson = require('./package');
module.exports = {
...base,
testEnvironment: 'jsdom', // This is overriden, from the base testEnvironment
name: packageJson.name,
displayName: packageJson.name,
};
We can now run our tests by running jest
. I'd recommened adding this to the root package.json
.
In order to run tests we need to have built our files. We can modify our test
script to something like:
// package.json
"scripts": {
"build": "lerna run build --stream",
"watch": "lerna run watch --parallel",
"lint": "eslint ./packages --ext .ts,.tsx",
+ "test": "yarn build && jest",
+ "test:watch": "yarn build && jest --watch",
Keep in mind that we will still need to run our watch
script to pick up code changes made across our packages.
Husky and Lint-Staged
Ok, great, now for the last bit: Husky and Lint-Staged
yarn add -D -W husky lint-staged
Now some config files:
// husky.config.js
module.exports = {
hooks: {
'pre-commit': 'lint-staged',
'pre-push': 'yarn test',
}
};
// lint-staged.config.js
module.exports = {
'*.{ts,tsx}': ['eslint --fix'],
};
That's it! In the next part we'll look at adding React and consuming a ui-components
package.