yeoman構建項目generator(yeoman-generator)

前言:在開始一個新項目得時候,如果我們已經有存在得項目,大多數都會借鑑這個項目,包括他的基礎配置,以及一些公共的組件。而往往採用的方式都是複製粘貼,如果項目比較複雜,這樣做有幾個很明顯的弊端,一是比較耗時,二是容易出錯。即使我們重新構建一個新的腳手架,也需要根據項目的需求做不少的添加更改等等,其實經過簡單的分析,在我們的許多項目當中都有很多共性的東西,哪我們有沒有一個方法,在每次需開發新項目的時候,迅速生成這些基礎的配置,就像我們下載一個npm包一樣簡單。答案當然是可以的,接下來我們我們就介紹這樣一個腳手架yeoman-generator。

簡介

yeoman是一個可以幫助開發者快速開啓一個新項目的工具集。yoeman提出一個yeoman工作流的概念,通過腳手架工具(yo),構建工具(grunt gulp等)和包管理器(npm bower等)的配合使用讓開發者專注於業務的邏輯。在yeoman的官網中可以搜索到用於初始化項目的generator,可以用於快速開啓項目。同時yeoman也提供給開發者如何定義自己的generator,所有我們自己開發的generator都作爲一個插件可以通過yo工具創建出我們需要的結構。

自己創建的generator可以是很簡單的創建幾個模板頁面,也可以通過和用戶交互構建一套量身定製的項目,取決於項目初始化的策略。可以利用yeoman的generator-generator工具來開始構建自己的generator。

一.準備:

  • npmjs 賬號,用於publish到npm。
  • Github 賬號,npm中顯示你的源碼(也是發佈npm包所需要),同時方便Yeoman官網中能搜到你的generator。

下載安裝yo和generator-generator

注:在已經有裝好node和npm的前提下,需要全局安裝yogenerator-generator

npm install -g yo
npm install -g generator-generator

之後運行generator-generator來創建我們自己需要的generator的基礎框架

yo generator

在一系列設置問題之後

最終得到的generator的目錄:

.
├── generators/
│   └── app/
│       ├── index.js
│       └── templates/
│           └── dummyfile.txt
├── .editorconfig
├── .gitattributes
├── .gitignore
├── .eslintrc
├── .travis.yml
├── .yo-rc.json
├── package.json
├── gulpfile.js
├── README.md
├── LICENSE
└── test/
    └── app.js

二.從一個簡單的例子開始

我們的generator是一個插件,所以首先需要創建成一個node module包,在yeoman中這個包的名字必須是generator開頭的,那麼我們這個generator就叫做generator-xxx。每一個package.json的keyword中必須包含yeoman-generator。files屬性要指向項目的模板目錄。

三.通過npm init或是自己手動創建generator的package.json,項目依賴yeoman-generator。建議用yo  generator來初始化

{
    "name": "generator-demo",
    "version": "0.1.0",
    "description": "",
    "files": [
        "generators"
    ],
    "keywords": [
        "yeoman-generator"
    ],
    "main": "generators/app/index.js",
    "dependencies": {
        "yeoman-generator": "^1.0.0"
    }
}

四.往template中填充內容,也就是demo項目的三個基本文件的內容。這裏簡單提供一個例子: template/index.html

<!DOCTYPE html>
<html lang="zh_CN">
<head>
    <title>generator-demo</title>
    <link rel="stylesheet" href="styles/style.css">
</head>
<body>
    <h1>helle <%= name %></h1>
    <script src="scripts/main.js"></script>
</body>
</html>

yeoman採用ejs模板語法,可以在模板文件中傳入參數。

template/styles/style.css

* {
  margin: 0;
  padding: 0;
}

template/sctipts/main.js

'use strict';

window.onload = function() {
    console.log('generator success');
};

五.到這一步後就是擴展generator。yeoman提供了一個基礎的generator模板,它有自己的生命週期和事件,功能強大。可以通過擴展這個基礎generator來實現我們項目的初始化需求。接下來就是編輯app/index.js來擴展它:

const Generator = require('yeoman-generator');
const path = require('path');
const fs = require('fs');
const mkdirp = require('mkdirp');
const utils = require('../utils');
const log = utils.log;

module.exports = class extends Generator {
  constructor(args, opts) {
      super(args, opts);

      this.props = {
          projectName: 'demo',
          name: 'world'
      };
  }

  writing() {
      const { projectName, name } = this.props;
      const temps = {
        'index.html': { name: this.props.name }
      };

      fs.readdir(this.sourceRoot(), (err, items) => {
          for(let item of items) {
              if(temps[item]) {
                  this.fs.copyTpl(
                      this.templatePath(item),
                      this.destinationPath(projectName, item),
                      temps[item]
                  );
              } else {
                  this.fs.copy(
                      this.templatePath(item),
                      this.destinationPath(projectName, item)
                  );
              }
          }
      });
  }

  end() {
      log.info('generator success');
  }
};

六.就是運行generator。yoeaman的generator是一個全局npm module,我們在本地開發的generator可以通過軟連接的方式在本地生成它的全局npm包。在工程的根目錄下運行npm link,它會在本地的全局npm目錄下安裝我們新建的generator。

在確定本地已經安裝yo工具(npm install -g yo)後,在你需要初始化項目的地方運行yo demo,等命令執行完畢,就可以看到新建的項目了。

 

七.在擴展基礎generator時,我們給實例添加自定義的方法,每一個添加進去的方法都會在generator調用的時候被調用,而且通常來講,這些方法是按照順序調用的。除非是已下劃線_開頭的私有方法,或是定義在實例上的方法。

module.exports = class extends Generator {
    constructor(args, opts) {
        super(args, opts);

        this.task = () =>  {
            this.log('instance task');
        }
     }

      method1() {
          this.log('method 1');
      }

      method2() {
          this.log('method 2');
      }

     _task() {
        this.log('private task');
     }
};

// 輸出:
// 'method 1'
// 'method 2'

每一個方法在yeoman中都被認爲是一個任務,這些任務都會被run loop調用。yeoman的run loop是一個有優先級的隊列系統。採用Grouped-queue來維護yeoman的事件隊列。除了自定義的方法外,yeoman有很多特殊的事件方法,按照優先級排序:

  1. initializing - 初始化開始
  2. prompting - 調用this.prompt()與用戶產生交互
  3. configuring - 創建配置文件(package.json,config.js等)
  4. default - 方法都不匹配這些優先級時,就會是default優先級(自定義方法會被劃入default)
  5. writing - 創建項目文件
  6. conflicts - 文件創建中產生衝突的處理
  7. install - 調用(npm, bower)包install
  8. end - 結束項目初始化 其他自定義方法在configuring和writing按順序優先級調用。

 

八.現在我們來給generator增加用戶交互和package.json,讓它能構建出一個更復雜的項目。還是修改app/index.js,首先增加prompting:

prompting() {
  return this.prompt([{
      type: 'input',
      name: 'projectName',
      message: '請輸入項目名字',
      default: 'default-name'
  }, {
      type: 'confirm',
      name: 'package',
      message: '需要package.json文件',
      default: true
  }, {
      type: 'input',
      name: 'name',
      message: '請輸入你的名字',
      default: 'world'
  }]).then((answers) => {
      this.log('create project: ', answers.projectName);
      this.log('by: ', answers.name);
      this.props = answers;
  });
}

增加configuring:

configuring() {
  const { projectName, name } = this.props;
  let packageSettings = {
    name: projectName,
    version: '0.0.1',
    description: 'YOUR DESCRIPTION - Generated by generator-demo',
    main: '',
    scripts: {},
    repository: '',
    keywords: [],
    author: name,
    devDependencies: {},
    dependencies: {}
  };

  this.fs.writeJSON(this.destinationPath(projectName, 'package.json'), packageSettings);
}

九. 重寫constructor方法

有些generator方法只有定義在構造方法內才能被調用到.這些特殊的方法可以做的一些重要的操作等,而這些操作可能在構造之外無法正常運行。

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
      }
    });

 

十.找到工程根目錄

當使用yo命令來運行generator的生活,yeoman會把 .yo-rc.json文件所在的目錄作爲工程的根目錄,之後Yeoman將當前文件目錄跳轉到根目錄下運行請求的生成器。當我們使用this.config.save()的時候,storage模塊會創建它。如果.yo-rc.json 不在當前的工作目錄,請確保他也不在其他的項目目錄裏。

 

十一. 運行上下文

在generator內,所有的靜態方法都會被作爲action而自定執行,當然generator也提供了可以聲明不自動執行的輔助函數,generator提供了三種可以創建輔助函數的方法.

通過下劃線開頭定義函數,如:CopyFiles 2 使用實例函數聲明:

generators.Base.extend({
        constructor: function () {
          this.helperMethod = function () {
            console.log('won\'t be called automatically');
          };
        }
      });

繼承一個父級generator:

var MyBase = generators.Base.extend({
        helper: function () {
          console.log('methods on the parent generator won\'t be called automatically');
        }
      });

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

 

十二.generator Arguments

Arguments是在命令行中直接傳遞的。 如:yo webapp my-project,接受鍵值對的條件。

desc:描述argument

required:定義是否必須

optional:是否可選擇的

type:參數類型,支持的類型有String Number Array Object

defaults: argument默認值

banner:字符串顯示的使用說明(這是默認提供)

注意:參數必須的定義在construct函數內,否則當你使用generator調用命令(如:yo webapp --help)的時候,不能夠輸出相關的幫助信息。

示例:

var _ = require('lodash');

    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 = _.camelCase(this.appname);
      }
    });

十三. Options

option和argument很相似,但是option是作爲命令行標識使用的,如yo webapp --coffee。

我們可可以通過generator.option()添加option。

示例:

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");
   }
 });

十四. 輸出消息

輸出消息是通過generator.log模塊來處理實現的。不建議使用console.log輸出命令。

示例:

module.exports = generators.Base.extend({
      myAction: function () {
        this.log('Something has gone wrong!');
      }
    });

十五. 處理依賴關係

一般當你運行你的generator的時候,你經常需要通過 npm 和 Bower來安裝一些generator用到的依賴模塊。而這些任務是非常繁瑣的,爲了方便,yeoman將這部分任務抽離了出來。

npm

你只需要調用generator.npmInstall() 命令就可以執行npm安裝命令,yeoman確保了npm install只執行了一次,即使他被多個generator調用。

例如你想安裝lodash作爲dev dependency:

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

上面代碼等同於調用了npm install lodash --save-dev命令。

Bower

你只需要調用generator.bowerInstall()即可啓動安裝命令。yeoman確保了bower install只執行了一次,即使他被多個generator調用。

npm && Bower

調用enerator.installDependencies()即可同時運行npm 和 bower。

其他tools

yeoman抽離了spawn命令,這個抽離保證了我們可以在Linux ,mac 以及windows系統上可以很好的運行。

假如你是一個PHP狂熱愛好者,你想運行composer命令,你可以這樣做:

generators.Base.extend({
      install: function () {
        this.spawnCommand('composer', ['install']);
      }
    });

請確保面spawn命令在install隊列裏。因爲您的用戶不願意等待在那兒直到安裝命令完成。

十六. 上下文路徑

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

1. 目標上下文

目標上下文定義爲當前工作目錄或含.yo-rc.json文件最接近的父文件夾。該.yo-rc.json文件定義了一個generator項目的根目錄。該文件允許用戶在子目錄中運行命令,並讓他們在項目中可以運行。這確保了用戶行爲的一致。

你可以通過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'
      }
    });

2. 模板上下文

模板上下文是你保存模板文件的目錄,他一般是你要讀取和複製的目錄。模板上下文一般是默認是定義在./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'
      }
});

3. “內存”文件系統

當涉及到覆蓋用戶的文件的時候,yeoman非常的謹慎,基本上,每一個write動作都是一個爲已經存在的文件解決衝突的過程。幫助用戶嚴重需要覆蓋的內容。

4. 文件工具

generator的this.fs暴露出所有的文件方法,通過mem-fs editor .

十七.完整示例

'use strict';

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

        constructor: function() {
            generators.Base.apply(this, arguments);
            this.option('coffee');
            this.scriptSuffix = (this.options.coffee ? ".coffee": ".js");
        },

        initializing: function() {
            var message = chalk.bgBlack.bold('\nWelcome to webApp\n') + chalk.underline('webApp.github.io\n');
            this.log(yosay(message));
        },

        prompting: function() {
             var prompts = [{
                type:'input',
                name: 'appName',
                message: 'input app name .',
                default: 'webApp'
            }];
            this.prompt(prompts, function (answers) {
                this.log(answers);
            }.bind(this));
        },

        configuring: function() {
            this.config.save();
        },

        selfFunction: function () {
            this.log("執行了自定義方法");
        },

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

實列:

注:使用_.template(lodash的template功能)和this.fs.write將模版中的關鍵字替換爲用戶的輸入項。

this.fs.readJSONthis.fs.writeJSON,則是將package.json模版中的數據讀取出來,作出一定修改寫成新的文件。

最後使用mkdirpthis.fs.copy構建工程目錄結構和將一些不要修改的配置文件copy到指定目錄。

發佈

首先npm官網註冊一個npm賬號,如果有則運行npm login登陸。然後到工程根目錄下,運行npm publish進行發佈。

官網api:https://yeoman.github.io/generator/Generator.html#destinationRoot

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