隨着開發團隊不斷髮展壯大,在人員增長的同時也帶來了協做成本的增長;業務項目愈來愈多,類型也各不相同。常見的類型有基礎組件、業務組件、基於React的業務項目、基於Vue的業務項目等等。若是想要對每一個項目進行一些規範上的約束好比Git提交規範、Javascript規範簡直難於登天。全部的這些,只是由於還欠缺一個好用的工程化工具,在項目建立的初期自動的將這些目錄結構和文件生成、而且集成工程常見的規範來進行約束。html
2.1 談談目前團隊的痛點前端
l 業務部門從git上拉下模板代碼後,還要根據具體的業務須要進行配置,修改代碼對於前端框架有必定的學習的成本,比較麻煩,對於業務來說也沒有必要學習這些框架知識vue
l 修改配置代碼的工做基本上是在項目開始就要完成的,是一次性的,後續不用關心有什麼變化node
l 對於頁面生成,路由生成這些操做,基本上是固定的操做,每次新建js文件,並在舊的文件中添加固定格式的代碼,既繁瑣又有規律可循,因此考慮是否是能夠一個命令就可以添加一個頁面文件,並生成路由呢linux
基於上面幾點,考慮作一個前端腳手架項目,經過腳手架來複用項目結構,並把對於項目結構的配置工做和複雜枯燥的操做都打包到這個腳手架的功能上,經過命令行就能夠完成之前好幾步才能完成的步驟,方便業務開發並提高研發效率。git
好了,說幹就幹,在網上搜索了一下,發現Yeoman就是幹這個事情的,並且是谷歌開發的腳手架工具,嘗試了一下很是好,下面就記錄一下yeoman的設計思路和使用過程。github
咱們須要給每一個工程類型的項目建立一個generator。按照目前前端技術棧的發展狀況來看,一個團隊通常會有3~5個generator。把這些generator當作一個個的插件,經過工具上層的CLI命令來暴露給開發者使用。npm
在generator之下,須要開發一系列服務和集成規範。包括和Git倉庫打通,也就是經過腳手架初始化目錄時,先對開發者鑑權。以後根據開發者輸入的項目名稱在遠程Git倉庫裏面建立倉庫而且授予開發者權限。後期功能完善以後,能夠作一些錦上添花的工做,好比進行數據統計,分析各個業務倉庫使用的generator版本信息,是否集成了最新的feature等等。json
npmjs 帳號,用於publish到npm。數組
Github 帳號,npm中顯示你的源碼,同時方便Yeoman官網中能搜到你的generator。正如其描述的:
Your generator must have a GitHub repository description, the yeoman-generator keyword and a description in package.json to be listed.
在已經有裝好node和npm的前提下,須要全局安裝yo和generator-generator
npm install -g yo
複製代碼
以後運行generator-generator來建立咱們本身須要的generator的基礎框架
npm install -g generator-generator
複製代碼
運行generator-generator來建立咱們的腳手架基礎框架,運行以下命令:
yo generator
接下來會有一系列的詢問問題,其中generator name須要設置爲必須以generator-爲前綴,由於generator都是普通全局安裝的Node.js模塊,因此Yeoman徹底依賴於文件系統找到它們。
基礎框架安裝完成以後的目錄結構以下圖:
咱們對於腳手架的定製開發工做主要在下面兩個文件夾
l generators/app/templates 目錄
放置腳手架代碼模板的文件夾
l generators/app/index.js 文件
配置用戶輸入信息,模板遷移和替換規則,安裝項目依賴模塊;該文件是Generator的子類,重點完成三個方法的定製,分別是prompting,writing,install,下面會重點講解。
腳手架的工做方式是:先詢問用戶的配置需求,好比你的項目名字是什麼?要使用哪些工具類?而後根據用戶的輸入,完成項目文件的初始化工做。
咱們先看下用戶輸入配置的代碼,地址:generators/app/index.js
prompting() { let prompts = []; // 若是沒有指定appname則提示用戶輸入 if (!this.options.appname) { prompts.unshift({ type: 'input', name: 'appname', message: "Input your project's name", default: DEFAULT_APPNAME, validate: name => { return !/\s+/.test(name.trim()); } }); }
prompts = prompts.concat(require('./_prompts'));
return this.prompt(prompts).then(res => { // 後續可經過this.props.xxx使用props; let appname = res.appname || this.options.appname; let options = Object.assign({}, res, { appname }); // 模塊依賴 this.renderOpts = options; }); }複製代碼
prompting
方法主要是來完成和用戶交互的,交互的用戶輸入信息都放在prompts
數組中:
· name 用戶輸入項的標識,在獲取用戶輸入值的時候會用到
· message 是給用戶的提示信息
· type 非必填,默認是text,即讓用戶輸入文本;confirm是選擇輸入「Yes/No"
· default 非必填,用戶輸入的默認值
用戶輸入詳細解讀請參考inquirer類庫,此處再也不展開www.npmjs.com/package/inq…
上面已經提到generators/app/templates目錄存儲的是文件的模板文件,是生成新的項目結構的原材料;
一個新的項目的文件和結構,是由三部分組成
· 固定文件 直接從template目錄copy到項目目錄便可
· 加工文件 根據上一步用戶的輸入,對templates目錄下的模板進行二次加工,再copy到用戶指定的目錄中,以完成項目的初始化
· 可選文件 根據用戶的選擇,若是須要則copy,不須要則不copy
對於加工文件,在templates文件中可使用EJS模板語法,把模板和用戶的輸入信息結合起來,就生成了一個新的項目文件,EJS的模板語法以下:
· 賦值 <%= appName%>
· 表達式 <% if(someAnswer){ xxx } %>
項目文件的模板拷貝和用戶輸入替換工做都在writing方法中實現,示例代碼以下:
writing() { const DestFolder = this.options.current ? '' : Path.join(this.renderOpts.appname, '/'); // 添加隱藏文件 .文件名稱在linux下會有問題,因此.xxx在template裏改成_xxx let targets = [ // 不須要加工的文件 ['mock/**/*', 'mock', false], ['src/**/*', 'src', false], ['README.md', 'README.md', false], ['_editorconfig', '.editorconfig', false], ['_env', '.env', false], ['_eslintrc', '.eslintrc', false], ['_gitignore', '.gitignore', false], ['_prettierignore', '.prettierignore', false], ['_prettierrc', '.prettierrc', false], // 須要加工的文件 ['package.ejs', 'package.json', true], ['umirc.ejs', 'umirc.js', true] ]; // 遍歷文件數組,處理並移動文件到目標文件夾下 _.forEach(targets, file => { let fromFile = ''; let toFile = ''; let opts = {}; fromFile = file[0]; toFile = file[1]; opts = file[2] ? this.renderOpts : opts; this.fs.copyTpl( this.templatePath(fromFile), this.destinationPath(DestFolder + toFile), opts, {}, { globOptions: { dot: true } } ); }); }複製代碼
須要重點關注四個方法:
· this.templatePath:返回template目錄下文件的地址
· this.destinationPath:指定加工完成後文件的存放地址,通常是項目目錄
· this.fs.copy:把文件從一個目錄複製到另外一個目錄,通常是從template目錄複製到你所指定的項目目錄,用於固定文件和可選文件(根據用戶選擇)
· this.fs.copyTpl:和上面的函數做用同樣,不過會事先通過模板引擎的處理,通常用來根據用戶輸入處理加工文件
那麼怎麼加工文件呢?上面咱們已經介紹了EJS模板的賦值和表達式語法,好比咱們上面有一個輸入項是appName
,
const prompts = [
{
name: 'appName',
message: 'your appName name?'
}];複製代碼
咱們把這個輸入項做爲html的title,那麼咱們就能夠在template文件夾裏面放入下面的模板文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title><%= appName %></title>
</head>
<body>
<div id="vue"></div>
</body>
</html>複製代碼
好了,項目文件加工完成並複製到了指定的目錄,接下來要運行咱們的項目,還須要使用npm install
安裝項目的依賴,那麼可不能夠把這個操做也放到腳手架的安裝過程當中呢?下面的 install()
方法就能夠:
install() {
// 安裝npm依賴和bower依賴
//this.installDependencies();
// 只安裝bower依賴
//this.bowerInstall();
// 只安裝npm組件 this.npmInstall();
}
複製代碼
其中有三個方法可使用,既能夠安裝npm依賴包,也能夠安裝bower依賴包,具體請參考上面的代碼註釋。
綜上,一個腳手架最基本的功能就完成了,首先先將這個腳手架連接到本地:
npm link // 若是提示權限問題請使用sudo npm link
此時腳手架已經能夠本地使用了,在本地建立一個項目目錄,進入該目錄,嘗試使用該腳手架,好比你的腳手架項目名稱爲generator-name,命令行中應該去掉腳手架項目的前綴「generator-」來運行:
yo name
根據設定的提示和輸入信息,Yeoman會一步一步安裝你的項目文件,最終生成你指定的項目結構。
若是你但願本身的腳手架給更多的人提供方便,能夠把它發佈到npm上。
首先須要一個npm帳號,若是沒有可使用npm adduser建立;
若是有則運行npm login登錄,而後到工程根目錄下,運行npm publish就能夠發佈了。
好了,關於Yeoman的用法就介紹到這了,關於yeoman生命週期以及高級功能,可參考Yeoman官網。其實Yeoman生成腳手架不僅限於前端,任何語言的腳手架均可以用Yeoman來實現,具體可參考官網。