J'ai découvert Knip il y a quelque temps mais sans prendre le temps de tester. J'ai testé ce week-end : je suis devenu instantanément fan ! Je vous explique !

Note: n'hésitez pas à aller sur la documentation officielle pour des infos plus complète et plus à jour !

Mise en place

J'opte pour la version facile :

npm init @knip/config
ou
pnpm create @knip/config

Rien de compliqué sous le capot de l'initialisation : un script "knip" pour lancer l'outil et l'ajout de knip en dépendance.

Premier lancement !

Je teste sur le code de mon futur blog (dont je ne partage pas encore le code), et j'ai cette sortie :

Premier lancement

C'est un tout petit projet, mais la sortie est déjà intéressante je trouve !

Première section :

Unused files (4)
data/2022/03/02/quelques-chiffres-sur-le-blog-en-2021/data/stat.ts
data/2023/03/07/2022-cest-fini-en-route-pour-2023/data/array.utils.ts
data/2023/03/07/2022-cest-fini-en-route-pour-2023/data/extract-stats.ts
data/2023/03/07/2022-cest-fini-en-route-pour-2023/data/goaccess.util.ts

On voit qu'il me pointe que j'ai des fichiers .ts inutiles dans mon projet. En effet ils sont inutiles car importés nulle part mais c'est parce que ce sont des scripts pour des articles. En effet par défaut knip va inspecter tous les fichiers mais considérer comme utile uniquement les fichiers importés dans l'arbre qui commence aux points d'entrées de l'application.

Dans mon cas je vais exclure complètement le dossier data/, mais on pourrait aussi ajouter des points d'entrées pour que la détection soit correcte.

Dans la seconde section on parle de dépendances inutiles.

Unused dependencies (2)

@iconify-json/mdi     package.json
@iconify-json/tabler  package.json

Unused devDependencies (1)

prettier-plugin-astro  package.json

On voit que knip m'en trouve deux. Alors oui mais en fait non. Autant il détecte les plugins Astro que j'utilise (comme l'adapter node, ou astro-icon), autant il n'a pas détecté que j'ai aussi des imports de pack d'icône via astro-icon. Il faudra donc que j'exclue ces dépendances.

Je découvre aussi que j'ai oublié d'importer le plugin prettier-plugin-astro… C'est dommage… Mais du coup c'est facile à corriger et sans knip je ne l'aurais peut-être jamais fait !

Ensuite on passe au pourquoi j'ai été intéressé par knip à la base :

Unused exports (2)

isDefined           function  src/utils/fp.utils.ts:1:17            
isBetterImageToken  function  src/utils/marked-better-image.ts:62:17

Unused exported types (6)

ImgOptions                            type       src/utils/html.utils.ts:13:13                    
FigureOptions                         type       src/utils/html.utils.ts:4:13                     
MarkedBetterImageOptions              interface  src/utils/marked-better-image.ts:5:18            
MarkedHeadingLinksOptions             interface  src/utils/marked-heading-links.ts:4:18           
MarkedImageGalleryOptions             interface  src/utils/marked-image-gallery.ts:6:18           
MarkedInlineAsciiPunctuationsOptions  interface  src/utils/marked-inline-ascii-punctuation.ts:3:18

J'ai 2 fichiers qui exportent des fonctions qui ne sont importées nulle part, je vois aussi que j'ai des types et interface qui ne sont importés nulle part. Je vois au passage qu'il ne se rend pas compte qu'il y a aussi des fonctions qui ne servent pas… Et en fait il a raison, car j'ai copié dans des packages à part les fonctions contenues dans les fichiers src/utils/marked-*.ts mais je ne l'ai pas importé…

Franchement c'est déjà très fort : sans aucune configuration, il a détecté le framework utilisé par mon projet, inspecté le projet pour finalement me pointer du doigt quelques éléments inutiles ! Malgré quelques erreurs, c'est déjà très bien à mon avis !

Un peu de configuration

Il y a plusieurs options pour configurer knip, du json au module TypeScript en passant par toutes les variantes possibles de JavaScript et JSON. De mon côté, j'ai choisi une configuration en TypeScript pour avoir le même niveau d'autocomplétion que sur mon code :

// knip.config.ts
import type { KnipConfig } from 'knip';

export default {
  project: ['**/*.{js,ts,tsx,astro}', '!data/**'],
  ignoreDependencies: ['@iconify-json/mdi', '@iconify-json/tabler'],
} satisfies KnipConfig;

Configuration très simple, mais qui, à priori, fait ce que je veux. Dans project j'ai spécifié 2 choses : les fichiers que je veux que knip considère (je suis obligé comme j'ai besoin d'ajouter une configuration, et si on définit la clé project il écrase la valeur par défaut), l'exclusion des fichiers qui sont dans data/ en indiquant le pattern avec un ! devant pour indiquer la négation. Je précise aussi via ignoreDependencies que je ne veux pas qu'il pointe mes deux packs d'icônes.

À noter que bien que knip auto-détecte les plugins à activer en fonction de ce qu'il trouve dans le projet, il est aussi possible d'activer/désactiver certains plugins manuellement en passant par les clés include et exclude et en indiquant pour chaque une liste de plugin. Ici je n'ai pas de problème particulier avec ce qui est auto-détecté, donc je ne touche à rien ; j'ai par contre testé sur un projet du boulot par curiosité, et là je pourrais par exemple désactiver le plugin pour les dépendances car il me demande d'ajouter des dépendances dans le package.json, dépendances qu'on ne veut pas importer comme ça (car on les charge autrement).

J'ai aussi appliqué quelques points qui manquaient dans mon projet et que j'ai repéré en utilisant knip : configurer l'utilisation du plugin prettier-plugin-astro, ajouté une lib perso pour les fonctions dans src/utils/fp.utils.ts, importés les plugins Marked via les libs que j'ai créées.

Je relance knip et j'obtiens :

Unused files (5)

src/utils/html.utils.ts
src/utils/marked-better-image.ts
src/utils/marked-heading-links.ts
src/utils/marked-image-gallery.ts
src/utils/string.utils.ts

Unused exports (2)

isDefined     function  src/utils/fp.utils.ts:1:17
isNotDefined  function  src/utils/fp.utils.ts:5:17
Unused exported types (1)
MarkedInlineAsciiPunctuationsOptions  interface  src/utils/marked-inline-ascii-punctuation.ts:3:18

C'est pratiquement bon ce coup-ci. Il me détecte bien quelques fichiers inutiles, par contre, il ne détecte pas que src/utils/marked-inline-ascii-punctuation.ts ne sert plus, car il y a un fichier de test associés qui lui fait directement référence.

Mais on peut rectifier ça facilement en définissant clairement quels fichiers contiennent notre code de prod et quels fichiers contiennent nos tests :

export default {
  project: ['**/*.{js,ts,tsx,astro}!', '**/*.{spec|test}.{js,ts,tsx}', '!data/**'],
  // ...
} satisfies KnipConfig;

Vous noterez l'ajout à la fin du premier pattern d'un ! indiquant qu'il s'agit de fichier de production, et du pattern pour les tests qui lui n'a pas le !. Ensuite on peut lancer knip avec l'option --production ce qui va nous indiquer que tous les fichiers src/utils/marked-*.ts sont inutiles.

Corrigeons tout ça !

J'ai ici alterné entre deux modes pour pouvoir détecter tout correctement. Je pense que le plus efficace et le moins cassant, c'est de passer par la routine automatique de nettoyage dans le mode normal, puis inspecter à la main les fichiers indiqués comme inutiles en productions.

Pour faire ça, rien de plus simple : j'ajoute un script knip:fix qui va appeler knip --fix --allow-removes-files. Par défaut le fix ne va pas supprimer de fichier seulement faire des éditions. Mais si notre projet est versionné avec git (par exemple), on pourra toujours valider que les changements appliqués ne posent pas de problèmes.

Après avoir laissé knip faire le ménage, je repère qu'il ne sait pas supprimer les fonctions, donc il faut le faire à la main.

Diff après suppression d'export sur des fonctions : il n'a pas supprimé les fonctions alors qu'elles sont maintenant inutiles

Note : knip ne nettoie pas tout à 100% magiquement et il faut faire une passe à la main pour finir le nettoyage (comme dans mon cas pour les fonctions), mais dans tous les cas, il vaut mieux éviter de pousser sans relire du code éditer par des outils à mon avis.

Conclusion

En bilan, je dirais que sur un projet si petit que mon blog en Astro j'ai pu supprimer une petite dizaine de fichier, quelques exports inutiles et me rendre compte de quelques erreurs de configuration. Je pense donc que ça vaut le coup de jeter un coup d'œil à knip !

Pour aller plus loin il faudrait mettre en place knip sur la CI. Je vous laisse décider si c'est nécessaire ou pas, en tout cas, il existe une documentation expliquant comment le faire avec Github Actions comme exemple : https://knip.dev/guides/using-knip-in-ci.

En tout cas, je reste bluffé par l'efficacité de cet outil qui juste fonctionne ! On peut commencer sans aucune configuration pour explorer et voir ce qui ne va pas, on peut très facilement le configurer avec toute la documentation et l'autocomplétion, j'ai dû mal à demander plus !

Je pense qu'on pourrait encore en dire pas mal sur l'outil, mais je vous laisse explorer ça par vous-même ! 🤓

Crédit photo : Générée via Microsoft Designer avec le prompt suivant :

declutter, cut, useless, files, directory, archive, trash, bin, happy