前端工程化系列[06]-Yeoman腳手架核心機制
❏ Yeoman腳手架工具的價值討論
❏ generator[生成器
]的內部結構
❏ generator[生成器
]的專案模板
❏ Yeoman腳手架工具的核心運轉機制
❏ Yeoman 的主要組裝流程
Yeoman這樣的腳手架工具解決了什麼問題?
所有新事物都不是憑空產生的,它們的出現總有某些內在的驅動力。一項新技術,一個新工具的出現更是如此。不知道從什麼時候開始起,我接觸新事物新技術以及某些工具的時候,總願意多花點時間想一想它出現的原因是什麼?因為時間、精力等等這些東西都很寶貴,IT從業人員對這些資源尤其敏感,所以新技術或者新工具的出現我認為有幾種情況:
- 已有的技術或工具存在缺陷,作者們靠自己的才學推出
更完美的替代方案
- 已有的技術或工具無法解決既定的需求,作者們探索出
解決問題的技術方案
- 純粹閒的蛋疼(這種情況一般比較少見)
現在,我們來研究下Yeoman的價值,或者說Yeoman出現的意義是什麼?Yeoman的出現解決了什麼樣的問題?
我們假設有這樣的開發場景:公司的開發團隊,基於某些特定的技術棧已經完成了專案A的開發和上線等工作,專案A的基本情況如下
技術棧:JavaScript + HTML + CSS + Bootstrap + jQuery
工作流:npm(包管理工具) + bower(下載器) + grunt
版本管理工具:Git
專案整體目錄結構(簡化後)
. ├── Gruntfile.js ├── bower.json ├── node_modules │ ├── abbrev ··· │ └── xtend ├── package-lock.json ├── package.json ├── build │ ├── css │ │ └── style.min.css │ └── js │ ├── index.js │ └── index.min.js ├── dist └── src ├── css │ └── style.css ├── index.html ├── js │ └── index.js ├── libs │ ├── bootstrap │ └── jquery └── template說明:上面的目錄中src為程式碼的工作目錄,bulid為構建後目錄,dist為釋出目錄。
因為專案A已經上線釋出,現在公司要求著手開展新的專案B,經過需求評審和技術選型後,新專案B採用的工作流和專案A保持一致,技術棧在原有的基礎上嘗試使用TypeScript來處理指令碼部分引入Vue框架,其它部分保持不變。我們發現專案A和專案B它們的結構基本上是一致的(比如專案的目錄就夠,都需要擁有Gruntfile.js和package.json等檔案),但是有些部分又不太一樣,比如package.json檔案中的專案名稱、開發依賴等。
這個時候,我們在對專案B進行初始化的方式可以嘗試以下操作方式:
- 方案① 從0開始建立目錄結構,整合工作流配置開發環境
- 方案② 從專案A中拷貝目錄結構和固定檔案,對於不同的部分一個個修改
如果我們採用方案① 你會發現這個過程你在初始化專案A的時候就已經做過了,是重複性的工作,毫無技術含量但是又費時費力。
如果我們採用方案② 你會發現要修改的檔案有些多,每個檔案要改的欄位也比較多,而且容易遺漏總是調不通會出現各種問題,心煩意亂。
如果你會使用Yeoman腳手架工具的話,那麼對於上面的開發場景你就會多一個方案③,在使用方案③來初始化專案B的時候,你只需要動動手指在終端中輸入$ yo 生成器名稱
再使用互動方式簡單配置某些特定值,初始化的工作就完成了。這就是Yeoman的價值所在,初始化專案的時候你不必再把自己沉入到瑣碎重複無技術成長的費力工作中,也不必總是像個機器人般進入到拷貝-貼上-修改這樣無止境的迴圈中。腳手架工具是那麼的簡單直接和高效,你甚至可以省出點加班的時間來看世界盃了 : )
我知道有一些槓精要出來噴了。“解決這種初始化問題不用搞的這麼複雜,我完全可以把專案結構和固定不變的部分抽取出來託管到gitHub倉庫,要初始化專案的時候 $ git clone一下不就好了嗎?”
說的很有道理,但是clone下來的倉庫雖然結構和必要檔案已經準備好了,但很多檔案是不是還得修改?那你會頂回來“難道使用Yeoman初始化就不需要修改了嗎?”
當然也要修改,不過就算是修改那改起來也很有趣味還So快!
另外,如果新專案的整體結構以及技術選型和已有的專案很不一樣,那你抽取後交由git管理的倉庫就沒用了,因為八字不合啊。使用Yeoman就沒用這樣的顧慮,在Yeoman-generator列表有好幾千現成的generator供你選擇,總有一款適合你!!!
我要求太太…太高,實在誰也看不上?沒關係,generator這傢伙還可以私人訂製,你完全可以根據自己的需求來定製需要的generator,你一高興甚至還能把它釋出到社群造福全人類。
Yeoman-generator的內部結構
搞清楚 generator的價值所在和應用場景之後,我們就可以開始談論generator相關的話題了,前面介紹過Yeoman腳手架工具的作用是幫助我們依據特定的技術棧需求來初始化專案,在安裝了yo工具之後,只需要在終端中使用類似$ yo generator--xx
的命令先安裝對應的generator然後再$ yo xx
搭建即可。至於如何找到匹配當前技術選型的generator,可以去官網的generator列表搜尋,這些生成器中有很大一部分來自於對應框架的作者或者Yeoman官方團隊,質量有保證且更新很及時。當然,我們也可以建立自己的generator併發布。關於如何建立自己的generator,我們放到另一篇文章Yeoman腳手架生成器建立來解決。
簡單說Yeoman做的工作其實就是根據當前的生成器(generator)來複制固定的專案模板檔案到新專案中,而新專案中的某些檔案需要配置,這部分工作由安裝時候的互動式指令來完成(相當於傳遞引數給模板檔案)。
需要注意的是,Yeoman的設計僅僅只提供了一小部分核心的API,而真正繁重的初始化工作是交給每個具體的generator來完成的。generator主要由組裝指令
和專案模板
兩部分組成。
組裝指令
Yeoman generator中的generators/app/index.js檔案是整個生成器的核心部分,該檔案用於告知Yeoman該如何來組織並搭建專案,我們可以在該檔案中設定初始化專案時必要的安裝提示和選項來讓使用者選擇,以及每個檔案應該如何複製和修改,是否需要載入依賴和Node包等內容。
專案模板
專案模板包括初始化專案需要的所有必須檔案。這些檔案又可以簡單的劃分為固定檔案
、靈活檔案
、可選檔案
和依賴檔案
。所謂固定檔案
就是在每個初始專案中都一模一樣的檔案,譬如index.js、style.css等檔案,在具體處理的時候這些檔案只需要簡單複製即可。靈活檔案
指的是那些需要根據使用者選擇來做簡單修改然後才能複製的檔案,譬如index.html檔案(title等資訊需根據使用者輸入來指定)。對於可選檔案
來說,它們並不是必須的,譬如某些基礎框架有的專案中需要,有的專案中也許並不需要,這部分檔案的處理方式需要交給使用者來決定。
專案模板檔案的類別
前面已經介紹過了Yeoman生成器的組成部分主要是組裝指令和專案模板。對於整個Yeman腳手架工具來說,專案模板這部分就相當於是搭建腳手架需要用到的原材料,而組裝指令用來決定和控制所有的具體行動是什麼。
現在我們開始深入的來討論專案模板這部分內容,需要先明白的是“能夠滿足所有需求的萬能的專案模板是不存在的”
。因為這世界上每個專案組,每個產品甚至每個人的需求(要求)都各有不同。所以,在實踐中你必須要對當前專案的需求和採用的技術棧有深入的理解,這樣你才能知道目標專案的目錄結構會是什麼樣的? 哪些檔案是必不可少的。
如果你的專案和採用的技術棧比較大眾化,那麼搜尋一個合適的generator基本就能滿足需求,拿來主義即可。如果你的專案不管結構還是所採用的技術看上去都那麼的非凡和特別,那麼就多花一點點時間建立個自己的generator吧,如果你需要處理多個這樣的專案,那就更應該了。在建立或者理解generator的時候,我們可以根據前面對專案模板檔案的劃分情況來區別對待不同的檔案。
固定檔案
固定檔案是在每個專案中初始內容都一樣的必要檔案。
比如我們可能總是會把程式碼的結構劃分為src
、build
和dist
三個目錄,在src目錄下面擁有js、css和lib檔案目錄,index.js和style.css等檔案。這些檔案都是必要的,剛開始的時候可能是空的或者只有幾行簡單的程式碼。這些檔案的特點是,在使用組裝指令操作(通常是複製-移動)這些檔案的時候,不需要對它們進行任何的修改。
靈活檔案
靈活檔案和固定檔案差不多,也是初始化專案所必須的,但不同的專案中這些檔案的內容也會稍有不同,這些不同之處可能很細微(比如僅僅是名字、協議這些),也可能差異巨大。比如,我們常用的構建工作流中的bower.json
和package.json
檔案,它們是必不可少的,但是它們都需要當前專案的專案名稱和協議等資訊才能正常工作。像這樣的靈活檔案還有index.html,在這個檔案中的title標籤中應該使用當前專案的名稱。
靈活檔案中的部分內容需要在安裝該生成器的時候,由使用者互動式配置輸入的資訊來進行設定。
可選檔案
可選檔案並不是搭建初始化專案時所必須的檔案,如果沒有那麼沒關係,如果有那似乎更好。這些一般在使用者互動式配置的時候,以是否題的方式交由使用者決定,譬如是否使用less 是否安裝Bootstrap等。
依賴檔案
依賴檔案指的是某些常用的框架、外掛或者是Node模組,這些檔案並不需要你在專案模板檔案中提供,然後通過組裝指令去一個個複製。因為基本上成熟的專案中都會使用既定的工作流(主要包括依賴和包的下載、專案的自動化構建等),所以我們完全只需要在package.json
或者bower.json
等檔案中設定好依賴即可,然後在組裝指令的相關程式碼中通過this.installDependencies()
類似的程式碼來呼叫npm或者是bower執行install命令即可。
Yeoman腳手架運轉的核心機制
當您為專案準備好(搜尋或自己建立)合適的generator之後,就可以用它們來搭建專案了。generator的執行需要在終端中使用yo命令來操作。yo是Yeoman的核心命令,主要用來連線生成器和專案結構。我們可以把yo命令理解為generator的執行器,它知道怎麼找到對應的generator,也知道該如何執行它們。
注意:yo基於NodeJS且需要在任何檔案目錄中使用,所以在安裝yo命令的時候應該使用-g來進行全域性安裝。安裝過程請參考: Yeoman腳手架使用入門。在使用yo命令列工具和生成器來初始化專案之前,需要先把指定的生成器(generator)下載安裝到本地(如果是自己建立的生成器,那麼可以通過$ npm link
命令以軟連線的方式生成一個全域性的npm包,我的是mac OSX系統,生成的npm包會儲存在/usr/local/lib/node_modules/路徑,如果使用的是別人釋出的generator,那麼請使用$ npm install -g generator-xxx
的方式來安裝)。
這裡需要注意的是yo命令列工具主要負責前期工作,在使用的時候它主要檢查當前安裝的generator有哪些,指定的generator是否能夠正常工作,如果能,那麼它就會呼叫generator的組裝指令,把剩下部分的工作交接給generator來完成。generator接管專案的組裝流程之後,會按app/index.js中的要求來處理檔案的複製等工作
。
下面給出腳手架工具初始化專案時的核心流程。
這裡對yo的主要命令進行簡單說明
$ yo
執行該命令的時候,yo會搜尋並列出所有本地可用的生成器$ yo 生成器名稱
比如對於generator-typescript
生成器,那麼執行的命令就是$ yo typescript
。該命令會先檢查enerator-typescript
生成器是否可用。如果可用,那麼就接著以 ①互動式配置 ② 寫入檔案 ③ 下載安裝依賴的順序來執行組裝指令。
Yeoman的主要組裝流程
組裝指令是用來讓Yeoman建立專案所需檔案的一系列具體的命令(程式碼)。典型的組裝流程分為三個步驟:
① 互動式配置。這個步驟通過向用戶提問或直接輸入配置資訊來完成模板傳參。
② 寫入檔案。把專案模板中的指定檔案複製到新專案的指定目錄中。
③ 安裝依賴。下載並安裝所有儲存在bower.json和package.json檔案中的依賴和Node模組。
① 互動式配置
Yeoman在執行生成器的時候,首先會執行安裝提示以互動式的方式來詢問使用者,目的是為了獲取生成器所需要的一些引數,比如專案的名稱、作者、使用的開原協議以及是否安裝和使用某些元件等。
這部分功能,需要使用到inquirer包,這個包的作用是生成選項來讓使用者選擇。下面給出程式碼示例:
prompting() { const prompts = [ { type : 'input', name : 'appName', message : '請輸入專案名稱:', default : this.appname //appname是內建物件,代表工程名,這裡就是ys }, { type : 'input', name : 'appAuthor', message : '請輸入作者姓名:', default : '文頂頂' }, { type: 'list', name: 'appLicense', message: '請選擇使用的license:', choices: ['MIT', 'ISC', 'Apache-2.0', 'AGPL-3.0'] }, { type : 'confirm', name : 'isIncludeBootstrap', message : '是否需要使用bootStrap框架?', default : false }, ]; return this.prompt(prompts).then(props => { // To access props later use this.props.someAnswer; this.props = props; });
我們可以看到在程式碼中,這些互動式配置都由prompts來進行維護,prompts是一個物件陣列,陣列中的每個元素物件就代表著一個具體的安裝提示,在使用yo命令執行該生成器的時候,它的執行情況如下:
_-----_ ╭──────────────────────────╮ | | │ 歡迎使用 │ |--(o)--| │ generator-wen! │ `---------´ │ Author:文頂頂 │ ( _´U`_ ) ╰──────────────────────────╯ /___A___\ / | ~ | __'.___.'__ ´ ` |° ´ Y ` ? 請輸入專案名稱: wendingdingTest ? 請輸入作者姓名: 文頂頂 ? 請選擇使用的license: Apache-2.0 ? 是否需要使用bootStrap框架? (y/N) yes
prompts中的每個物件元素就代表著一個安裝提示,上面程式碼一共提供了四個安裝提示。每個物件中的type屬性用於表明互動的型別,其中輸入專案名稱和作者姓名是input型的
,表示接收使用者的輸入,相當於填空題。選擇使用的license是list
型的,它提供了多個選項供使用者選擇,您可以認為這種型別是單選題。是否需要使用bootStrap框架是confirm
型的,預設為false,如果需要安裝那麼需要輸入YES,這相當於是非題。
互動式配置過程中使用者做出的所有選擇和輸入都會被儲存到this.props物件中,可以通過訪問this.props.isIncludeBootstrap屬性來確定是否需要安裝Bootstrap。
message屬性
儲存是每一條安裝提示的提示資訊。name屬性
是最重要的屬性之一,它作為key用來訪問使用者的選擇結果。default屬性
儲存的是預設值,即當用戶跳過當前安裝提示的時候,name對應的value值將使用default中儲存的預設值來設定。
② 寫入檔案
寫入檔案這個過程會把專案模板複製到指定的目錄中,如果是固定檔案那麼就直接拷貝,如果是靈活檔案那麼還需要把某些引數傳遞給指定的模板檔案。這個過程在程式碼中由writing() 函式體現,另外系統還提供了兩個函式(fs.copyTpl和fs.copy
)用來執行具體的操作。
writing() { mkdirp("build"); //建立build檔案目錄 mkdirp("dist"); //建立dist檔案目錄 mkdirp("src/template"); //建立src/template檔案目錄 //傳遞引數this.props.appName渲染index.html檔案 //把專案模板中的index.html檔案複製到新專案的src路徑下 this.fs.copyTpl( this.templatePath('index.html'), this.destinationPath('src/index.html'), {appName: this.props.appName} ); //把專案模板中的style.css檔案複製到新專案的src/css路徑下 this.fs.copy( this.templatePath('css/style.css'), this.destinationPath('src/css/style.css') ); //...... }
fs.copy方法會把指定檔案複製到目標路徑。
fs.copyTpl方法會先傳遞引數給模板檔案,經過模板引擎處理後再進行復制。
③ 下載和安裝依賴
這個階段做的事情非常簡單,就是呼叫npm或者是bower來下載並安裝依賴和相關的node模組。Yeoman提供了幾個對應的方法來處理這個過程。
this.npmInstall()
使用Npm來安裝package.json中的依賴和模組,相當於在終端中輸入$ npm install
指令。
this.bowerInstall()
使用Bower來安裝bower.json中的依賴和模組,相當於在終端中輸入$ bower install
指令。
this.installDependencies()
呼叫Bower和Npm並且安裝package.json和bower.json中依賴的所有模組,相當於先後呼叫了npmInstall和bowerInstall方法。
最後,為了幫助更好的理解Yeoman組裝流程的三個階段,給出下面的示意圖。