使用 Yeoman 自定义 Generator,包含新建 Vue 模板案例

Posted GoldenaArcher

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用 Yeoman 自定义 Generator,包含新建 Vue 模板案例相关的知识,希望对你有一定的参考价值。

本章学习内容如下:

  • 学习 Yeoman 即 Generator 的使用
  • 使用 Yeoman 自定义一个 Generator
  • 案例学习:使用 Yeoman 自定义一个 Vue 项目
  • 案例学习:使用 inquirer 和 ejs 实现一个小型的脚手架工具

什么是 Yeoman

Yeoman 是一个通用型的网页应用的脚手架工具,和 angular-cli, create-react-app, vue-cli 比起来,它是一个更加通用的脚手架工具,但是也因此它的灵活性更高。

Yeoman 支持的项目结构包含但不限于:

Yeoman 提供了 generator 的平台,而使用 generator 就能够通过 generator 中已经写好的配置构建项目模块。并且,不同的 generators 可以组合使用,yeoman 也支持用户自行上传 generator。

Yeoman 的安装,及使用 generator

先确定 npm 或是 yarn 有没有安装,随后通过 npm/yarn 全局安装 yeoman:

# 全局安装 yeoman
$ yarn global add yo

windows 环境下,如果是通过 yarn 安装的,需要将 %LOCALAPPDATA%/Yarn/config/global 加到系统变量 PATH 之中。

再安装对应 generator,也是全局安装。

这里安装的是 generator-node,使用该 generator 可以生成一个 node 模块项目。

$ npm install generator-node --global
some output here

这里 npm 和 yarn 串用只是表示两个都能够成功的安装 yeoman 和 generator-node,工具二选一,选自己用的最顺手的即可。

最后可以通过命令 yo 去生成一个新的 node 模块项目:

# 可以通过 yo --generators 去查看所有已经安装的 generatos
$ yo --generators
Available Generators:

  node
    boilerplate
    cli
    editorconfig
    eslint
    git
    readme
  jasmine

  mocha

  webapp

# 通过yo运行generator
$ yo node

界面上会出现一系列的提示,根据提示就可以填写完成模块的信息,并且实现模块的安装:

通过 vscode 打开当前根目录,能发现 yeoman 已经生成了一个 node 项目模块,并且已经完成了基础的项目配置。

windows 系统上安装 yeoman 的报错记录,之前我也单独开了一篇文章,如果出现因为 visual studio 报错,或是缺少 cairo.h 文件报错,可以看下这篇:完整记录一次 Yeomen 安装记录,报错到三压飙升,说不定能节省很多时间。


生成 sub generator

之前使用 yo generator 会生成一个单独的项目,但是很多情况下的需求并不是生成一个新的项目,而是在原有的项目中添加新的功能。

这时候就可以通过 yeoman 提供的 sub generator 去实现,具体的语法为:

yo generator:sub-generator

以在上一个步骤生成的 node 项目为例,这里想要加入一个 cli 功能,并加入对应的文件和模块,就可以根据上面的语法去实行:

# 语法为 yo generator:sub-generator
# 这里的sub-generator 为 cli
$ yo node:cli
# 随后会提示是否重写原有的 package.json,选yes即可

package.json 中新增的 cli 部分:

{
  "bin": "lib/cli.js",
  "dependencies": {
    "meow": "^3.7.0"
  }
}
# 将项目关联到全局范围内,这样就可以使用 这个模块名称 作为 cli
yeoman> yarn link
yarn link v1.22.10
success Registered "test-modules".
info You can now run `yarn link "test-modules"` in the projects where you want to use this package and it will be used instead.
Done in 0.14s.

# yarn 进行安装新的依赖包
yeoman > yarn
# 省略掉大量的安装信息

# cli 应用就正常工作了
yeoman> test-modules --help

  test whether yo works or not

  Usage
    $ test-modules [input]

  Options
    --foo  Lorem ipsum. [Default: false]

  Examples
    $ test-modules
    unicorns
    $ test-modules rainbows
    unicorns & rainbows

注:并不是所有的 generator 都提供子集的生成器,所以还是需要通过官方文档去查看一下具体的配置信息。

如何正确使用 Yeoman

首先,需要确定以下的需求:

  1. 明确需求
  2. 找到合适的 Generator
  3. 全局范围安装找到对应的 Generator
  4. 通过 Yo 运行对应的 Generator
  5. 通过命令行交互填写选项
  6. 生成所需的项目结构

以生成一个通用的网页应用为例,比较合适的 generator 是一个名为 webapp 的 generator。

这里就以安装一个 webapp 为例,再使用 yeoman 去建立一个新的项目

# 假设没有安装 generator-webapp,需要先安装
my-web-app> yarn global add generator-webapp
# 下面会挑出一些提示,例如说是使用sass还是bootstrap,BDD还是TDD等,按需修改即可

命令行跳出来的提示:

最后获得 web 应用的基础结构:

随后通过 yarn start 命令去启动这个 web-app:

一个基础的网页应用就已经搭建成功了。

大多数情况下,Yeoman 中已有的 generator 可以满足大部分的需求。对于特别具体的需求,Yeoman 尚且还没有 generator 去提供实现的,可以通过自定义一个 generator 的方法去实现。

自定义 Generator

这里会一步一步过一遍,使用 Yeoman 搭建自己的脚手架的步骤。

在找不到合适的 generator 的前提下,自己写一个脚手架的意义还是很大的,官方的脚手架毕竟更加的通用,在我使用 CRA 的时候会遇到以下问题:

  1. 没有 react-redux,

    我的项目用的都是 redux 做状态管理,而 react 也支持 mobx 或是其它的工具,所以并不会强制规定一定要用 redux。

    这也就意味着在使用 CRA 之后还需要手动配置 redux 以及相关的 store、reducer 和 actions

  2. react-router

    react-router 好像也是社区支持,没有并在官方项目中的依赖包,所以也有同样的问题。

  3. eslint

    同样的问题,同样的配置需要复制黏贴很多遍

创建 Generator 模块

Generator 本质上就是一个有着特定结构的 npm 模块:

Generator 的目录结构:

|- generators # 生成器目录
|  |- app # 默认声称其目录
|  |  |- index.js # 默认生成器实现
|- package.json # 模块包配置文件

如果需要在项目中插入其他的生成器,结构则如下:

 |- generators # 生成器目录
 |  |- app # 默认声称其目录
 |  |  |- index.js # 默认生成器实现
+|  |- components # 其他生成器目录
+|  |  |- index.js # 其他生成器实现
 |- package.json # 模块包配置文件

新加的 components 中的内容,就是使用其他生成器添加的新模块。

要注意的是,Yeoman 中的 Generator 命名规范必须遵从generator-<name> 的规范,否则会无法被识别。

下面就一步步的完成一个 Generator 的实现。

案例-创建 generator 模块

  1. 新建一个新的目录,这里的目录名为 generator-sample

  2. cd 到 generator-sample 中

  3. 初始化一个 node 项目

    generator-sample> yarn init
    yarn init v1.22.10
    # 省略若干初始化配置
    
  4. 安装 yeoman-generator 依赖包

    generator-sample> yarn add yeoman-generator
    

    初始化完成后,一个基础的 node 项目就被构建完成了:

  5. 添加 generator 结构目录

    就是上文标注的目录,

  6. 实现 index.js

    这里 index.js 的作用非常简单,就是在跟目录下生成一个 temp.txt,随后写入一个随机数

    // Generator 的核心入口
    // 道出一个继承自 Yeoman Generator 的类型
    // 可以通过调用父类,Yeoman Generator,提供的一些方法去进行功能的实现
    const Generator = require('yeoman-generator');
    
    module.exports = class extends Generator {
      writing() {
        // Yeomen 自动在生成文件阶段调用此方法
        // 这里尝试往根目录中写入文件
        this.fs.write(
          this.destinationPath('temp.txt'),
          Math.random().toString()
        );
      }
    };
    
  7. 全局连接,使得当前模块可以被 yarn 找到

    generator-sample> yarn link
    
  8. 创建一个新的文件夹去使用 generator-sample

    generator-sample> cd ..
    yeoman> mkdir test-proj
    yeoman> ls
    Mode                 LastWriteTime         Length Name
    ----                 -------------         ------ ----
    d-----         6/16/2021   4:18 AM                generator-sample
    d-----         6/16/2021   4:30 AM                test-proj
    
    yeoman> cd .\\test-proj\\
    test-proj>
    
  9. 使用 generator-sample

    test-proj> yo sample
    create temp.txt
    
  10. 通过自定义 Generator 实现的项目就完成了

    如下:

    temp.txt 中的内容就是一串随机数

根据模板创建文件

大多数情况下的代码都是比较固定化的,有一定的模板,如 React 中的类的格式就是:

import React, { Component } from 'react';

export class Name extends Component {
  render() {
    return <div></div>;
  }
}

export default Name;

所以能够使用模板创建文件,也是一个可以极大地缩减脚手架开发时间的功能。

在 Generator 中,模板的结构如下:

|- generators # 生成器目录
|  |- app # 默认声称其目录
|  |  |- templates
|  |  |  |- template-here
|  |  |- index.js # 默认生成器实现
|- package.json # 模块包配置文件

接下来就按照步骤,实现一个从模板中生成文件的案例

  1. 创建模板文件

     // 这是一个 EJS 模板文件
    
     import React, { Component } from 'react';
    
     export class <%= compName %> extends Component {
       render() {
         return <div></div>;
       }
     }
    
     export default <%= compName %>;
    

    这里生成的 templage 文件名为 foo.txt,它使用的是 EJS 的语法。亲测来说,使用 .txt.html 都没什么问题,使用 .ejs 好想要将输入地址修改为一个目录,这点之后也可以玩着试试看。

  2. 修改 index.js

    这里就不需要使用 this.fs.write() 去实现这个功能了,因为 Generator 自带了导入模板引擎的函数:copyTpl()

    copyTpl() 接受 3 个参数:输入文件,输出文件,以及内容,用法如下:

    const Generator = require('yeoman-generator');
    
    module.exports = class extends Generator {
      writing() {
        const templatePath = this.templatePath('foo.txt');
        const output = this.destinationPath('foo.js');
        const context = { compName: 'Home' };
    
        this.fs.copyTpl(templatePath, output, context);
      }
    };
    
  3. 运行 generator

    # 这里已经使用 yarn link 过了,新的项目需要关联到全局能够让 yeoman 找得到
    test-proj> yo sample
    create foo.js
    
  4. 查看输出结果

    这里就生成了一个标准的 React class-based component 了:

    // 这是一个 EJS 模板文件
    
    import React, { Component } from 'react';
    
    export class Home extends Component {
      render() {
        return <div></div>;
      }
    }
    
    export default Home;
    

在数据结构重复并且有一定程度的重复性时,使用模板引擎可以有效的减少代码重复量,并且有效的提升性能。

接受用户输入

在很多 cli 工具中,命令行会提示用户输入对应的信息,例如说在新建一个 node 项目时,命令行会提示需要项目名称、版本号、用户名,等。

Yeoman 也内部实现了这个功能,使用 prompting() {} 就可以获取用户输入的信息。

下面是具体的流程:

  1. 修改模板引擎

    这里稍微修改一下流程,使用 .html 作为模板引擎。

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title><%= title %></title>
      </head>
      <body>
        <%= title %>
      </body>
    </html>
    
  2. 修改 index.js

    这里主要就是在 prompting 中提示用户输入信息,以及在 writing 中获取用户输入的信息

    const Generator = require('yeoman-generator');
    
    module.exports = class extends Generator {
      prompting() {
        // Yeoman在向用户询问输入时会调用这个函数
        // prompting 会返回一个回调函数
        return this.prompt([
          {
            type: 'input',
            name: 'title',
            message: 'Your project name',
            default: this.appname, // 当前目录下文件夹的名字
          },
        ]).then((answers) => {
          // 将获取的所有输入保存起来,让 writing 可以接受到
          this.answers = answers;
        });
      }
      writing() {
        const templatePath = this.templatePath('project-name.html');
        const output = this.destinationPath('project-name.html');
        const context = this.answers; // {title: 'user-input-value' }
    
        this.fs.copyTpl(templatePath, output, context);
      }
    };
    
  3. 运行 generator

    依旧是老一套了

    test-proj> yo sample
     ? Your project name project-name
    create project-name.html
    
  4. 查看输出结果

    这是输出的 HTML 文档,其中原本是 ejs 模板引擎的地方,就被替换成了之前输入的 project-name

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>project-name</title>
      </head>
      <body>
        project-name
      </body>
    </html>
    

Vue Generator 案例

按步骤实现一个 vue generator

  1. 新建一个文件夹

    这里将文件夹命名为 generator-vue-sample

  2. cd 到 generator-vue-sample,初始化一个新的 node 项目

    generator-vue-sample> yarn init
     question name (generator-vue-sample):
     question version (1.0.0):
     question description:
     question entry point (index.js):
     question repository url:
     question author:
     question license (MIT):
     question private:
     success Saved package.json
    
  3. 添加 yeoman-generator 的依赖

    generator-vue-sample> yarn add yeoman-generator
    # 省略若干安装输出
    

    一直到这一步,都和上面新建一个 generator 的步骤是完全一致的。

    但是,从下面开始实现 templates 和 index.js 这里开始,就不太一样了

  4. 新建 templates 文件夹,并且将一个 vue 的项目复制进去

    • 被复制进去的 vue 项目结构:

      src 文件目录:

      基本上说大部分的项目所需要的配置,包括 router,eslint,production,development 之类的都内容结构都已经新建好了。

    • 复制完成后 generator 的结构:

  5. 将原本的项目名称改为 EJS 模板引擎

    这里包括以下的文件名称:

    • README.md
    • package.json
    • src/index.html

    这里只需要将原本实现好的名称,如 my-project,改为 EJS 模板引擎 <%= name %> 即可。

    另外,如果用的是 Vue 自动生成的项目,需要将 index.html 中的 <%= BASE_URL %> 改为 <%%= BASE_URL %>favicon.ico,将 <%= htmlWebpackPlugin.options.title %> 改为 <%%= htmlWebpackPlugin.options.title %>

    <%% %> 指的是保留当前变量,使其不被转义。

    在复制 templates 中的内容过程中,copyTpl() 会解析对应的模板引擎内容,并且替换成 prompting() 中获得的用户输入。

    这与 接受用户输入 中的内容是相似的,只不过这里要替换更多的内容而易。

  6. 按照 generator 的目录结构,新建并实现 index.js

    这里其实变动还挺多的,因为之前的案例中只是输出单一文件,但是这里要输出多个文件,所以内容会有相应的改变:

    const Generator = require('yeoman-generator');
    
    module.exports = class extends Generator {
      prompting() {
        // 这里可以输出多个 input
        // 当前案例中值获取了项目名称
        return this.prompt([
          {
            type: 'input',
            name: 'name',
            message: 'Your project name',
            default: this.appname,
          },
        ]).then((answers) => {
          this.answers = answers;
        });
      }
    
      writing() {
        // 文件名组成的数组
        const templates = [
          'babel.config.js',
          'package.json',
          'public/favicon.ico',
          'public/index.html',
          'README.md',
          '.browserslistrc',
          '.editorconfig',
          'src/App.vue',
          'src/assets/logo.png',
          'src/components/HelloWorld.vue',
          'src/main.js',
          'src/router.js',
          'yarn.lock',
          'src/store/index.js',
          'src/utils/index.js',
          'src/views/index.js',
          'src/App.vue',
          'src/main.js',
          'src/router.js',
        ];
    
        // 循环输出,而不是只输出单独的一个文件
        templates.forEach((item) => {
          this.fs.copyTpl(
            this.templatePath(item),
            this.destinationPath(item),
            this.answers
          );
        });
      }
    };
    
  7. 连接当前 generator

    依旧是用 yarn link 将当前的 generator 连到全局使用

  8. 尝试生成新的 vue 项目

    创建一个新的文件夹,并且使用当前 Generator

    使用 vscode 打开项目结构,其结构如下:

    是一个有着 store,路由等功能的项目了。

    少了一些配置文件是因为 templates 的文件名没有写全,这个最好是写个函数去动态获取 templates 下的所有文件和文件夹,不要写死。

    这里是写死值是处于案例方便展示的效果。

这样,一个自定义的 vue 项目模板就完成了。

发布 Generator

Generator 的发布还是简单的,毕竟本质上来说,Generator 就是一个 npm 的模块。

发布的步骤就是:

  1. 新建一个 github 的仓库用来托管写好的代码

    千万别忘了新建一个 .gitignore 文件,然后将 node_modules 写进 .gitignore

  2. 使用 yarn 或是 npm 将发布插件

    如果发布失败就查看一下是不是使用淘宝镜像了,淘宝镜像是一个只读镜像,不接受任何的改变,所以在发布前需要将镜像改回官方(npm 或是 yarn)的镜像。

发布完成的模块就可以直接在 npm 的官网上找到,要安装就可以使用以下命令:

npm install 发布的Generator

脚手架的工作原理

脚手架的工作原理说起来还挺简单的:

  1. 命令行交互获取信息
  2. 通过模板引擎将信息写入文档中

所以这里就学习去创建一个 cli 应用。

依旧是一步一步的来,按操作尝试生成一个脚手架 cli。

  1. 新建一个目录,并初始化一个 package.json

    front> mkdir sample-cli
    front> cd .\\sample-cli\\
    yarn init v1.22.10nit --yes
    warning The yes flag has been set. This will automatically answer yes to all questions, which may have security implications.
    success Saved package.json
    Done in 0.10s.
    
  2. 修改 package.json

    设置 cli 的入口文件

    {
      "name": "sample-cli",
      "version": "1.0.0",
      "main": "index.js",
      "bin": "cli.js",
      "license": "MIT"
    }
    
  3. 添加 cli.js 这个文件

    #!/usr/bin/env node
    
    // 设置环境变量的 文件头
    
    console.log('cli start');
    
  4. 依旧是使用 yarn link 将这个 node modules 关联到全局

    完成这一步就可以完成一个最基本的交互了:

    success Registered "sample-cli".
    info You can now run `yarn link "sample-cli"` in the projects where you want to use this package and it will be used instead.
    Done in 0.12s.
    sample-cli> sample-cli
    cli start
    
  5. 具体业务 1:发起命令行交互

    交互使用的是 inquirer 这个依赖包,先安装依赖包:

    sample-cli> yarn add inquirer
    

    inquirer 提供了一个 prompt 的函数去进行命令行交互,其实现如下:

     #!/usr/bin/env node
    
     // 设置环境变量的 文件头
    
     const inquirer = require("inquirer");
    
     inquirer
       .prompt([
         {
           type: "input",
           name: "name",
           message: "Project name",
         },
       ])
       .then((answers) => {
         console.log(answers);
       });
    
    

    和之前使用 Yeoman 的过程非常的相似,交互过程如下:

     sample-cli> sample-cli
     ? Project name project-name
     { name: 'project-name' }
    
  6. 生成模板用的文件

    这一步应该也很熟了。

    现在根目录下创建 templates 文件夹,里面先设置一个模板案例
    templates/index.html,它所包含的代码为:

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title><%= name %></title>
      </head>
      <body>
        <h1><%= name %></h1>
      </body>
    </html>
    

    后面又新建了一个 index.css,不过里面没什么内容,只是表示可以读取多个文件而已

  7. 读取模板用的文件

    就是自行封装一个函数去读取 templates 文件夹下的文件,实现的代码为:

    // 新增的引用
    const path = require('path');
    const fs = require('fs');
    // 上面的函数没有变化,就不放进来了
    then((answers) => {
      // 根据用户提供的内容生成文件
      const tmplDir = path.join(__dirname, 'templates');
      // 目标输出目录
      const destDir = process.cwd();
    
      // 读取模板目录下所有文件
      fs.readdir(tmplDir, (err, 

    以上是关于使用 Yeoman 自定义 Generator,包含新建 Vue 模板案例的主要内容,如果未能解决你的问题,请参考以下文章

    npm / yeoman 在没有 sudo 的情况下安装 generator-angular

    Yeoman

    Yeoman

    使用yeoman起一个新项目(个人练习记录,勿喷!)

    Nodejs开发Office插件

    前端工程化系列[06]-Yeoman脚手架核心机制