您好,登錄后才能下訂單哦!
1 寫在前面的話
搭一個腳手架,考驗了你的 nodejs 水平、工程化能力、以及工具服務(wù)的設(shè)計能力,是前端進階不可或缺的過程
筆者在開發(fā) cli 的過程中,調(diào)研流行的 cli 并形成最佳實踐,本文旨在用最短的篇幅實現(xiàn)主要功能,揭露核心原理,同時提供 demo 倉庫與大家學(xué)習(xí)探討。
通篇閱讀大約需要 10 分鐘,基于本教程自己擼一個 cli 大約需要花費 15 分鐘
2 腳手架的雛形
其實腳手架的初衷,就是提供一個最佳實踐的基礎(chǔ)模板,因此模板拷貝是其核心功能
幾年前我曾寫過一個極簡的腳手架,大該干了這么一件事兒
一個命令,就可以把我預(yù)設(shè)的完整的工程目錄創(chuàng)建好,特別方便效率。
我想,這應(yīng)該算是一個雛形腳手架吧
3 腳手架需要考慮的
上面雛形腳手架可以很好的服務(wù)于個人需求,但是畢竟過于干癟和簡陋,要想成為被大家廣泛接受的工具,還需要完善。
大家熟知的 vue-cli create-react-app @tarojs/cli umi 最基本功能:首先提出一些列問題選項,然后為你的新建項目提供一份模板并安裝依賴,再提供調(diào)試構(gòu)建命令
沒錯,最核心的部分就是這個思路;但如果要做成一個可伸縮的、用戶友好的,還需考慮這些需求:
看起來信息量有點大,但其實都并不晦澀,我們一一說明一下意圖
3.1 模板支持版本管理
比如用戶使用 v1.0.0 的模板創(chuàng)建了項目,半年后,已經(jīng)迭代升級到了 v2.0.0。我們需要依舊能夠找到 v1.0.0 版本,因為老用戶不想或者不方便升級。
像我之前的雛形腳手架,將模板打一個壓縮包放在云服務(wù)器上是不可行的,一旦更新就全量替換了
npm 倉庫天然支持版本管理,因此將模板發(fā)布到 npm 上自然解決了這個問題 (非開源項目,可考慮自建倉庫或者私有的倉庫)
3.2 支持擴展新模板
比如我們一開始我們的腳手架支持 H5 的模板。
半年后,隨著業(yè)務(wù)發(fā)展,需支持微信小程序的模板。
此時,我們無需額外再開發(fā)一個 cli,而是讓 cli 一開始設(shè)計的就支持擴展,這符合了開放封閉的設(shè)計原則
3.3 自動檢測版本更新
npm 提供了一些命令來檢測包的版本,比如你 npm view react version 返回 16.9.0,告知你最新版本
借此,可以判斷用戶目前安裝的是否最新版本,并提示用戶更新
3.4 根據(jù)用戶選擇,生成個性化模板
模板雖說是為了統(tǒng)一,但也要在統(tǒng)一中支持差異,可通過問詢用戶,來提供差異化支持,比如:
這些問詢的結(jié)果,將影響我們最終的模板,比如我們根據(jù)是否 TypeScript 會在兩套預(yù)設(shè)的模板中選一個套,將用戶輸入的「項目介紹」插入 package.json 的 description 字段等等
3.5 友好的UI界面
合適的格式、顏色、字體、進圖條等,給與用戶良好的信息反饋
下文會介紹一些常用的庫,來提供這些功能
3.6 構(gòu)建功能獨立,可因模板而異
我們通常使用 webpack 來構(gòu)建/調(diào)試,對于不同的模板,構(gòu)建流程存在較大差異,我們需要支持為不同的模板配置不同的構(gòu)建
因此構(gòu)建能力也被抽離成單獨的 npm 包,模板中可指定其構(gòu)建包
3.7 多人合作項目,能確保構(gòu)建結(jié)果一致
因為存在多版本,我們需要約束,讓所有項目的貢獻者的產(chǎn)出是一致的
其核心原則就是:針對那些可能導(dǎo)致差異的因素,我們都收錄到工程中,讓 git 倉庫記錄,從而實現(xiàn)同樣,因此,現(xiàn)在流行的腳手架,如 umi taro,都將 構(gòu)建能力 local 化到本地工程中,后續(xù)會做詳細闡明
4 腳手架的三類包
一個被實踐檢驗,能夠符合上述需求的腳手架架構(gòu),其實非常簡單,首先我們拆分成三類 npm 包:
包 | 功能 | 安裝位置 | 備注 |
---|---|---|---|
全局命令包 | 就像一個大腦,負責(zé)響應(yīng)全局命令,并進行調(diào)度 | 全局包路徑 | global 安裝,提供全局命令 |
模板插件包 | 初始化工程所拷貝的模板 | 某個約定路徑,如 ~/.maoda |
模板可隨業(yè)務(wù)擴展 |
構(gòu)建插件包 | 提供構(gòu)建(webpack)能力 | 工程內(nèi) (目前主流腳手架都改用此方案) | 不同模板可使用同一構(gòu)建包,也可不同 |
注:構(gòu)建插件包,早期很多腳手架都把它放在工程外,比如放在全局,優(yōu)勢是多工程可復(fù)用一套 webpack 能力,但弊端也暴露出來,即在多人協(xié)同開發(fā)的項目中,由于構(gòu)建插件包不在工程里沒能被 git 倉庫收錄,導(dǎo)致一些不可預(yù)期的差異結(jié)果。
其調(diào)度關(guān)系如下:
5 全局命令包
前面說了一通理論,下面開始正式搭建
全局命令包的功能:負責(zé)接收全局命令,并調(diào)度。
比如我做的 cli 的模板 demo cli-tpl
npm i cli-tpl -g # 或 yarn global add cli-tpl
全局安裝后,暴露出一個 dcli 命令 (自己隨便取的名字),該命令有以下典型功能:
暴露全局命令通過 package.json 中 bin 來指定,可參考我的 demo
命令 | 效果 |
---|---|
dcli install [pkgName] |
安裝一個「模板插件包」到 ~/.maoda 路徑,如果已經(jīng)安裝再執(zhí)行,則詢問更新到最新版,如安裝 dcli install gen-tpl |
dcli init |
以某個模板初始化一個新工程,執(zhí)行后會讓你從已裝模板里選擇 |
dcli build |
在工程根目錄執(zhí)行 (或?qū)戇M工程的 scripts 里),嘗試讀取工程依賴的「構(gòu)建插件包」并執(zhí)行構(gòu)建 |
dcli dev |
與 dcli build 類似,只不過是執(zhí)行調(diào)試 |
5.1 cli 開發(fā)中值得收藏的一些第三方調(diào)料包
重要性 | 包名稱 | 功能 |
---|---|---|
必要 | minimist | 解析用戶命令,將 process.argv 解析成對象 |
必要 | fs-extra | 對 fs 庫的擴展,支持 promise |
必要 | chalk | 讓你 console.log 出來的字帶顏色,比如成功時的綠色字 |
必要 | import-from | 類似 require,但支持指定目錄,讓你可以跨工程目錄進行 require,比如全局包想引用工程路徑下的內(nèi)容 |
必要 | resolve-from | 同上,只不過是 require.resolve |
必要 | inquirer | 詢問用戶并記錄反饋結(jié)果,界面互動的神器 |
必要 | yeoman-environment | 【核心】用于執(zhí)行一個「模板插件包」,后文詳細描述 |
錦上添花 | easy-table | 類似 console.table,輸出漂亮的表格 |
錦上添花 | ora | 提供 loading 菊花 |
錦上添花 | semver | 提供版本比較 |
錦上添花 | figlet | console.log出一個漂亮的大logo |
錦上添花 | cross-spawn | 跨平臺的child_process (跨 Windows/Mac) |
錦上添花 | osenv | 跨平臺的系統(tǒng)信息 |
錦上添花 | open | 跨平臺打開 app,比如調(diào)試的時候開打 chrome |
5.2 命令解析與分發(fā)
命令的解析與分發(fā),是「全局命令包」的核心功能,其過程比較簡單。大家也可以直接看倉庫cli-tpl (全部功能壓縮到大約300行代碼)
cli 版本更新判斷:
解析用戶命令
處理命令
接下來我們看下 4 個核心命令,主要是:
命令 | 效果 |
---|---|
install | 幫用戶安裝/升級一個「模板插件包」 |
init | 幫用戶初始化一個工程,并拷貝模板 |
build | 調(diào)用工程中的「構(gòu)建插件包」,幫用戶webpack構(gòu)建 |
dev | 幫用戶啟動 devServer 進行調(diào)試 |
下面逐一闡述每個命令的實現(xiàn)過程以及效果:
5.3 install命令:安裝一個「模板插件包」
install 意思就是把這個模板插件包下載到硬盤;此處我做了一個最小功能的 demo 包gen-tpl (后文詳細分解) 來輔助講解
dcli install gen-tpl
核心處理流程如下:
先判斷是否硬盤緩存目錄 ~/.maoda 下是否已經(jīng)有安裝過 gen-tpl 包
安裝過程即 execSync('npm i gen-tpl@latest -S', { cwd: '~/.maoda' })
我們可以為「模板插件包」的名稱做一個約定,即具備固定的前綴,諸如 gen-xxx
5.4 init命令: 選一個「模板插件包」來初始化一個新工程
這是一個腳手架高頻而核心的功能
dcli init
此時會分發(fā)去執(zhí)行 script/init.js 文件,我們看看其邏輯
查詢硬盤緩存目錄 ~/.maoda 下的 package.json 文件,讀取其中 dependacies 字段,拿到已安裝的「模板插件包」
讓用戶選擇一套模板
觸發(fā)模板初始化流程
「模板插件包」被執(zhí)行,則啟動了常規(guī)的模板拷貝過程 (后面展開細說)
這里直接用包名稱做選項,為了演示更直觀,實際通常用包的 description 做選項,更友好一些,比如 gen-pc 包可能描述為 生成PC模板
5.5 build命令:在工程里執(zhí)行構(gòu)建
dcli build
確定工程目錄
讀取該工程所用的構(gòu)建插件
使用制定的構(gòu)建插件包來進行 webpack 打包
通常,我們用配置文件指明「構(gòu)建插件包」,也可以直接在命令里指明,比如 dcli build --builder=build-h6;后者往往適用于一套代碼打包出多種結(jié)果,如京東的 Taro cli
平時大家用慣了 npm run build yarn build,只需在我們的模板中的 package.json 添加一行:
{ "script": { ++ "build": "dcli build" } }
5.6 dev命令:啟動 devServer 進行調(diào)試
類似 build 只不過 webpack 配置不同,此處略
6 模板插件包
核心功能:提供模板文件夾 + 文件夾的拷貝。這里同樣提供了一個樣例工程 gen-tpl (僅 50 行代碼)
處理流程如下:
詢問用戶,并獲取反饋的答案
根據(jù)用戶的答案,拷貝對應(yīng)的模板,細分兩種拷貝
在工程中執(zhí)行 npm 依賴的安裝
【重點來了】看似流程蠻多,其實只用一個現(xiàn)成的輪子即可搞定,即yeoman-generator,它幫我們把這些過程都封裝好了,我們只需繼承基類,并寫幾個預(yù)設(shè)的生命周期函數(shù)即可,無腦到令人發(fā)指 (細節(jié)處理,可參考模板倉庫)
module.exports = class extends Generator { // 【問詢環(huán)節(jié)】 prompting() { return this.prompt([ { type: 'input', name: 'appName', message: '請輸入項目名稱:', }, { type: 'list', choices: ['Javascript', 'TypeScript'], name: 'language', message: '請選擇項目語言', default: 'TypeScript', }, ]).then(answers => { this.answers = answers }) } // 【模板拷貝】 writing() { // 從模板路徑拷貝到工程路徑 this.fs.copy(this.templatePath(), this.destinationPath()) } // 【安裝依賴】 install() { this.installDependencies() } end() { this.log('happy coding!') } }
很明顯,「模板插件包」導(dǎo)出的是一個 class,我們需要通過上文提到的「全局命令包」里的 yeoman-environment 來啟動:
// 【節(jié)選自 全局命令包 init 命令,略修改以增加可讀性】 yoemanEnv.register(resolveFrom('./maoda', 'gen-tpl'), 'gen-tpl') yoemanEnv.run('gen-tpl', (e, d) => { d && this.console('happy coding', 'green') })
這里同樣用到前文提到的 resolve-from 包,進行跨目錄的引用解析
yeoman 是一個比較完善的生態(tài),模板插件包可用 yeoman 提供的全局命令 yo 來創(chuàng)建,但并非必要,此處就不展開說了
7 構(gòu)建插件包
同樣我們提供了一個構(gòu)建插件包的模板build-tpl (20行代碼,啟動 webpack),webpack 配置都是空的,大家在開發(fā)過程中可自行定制
構(gòu)建插件包其實核心就是 webpack 能力,webpack 能力這里就不展開說了,這里只描述一下調(diào)用關(guān)系
以 dcli build 為例,「全局命令包」在收到 build 命令后,啟動「構(gòu)建插件包」
importFrom(process.cwd(), 'build-tpl')
沒錯,就是這么簡單,import-from 庫能跨文件目錄,指定使用特定目錄的文件;使得全局包可以直接去執(zhí)行工程目錄的包 效果與同工程下 require('build-tpl') 一樣
此處也可以使用 import-cwd 庫
而 build-tpl 這個構(gòu)建插件包,負責(zé)將內(nèi)置的 webpack.config.js 與用戶工程下自定義的 webpackCustom 進行 merge,然后執(zhí)行 webpack 流程
當然,構(gòu)建工具不一定非要使用 webpack,比如可以選擇 rollup 或者像 Taro 在構(gòu)建小程序代碼時候,自己創(chuàng)建一套工具
8 寫在最后的話
筆者認為,只有夠精簡,才能降低入門門檻,才能強化記憶;因此,本文的案例,在成熟的腳手架上進行不斷刪減,剔除掉哪些徒增記憶負擔的部分,只保留精髓和核心,旨在快速在腦海里建模出一個企業(yè)級腳手架
同時提供了腳手架 3 個組成部分的 倉庫/npm 包,以增加可操作性
如需引用與實際開發(fā)中,我們需要繼續(xù)豐滿其血肉,包括但不限于:
文章博客地址:https://github.com/imaoda/js-front-end-practice 歡迎批評指正
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。