手把手入門 Yeoman 模板開發

VanMess發表於2018-12-19

對大多數一個前端團隊來說,Yeoman(簡稱yo)是一個非常值得學習的工具,它為前端專案提供了一種行之有效的方法,開發、分發、使用專案手腳架,提高專案啟動速度,複用專案結構。

本文以generator-iview-admin 為例,簡單說明手腳架,即generator的開發過程。

準備

開發 Yeoman 模板,需預先安裝yo:

npm i -g yo
複製程式碼

yo 細心的為我們準備了手腳架專案的手腳架generator-generator

npm i -g generator-generator
複製程式碼

安裝後,進入開發目錄,執行:

yo generator
複製程式碼

專案結構

執行上述命令後,主要生成如下檔案:

├── .yo-rc.json
├── package.json
├── generators
│   ├── app
│       ├── templates
│           ├── dummyfile.txt
│       ├── index.js
複製程式碼

其中

  • .yo-rc.json 用於儲存專案配置,一般不會用到,無需關注
  • package.json npm 專案資訊檔案,主要關注 author、version 域即可
  • generators 目錄即專案模板程式碼
  • generators/templates 用於存放專案模板檔案
  • generators/app/index.js 定義專案手腳架的程式碼

開發

每個generator都會繼承yeoman-generator類,即上述的generators/app/index.js檔案。該類有幾個重要的生命週期節點:

  1. initializing - 初始化方法,用於獲取專案狀態、配置等
  2. prompting - 呼叫inquire方法獲取使用者輸入
  3. configuring - 儲存配置,建立 .editorconfig 等檔案
  4. writing - 執行檔案寫操作,即專案檔案寫入檔案系統中
  5. install - 執行安裝操作,需呼叫 this.installDependencies 方法
  6. end - 最後執行,可清楚臨時檔案等

上述方法均支援返回 Promise 方式實現非同步操作,僅當返回的Promise 例項 resolve 時才會執行下一步操作。

Step 1. 獲取使用者配置

首先,我們需要在 prompting 中詢問使用者配置(完整例項在此處):

prompting() {
  // Have Yeoman greet the user.
  this.log(yosay('Welcome to the divine ' + chalk.red('generator-iview-admin') + ' generator!'));

  const prompts = [{
    type: 'input',
    name: 'name',
    message: 'Your project name',
    default: this.appname
  }, {
    type: 'confirm',
    name: 'lint',
    message: 'Use ESLint to lint your code?'
  }, {
    name: 'lintStyle',
    type: 'list',
    message: 'Pick an ESLint preset',
    when(answers) {
      return answers.lint;
    },
    choices: [{
      name: 'Airbnb (https://github.com/airbnb/javascript)',
      value: 'airbnb',
      short: 'Airbnb'
    }, {
      name: 'Standard (https://github.com/feross/standard)',
      value: 'standard',
      short: 'Standard'
    }]
  }];

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

這裡,將使用者輸入的結果配置到 this.props 物件中,方便後續訪問。

Step 2. 定義模板

yo 的核心,本質上是按需修改模板檔案,一般包含三種方法:

  1. 直接寫入檔案 對於簡單物件,如JSON,可以使用 initPackage 方式。即:讀入模板、按配置修改物件、寫入檔案
  2. 使用模板方式,將結果寫入檔案 對於複雜檔案,可使用 yo 提供的模板(ejs)引擎進行編寫。
  3. 通過AST樹修改檔案 通過解析語法樹,深入理解模板內容,一般用於修改已存在的檔案內容。建議使用 esprimacheerio 解析AST。

以ejs方式為例,編寫模板(完整例項在此處):

<% if(vueFile==='standalone'){ %>
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
<% } %>

...

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,<% if(vueFile==='runtime'){ %>
  render: h => h(App)<% } else if(vueFile==='standalone'){ %>
  template: '<App/>',
  components: { App }<% } %>
});

複製程式碼

Step 3. 按需寫入

獲取配置後,可以正式開始將模板寫入硬碟中(完整例項在此處):

writing() {
  this.initPackage();
  this.renderTplFile();
}

initPackage() {
  let pkg = this.fs.readJSON(this.templatePath('package.json'), {});
  const {
    props
  } = this;

  pkg = _.merge(pkg, {
    name: props.name,
    description: props.description
  });
  
  ... 
  
  this.fs.writeJSON(this.destinationPath('package.json'), pkg);
}

renderTplFile() {
  let target = [
    ...
    'src/components/Hello.vue',
  ];

  if (this.props.unitTest) {
    target = target.concat([
      ...
      'build/webpack.test.conf.js'
    ]);
  }
  ...
  _.forEach(target, (file) => {
    this.fs.copyTpl(
      this.templatePath(file),
      this.destinationPath(file),
      this.props
    );
  });
}
複製程式碼

yo 提供mem-fs-editor例項介面,包含一系列fs工具:

  • this.fs.read - 讀取檔案
  • this.fs.readJSON - 以JSON方式讀取檔案
  • this.fs.write - 寫檔案
  • this.fs.writeJson - 以JSON 方式寫檔案
  • this.fs.append - 將內容已追加方式寫入檔案
  • this.fs.extendJSON - 擴充套件JSON檔案內容
  • this.fs.delete - 刪除檔案

此外,還有一系列路勁及模板介面:

  1. this.fs.copyTpl - 複製模板檔案,並按引數解析模板內容,寫入目標檔案中
  2. this.templatePath - 返回模板檔案路徑,即上述 generator/app/templates 中的檔案路徑
  3. this.destinationPath - 返回目標檔案路徑,即執行 yo 生成模板檔案的路徑
  4. this.registerTransformStream - 生命鉤子介面,用於轉化檔案內容,相容gulp外掛

至此,我們已瞭解開發一個yo模板所需要的所有介面。

新增子模板

yo允許新增任意數量的子模板,只需執行:

yo generator:subgenerator [name]
複製程式碼

yo generator:subgenerator test 為例,生成如下檔案:

├── generators
│   ├── app
│   ├── test
│       ├── templates
│           ├── dummyfile.txt
│       ├── index.js
複製程式碼

templates、 index.js 檔案的作用與上述無異,可直接開發。

試執行

可以通過如下方式,將專案加入本地generator 庫:

npm link
複製程式碼

之後,就可以執行如下命令,生成手腳架:

yo [your template name]
複製程式碼

釋出

模板開發完畢後,如需釋出可執行如下命令:

npm publish
複製程式碼

注意:

  1. 如果npm尚未登入,可執行 npm adduser 操作進行登入
  2. 釋出npm包必須使用 https://registry.npmjs.org/ 源,切記切換。

相關文章