前端脚手架工具yoman和plop的使用

1,185 阅读5分钟

illustration-home-inverted.91b07808be.png

Yeoman

起步

yeoman的工作流程很简单,使用yeoman运行一个类似插件的generator,我们称之为生成器(发电机),就可以很快构搭建起一个特定的工程。

首先是全局安装yeoman模块:

npm install -g yo

然后找到一个符合你工程需求的generator,本质上也是一个npm包,因此同样安装在全局目录:

npm install -g generator-webapp

然后执行yoman的脚手架启动指令:

yo webapp

执行过程中会有命令行输入信息,以供你选择符合工程设计的文件选项,最终你就得到了一个完整的脚手架工程,达到开箱即用的效果。

generator

生成器是 Yeoman 生态系统的基石。它们是yo为最终用户生成文件所运行的插件。

可以发现,yeoman是执行脚手架的平台,真正生成脚手架的逻辑是generator所控制的,这种插件流的方式正是其通用的基础。

官方提供了一些实用的genaratorofficial_genarators,我们可以从中找到适合预期工程的生成器,如果不符合我们还可以自定义generator,具体细节可参考:yeoman.io/authoring/

自定义generator

生成器的核心是一个 Node.js 模块。

其实官方文档已经写得十分详尽了,下面作为实例进行演示。

generator基本的组织结构

由于generator的核心是一个node.js模块,因此我们先创建一个目录,并进行npm初始化,但需要注意的是:

  • 包名即文件夹名称必须为generator-name的格式,其中name为生成器的名称。
  • npm init生成的package.json文件中,keywords属性必须包含"yeoman-generator"并且 repo 必须有一个描述,以便生成器页面索引。
  • 确保将最新版本设置yeoman-generator为依赖项。
  • files属性必须是生成器使用的文件和目录数组。
{
  "name": "generator-name",
  "version": "0.1.0",
  "description": "",
  "files": [
    "generators"
  ],
  "keywords": ["yeoman-generator"],
  "dependencies": {
    "yeoman-generator": "^1.0.0"
  }
}

generator文件树

除了上述模块信息的要求,Yeoman 的功能取决于如何构建目录树。每个子生成器都包含在其自己的文件夹中。

调用时使用的默认生成器yo nameapp生成器。这必须包含在app/目录中。调用时使用的子生成器yo name:subcommand存储在与 sub 命令完全相同的文件夹中。

目录树可能是这样的:

├───package.json
└───generators/
    ├───app/
    │   └───index.js
    └───router/
        └───index.js

Yeoman 允许两种不同的目录结构。它将查找./generators/注册可用的生成器。所以目录树还可能是这样的:

├───package.json
├───app/
│   └───index.js
└───router/
    └───index.js

但这时需要确保package.json中的files字段包含目录信息:

{
  "files": [
    "app",
    "router"
  ]
}

基本的模块结构

生成器的入口文件:generators/app/index.js,模块需要导出一个继承Generator对象的子类对象:

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);
    }
};

值得注意的是,generator的函数是从Object.getPrototypeOf(Generator)自动调用的,对于异步函数,返回一个Promise对象即可,如果想要避免函数被调用,使用_前缀作为函数命名即可。

用户交互

通过询问用户来获取用户的项目偏好以及更新一些用户设置。yoman中使用prompt函数来完成该任务,该工具函数是Inquirer.js的封装工具函数。

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;
        });
    }
};

文件交互

生成器的最终目的就是要创建文件,yoman封装了fs模块并提供fs工具函数便于文件相关的操作。

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'); // 模块路径(默认路径为生成器项目下的genrators/templates目录)
        const dest = this.destinationPath('pages/index.html'); // 目标路径(默认路径为当前执行环境)
        this.fs.copyTpl(temp, dest, this.answer);
    }
};

copyTpl函数采用ejs模板引擎的方式对文件进行模板渲染。

发布generator模块

用户交互和文件交互逻辑完成后,生成器的逻辑基本就已经完成了,为了方便使用,你可以将该生成器发布到npm官网,这和发布普通模块没什么区别。

Polp

Plop 是一个小工具,可以节省您的时间并帮助您的团队以一致的方式构建新文件。

Plopyoman更小的小而美的脚手架工具,甚至你都可以不认为它是一个脚手架工具,而只是在项目中用于快速复用文件的小工具。

Plop的使用逻辑同yoman基本一致:询问用户,然后根据用户输入信息写入文件。

简单使用

首先在项目中安装Plop模块:

npm install --save-dev plop

在项目根目录创建一个plopfile.js的入口文件:

module.exports = function (plop) {
    // controller generator
    plop.setGenerator('controller', {
        description: 'application controller logic',
        // 用户交互
        prompts: [{
            type: 'input',
            name: 'name',
            message: 'controller name please'
        }],
        // 文件交互
        actions: [{
            type: 'add',
            path: 'src/{{name}}.js',
            templateFile: 'plop-templates/controller.hbs'
        }]
    });
};

然后在package.json中添加脚本plop即可运行。

脚手架的工作原理

可以发现,脚手架的本质就是一个cli应用,通过与用户交互,根据用户偏好来设置预设好的工程脚手架文件,自动帮用户创建好这些文件。

现在我们就简单模拟一下这个过程。

创建一个node模块:

mkdir scaffold-application
cd scaffold-application
npm init -y

编写package.jsonbin字段指定cli的入口文件:

{
    bin: "cli.js"
}

编写cli.js

cli应用和node.js没什么区别,主要是多了一个固定文件头\#!/usr/bin/env node作为标识。

#!/usr/bin/env node

// 使用inquirer.js进行用户交互
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 => {
    // 通过ejs进行模板渲染,然后使用fs模块写入文件
    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);
            });
        });
    });
})

然后通过npm linklink到全局,然后就可以通过scaffold-application命令使用了。