溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

手把手15分鐘搭一個企業(yè)級腳手架

發(fā)布時間:2020-10-02 03:57:02 來源:腳本之家 閱讀:136 作者:maodayeyeye 欄目:web開發(fā)

1 寫在前面的話

搭一個腳手架,考驗了你的 nodejs 水平、工程化能力、以及工具服務(wù)的設(shè)計能力,是前端進階不可或缺的過程

筆者在開發(fā) cli 的過程中,調(diào)研流行的 cli 并形成最佳實踐,本文旨在用最短的篇幅實現(xiàn)主要功能,揭露核心原理,同時提供 demo 倉庫與大家學(xué)習(xí)探討。

通篇閱讀大約需要 10 分鐘,基于本教程自己擼一個 cli 大約需要花費 15 分鐘

2 腳手架的雛形

其實腳手架的初衷,就是提供一個最佳實踐的基礎(chǔ)模板,因此模板拷貝是其核心功能

幾年前我曾寫過一個極簡的腳手架,大該干了這么一件事兒

  1. npm publish 一個全局安裝的包
  2. 執(zhí)行命令時,wget 我云服務(wù)上的一個壓縮包,并在當前文件夾下解壓

一個命令,就可以把我預(yù)設(shè)的完整的工程目錄創(chuàng)建好,特別方便效率。

我想,這應(yīng)該算是一個雛形腳手架吧

3 腳手架需要考慮的

上面雛形腳手架可以很好的服務(wù)于個人需求,但是畢竟過于干癟和簡陋,要想成為被大家廣泛接受的工具,還需要完善。

大家熟知的 vue-cli create-react-app @tarojs/cli umi 最基本功能:首先提出一些列問題選項,然后為你的新建項目提供一份模板并安裝依賴,再提供調(diào)試構(gòu)建命令

沒錯,最核心的部分就是這個思路;但如果要做成一個可伸縮的、用戶友好的,還需考慮這些需求:

  • 模板支持版本管理
  • 支持擴展新模板
  • 自動檢測版本更新
  • 根據(jù)用戶選擇,生成個性化模板
  • 友好的UI界面
  • 構(gòu)建功能獨立,可因模板而異 (如區(qū)分H5/PC/weapp/RN)
  • 多人合作項目,能確保構(gòu)建結(jié)果一致

看起來信息量有點大,但其實都并不晦澀,我們一一說明一下意圖

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)一中支持差異,可通過問詢用戶,來提供差異化支持,比如:

手把手15分鐘搭一個企業(yè)級腳手架

這些問詢的結(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)系如下:

手把手15分鐘搭一個企業(yè)級腳手架

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 版本更新判斷:

  1. 先獲取本 package.json 中的 version
  2. 再通過 npm view cli-tpl version 命令查詢當前 npm 庫最新版本
  3. 兩者比較得出結(jié)論,提醒用戶更新

解析用戶命令

  • 通過 process.argv[2] 獲取到用戶執(zhí)行的實際命令,比如 dcli install 可拿到 install (正式版推薦使用 minimist 解析參數(shù))

處理命令

  • 比如 install 命令,則通過 require 動態(tài)映射 install.js 文件來處理該邏輯
  • 注:require 支持動態(tài)名稱,如 require('./scripts/' + command) 這樣,如果 command 是 install 則映射執(zhí)行 script/install.js 文件

接下來我們看下 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

手把手15分鐘搭一個企業(yè)級腳手架

核心處理流程如下:

先判斷是否硬盤緩存目錄 ~/.maoda 下是否已經(jīng)有安裝過 gen-tpl 包

  1. 如果沒有,則接下來進行安裝 (相當于在 ~/.maoda 目錄下執(zhí)行 npm install)
  2. 如果有,且版本低,則提示升級
  3. 如果有,且版本最新,則不作為

安裝過程即 execSync('npm i gen-tpl@latest -S', { cwd: '~/.maoda' })

我們可以為「模板插件包」的名稱做一個約定,即具備固定的前綴,諸如 gen-xxx

5.4 init命令: 選一個「模板插件包」來初始化一個新工程

這是一個腳手架高頻而核心的功能

dcli init

手把手15分鐘搭一個企業(yè)級腳手架

此時會分發(fā)去執(zhí)行 script/init.js 文件,我們看看其邏輯

查詢硬盤緩存目錄 ~/.maoda 下的 package.json 文件,讀取其中 dependacies 字段,拿到已安裝的「模板插件包」

  • 如果一個都沒安裝,則提示用戶要先 install

讓用戶選擇一套模板

  • 利用 inquery 庫發(fā)起對話,羅列出已裝模板,讓用戶選擇,比如上圖的 gen-pc gen-h6 gen-tpl

觸發(fā)模板初始化流程

  • 比如用戶選擇了 gen-tpl 這個模板,則用 yeoman-environment 這個庫去執(zhí)行緩存目錄里的這個包 ~/.maoda/gen-tpl/index.js
  • 注:這里相當于跨目錄的兩個 js 文件引用執(zhí)行,用到了之前說的 import-from 這個庫

「模板插件包」被執(zhí)行,則啟動了常規(guī)的模板拷貝過程 (后面展開細說)

這里直接用包名稱做選項,為了演示更直觀,實際通常用包的 description 做選項,更友好一些,比如 gen-pc 包可能描述為 生成PC模板

5.5 build命令:在工程里執(zhí)行構(gòu)建

dcli build

手把手15分鐘搭一個企業(yè)級腳手架

確定工程目錄

  • 工程目錄即執(zhí)行目錄,通過 process.cwd() 獲取

讀取該工程所用的構(gòu)建插件

  • 讀取工程中約定的配置文件,本demo中為 maoda.js (采用約定式的配置,類似 webpack.config.js .babelrc .prettierrc)
  • 讀取 maoda.js 中 builder 配置項 (即指定的構(gòu)建插件包),比如本 demo 中指定為 build-tpl
  • 如果有的話,讀取自定義 webpack 配置 (約定為 webpackCustom 字段,后續(xù)會被合并/覆蓋到默認 webpack 配置上)

使用制定的構(gòu)建插件包來進行 webpack 打包

  • 判斷工程中是否已經(jīng)安裝 build-tpl
  • 未安裝,則在工程中路徑中執(zhí)行 npm install (或 yarn add,此處有個小技巧,可根據(jù)用戶工程中 lock 文件的類型,判斷用戶使用的 npm 還是 yarn)
  • 已安裝,則直接執(zhí)行 build-tpl

通常,我們用配置文件指明「構(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 行代碼)

處理流程如下:

詢問用戶,并獲取反饋的答案

  • 比如工程名是什么,描述一下你的工程,是否使用 TypeScript,是否使用 Sass/Less/Stylus 等

根據(jù)用戶的答案,拷貝對應(yīng)的模板,細分兩種拷貝

  • 直接拷貝,直接把模板插件包里的文件夾/文件,拷貝到用戶工程目錄
  • 填充模板拷貝,將用戶答案,填充到文檔的對應(yīng)位置,類似 WebpackHTMLPlugin、ejs,如將 name: <%= packageName %> 填充成 name: 我的工程

在工程中執(zhí)行 npm 依賴的安裝

手把手15分鐘搭一個企業(yè)級腳手架

【重點來了】看似流程蠻多,其實只用一個現(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 流程

 手把手15分鐘搭一個企業(yè)級腳手架

當然,構(gòu)建工具不一定非要使用 webpack,比如可以選擇 rollup 或者像 Taro 在構(gòu)建小程序代碼時候,自己創(chuàng)建一套工具

8 寫在最后的話

筆者認為,只有夠精簡,才能降低入門門檻,才能強化記憶;因此,本文的案例,在成熟的腳手架上進行不斷刪減,剔除掉哪些徒增記憶負擔的部分,只保留精髓和核心,旨在快速在腦海里建模出一個企業(yè)級腳手架
同時提供了腳手架 3 個組成部分的 倉庫/npm 包,以增加可操作性
如需引用與實際開發(fā)中,我們需要繼續(xù)豐滿其血肉,包括但不限于:

  1. 異常處理 (如一些邊界情況)
  2. webpack 配置部分需完善 (本 demo 中 webpack.config 是空的)
  3. UI 和提升語可更友好
  4. 根據(jù)業(yè)務(wù)需求,擴展額外的命令,比如卸載包,發(fā)布cdn

文章博客地址:https://github.com/imaoda/js-front-end-practice 歡迎批評指正

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。

向AI問一下細節(jié)

免責(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)容。

AI