如何快速開發一個自己的項目腳手架?


1. 腳手架怎么工作?

功能豐富程度不同的腳手架,復雜程度自然也不太一樣。但是總體來說,腳手架的工作大體都會包含幾個步驟:

  • 初始化,一般在這個時候會進行環境的初始化,做一些前置的檢查
  • 用戶輸入,例如用 vue-cli 的時候,它會“問”你很多配置選項
  • 生成配置文件
  • 生成項目結構,這是候可能會使用一個項目模版
  • 安裝依賴
  • 清理、校驗等收尾工作

此外,你還需要處理命令行行為等。往往我們只是想輕量級、快速得創建一個特定場景的腳手架(不用想vue-cli那么完備)。而對於想要快速創建一個腳手架,其實我們不用完全從零開始。Yeoman 就是一個可以幫我們快速創建腳手架的工具。

可能很多同學都不太了解,那么先簡單介紹一下 Yeoman 是什么,又是如何幫我們來簡化腳手架搭建的。

首先,Yeoman 可以簡單理解為是一個腳手架的運行框架,它定義了一個腳手架在運行過程中所要經歷的各個階段(例如我們上面說的,可能會先讀取用戶輸入,然后生成項目文件,最后安裝依賴),我們所需要的就是在生命周期的對應階段,填充對應的操作代碼即可。而我們填充代碼的地方,在 Yeoman 中叫做 generator,物如其名,Yeoman 通過調用某個 generator 即可生成(generate)對應的項目。

如果你還不是特別清楚它們之間的關系,那么可以舉個小例子:

將腳手架開發類比為前端組件開發,Yeoman 的角色就像是 react,是一個框架,尤其是定義了組件的生命周期函數;而 generator 類似於你寫的一個 React 業務組件,根據 React 的規則在各個生命周期中填代碼即可。

Yeoman 內置的“生命周期”方法執行順序如下:

  1. initializing
  2. prompting
  3. default
  4. writing
  5. conflicts
  6. install
  7. end

其中 default 階段會執行你自定義地各種方法。

同時,Yeoman 還集成了腳手架開發中常用的各類工具,像是文件操作、模版填充、終端上的用戶交互功能,命令行等,並且封裝成了簡單易用的方法。

通過這兩點,Yeoman 可以幫我們大大規范與簡化腳手架的開發。

 

2. 開發一個自己的腳手架

了解了一些腳手架的工作方式與 Yeoman 的基本概念,咱們就可以來創建一個屬於自己的腳手架。作為例子,這個腳手架的功能很簡單,它會為我們創建一個最簡版的基於 webpack 的前端項目。

 

2.1. 准備一個項目模版

腳手架是幫助我們快速生成一套既定的項目架構、文件、配置,而最常見的做法的就是先寫好一套項目框架模版,等到腳手架要生成項目時,則將這套模版拷貝到目標目錄下。這里其實會有兩個小點需要關注。

第一個是模版內變量的填充。

在模版中的某些文件內容可能會需要生成時動態替換,例如根據用戶在終端中輸入的內容,動態填充package.json中的name值。而 Yeoman 內置了 ejs 作為模版引擎,可以直接使用。

第二個就是模版的放置位置。

一種是直接放在本地,也就是直接放到 generator 中,跟隨 generator 一起下載,每次安裝都是本地拷貝,速度很快,但是項目模版自身的更新升級比較困難,需要提示用戶升級 generator。

另一種則是將模版文件放到某個服務器上,每次使用腳手架初始化時通過某個地址動態下載,想要更新升級模版會很方便,通常會選擇托管在 github 上。

關於第二個模版放置究竟是選擇在本地好,還是遠端好,其實還是依據你個人的業務場景而定,在不同的場景的限制的需求不同,我之前既寫過模版放在本地的腳手架(即和腳手架一起通過 npm 安裝),也寫過托管在 git 倉庫上的這種方式。

回到我們「創建一個最簡版的基於 Webpack 的前端項目」的目標,我准備了一個項目模版,之后就會用它來作為腳手架生成的項目內容。

 

2.2. 創建 generator(yeoman-generator)

創建 Yeoman 的 generator 需要遵循它的規則。

首先是 generator 命名規則。需要以generator打頭,橫線連接。例如你想創建一個名為 webpack-kickoff 的 generator,包名需要取成 generator-webpack-kickoff。

這樣,當你通過

npm i -g yo

安裝完 Yeoman 的 CLI 后,就可以通過yo命令來使用 generator 來啟動腳手架:

yo webpack-kickoff

這里的 webpack-kickoff 就是包名里generator-后面的內容,Yeoman 會按這個規則去全局找相匹配的包。

其次,依據 Yeoman 的規范,默認情況下你需要在項目(即 generator)的generators/app/目錄下創建index.js,在其中寫入你的腳手架工作流程。當然,也可以通過修改配置來擴展或改變這個規則。

此外,你創建的 generator 類需要繼承 yeoman-generator。所以我們會在generators/app/index.js中寫如下代碼:

const Generator = require('yeoman-generator'); class WebpackKickoffGenerator extends Generator { constructor(params, opts) { super(params, opts); } } module.exports = WebpackKickoffGenerator;

還記得之前提到的“生命周期”方法么?包括 initializing、prompting、default、writing、conflicts、install 和 end。除了default,其他都代表了 Generator 中的一個同名方法,你需要的就是在子類中重寫后所需的對應方法。default階段則會執行用戶定義的類方法。

例如,你想在初始化時打印下版本信息,可以這么做:

const Generator = require('yeoman-generator'); class WebpackKickoffGenerator extends Generator { constructor(params, opts) { super(params, opts); } initializing() { const version = require('../../package.json').version; this.log(version); } } module.exports = WebpackKickoffGenerator;

可見,剩下的工作就是在 WebpackKickoffGenerator 類中填充各種方法的實現細節了。

 

2.3. 處理用戶交互

腳手架工作中一般都會有一些用戶自定義的內容,例如創建的項目目錄名,或者是否啟用某個配置等。這些交互一般都是通過交互式的終端來實現的,例如下面這個功能。

可以使用 Inquirer.js 來實現。而 Yeoman 已經幫我們集成好了,直接在 generator 里調用 this.prompt 即可。

在用戶交互部分的需求也比較簡單,只需要詢問用戶所需創建的項目目錄名即可,隨后也會作為項目名。按照 Yeoman 的流程規范,我們將該部分代碼寫在 prompting 方法中:

class WebpackKickoffGenerator extends Generator { // …… prompting() { const done = this.async(); const opts = [{ type: 'input', name: 'dirName', message: 'Please enter the directory name for your project:', default: 'webpack-app', validate: dirName => { if (dirName.length < 1) { return '⚠️ directory name must not be null!'; } return true; } }]; return this.prompt(opts).then(({dirName}) => { this.dirName = dirName; done(); }); } // …… }

注意,由於用戶交互是一個“異步”的行為,為了讓后續生命周期方法在“異步”完成后再繼續執行,需要調用this.async()方法來通知方法為異步方法,避免順序執行完同步代碼后直接調用下一階段的生命周期方法。調用后會返回一個函數,執行函數表明該階段完成。

 

2.4. 下載模版

正如2.1.中所述,我們選擇將模版托管在 github 上,因此在生成具體項目代碼前,需要將相應的文件下載下來。可以使用 download-git-repo 來快速實現。

class WebpackKickoffGenerator extends Generator { // …… _downloadTemplate() { return new Promise((resolve, reject) => { const dirPath = this.destinationPath(this.dirName, '.tmp'); download('alienzhou/webpack-kickoff-template', dirPath, err => { if (err) { reject(err); return; } resolve(); }); }); } // …… }

這里我們使用了this.destinationPath()方法,該方法主要用於獲取路徑。不傳參時返回當前命令行運行的目錄;如果收到多個參數,則會進行路徑的拼接。

此外,如果你細心的話,會發現_downloadTemplate()方法帶了一個下划線前綴。這是 Yeoman 中的一個約定:Yeoman 執行順序中有個default階段,該階段包含了所有用戶自定義的類方法。但是,如果某些方法你不希望被 Yeoman 的腳手架流程直接調用,而是作為工具方法提供給其他類方法,則可以添加一個下划線前綴。對於這種命名的方法,則會在default階段被忽略。

 

2.5. 模版文件拷貝

項目模版下載完畢后,下面就可以將相關的目錄、文件拷貝到目標文件夾中。這些都可以在writing階段操作。此時需要遍歷模版中的所有目錄,將所有文件進行模版填充與拷貝。遍歷方式如下:

class WebpackKickoffGenerator extends Generator { // …… _walk(filePath, templateRoot) { if (fs.statSync(filePath).isDirectory()) { fs.readdirSync(filePath).forEach(name => { this._walk(path.resolve(filePath, name), templateRoot); }); return; } const relativePath = path.relative(templateRoot, filePath); const destination = this.destinationPath(this.dirName, relativePath); this.fs.copyTpl(filePath, destination, { dirName: this.dirName }); } // …… }

這里使用了this.fs.copyTpl()方法,它支持文件拷貝,同時還可以指定相應的模版參數,此外,如果出現重名覆蓋情況會在控制台自動輸出相應信息。

最后,把下載與拷貝整合起來即可完成writing階段。

class WebpackKickoffGenerator extends Generator { // …… writing() { const done = this.async(); this._downloadTemplate() .then(() => { const templateRoot = this.destinationPath(this.dirName, '.tmp'); this._walk(templateRoot, templateRoot); fs.removeSync(templateRoot); done(); }) .catch(err => { this.env.error(err); }); } // …… }

 

2.6. 依賴安裝

到目前,腳手架已經可以幫我們把項目開發所需的配置、目錄結構、依賴清單都准備好了。這時候可以進一步幫開發人員將依賴安裝完畢,這樣腳手架創建項目完成后,開發人員就可以直接開發了。

Yeoman 也提供了this.npmInstall()來方法來實現 npm 包的安裝:

class WebpackKickoffGenerator extends Generator { // …… install() { this.npmInstall('', {}, { cwd: this.destinationPath(this.dirName) }); } // …… }

到這里,腳手架的核心功能就完成了。已經可以使用咱們的這個 generator 來快速創建項目了。很簡單吧~

完整的代碼可以參考 generator-webpack-kickoff。

 

3. 使用腳手架 

使用該腳手架會同時需要 Yeoman 與上述咱們剛創建的 yeoman-generator。當然,有一個前提,Yeoman 與這個 generator 都需要全局安裝。全局安裝 Yeoman 沒啥有問題(npm install -g yo),處理 generator-webpack-kickoff 的話可能有幾種方式:

  1. 直接發布到 npm,然后正常全局安裝
  2. 直接手動拷貝到全局 node_modules
  3. 使用npm link將某個目錄鏈接到全局

依據2.2.節的內容,咱們的 generator 名稱為 generator-webpack-kickoff。由於我的包已經發到 npm 上了,所以要使用該腳手架可以運行如下指令:

# 安裝一次即可 npm i -g yo npm i -g generator-webpack-kickoff # 啟動腳手架 yo webpack-kickoff

資源搜索網站大全 https://www.renrenfan.com.cn 廣州VI設計公司https://www.houdianzi.com

4. 優化

從上文這個例子可以看出,實現一個腳手架非常簡單。例子雖小,但也包含了腳手架開發的主要部分。當然,這篇文章為了簡化,省略了一些“優化”功能。例如

  • 項目目錄的重名檢測,生成項目時,檢查是否目錄已存在,並提示警告
  • 項目模版的緩存。雖然我們使用 github 托管方式,但也可以考慮不必每次都重新下載,可以放一份本地緩存,然后每天或每周更新;
  • CLI 的優化。完整版里還會包含一些更豐富的 CLI 使用,例如我們在動圖中看到的 loading 效果、頭尾顯示的信息面板等。這些工具包括

    • ora,用於創建 spinner,也就是上面所說的 loading 效果
    • chalk,用於打印彩色的信息
    • update-notifier,用於檢查包的線上版本與本地版本
    • beeper,可以“嗶”一下你,例如出錯的時候
    • boxen,創建頭尾的那個小“面板”
  • 版本檢查。上面提到可以用 update-notifier 來檢查版本。所以可以在 initializing 階段進行版本檢查,提示用戶更新腳手架。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM