Configuration de Turborepo

Nous configurons Turborepo, en ajoutant les packages Typescript et Eslint.

5 min read

Ajout des commandes typecheck et lint

Nous avons déjà ajouté le mot clé workspaces dans le package.json principal. Il informe NodeJS que nous utilisons un monorepo.

package.json
1
{
2
"workspaces": ["frontend", "backend", "packages/*"]
3
}

Maintenant, lorsque nous installons nos dépendances avec npm install, NodeJS saura qu'il doit installer les dépendances de chaque projet.

Nous avions déjà ajouté la commande dev aux scripts du package.json. Elle exécute la commande turbo dev

package.json
1
{
2
"name": "nestjs-remix-monorepo",
3
"version": "1.0.0",
4
"description": "",
5
"main": "index.js",
6
"scripts": {
7
"test": "echo \"Error: no test specified\" && exit 1",
8
"dev": "turbo dev"
9
},
10
"keywords": [],
11
"author": "",
12
"license": "ISC",
13
"packageManager": "npm@10.2.3",
14
"workspaces": ["frontend", "backend", "packages/*"]
15
}

Pourquoi turbo dev ? Parce que nous avons défini une pipeline nommée dev dans la configuration Turborepo (le fichier turbo.json)

Pour rappel, voici son contenu

turbo.json
1
{
2
"$schema": "https://turbo.build/schema.json",
3
"pipeline": {
4
"build": {
5
"outputs": ["dist/**"]
6
},
7
"dev": {
8
"cache": false,
9
"persistent": true
10
}
11
}
12
}

Nous allons rajouter deux autres pipelines à cette configuration, pour utiliser Eslint et Typescript.

turbo.json
1
{
2
"$schema": "https://turbo.build/schema.json",
3
"pipeline": {
4
"build": {
5
"outputs": ["dist/**"]
6
},
7
"dev": {
8
"cache": false,
9
"persistent": true
10
},
11
"typecheck": {},
12
"lint": {}
13
}
14
}

Il nous suffit de rajouter ces deux clés typecheck et lint, qui prendront le même nom que les scripts que l'on va ajouter à nos fichiers package.json.

Pour exécuter ces pipelines, nous devons aussi les rajouter au fichier package.json principal :

package.json
1
{
2
"name": "nestjs-remix-monorepo",
3
"version": "1.0.0",
4
"description": "",
5
"main": "index.js",
6
"scripts": {
7
"test": "echo \"Error: no test specified\" && exit 1",
8
"build": "turbo build",
9
"dev": "turbo dev",
10
"typecheck": "turbo typecheck",
11
"lint": "turbo lint"
12
},
13
"keywords": [],
14
"author": "",
15
"license": "ISC",
16
"packageManager": "npm@10.2.3",
17
"workspaces": ["frontend", "backend", "packages/*"]
18
}

Maintenant, lorsque nous exécutons la commande npm run typecheck, Turborepo va regarder la configuration de chaque projet (frontend/package.json, backend/package.json, packages/eslint-config/package.json, packages/typescript-config/package.json respectivements). Si ces projets possèdent un script nommé typecheck, Turbo va l'exécuter en parallèle. S'ils n'en possèdent pas, Turbo ne l'exécutera pas.

En lançant la commande npm run dev à la racine du monorepo, on exécute le scripts nommé dev de chaque projet. Mais seul le projet backend possède un script nommé dev. C'est ce que nous souhaitons : Le serveur NestJS va se charger de lancer l'application Remix.js. Nous n'avons pas besoin d'avoir une commande dev dans le package.json du frontend. Si c'est le cas, nous devons la retirer.

Pour rappel, voici notre package.json.

frontend/package.json
1
{
2
"name": "@virgile/frontend",
3
"private": true,
4
"sideEffects": false,
5
"type": "module",
6
"main": "./index.cjs",
7
"types": "./index.d.cts",
8
"scripts": {
9
"start": "remix-serve ./build/server/index.js",
10
"old-dev": "remix vite:dev",
11
"build": "remix vite:build",
12
"lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .",
13
"typecheck": "tsc"
14
},
15
"dependencies": {
16
"@remix-run/node": "^2.8.1",
17
"@remix-run/react": "^2.8.1",
18
"@remix-run/serve": "^2.8.1",
19
"isbot": "^5.1.2",
20
"react": "^18.2.0",
21
"react-dom": "^18.2.0",
22
"remix-flat-routes": "^0.6.4"
23
},
24
"devDependencies": {
25
"@remix-run/dev": "^2.8.1",
26
"@types/react": "^18.2.67",
27
"@types/react-dom": "^18.2.22",
28
"@virgile/eslint-config": "*",
29
"@virgile/typescript-config": "*",
30
"eslint": "^8.57.0",
31
"eslint-import-resolver-typescript": "^3.6.1",
32
"eslint-plugin-import": "^2.29.1",
33
"eslint-plugin-jsx-a11y": "^6.8.0",
34
"eslint-plugin-react": "^7.34.1",
35
"eslint-plugin-react-hooks": "^4.6.0",
36
"eslint-plugin-remix-react-routes": "^1.0.5",
37
"eslint-plugin-tailwindcss": "^3.15.1",
38
"typescript": "^5.4.3",
39
"vite": "^5.2.2",
40
"vite-tsconfig-paths": "^4.3.2"
41
},
42
"engines": {
43
"node": ">=18.0.0"
44
}
45
}

Il possède les commandes suivantes :

  • Une commande typecheck, pour détecter les erreurs Typescript
  • Une commande lint pour détecter les erreurs Eslint (et appliquer les bonnes pratiques)
  • Une commande build pour construire l'application Remix.js

En environnement de développement, NestJS se charge de build le code source de Remix. Cependant en production, nous avons besoin de la commande build de Remix pour compiler le code source du frontend.

Ajout de la commande build

Notre fichier de configuration turbo.json contient déjà une commande build. Mais elle est erronée (nous avons laissé la configuration par défaut).

turbo.json
1
{
2
"$schema": "https://turbo.build/schema.json",
3
"pipeline": {
4
"build": {
5
"outputs": ["dist/**"] // <= Cette ligne est erronée
6
},
7
"dev": {
8
"cache": false,
9
"persistent": true
10
},
11
"typecheck": {},
12
"lint": {}
13
}
14
}

Effectivement, l'instruction outputs défini un tableau de dossiers contenant le code source compilé, une fois la commande build exécutée. Mais le chemin n'est pas bon.

Notre application NestJS compile bien son code dans le dossier dist, le bon chemin est donc backend/dist/**. Notre application Remix compile son code dans le dossier build, le bon chemin est donc frontend/build/**.

De plus, avant de pouvoir compiler le code source de NestJS, nous avons besoin de compiler l'application Remix. (Car NestJS utilise le build de Remix, dans son main.ts. Il est donc important de build le code source de Remix, avant de build l'application NestJS.)

Comme notre backend dépend du frontend (nous avions ajouté l'instruction ci-dessous), Turborepo saura qu'il faut d'abord compiler le frontend avant de compiler le backend.

package.json
1
{
2
"dependencies": {
3
"@virgile/frontend": "*"
4
}
5
}

Ajoutons la clé "dependsOn": ["^build"] au fichier de configuration pour que Turbo en soit informé.

turbo.json
1
{
2
"$schema": "https://turbo.build/schema.json",
3
"pipeline": {
4
"build": {
5
"outputs": ["backend/dist/**", "frontend/build/**"],
6
"dependsOn": ["^build"]
7
},
8
"dev": {
9
"cache": false,
10
"persistent": true
11
},
12
"typecheck": {},
13
"lint": {}
14
}
15
}

Maintenant, exécuter la commande npm run build à la racine du monorepo va compiler le code source de l'application Remix, puis celui de l'application NestJS.

Cependant, nous avons un problème dans le terminal. Comme nous utilisons un monorepo, nous avons besoin d'utiliser un chemin absolu dans le fichier de configuration vite.config.ts

Le build de notre application échoue