I try to help

Monorepo - Jest and ESLint

Monorepo

Approximate reading time: 3 minute(s)

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.