Maison >interface Web >js tutoriel >Création d'un outil Codemod pour réécrire les exportations par défaut

Création d'un outil Codemod pour réécrire les exportations par défaut

DDD
DDDoriginal
2024-11-03 02:36:021018parcourir

Building a Codemod Tool for Rewriting Default Exports

Récemment au travail, nous avons décidé de migrer vers les exportations/importations nommées et d'ajouter la règle eslint no-default-export.

La motivation ressemblait à ceci :

Les exportations par défaut peuvent rendre le code plus difficile à maintenir, en particulier dans les bases de code volumineuses. Les noms importés peuvent être différents pour la même entité, affectant le processus de lecture du code et l'écriture des analyseurs statiques, ce qui rend la tâche plus difficile. A l’inverse, passer aux exports nommés supprime tous les inconvénients des exports par défaut.

Bien sûr, nous avons une énorme base de code et ce n'est pas un travail intéressant de remplacer manuellement ~1500 exportations par défaut et ~12000 importations par défaut ?

La principale difficulté était de mettre à jour tous les fichiers liés avec le même nouvel identifiant, créé pour l'export nommé.

Je vous donne un exemple :

// Button/Button.tsx
const Button = () => {};
export default Button;

// Button/index.ts
export { default } from './Button.tsx';

// SomePage1.tsx
import OldButton from './component/Button';

// SomePage2.tsx
import TestButton from './component/Button';

Et le résultat cible que je suppose ressemblerait à ceci :

// Button/Button.tsx
export const Button = () => {};

// Button/index.ts
export { Button } from './Button.tsx';

// SomePage1.tsx
import { Button as OldButton } from './component/Button';

// SomePage2.tsx
import { Button as TestButton } from './component/Button';

Chaque solution que j'ai trouvée sur Internet n'était qu'un codemod pour transformer chaque fichier indépendamment sans rien connaître d'autre en dehors de ce fichier.

J'ai commencé à rêver d'un analyseur qui :

  1. résoudre toutes les importations dans le projet et enregistrer les relations entre les fichiers
  2. recueillir des informations sur l'importation/exportation par défaut
  3. créer un nouveau nom d'identifiant pour l'export nommé
  4. remplacer toutes les entrées du dépôt ?

J'ai donc relevé un nouveau défi : développer un outil codemod qui réécrit automatiquement les exportations/importations par défaut en celles nommées.

spoiler

Je l'ai déjà développé ! ? ?

Processus de développement

Premières réflexions
Cela s'est produit juste après mon expérience précédente Visualiser l'arborescence des composants de réaction et la première idée était de réutiliser les plugins babel et webpack pour parcourir tous les modules et analyser l'AST, mais pourquoi, si jscodeshift a déjà l'analyseur, et si j'ai trouvé un remplaçant pour le plugin webpack, je serais capable d'écrire un outil indépendant du bundler, super ?

Outils
Ok, j'ai un jscodeshift comme analyseur. Mais pour trouver les relations entre tous les fichiers à partir du point d'entrée, j'ai trouvé le package de résolution, qui aide à résoudre des chemins comme les nodejs natifs require.resolve, mais il est plus similaire à la résolution de chemins comme les bundlers, vous avez plus de contrôle sur les extensions, la synchronisation /comportement asynchrone, etc.

Ingénierie du processus en deux étapes
La version initiale de mon outil ressemblait à tout dans un seul script. Cependant, pour améliorer la flexibilité et les performances et également simplifier le processus de développement avec le débogage, j'ai refactorisé l'outil en deux étapes :

  1. Collecte de données : La première phase rassemble toutes les instances d'importations et d'exportations par défaut dans la base de code

    • J'ai introduit une variable d'environnement, IS_GATHER_INFO, pour contrôler cette phase. Le script utilise la résolution pour trouver chaque utilisation d'une exportation/importation par défaut
    • Une autre var d'environnement ENTRY contient un chemin relatif vers votre point d'entrée de base de code, à partir de ce fichier, toutes les importations seront résolues et analysées
  2. Transformation : Une fois les données collectées, la deuxième phase réécrit les exports par défaut en exports nommés. Grâce à jscodeshift, j'ai transformé le code source en parallèle et facilement.

    • J'ai introduit une variable d'environnement, IS_TRANSFORM, pour contrôler cette phase

En se divisant en ces deux étapes :

  • J'ai pu dissocier la collecte de données de la transformation, réduisant ainsi la quantité de code exécuté et le temps passé pendant le développement et le débogage.
    • C'est un moyen très pratique de voir le résultat de la fonction rassemblerInfo, de l'analyser, de réexécuter votre code
    • Testez les transformations sans exécuter à plusieurs reprises l'ensemble du pipeline avec les données collectées
  • La collecte du vidage des données est utile si vous devez exécuter cet outil pour différents points d'entrée mais réutiliser les données collectées

Au fur et à mesure que les cas commençaient à s'accumuler (comme les importations dynamiques, les valeurs par défaut réexportées, différentes entités exportées : variables, fonctions et classes, et les noms déjà utilisés de problèmes de variables), j'ai passé plus de temps à configurer des cas de test. En 30 minutes environ, j'avais une configuration de test solide, me permettant de passer au développement piloté par les tests (TDD). Croyez-moi, cela vaut la peine de consacrer du temps au TDD pour de tels outils, qui ont un nombre énorme de cas. Plus vous avancez, plus vous ressentez de la valeur dans vos cas de test. Je dirais qu'après avoir couvert la moitié des cas, si vous n'avez pas de tests, cela devient un cauchemar d'exécuter et de déboguer sur un énorme projet car chaque fois que vous devez ajouter des modifications, cela peut casser beaucoup d'autres cas.

AST :
J'ai utilisé les types de nœuds AST suivants :

  • ImportDefaultSpecifier pour rechercher uniquement les instructions d'importation par défaut
    • importer quelque chose de '...'
  • ExportDefaultDeclaration pour rechercher uniquement les instructions d'exportation par défaut
    • exporter quelque chose par défaut ;
  • ExportNamedDeclaration pour rechercher les instructions d'importation par défaut et d'exportation par défaut
    • exporter { quelque chose par défaut } depuis '...' - exportation par défaut
    • exporter { par défaut comme quelque chose } depuis '...' - importation par défaut
    • export { default } from '...' - importation par défaut et exportation par défaut simultanément
  • ImportExpression pour rechercher l'importation dynamique et marquer ce fichier comme nécessaire pour conserver l'exportation par défaut. Certains outils comme React.lazy fonctionnent uniquement avec l'exportation par défaut.
    • importer('...')
  • De plus, j'ai enregistré des informations sur les fichiers proxy, ce sont des fichiers qui importent quelque chose par défaut et exportent ce quelque chose par défaut
    • Utilisé pour trouver le nouveau nom de l'export nommé dans n'importe quel fichier : fichier a -> fichier b -> fichier c

Considérations techniques et limitations connues
Bien que l'outil soit fonctionnel, il existe certains cas extrêmes qu'il ne gère pas encore :

utilisation de l'espace de noms.default
le code suivant ne sera pas encore transformé :

// Button/Button.tsx
const Button = () => {};
export default Button;

// Button/index.ts
export { default } from './Button.tsx';

// SomePage1.tsx
import OldButton from './component/Button';

// SomePage2.tsx
import TestButton from './component/Button';

Conflits dans les fichiers proxy
source :

// Button/Button.tsx
export const Button = () => {};

// Button/index.ts
export { Button } from './Button.tsx';

// SomePage1.tsx
import { Button as OldButton } from './component/Button';

// SomePage2.tsx
import { Button as TestButton } from './component/Button';

résultat :

import * as allConst from './const';
console.log(allConst.default);

Exportations foirées comme
source :

export { Modals as default } from './Modals';
export { Modals } from './Modals';

entraînera une logique brisée, car il y a maintenant deux exportations identiques avec une implémentation différente :

export { Modals } from './Modals';
export { Modals } from './Modals';

Et les importations pour l'entité précédente doivent également être corrigées manuellement
source :

export class GhostDataProvider {}
export default hoc()(GhostDataProvider);

résultat :

export class GhostDataProvider {}
const GhostDataProviderAlias = hoc()(GhostDataProvider);
export { GhostDataProviderAlias as GhostDataProvider };

Malgré ces limitations, j'ai corrigé manuellement le reste des erreurs en 15 à 20 minutes et j'ai réussi à lancer notre vrai projet. Les exportations par défaut de réécriture.

Links

  • jscodeshift
  • astexplorer

Ça y est, bienvenue dans les commentaires ci-dessous ! ?

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn