前端工程化系列[07] Yeoman-generator的創(chuàng)建

Yeoman腳手架使用入門(mén)Yeoman腳手架核心機(jī)制這兩篇文章中已經(jīng)對(duì)Yeoman腳手架工具的基本使用以及去核心運(yùn)轉(zhuǎn)機(jī)制進(jìn)行了深入的介紹裁赠,這篇文章將以實(shí)例的方式來(lái)教會(huì)如何從零開(kāi)始創(chuàng)建屬于我們自己的generator啦租。

點(diǎn)擊獲取本文示例的generator

generator創(chuàng)建準(zhǔn)備

這里我們一切從零開(kāi)始,在創(chuàng)建自己的generator之前需要做一些準(zhǔn)備工作募强,比如準(zhǔn)備好yo命令行工具,比如對(duì)生成器生成的項(xiàng)目結(jié)構(gòu)和目錄文件有清晰的規(guī)劃等。

yo命令行工具

在安裝了NodeJS和npm的前提下,可以通過(guò)下面的命令來(lái)安裝yo命令行工具我纪,并檢查安裝是否成功。

$ npm install -g yo
$ yo --version

generator-generator的安裝

創(chuàng)建generator可以完全從零開(kāi)始丐吓,也可以使用Yeoman官方提供的generator引導(dǎo)浅悉,這里我們選擇使用Yeoman官方推薦的方式來(lái)處理。
$ mkdir YeomanTest && cd YeomanTest/ 創(chuàng)建新的目錄并進(jìn)入
$ npm install -g generator-generator 安裝Yeoman引導(dǎo)generator

列出具體的執(zhí)行情況

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      │
   │                                     │
   ╰─────────────────────────────────────╯

執(zhí)行Yeoman官方的引導(dǎo)generator券犁,并處理交互式配置部分术健,下面列出具體的執(zhí)行情況。

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 博客文章測(cè)試創(chuàng)建生成器
? 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.

在執(zhí)行g(shù)enerator-generator這個(gè)生成器的過(guò)程中粘衬,會(huì)詢問(wèn)項(xiàng)目名稱荞估、作者、使用協(xié)議稚新、主頁(yè)地址等等信息勘伺,依次選擇填空即可。

注意:按照約定褂删,Yeoman generator的名字必須以“generator-”的前綴開(kāi)頭娇昙,這是因?yàn)樗械膅enerator其實(shí)都是全局安裝的node模塊,所以Yeoman其實(shí)是完全依靠文件系統(tǒng)來(lái)對(duì)這些生成器進(jìn)行查找操作的笤妙。

當(dāng)上面的命令執(zhí)行完畢后,會(huì)發(fā)現(xiàn)在當(dāng)前的路徑下面生成了generator-wendingding目錄噪裕,進(jìn)入到generator-wendingding目錄蹲盘,使用tree命令查看當(dāng)前目錄結(jié)構(gòu),顯示如下:

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

上面目錄結(jié)構(gòu)中雖然有很多文件膳音,但我們真正需要關(guān)注的應(yīng)該是generators路徑下面的app/index.js文件以及templates目錄召衔,其中index文件對(duì)應(yīng)是generators的組裝指令部分,templates路徑用于存放項(xiàng)目所有的模板文件祭陷。

項(xiàng)目模板文件準(zhǔn)備

上面這些準(zhǔn)備工作完成之后苍凛,接下來(lái)我們開(kāi)始著手分析目標(biāo)項(xiàng)目的文件結(jié)構(gòu),即我們使用自己創(chuàng)建的這個(gè)腳手架來(lái)搭建項(xiàng)目兵志,其結(jié)構(gòu)目錄應(yīng)該是怎樣的醇蝴?需要包含哪些文件等等。任何時(shí)候想罕,明確知道你的目標(biāo)悠栓,知道自己正在做什么至關(guān)重要。

下面試著給出目標(biāo)項(xiàng)目的文件結(jié)構(gòu)。

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

我們可以看到該項(xiàng)目應(yīng)該包含bulid惭适、src以及dist三個(gè)目錄笙瑟,其中src目錄中需要?jiǎng)?chuàng)建名為cssjs癞志、libstemplate的文件夾往枷,分別用來(lái)保存樣式文件、腳本文件凄杯、依賴的框架以及模板文件等错洁。

除了這些必要的文件外,假設(shè)目標(biāo)項(xiàng)目需要使用bower來(lái)進(jìn)行依賴管理盾舌,使用Grunt來(lái)進(jìn)行自動(dòng)化構(gòu)建墓臭,所以自然還應(yīng)該擁有Gruntfile.js、bower.json以及package.json文件妖谴。

假設(shè)目標(biāo)項(xiàng)目中一定會(huì)使用到j(luò)Query框架窿锉,可能會(huì)使用到bootstrap框架。

現(xiàn)在我們可以開(kāi)始分析生成器中應(yīng)該包含項(xiàng)目模板文件了膝舅,也就是在generators/templates路徑中應(yīng)該包含哪些文件嗡载。

固定文件

index.jsstyle.css創(chuàng)建空文件即可。
Gruntfile.js文件因?yàn)閮?nèi)容固定不變仍稀,所以選擇直接從舊項(xiàng)目中拷貝洼滚。
.jshintrc文件用于js文件語(yǔ)法檢查,內(nèi)容也是固定不變的技潘。
.bowerrc文件用于重置Bower下載包的安裝路徑遥巴,內(nèi)容為{"directory": "src/libs/"}

靈活文件

package.json文件中項(xiàng)目名稱整份、作者以及開(kāi)源協(xié)議等需要用戶配置
bower.json文件的項(xiàng)目名稱杨蛋、作者毅否、開(kāi)源協(xié)議以及依賴框架等需要用戶配置

可選文件

bootstrap框架相關(guān)的部分為可選文件聋亡,需要根據(jù)用戶配置進(jìn)行處理谱仪。

依賴文件

jQuery框架相關(guān)的部分為依賴文件开皿,在組裝指令部分通過(guò)在代碼中調(diào)用方法來(lái)下載和安裝椰棘。

根據(jù)上面的分析别洪,我們?cè)趃enerators/templates準(zhǔn)備了多個(gè)模板文件奔坟,下面列出文件結(jié)構(gòu)以及主要文件的具體內(nèi)容:

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

package.json文件內(nèi)容

{
  "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文件內(nèi)容

{
  "name": "<%= appName %>",
  "description": "\"測(cè)試使用\"",
  "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文件內(nèi)容

<!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文件內(nèi)容

//包裝函數(shù)
module.exports = function (grunt) {
    // 項(xiàng)目配置信息
    grunt.config.init({
        pkg:grunt.file.readJSON("package.json"),
        //代碼合并
        concat:{
            options:{
                stripBanners:true,
             banner:'/*項(xiàng)目名稱:<%=pkg.name%> 項(xiàng)目版本:<%=pkg.version%> 項(xiàng)目的作者:<%=pkg.author%> 更新時(shí)間:<%=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語(yǔ)法檢查
        jshint:{
            target:['Gruntfile.js',"dist/js/index.js"],
        },
        //監(jiān)聽(tīng) 自動(dòng)構(gòu)建
        watch:{
            target:{
                files:["src/js/*.js","src/css/*.css"],
                //只要指定路徑的文件(js和css)發(fā)生了變化携栋,就自動(dòng)執(zhí)行tasks中列出的任務(wù)
                tasks:["concat","jshint","uglify","cssmin"]
            }
        }
    });
    //通過(guò)命令行安裝插件(省略...)
    //從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");
    //注冊(cè)任務(wù):在執(zhí)行$ grunt命令的時(shí)候依次執(zhí)行代碼的合并|檢查|壓縮等任務(wù)并開(kāi)啟監(jiān)聽(tīng)
    grunt.registerTask("default",["concat","jshint","uglify","cssmin","watch"]);
};

注意:上面部分文件中很多地方使用模板語(yǔ)法來(lái)傳遞參數(shù),Yeoman所用的模板語(yǔ)言是EJS咳秉,具體用法請(qǐng)參考EJS官網(wǎng)

組裝指令

處理完上面這些工作之后婉支,接下來(lái)就是最最核心的部分了,我們需要在app/index.js文件中編寫(xiě)組裝指令澜建,這部分代碼控制著這個(gè)生成器應(yīng)該怎么執(zhí)行磅摹,包括交互式配置的具體內(nèi)容滋迈、如何復(fù)制文件以及框架依賴和Node模塊下載等內(nèi)容。

下面列出該示例中的index.js文件內(nèi)容

'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 : '請(qǐng)輸入項(xiàng)目名稱:',
        default : this.appname        //appname是內(nèi)置對(duì)象户誓,代表工程名饼灿,這里就是ys
     },
     {
       type    : 'input',
       name    : 'appAuthor',
       message : '請(qǐng)輸入作者姓名:',
       default : '文頂頂'
    },
    {
        type: 'list',
        name: 'appLicense',
        message: '請(qǐng)選擇使用的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函數(shù)用來(lái)處理安裝提示碍彭,第二部分為writing函數(shù)用來(lái)設(shè)置模板文件的復(fù)制操作,第三部分為install函數(shù)用來(lái)處理框架依賴和node包的安裝悼潭。

generator的發(fā)布和測(cè)試

項(xiàng)目模板文件和組裝指令都準(zhǔn)備好了后庇忌,我們就可以發(fā)布自己的generator了,可以先通過(guò)$ npm link命令以軟連接的方式生成一個(gè)全局的npm包舰褪,測(cè)試使用皆疹。
具體的執(zhí)行細(xì)節(jié)如下

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$

測(cè)試·使用自己創(chuàng)建的generator來(lái)生成初始化項(xiàng)目

隨便找個(gè)目錄新建文件夾,使用$ yo wendingding命令即可完成項(xiàng)目的初始化工作占拍。

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

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

? 請(qǐng)輸入項(xiàng)目名稱: Demo
? 請(qǐng)輸入作者姓名: 文頂頂
? 請(qǐng)選擇使用的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

如果需要把這個(gè)生成器發(fā)布到社區(qū),可以先到npm官網(wǎng)注冊(cè)一個(gè)自己的npm賬號(hào)晃酒,然后在該生成器的目錄下執(zhí)行$ npm publish命令即可表牢。


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市蛔翅,隨后出現(xiàn)的幾起案子敲茄,更是在濱河造成了極大的恐慌,老刑警劉巖山析,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件折汞,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡盖腿,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)损同,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)翩腐,“玉大人,你說(shuō)我怎么就攤上這事膏燃∶裕” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵组哩,是天一觀的道長(zhǎng)等龙。 經(jīng)常有香客問(wèn)我处渣,道長(zhǎng),這世上最難降的妖魔是什么蛛砰? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任罐栈,我火速辦了婚禮,結(jié)果婚禮上泥畅,老公的妹妹穿的比我還像新娘荠诬。我一直安慰自己,他們只是感情好位仁,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布柑贞。 她就那樣靜靜地躺著,像睡著了一般聂抢。 火紅的嫁衣襯著肌膚如雪钧嘶。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天琳疏,我揣著相機(jī)與錄音有决,去河邊找鬼。 笑死轿亮,一個(gè)胖子當(dāng)著我的面吹牛疮薇,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播我注,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼按咒,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了但骨?” 一聲冷哼從身側(cè)響起励七,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎奔缠,沒(méi)想到半個(gè)月后掠抬,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡校哎,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年两波,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片闷哆。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡腰奋,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出抱怔,到底是詐尸還是另有隱情劣坊,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布屈留,位于F島的核電站局冰,受9級(jí)特大地震影響测蘑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜康二,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一碳胳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧赠摇,春花似錦固逗、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至洽故,卻和暖如春贝攒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背时甚。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工隘弊, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人荒适。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓梨熙,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親刀诬。 傳聞我的和親對(duì)象是個(gè)殘疾皇子咽扇,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容