前言
隨着開發團隊不斷發展壯大,在人員增加的同時也帶來了協作成本的增加;業務項目越來越多,類型也各不相同。常見的類型有基礎組件、業務組件、基於React的業務項目、基於Vue的業務項目等等。如果想要對每個項目進行一些規范上的約束比如Git提交規范、Javascript規范簡直難於登天。所有的這些,只是因為還欠缺一個好用的工程化工具,在項目創建的初期自動的將這些目錄結構和文件生成、並且集成工程常見的規范來進行約束。
本文分為兩部分,首先會談談目前團隊的痛點以及基於yeoman generator的設計思路;然后會詳細介紹如何實現定制的generator,過程中遇到的問題和解決辦法。
痛點一:工程創建不智能
- 代碼目錄文件手工拷貝
- 不同場景的工程對目錄結構的要求不盡相同
痛點二:規范約束難以統一集成
- 難以在新的工程項目中集成新的規范,需要手動加hook
- 缺少增量機制對舊項目集成
基於Yeoman generator的設計思路
我們需要給每個工程類型的項目創建一個generator。按照目前前端技術棧的發展情況來看,一個團隊一般會有3~5個generator。把這些generator看成一個個的插件,通過工具上層的CLI命令來暴露給開發者使用。
在generator之下,需要開發一系列服務和集成規范。包括和Git倉庫打通,也就是通過腳手架初始化目錄時,先對開發者鑒權。之后根據開發者輸入的項目名稱在遠程Git倉庫里面創建倉庫並且授予開發者權限。后期功能完善之后,可以做一些錦上添花的工作,比如進行數據統計,分析各個業務倉庫使用的generator版本信息,是否集成了最新的feature等等。
整體系統架構如下:
下面我准備開發一個適用於Now直播活動類搭建的腳手架了,名字是generator-now-activity
自定義generator的目錄結構
├───package.json
└───generators/
├───app/
| ├───templates/
| | ├─── src/
| | |─── _cilintrc.js
| | |─── _eslintrc.js
| | |─── _fis-conf.js
| | |─── _package.json
| | |─── _project.js
| | |─── _README.md
| | |─── editorconfig
| | |─── gitignore
| | └─── vcmrc
│ └───index.js
└───utils.js
擴展generator
在generator的外層index.js文件里,通過繼承yeoman-generator來擴展我們自己的generator,然后模塊暴露給外部。
const Generator = require('yeoman-generator');
module.exports = class extends Generator {
}
Yeoman的運行周期
一個 Yeoman Generator 被創建后(構造函數必然是最先被調用的),會依次調用它原型上的方法,且每一個方法中的 this 都被綁定為 Generator 實例本身,調用的順序如下:
- initializing - 初始化一些狀態之類的,通常是和用戶輸入的 options 或者 arguments 打交道,這個后面說。
- prompting - 和用戶交互的時候(命令行問答之類的)調用。
- configuring - 保存配置文件(如 .babelrc 等)。
- default - 其他方法都會在這里按順序統一調用。
- writing - 在這里寫一些模板文件。
- conflicts - 處理文件沖突,比如當前目錄下已經有了同名文件。
- install - 安裝依賴
- end - 結束部分
與用戶交互
Yeoman提供了API來讓generator和用戶進行交互,直接通過this.prompts函數,它的內部實現是使用了Inquire.js。
/**
* 提示用戶輸入配置項
* @returns {Promise.<TResult>}
*/
prompting() {
return this.prompt([{
type: 'input',
name: 'projectName',
message: '請輸入活動的名稱 (now-activity):',
default: 'now-activity-default'
}]).then((answers) => {
this.log('活動名稱', answers.projectName);
this.props = answers;
});
}
模板拷貝策略
對於工程src目錄部分直接通過深度優先算法拷貝寫入。對於工程的規范類、配置的文件需要單獨寫入,這一類可能需要接受用戶的輸入,同時需要集中進行維護,因此需要和src的拷貝方式進行區分。
src深度優先拷貝代碼如下:
const fs = require('fs');
const path = require('path');
function read(root, filter, files, prefix) {
prefix = prefix || '';
files = files || [];
filter = filter || noDotFiles;
const dir = path.join(root, prefix);
if (!fs.existsSync(dir)) return files;
if (fs.statSync(dir).isDirectory())
fs.readdirSync(dir)
.filter(filter)
.forEach(function (name) {
read(root, filter, files, path.join(prefix, name));
});
else
files.push(prefix);
return files
}
function noDotFiles(x) {
return x[0] !== '.';
}
module.exports = {
read
};
在外層通過Yeoman提供的API this.fs.copy()方法來進行文件拷貝
/**
* 源代碼模板
*/
const sourceCode = () => {
const sourceDir = path.join(this.templatePath(), './src/');
const filePaths = utils.read(sourceDir);
_.each(filePaths, (filePath) => {
this.fs.copy(
this.templatePath('./src/' + filePath),
this.destinationPath('./src/' + filePath)
);
});
};
開發完generator之后,就可以通過yo now-activity來進行使用了。
generator和其它工具如CLI集成
前面提到的yo now-activity的方式使用可能存在一些問題,因為這種方式要求代碼必須上傳到github上。對於公司內部的工具,不走正常的開源流程顯然是不被允許的。那么,有沒有什么方法,不添加generator到Yeoman的generator列表里就能夠使用呢?
幸運的是,Yeoman提供了yeoman-environment來幫助我們在其它工具中集成編寫好的generator,yo其實也只是yeoman-environment暴露到上層的一個命令而已。
const yeoman = require('yeoman-environment');
const yeomanEnv = yeoman.createEnv();
/**
* Lookup方法會在本地查找已經安裝過的generator
*/
yeomanEnv.lookup(() => {
yeomanEnv.run('@tencent/now-activity', {'skip-install': true}, err => {
console.log('done');
});
});
一些細節
- 為了方便本地調試看效果,在generator根目錄下運行 tnpm link
- 使用Yeoman提供的API this.log來打印信息,而不要使用console.log
- 如果是內部工具,運行的時候命令為:yo @tencent/now-activity
最后
安裝示例(限內部)
$ tnpm install -g yo generator-generator @tencent/feflow-cli
$ feflow init