前端工程化系列[07] Yeoman-generator的创建

Yeoman脚手架使用入门Yeoman脚手架核心机制这两篇文章中已经对Yeoman脚手架工具的基本使用以及去核心运转机制进行了深入的介绍,这篇文章将以实例的方式来教会如何从零开始创建属于我们自己的generator。

点击获取本文示例的generator

generator创建准备

这里我们一切从零开始,在创建自己的generator之前需要做一些准备工作,比如准备好yo命令行工具,比如对生成器生成的项目结构和目录文件有清晰的规划等。

yo命令行工具

在安装了NodeJS和npm的前提下,可以通过下面的命令来安装yo命令行工具,并检查安装是否成功。

$ npm install -g yo
$ yo --version

generator-generator的安装

创建generator可以完全从零开始,也可以使用Yeoman官方提供的generator引导,这里我们选择使用Yeoman官方推荐的方式来处理。
$ mkdir YeomanTest && cd YeomanTest/ 创建新的目录并进入
$ npm install -g generator-generator 安装Yeoman引导generator

列出具体的执行情况

wendingding$ mkdir YeomanTest
wendingding$ cd YeomanTest/
wendingding$ pwd
/Users/文顶顶/Desktop/Yeoman/YeomanTest
wendingding$ npm install -g generator-generator + generator-generator@4.0.2

updated 1 package in 117.639s

   ╭─────────────────────────────────────╮
   │                                     │
   │   Update available 5.5.1 → 6.1.0    │
   │     Run npm i -g npm to update      │
   │                                     │
   ╰─────────────────────────────────────╯

执行Yeoman官方的引导generator,并处理交互式配置部分,下面列出具体的执行情况。

wendingding$ yo generator
? Your generator name generator-wendingding
Your generator must be inside a folder named generator-wendingding
I'll automatically create this folder.
? Description 博客文章测试创建生成器
? Project homepage url http://www.wendingding.com
? Author's Name 文顶顶
? Author's Email 18681537032@163.com
? Author's Homepage http://www.wendingding.com
? Package keywords (comma to split) wendingding
? Send coverage reports to coveralls Yes
? GitHub username or organization flowerField
? Which license do you want to use? Apache 2.0
   create package.json
   create README.md
   create .editorconfig
   create .gitattributes
   create .gitignore
   create generators/app/index.js
   create generators/app/templates/dummyfile.txt
   create __tests__/app.js
   create .travis.yml
   create .eslintignore
   create LICENSE
I'm all done. Running npm install for you to install the required dependencies.
If this fails, try running the command yourself.

在执行generator-generator这个生成器的过程中,会询问项目名称、作者、使用协议、主页地址等等信息,依次选择填空即可。

注意:按照约定,Yeoman generator的名字必须以“generator-”的前缀开头,这是因为所有的generator其实都是全局安装的node模块,所以Yeoman其实是完全依靠文件系统来对这些生成器进行查找操作的。

当上面的命令执行完毕后,会发现在当前的路径下面生成了generator-wendingding目录,进入到generator-wendingding目录,使用tree命令查看当前目录结构,显示如下:

.
├── LICENSE
├── README.md
├── __tests__
├── generators
├── app
│    ├── index.js
│    └── templates
│        └── dummyfile.txt
├── node_modules
├── package-lock.json
└── package.json

上面目录结构中虽然有很多文件,但我们真正需要关注的应该是generators路径下面的app/index.js文件以及templates目录,其中index文件对应是generators的组装指令部分,templates路径用于存放项目所有的模板文件。

项目模板文件准备

上面这些准备工作完成之后,接下来我们开始着手分析目标项目的文件结构,即我们使用自己创建的这个脚手架来搭建项目,其结构目录应该是怎样的?需要包含哪些文件等等。任何时候,明确知道你的目标,知道自己正在做什么至关重要。

下面试着给出目标项目的文件结构。

.
├── Gruntfile.js
├── bower.json
├── build
├── dist
├── package.json
└── src
    ├── css
    │   └── style.css
    ├── index.html
    ├── js
    │   └── index.js
    ├── libs
    │   └── jquery
    └── template

我们可以看到该项目应该包含bulidsrc以及dist三个目录,其中src目录中需要创建名为cssjslibstemplate的文件夹,分别用来保存样式文件、脚本文件、依赖的框架以及模板文件等。

除了这些必要的文件外,假设目标项目需要使用bower来进行依赖管理,使用Grunt来进行自动化构建,所以自然还应该拥有Gruntfile.js、bower.json以及package.json文件。

假设目标项目中一定会使用到jQuery框架,可能会使用到bootstrap框架。

现在我们可以开始分析生成器中应该包含项目模板文件了,也就是在generators/templates路径中应该包含哪些文件。

固定文件

index.jsstyle.css创建空文件即可。
Gruntfile.js文件因为内容固定不变,所以选择直接从旧项目中拷贝。
.jshintrc文件用于js文件语法检查,内容也是固定不变的。
.bowerrc文件用于重置Bower下载包的安装路径,内容为{"directory": "src/libs/"}

灵活文件

package.json文件中项目名称、作者以及开源协议等需要用户配置
bower.json文件的项目名称、作者、开源协议以及依赖框架等需要用户配置

可选文件

bootstrap框架相关的部分为可选文件,需要根据用户配置进行处理。

依赖文件

jQuery框架相关的部分为依赖文件,在组装指令部分通过在代码中调用方法来下载和安装。

根据上面的分析,我们在generators/templates准备了多个模板文件,下面列出文件结构以及主要文件的具体内容:

.
└── app
    ├── index.js
    └── templates
        ├── Gruntfile.js
        ├── bower.json
        ├── css
        │   └── style.css
        ├── index.html
        ├── js
        │   └── index.js
        └── package.json

package.json文件内容

{
  "name": "<%= appName %>",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "<%= appAuthor %>",
  "license": "<%= appLicense %>",
  "devDependencies": {
    "grunt": "^1.0.2",
    "grunt-contrib-concat": "^1.0.1",
    "grunt-contrib-cssmin": "^2.2.1",
    "grunt-contrib-jshint": "^1.1.0",
    "grunt-contrib-uglify": "^3.3.0",
    "grunt-contrib-watch": "^1.0.0"
  }
}

bower.json文件内容

{
  "name": "<%= appName %>",
  "description": "\"测试使用\"",
  "main": "js/index.js",
  "authors": [
    "<%= appAuthor %>"
  ],
  "license": "<%= appLicense %>",
  "keywords": [
    "generator-wendingding",
    "yeoman-generator"
  ],
  "homepage": "https://github.com/flowerField/generator-wen",
  "ignore": [
    "**/.*",
    "node_modules",
    "bower_components",
    "test",
    "tests"
  ],
  "dependencies": {
    "jquery": "^3.3.1"<% if(isIncludeBootstrap) { %>,
    "bootstrap": "^4.1.1" <% } %>
  }
}

index.html文件内容

<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title><%= appName %></title>
    <link rel="stylesheet" href="css/style.css">
    <script type="text/javascript" src="js/index.js"></script>
  </head>
  <body>

  </body>
</html>

Gruntfile.js文件内容

//包装函数
module.exports = function (grunt) {
    // 项目配置信息
    grunt.config.init({
        pkg:grunt.file.readJSON("package.json"),
        //代码合并
        concat:{
            options:{
                stripBanners:true,
             banner:'/*项目名称:<%=pkg.name%> 项目版本:<%=pkg.version%> 项目的作者:<%=pkg.author%> 更新时间:<%=grunt.template.today("yyyy-mm-dd")%>*/\n'
            },
            target:{
                src:["src/js/*.js"],
                dest:'build/js/index.js'
            }
        },
        //js代码压缩
        uglify:{
            target:{
                src:"build/js/index.js",
                dest:"build/js/index.min.js"
            }
        },
        //css代码压缩
        cssmin:{
            target:{
                src:"src/css/style.css",
                dest:"build/css/style.min.css"
            }
        },
        //js语法检查
        jshint:{
            target:['Gruntfile.js',"dist/js/index.js"],
        },
        //监听 自动构建
        watch:{
            target:{
                files:["src/js/*.js","src/css/*.css"],
                //只要指定路径的文件(js和css)发生了变化,就自动执行tasks中列出的任务
                tasks:["concat","jshint","uglify","cssmin"]
            }
        }
    });
    //通过命令行安装插件(省略...)
    //从node_modules路径加载插件
    grunt.loadNpmTasks("grunt-contrib-concat");
    grunt.loadNpmTasks("grunt-contrib-uglify");
    grunt.loadNpmTasks("grunt-contrib-cssmin");
    grunt.loadNpmTasks("grunt-contrib-jshint");
    grunt.loadNpmTasks("grunt-contrib-watch");
    //注册任务:在执行$ grunt命令的时候依次执行代码的合并|检查|压缩等任务并开启监听
    grunt.registerTask("default",["concat","jshint","uglify","cssmin","watch"]);
};

注意:上面部分文件中很多地方使用模板语法来传递参数,Yeoman所用的模板语言是EJS,具体用法请参考EJS官网

组装指令

处理完上面这些工作之后,接下来就是最最核心的部分了,我们需要在app/index.js文件中编写组装指令,这部分代码控制着这个生成器应该怎么执行,包括交互式配置的具体内容、如何复制文件以及框架依赖和Node模块下载等内容。

下面列出该示例中的index.js文件内容

'use strict';
const Generator = require('yeoman-generator');
const chalk = require('chalk');
const yosay = require('yosay');
const mkdirp = require('mkdirp');

module.exports = class extends Generator {
  prompting() {
    this.log(
      // yosay(`Welcome to the transcendent ${chalk.red('generator-wen')} generator!`)
      yosay(`欢迎使用\n${chalk.red('generator-wen')} !\n Author:文顶顶`)
    );

    const prompts = [
      {
        type    : 'input',
        name    : 'appName',
        message : '请输入项目名称:',
        default : this.appname        //appname是内置对象,代表工程名,这里就是ys
     },
     {
       type    : 'input',
       name    : 'appAuthor',
       message : '请输入作者姓名:',
       default : '文顶顶'
    },
    {
        type: 'list',
        name: 'appLicense',
        message: '请选择使用的license:',
        choices: ['MIT', 'ISC', 'Apache-2.0', 'AGPL-3.0']
      },
      {
        type    : 'confirm',
        name    : 'isIncludeBootstrap',
        message : '是否需要使用bootStrap框架?',
        default : false
     },

    ];

    return this.prompt(prompts).then(props => {
      // To access props later use this.props.someAnswer;
      this.props = props;
    });
  }

  writing() {
    mkdirp("build");
    mkdirp("dist");
    mkdirp("src/template");

    this.fs.copyTpl(
      this.templatePath('index.html'),
      this.destinationPath('src/index.html'),
      {appName: this.props.appName}
    );

    this.fs.copy(
      this.templatePath('css/style.css'),
      this.destinationPath('src/css/style.css')
    );

    this.fs.copy(
      this.templatePath('js/index.js'),
      this.destinationPath('src/js/index.js')
    );

    this.fs.copy(
      this.templatePath('.bowerrc'),
      this.destinationPath('.bowerrc')
    );

    this.fs.copy(
      this.templatePath('Gruntfile.js'),
      this.destinationPath('Gruntfile.js')
    );

    this.fs.copy(
      this.templatePath('.jshintrc'),
      this.destinationPath('.jshintrc')
    );

    this.fs.copyTpl(
      this.templatePath('package.json'),
      this.destinationPath('package.json'),
       {appName: this.props.appName,appAuthor:this.props.appAuthor,appLicense:this.props.appLicense}
    );

    this.fs.copyTpl(
      this.templatePath('bower.json'),
      this.destinationPath('bower.json'),
       {appName: this.props.appName,appAuthor:this.props.appAuthor,appLicense:this.props.appLicense,isIncludeBootstrap:this.props.isIncludeBootstrap}
    );
  }

  install() {
    //this.installDependencies();
    this.bowerInstall();
  }
};

上面的代码大概由三部分组成,第一部分为prompting函数用来处理安装提示,第二部分为writing函数用来设置模板文件的复制操作,第三部分为install函数用来处理框架依赖和node包的安装。

generator的发布和测试

项目模板文件和组装指令都准备好了后,我们就可以发布自己的generator了,可以先通过$ npm link命令以软连接的方式生成一个全局的npm包,测试使用。
具体的执行细节如下

wendingding:generator-wendingding wendingding$ npm link
up to date in 3.897s
/usr/local/lib/node_modules/generator-wendingding -> /Users/文顶顶/Desktop/Yeoman/YeomanTest/generator-wendingding
wendingding:generator-wendingding wendingding$

测试·使用自己创建的generator来生成初始化项目

随便找个目录新建文件夹,使用$ yo wendingding命令即可完成项目的初始化工作。

wendingding:YeomanTest wendingding$ mkdir Demo
wendingding:YeomanTest wendingding$ cd Demo/
wendingding:Demo wendingding$ yo wendingding

     _-----_     ╭──────────────────────────╮
    |       |    │         欢迎使用           │
    |--(o)--|    │      generator-wen !      │
   `---------´   │      Author:文顶顶        │
    ( _´U`_ )    ╰──────────────────────────╯
    /___A___\   /
     |  ~  |     
   __'.___.'__   
 ´   `  |° ´ Y `

? 请输入项目名称: Demo
? 请输入作者姓名: 文顶顶
? 请选择使用的license: Apache-2.0
? 是否需要使用bootStrap框架? Yes
   create bower.json
   create package.json
   create src/index.html
   create src/css/style.css
   create src/js/index.js
   create .bowerrc
   create Gruntfile.js
   create .jshintrc
bower invalid-meta  for:/Users/文顶顶/Desktop/Yeoman/YeomanTest/Demo/bower.json
bower invalid-meta  The "name" is recommended to be lowercase, can contain digits, dots, dashes
bower cached        https://github.com/jquery/jquery-dist.git#3.3.1
bower validate      3.3.1 against https://github.com/jquery/jquery-dist.git#^3.3.1
bower cached        https://github.com/twbs/bootstrap.git#4.1.1
bower validate      4.1.1 against https://github.com/twbs/bootstrap.git#^4.1.1
bower install       jquery#3.3.1
bower install       bootstrap#4.1.1

jquery#3.3.1 src/libs/jquery

bootstrap#4.1.1 src/libs/bootstrap
wendingding:Demo wendingding$ tree -L 3
.
├── Gruntfile.js
├── bower.json
├── build
├── dist
├── package.json
└── src
    ├── css
    │   └── style.css
    ├── index.html
    ├── js
    │   └── index.js
    ├── libs
    │   ├── bootstrap
    │   └── jquery
    └── template

9 directories, 6 files

如果需要把这个生成器发布到社区,可以先到npm官网注册一个自己的npm账号,然后在该生成器的目录下执行$ npm publish命令即可。


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容