il y a 5 mois

Démarrer un nouveau projet Javascript peut être éreintant : beaucoup de fichiers de configuration, de librairies a installer et à faire fonctionner ensemble.

Nous allons voir aujourd'hui un des outils pour copier un modèle de projet : Yeoman. L'acte de générer un projet automatiquement depuis un template s'appelle scaffolding ! Et Yeoman est donc un scaffolder.

Yeoman

Yeoman fonctionne en lignes de commande écrites en Node.js et combine un générateur de projet, un gestionnaire de dépendances, l'exécution des tests unitaires, un serveur d'application local et l'optimisation du code pour déploiement.

Dans cet article, nous allons découvrir comment créer un générateur yeoman, c'est à dire un template de projet que l'on pourra recopier à chaque nouveau projet. Nous apprendrons aussi à tester ce générateur, et a tester sa publication sur npm.

Utiliser un générateur

Pour utiliser un générateur yeoman, il faut installer en global deux paquets :

npm install -g yo
npm install -g generator-mygenerator

La première commande installe yeoman en global sur votre machine. La seconde installe le générateur de votre choix.

Enfin, pour créer un nouveau projet a partir de ce générateur, dans le dossier de votre choix, vous pouvez éxécuter la commande suivante :

yo mygenerator

Où mygenerator correspond au générateur installé plus tôt.

Créer son propre générateur

Nous arrivons maintenant à la partie qui nous intéresse : créer son propre générateur yeoman.

Nous allons commencer par installer... un générateur de générateur ! Puis dans le dossier de notre choix, nous allons l'exécuter.

npm install -g generator-generator
cd dossier/de/travail
yo generator 

Un ensemble de questions vous sera alors posé :

? Your generator name generator-tmp
Your generator must be inside a folder named generator-tmp
I'll automatically create this folder.
? Description
? Project homepage url
? Author's Name Noé Gambini
? Author's Email me@gambini.me
? Author's Homepage
? Package keywords (comma to split)
? Send coverage reports to coveralls Yes
? Enter Node versions (comma separated)
? GitHub username or organization
? Which license do you want to use? Apache 2.0

La structure de dossiers générée ressemble à ceci :

Deux choses vont nous intéresser :

  • le fichier index.js, qui contiendra le code et la logique de notre générateur
  • le dossier templates, qui contiendra les fichiers à copier.

Le code du générateur

module.exports = class extends Generator {
  prompting() {
  }

  writing() {
  }

  install() {
  }
};

Le code du générateur est divisé en trois sections principales :

  • prompting : le code qui sera responsable de poser des questions à l'utilisateur. Par exemple le nom du projet, le choix entre npm et yarn, la license...
  • writing : le code qui sera responsable de copier les fichiers
  • install : le code qui sera éxécuté après avoir copié les fichiers. Par exemple pour installer des dépendances, jouer des scripts, exécuter des tests...

Prompting

Pour la partie prompting, yeoman utilise Inquirer.js.

Voici un exemple :

prompting() {
    const prompts = [
      {
        // Différents types sont disponibles : input, checkbox, list...
        type: "input",
        // Le nom de la donnée à stocker
        name: "name",
        message: "Le nom de votre projet",
        // Valeur par défaut si rien n'est rempli
        default: this.appname.replace(/ /g, "").toLowerCase()
      },
      {
        type: "list",
        name: "package_manager",
        message: "Souhaitez vous utiliser npm ou yarn ?",
        choices: ["yarn", "npm"],
        default: 0
      }
    ];

    return this.prompt(prompts).then(props => {
      // Pour accéder plus tard aux infos, on pourra faire
	    // this.props.name
      this.props = props;
    });
  }

Writing

Une fois que l'utilisateur à répondu aux questions, nous pouvons commencer la copie des fichiers.

Copie simple

Pour copier tout le dossier template, ainsi que tout ses sous-fichiers, y compris les fichiers masqués (commençant par un point) vous pouvez utiliser la commande suivante :

this.fs.copy(this.templatePath("./"), this.destinationRoot(), {
    globOptions: { dot: true }
  });

Copie de template

Yeoman utilise pug pour effectuer du templating au sein des fichiers de code. Les fichiers de template doivent être préfixés par un underscore.

Pour copier un fichier de template et lui donner des variables, on peut utiliser le code suivant :

this.fs.copyTpl(
	// le fichier de template, dans le dossier "templates"
  this.templatePath("_package.json"),
	// le fichier a écrire, dans le dossier de destination
  this.destinationPath("package.json"),
  { // un objet qui contient les variables données au template
    name: this.props.name
  }
);

Le fichier _package.json quand à lui utilise une syntaxe pug classique :

{
  "name": "<%= name %>",

ici <%= name %> sera remplacée par la variable saisie par l'utilisateur dans la section prompting, et sauvegardée dans this.props.name.

Supprimer un fichier

Vous pouvez supprimer un fichier avec la commande this.fs.delete('fichier');

Code final de la section writing

writing() {
  // je copie tous les fichiers du dossier template
  // dans le répertoire de destination
  // y compris le fichier _package.json
  this.fs.copy(this.templatePath("./"), this.destinationRoot(), {
    globOptions: { dot: true }
  });

  // je copie le template _package.json dans un fichier package.json,
  // en lui donnant ses variables
  this.fs.copyTpl(
    this.templatePath("_package.json"),
    this.destinationPath("package.json"),
    {
      name: this.props.name
    }
  );

  // Je supprime le template _package.json du dossier de destination
  // car je ne veux pas le conserver
  this.fs.delete("_package.json");
}

Install

La section install nous permet d'éxécuter du code après la copie des fichiers. Par exemple, nous pouvons installer les dépendances avec yarn ou npm :

if (this.props.package_manager === "yarn") {
    this.yarnInstall();
  } else {
    this.npmInstall();
  }

Tester et publier son générateur

Tester

Pour tester localement, il est possible d'utiliser la commande npm link.

## dans le dossier du générateur 
npm link
## dans le dossier ou l'on souhaite créer un projet
yo NOMDUGENERATEUR

Le nom du générateur correspond au champ name du package.json du générateur. Par exemple, si l'on a :

"name": "generator-test-bonjour",

On exécutera la commande :

yo test-bonjour

Publier

Pour publier son générateur, il faut être connecté à npm, ce que l'on peut faire avec npm login. Ensuite, la commandenpm publish permettra d'envoyer en ligne le générateur. Il pourra alors être installé depuis npm et utilisé par n'importe qui.

Code final du générateur

const Generator = require("yeoman-generator");

module.exports = class extends Generator {
  prompting() {
    const prompts = [
      {
        // Différents types sont disponibles : input, checkbox, list...
        type: "input",
        // Le nom de la donnée à stocker
        name: "name",
        message: "Le nom de votre projet",
        // Valeur par défaut si rien n'est rempli
        default: this.appname.replace(/ /g, "").toLowerCase()
      },
      {
        type: "list",
        name: "package_manager",
        message: "Souhaitez vous utiliser npm ou yarn ?",
        choices: ["yarn", "npm"],
        default: 0
      }
    ];

    return this.prompt(prompts).then(props => {
      // To access props later use this.props.someAnswer;
      this.props = props;
    });
  }
  
  writing() {
    // je copie tous les fichiers du dossier template
    // dans le répertoire de destination
    // y compris le fichier _package.json
    this.fs.copy(this.templatePath("./"), this.destinationRoot(), {
      globOptions: { dot: true }
    });
  
    // je copie le template _package.json dans un fichier package.json,
    // en lui donnant ses variables
    this.fs.copyTpl(
      this.templatePath("_package.json"),
      this.destinationPath("package.json"),
      {
        name: this.props.name
      }
    );
  
    // Je supprime le template _package.json du dossier de destination
    // car je ne veux pas le conserver
    this.fs.delete("_package.json");
  }

  install() {
    if (this.props.package_manager === "yarn") {
      this.yarnInstall();
    } else {
      this.npmInstall();
    }
  }
};

Conclusion

Yeoman est un outil assez intéressant de l'écosystème Javascript lorsque l'on doit utiliser une base de code commune pour différents projets. Il pourrait être très intéressant pour votre école, votre agence web ou votre entreprise à partir du moment ou vous avez plusieurs projets à générer automatiquement, autour d'une base commune.