編寫自定義Yeoman生成器

 

轉載自JSCON-簡時空:《自定義Yeoman生成器

1、Getting Started

1.1、設置Node模塊

Yeoman提供了generator-generator方便快速編寫自己的生成器。

安裝: npm install -g generator-generator運行: yo generator
  • 創建一個名爲generator-name的文件夾(name爲你的生成器名);【important】
  • 創建package.json文件,這是NodeJS模塊的“信息圖”,可以手動或者使用命令npm init生成
{
  "name": "generator-name",
  "version": "0.1.0",
  "description": "",
  "keywords": ["yeoman-generator"],
  "dependencies": {
    "yeoman-generator": "^0.17.3"
  }
}
 name屬性必須要有generator-前綴;keywords屬性必須包含yeoman-generator,務必確保是最新的,可運行命令npm install --save yeoman-generator完成更新/安裝

 

1.2、文件樹結構

  • 當調用yo name命令時,默認調用的是app生成器,對於的邏輯放置在app/文件夾下
  • 當調用yo name:subcommand命令時,必須要有對於的subcommand/文件夾

如果文件結構如下,則該生成器暴露yo nameyo name:router兩個命令

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

如果你不想把所有代碼都放在根目錄下,Yeoman提供了另外的一種方式:可以放在generators/目錄下

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

 

1.3、繼承generator

結構寫好了,需要開始寫實際的邏輯代碼Yeoman提供了基礎生成器供你繼承,這些基礎生成器提供了很多方便的方法供你調用。基本寫法:

var generators = require('yeoman-generator');
module.exports = generators.Base.extend();

如果你的生成器需要name參數(比如yo name:router foo中的foo),想將它賦給this.name的話:

var generators = require('yeoman-generator');
module.exports = generators.NamedBase.extend();
 上面兩種方式都能用於創建app生成器或者子生成器Base多用於app生成器,NamedBase多用於需要指定文件名的子生成器

 

1.4、重寫構造函數

有些方法只能在constructor方法中調用,常用於狀態控制;可以傳入構造函數重寫默認的構造函數:

module.exports = generators.Base.extend({
  // The name `constructor` is important here
  constructor: function () {
    // Calling the super constructor is important so our generator is correctly set up
    generators.Base.apply(this, arguments);

    // Next, add your custom code
    this.option('coffee'); // This method adds support for a `--coffee` flag
  }
});

 

1.5、添加方法

一般給原型添加的方法是按順序執行的,不過後面我們會看到一些特殊的方法會觸發不同的執行順序:

module.exports = generators.Base.extend({
  method1: function () {
    console.log('method 1 just ran');
  },
  method2: function () {
    console.log('method 2 just ran');
  }
});

 

1.6、運行生成器

到了這一步,你已經擁有一個可以運行的生成器了。下一步就是檢驗生成器是否按自己的邏輯運行。由於是在本地開發生成器,在全局npm模塊中並不存在,需要手動鏈接。進入generator-name/文件夾,運行:

npm link

這將自動安裝工程依賴包,同時將本地文件鏈接進全局模塊;運行完畢之後,你就可以調用yo name並看到之前定義的console.log信息;至此,恭喜你完成了簡單的生成器! 

1.7、找到工程根目錄

當運行一個生成器,Yeoman將計算當前的文件目錄信息。最爲關鍵的是,Yeoman將.yo-rc.json所在的目錄作爲工程的根目錄,之後Yeoman將當前文件目錄跳轉到根目錄下運行請求的生成器。這個.yo-rc.json文件是由Storage模塊創建的,在生成器內部調用this.config.save()方法就會創建它。所以,如果你發現你的生成器不是在你當前工作目錄下運行,請確保。yo-rc.json不存在你目錄的其他層級中

 


 

2、運行上下文

2.1、靜態方法都是Action

如果一個函數直接作爲生成器的原型(prototype)的屬性,則會當做action自動(按順序)執行。如何聲明不會自動執行的輔助函數以及私有函數呢?有三種方法

  • 給方法前面添加前綴(例如:_method
  • 使用實例函數聲明(this.mehtod
generators.Base.extend({
  init: function () {
    this.helperMethod = function () {
      console.log('won\'t be called automatically');
    };
  }
});
  • 繼承自父類生成器
var MyBase = generators.Base.extend({
  helper: function () {
    console.log('won\'t be called automatically');
  }
});

module.exports = MyBase.extend({
  exec: function () {
    this.helper();
  }
});

 

2.2、運行順序

Yeoman是按照優先級順序依次執行所定義的方法。當你定義的函數名字是Yeoman定義的優先級函數名時,會自動將該函數列入到所在優先級隊列中,否則就會列入到default優先層級隊列中。依次執行的方法名稱爲:

  1. initializing - 你的初始化方法(檢測當前目錄狀態,獲取配置等)
  2. prompting - 給用戶展示選項提示(調用this.prompt()
  3. configuring - 保存用戶配置項,同時配置工程(創建.editorconfig文件或者其他metadata文件)
  4. default
  5. writing - 用於生成和生成器相關的文件(比如routes,controllers等)
  6. conflicts - 用於處理衝突異常(內部使用)
  7. install - 用於安裝相關庫 (npm, bower)
  8. end - 最後調用,常用於清理、道別等

 


 

3、UI

Yeoman默認是跑在終端的,但不限於終端。因此記住,不要使用console.log()或者process.stdout.write()向用戶反饋信息,應當使用generator.log方法。

 

3.1、提示框

Yeoman中最爲主要的UI交互就是提示框,由Inquirer.js組件提供。使用下列方式調用:

module.exports = generators.Base.extend({
  prompting: function () {
    var done = this.async();
    this.prompt({
      type    : 'input',
      name    : 'name',
      message : 'Your project name',
      default : this.appname // Default to current folder name
    }, function (answers) {
      this.log(answers.name);
      done();
    }.bind(this));
  }
})
 這裏我們使用promoting的優先層級由於諮詢用戶是一個異步的過程,會卡住命令邏輯的運行,所以需要調用yo的異步方法:var cb = this.async();

 

3.2、記住用戶偏好

當用戶運行你的生成器時,很多時候會輸入相同的答案;Yeoman擴展了Inquirer.js的API,額外增加了store的屬性表示用戶可以將之前填寫過的答案作爲後續的默認答案:

this.prompt({
  type    : 'input',
  name    : 'username',
  message : 'What\'s your Github username',
  store   : true
}, callback);
 提供默認答案時,程序會強制用戶輸入

 

3.3、命令行參數

可以直接像在命令中傳入參數:

yo webapp my-project

在這裏,my-project作爲第一個參數。爲了提示系統我們期望用戶傳入參數,需要調用generator.argument()方法,該方法接受name作爲參數,以及額外的限制條件。

該argument方法必須在構造器中調用。這些條件是(key/value型):desc :Description for the argumentrequired: Boolean whether it is requiredoptional :Boolean whether it is optionaltype :String, Number, Array, or Objectdefaults: Default value for this argumentbanner :String to show on usage notes (this one is provided by default)

示例代碼:

module.exports = generators.Base.extend({
  // note: arguments and options should be defined in the constructor.
  constructor: function () {
    generators.Base.apply(this, arguments);

    // This makes `appname` a required argument.
    this.argument('appname', { type: String, required: true });
    // And you can then access it later on this way; e.g. CamelCased
    this.appname = this._.camelize(this.appname);
  }
});

 

3.4、選項

選項看上去像參數,不過它前面多了兩短橫槓(flags):

yo webapp --coffee

使用generator.option()方法獲取選項值,該方法也有可選的限制屬性(key/value型):

desc: Description for the optiontype :Either Boolean, String or Numberdefaults: Default valuehide :Boolean whether to hide from help

舉例:

module.exports = generators.Base.extend({
  // note: arguments and options should be defined in the constructor.
  constructor: function () {
    generators.Base.apply(this, arguments);

    // This method adds support for a `--coffee` flag
    this.option('coffee');
    // And you can then access it later on this way; e.g.
    this.scriptSuffix = (this.options.coffee ? ".coffee": ".js");
  }
});

 


 

4、處理依賴

在運行生成器時,經常會伴隨着npm和bower命令去安裝依賴文件,Yeoman已經將這些功能抽離出來方便用戶使用

4.1、npm

使用generator.npmInstall()運行npm安裝命令,無論你調用多少次,Yeoman會確保該命令只執行一次。

generators.Base.extend({
  installingLodash: function() {
    var done = this.async();
    this.npmInstall(['lodash'], { 'saveDev': true }, done);
  }
}):

上面的代碼等價於命令行:

npm install lodash --save-dev

 

4.2、bower

使用generator.bowerInstall()運行bower安裝命令,無論你調用多少次,Yeoman會確保該命令只執行一次。

使用generator.installDependencies()會同時運行 上面兩種安裝命令,無論你調用多少次,Yeoman會確保該命令只執行一次。

 

4.3、使用其他工具

爲了方便調用其他程序命令,Yeoman跨平臺抽離出spawn命令;比如你想調用PHP的composer命令:

generators.Base.extend({
  end: function () {
    this.spawnCommand('composer', ['install']);
  }
});
記得在end隊列中調用spawnCommand命令,否則用戶沒有耐心等那麼久的。

 


  

5、文件系統

方便文件流的輸入輸出,Yeoman使用兩種位置環境。

5.1、目標位置上下文

第一種是destination context,這裏的“目標”是指你想架構應用的位置。這個位置要麼是當前文件夾,要麼就是文件.yo-rc.json所在的父文件夾位置;

.yo-rc.json文件確保所有的終端用戶都以同樣的方式方法生成器所在的子文件(夾)

使用generator.destinationRoot()獲取目標位置上下文;也可以手動傳參重新設置,當然沒有人願意那麼做的;用generator.destinationPath('sub/path')拼接所需要的路徑字符串;示例:

// Given destination root is ~/projects
generators.Base.extend({
  paths: function () {
    this.destinationRoot();
    // returns '~/projects'

    this.destinationPath('index.js');
    // returns '~/projects/index.js'
  }
});

 

5.2、模板位置上下文

template context就是你模板文件所在的文件夾位置,這個文件夾基本上是你讀取並拷貝文件的地方。默認的template context./templates/,你可以通過generator.sourceRoot('new/template/path')指定新的模板文件夾位置;與上面類似,可使用generator.sourceRoot()獲取模板位置,使用generator.templatePath('app/index.js')拼接路徑;示例:

generators.Base.extend({
  paths: function () {
    this.sourceRoot();
    // returns './templates'

    this.templatePath('index.js');
    // returns '~/templates/index.js'
  }
});

 

5.3、文件操作API

Yeoman把所有的文件方法都放在this.fs中了,它是mem-fs-editor的一個示例對象,可自行查看API接口。

示例:拷貝模板文件

假如。、templates/index.html文件內容爲:

<html>
  <head>
    <title><%= title %></title>
  </head>
</html>

我們使用copyTpl方法拷貝模板:(更多參看Lodash template syntax

generators.Base.extend({
  writing: function () {
    this.fs.copyTpl(
      this.templatePath('index.html'),
      this.destinationPath('public/index.html'),
      { title: 'Templating with Yeoman' }
    );
  }
});

一旦生成器運行完成,我們就會獲得public/index.html

<span class="hiddenSpellError" pre="" data-mce-bogus="1">Templating</span> with Yeoman
  
 Yeoman仍保留了舊的文件API,可參看 API documentation。舊的文件API總是假設文件來自template context,寫文件總是在destination context中,所以它們不要求你傳入文件路徑信息,程序會自動處理建議:儘可能使用新的fsAPI,它的使用起來比較清晰

 


  

6、儲存用戶設置

常常需要存儲用戶的設置項並在子生成器中使用,比如用戶使用什麼編程語言(比如使用CoffeeScript?)等這些配置項都存儲在.yo-rc.json中(使用 Yeoman Storage API.),可以通過generator.config對象獲取API方法。

 

6.1、常用方法

generator.config.save()保存配置項到文件.yo-rc.json文件中(若文件不存在將自動 創建),由於該文件決定工程的根目錄,因而一個最佳實踐就是:就算什麼也沒有也應當調用save方法。

每次設置配置項都會自動調用save方法,因此你可以不用顯示調用

generator.config.set(key,val)

Name描述
key 用於存儲的鍵
val 任何JSON類型的值(String,Number, Array, Object)

generator.config.get()根據鍵獲得配置項generator.config.getAll()獲取可用的所有配置信息;主要返回值不是按引用返回的,所以要更改裏面的配置項還是需要調用set方法。generator.config.delete()刪除某個鍵值(及其值)generator.config.defaults()將對象作爲默認的配置信息,採用不覆蓋原則。

 

6.2、.yo-rc.json 文件結構

該文件可存儲多個生成器的信息,每個生成器依據名字劃分命名空間防止衝突;這也意味着每個生成器的配置項只能被子生成器讀取到,不同生成器間的配置信息不能通過 Yeoman Storage API.訪問。(使用命令行參數或者選項在不同構造器間傳遞參數)

文件樣本:
{
  "generator-backbone": {
    "requirejs": true,
    "coffee": true
  },
  "generator-gruntfile": {
    "compass": false
  }
}

 


  

參考文獻

WRITING YOUR OWN YEOMAN GENERATOR學習Bower – 前端開發包管理工具Bower:客戶端庫管理工具自定義項目工程生成器上—Yeoman入門指南(2)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章