Yeoman

start

Yeoman’s workflow is simple, using Yeoman to run a plugin-like generator, which we call a generator, you can quickly set up a specific project.

First, install the Yeoman module globally:

npm install -g yo
Copy the code

Then find a generator that meets your project requirements, which is essentially an NPM package and therefore also installed in the global directory:

npm install -g generator-webapp
Copy the code

Then execute Yoman’s scaffolding start instruction:

yo webapp
Copy the code

Command line input during execution allows you to select file options that match the project design, and you end up with a complete scaffolding project, ready to use right out of the box.

generator

Generators are the cornerstone of the Yeoman ecosystem. These are plug-ins that Yo runs to generate files for the end user.

As you can see, Yeoman is the platform on which the scaffolding is executed, the logic for actually generating the scaffolding is controlled by the Generator, and this plug-in flow approach is the basis for its general use.

There are some useful genarators available, from which we can find generators that are suitable for our intended project, or if they are not, we can also customize a generator. For details, see yeoman.io/authoring/.

A custom generator

At the heart of the generator is a Node.js module.

In fact, the official documentation has been written in detail, as an example to demonstrate.

The basic organizational structure of the generator

As the core of the Generator is a Node.js module, we create a directory and initialize it with NPM, but note that:

  • The package name, namely the folder name, must begenerator-nameFormat, wherenameIs the name of the generator.
  • npm initThe generatedpackage.jsonFile,keywordsProperties must contain"yeoman-generator"And the REPO must have a description in order toGenerator pageThe index.
  • Make sure the latest version is setyeoman-generatorIs a dependency.
  • filesThe property must be an array of files and directories used by the generator.
{
  "name": "generator-name"."version": "0.1.0 from"."description": ""."files": [
    "generators"]."keywords": ["yeoman-generator"]."dependencies": {
    "yeoman-generator": "^ 1.0.0"}}Copy the code

The generator file tree

In addition to the above requirements for module information, Yeoman’s functionality depends on how the directory tree is built. Each child generator is contained in its own folder.

The default generator yo name used when called is the APP generator. This must be included in the app/ directory. The subgenerator yo Name: SubCommand used when invoked is stored in the exact same folder as the subcommand.

The directory tree might look like this:

├ ─ ─ ─ package. Json └ ─ ─ ─ generators / ├ ─ ─ ─ app / │ └ ─ ─ ─ index. The js └ ─ ─ ─ the router / └ ─ ─ ─ the index, jsCopy the code

Yeoman allows for two different directory structures. It will look for./ and generators/ register available generators. So the directory tree might also look like this:

├ ─ ─ ─ package. Json ├ ─ ─ ─ app / │ └ ─ ─ ─ index. The js └ ─ ─ ─ the router / └ ─ ─ ─ the index, jsCopy the code

But make sure that the files field in package.json contains directory information:

{
  "files": [
    "app"."router"]}Copy the code

Basic module structure

/ / generators/app/index.js/generator.js/generators/app/index.js

const Generator = require('yeoman-generator')
module.exports = class extends Generator {    
    constructor(args, opts) {
        super(args, opts);
        this.log('App init');
    }

    method() {
        this.log('method is run');
    }

    method2() {
        return new Promise((res, rej) = > {
            this.log('method2 is run');
            setTimeout(() = > {
                this.appname = 'new appname';
                res();
            }, 100);
        });
    }

    method3() {
        this.log('appname=>'.this.appname); }};Copy the code

Note that generator functions are called automatically from Object.getPrototypeof (Generator). For asynchronous functions, return a Promise Object. If you want to avoid the function being called, use the _ prefix as the function name.

The user interaction

Ask users to obtain their project preferences and update some user Settings. Yoman uses the prompt function to accomplish this task, which is the wrapper tool function of Inquirer.

const Generator = require('yeoman-generator')
module.exports = class extends Generator {    
    constructor(args, opts) {
        super(args, opts);
        this.log('App init');
    }

    prompting() {
        return this.prompt([{
            type: "input".name: "title".message: "Your project name?".default: this.appname
          },
          {
            type: "confirm".name: "cool".message: "Would you like to enable the Cool feature?"
        }]).then(answer= > {
            this.answer = answer; }); }};Copy the code

File interaction

The ultimate purpose of generators is to create files, and Yoman encapsulates the FS module and provides fs utility functions to facilitate file-related operations.

const Generator = require('yeoman-generator')
module.exports = class extends Generator {    
    constructor(args, opts) {
        super(args, opts);
        this.log('App init');
    }
    
    writing() {
        const temp = this.templatePath('index.html'); // Module path (the default path is the genrators/ Templates directory under the generator project)
        const dest = this.destinationPath('pages/index.html'); // Target path (default path is current execution environment)
        this.fs.copyTpl(temp, dest, this.answer); }};Copy the code

The copyTpl function templates the file using ejS template engine.

Publish generator module

Once the user and file interaction logic is complete, the generator logic is almost complete. For ease of use, you can publish the generator to the NPM website, just like a normal module.

Polp

Plop is a small tool that can save you time and help your team build new files in a consistent manner.

Plop is a nice little scaffolding tool even smaller than Yoman, and you can’t even think of it as a scaffolding tool, just a gadget for quickly reusing files on projects.

Plop uses the same logic as Yoman: it queries users and writes files based on user input.

Simple to use

First install the Plop module in the project:

npm install --save-dev plop
Copy the code

Create a plopfile.js entry file in the project root directory:

module.exports = function (plop) {
    // controller generator
    plop.setGenerator('controller', {
        description: 'application controller logic'.// User interaction
        prompts: [{
            type: 'input'.name: 'name'.message: 'controller name please'}].// File interaction
        actions: [{
            type: 'add'.path: 'src/{{name}}.js'.templateFile: 'plop-templates/controller.hbs'}}]); };Copy the code

Then add the script plop to package.json to run.

How scaffolding works

It can be found that the essence of scaffolding is a CLI application. Through interaction with users, preset engineering scaffolding files are set according to user preferences, and these files are automatically created for users.

Now let’s just simulate this very briefly.

Create a Node module:

mkdir scaffold-application
cd scaffold-application
npm init -y
Copy the code

Write the package.json bin field to specify the cli entry file:

{
    bin: "cli.js"
}
Copy the code

Write the cli. Js

Cli applications are no different from Node.js, except that they have a fixed file header. The /usr/bin/env node is used as an identifier.

#! /usr/bin/env node

// Use inquirer. Js for user interaction
const inquirer = require('inquirer');
const path = require('path');
const ejs = require('ejs');
const fs = require('fs');

inquirer.prompt({
    type: 'input'.name: 'name'.message: 'App name',
}).then(anwser= > {
    // Render the template using EJS, then write the file using fs module
    const tempDir = path.resolve(__dirname, 'templates');
    const destDir = process.cwd();
    fs.readdir(tempDir, (err, files) = > {
        if (err) throw err
        files.forEach(file= > {
            ejs.renderFile(path.join(tempDir, file), anwser, (err, res) = > {
                if (err) throw err
                fs.writeFileSync(path.join(destDir, file), res);
            });
        });
    });
})
Copy the code

This is then global via NPM linklink, which is then used by the scaffolds-application command.