<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 7580字,需瀏覽 16分鐘

           ·

          2020-10-29 16:17


          ? ?戳藍字「魚頭的Web海洋」關(guān)注我們哦!

          引言

          下面是一個使用腳手架來初始化項目的典型例子。

          隨著前端工程化的理念不斷深入,越來越多的人選擇使用腳手架來從零到一搭建自己的項目。其中大家最熟悉的就是create-react-appvue-cli,它們可以幫助我們初始化配置、生成項目結(jié)構(gòu)、自動安裝依賴,最后我們一行指令即可運行項目開始開發(fā),或者進行項目構(gòu)建(build)。

          這些腳手架提供的都是普遍意義上的最佳實踐,但是我在開發(fā)中發(fā)現(xiàn),隨著業(yè)務(wù)的不斷發(fā)展,必然會出現(xiàn)需要針對業(yè)務(wù)開發(fā)的實際情況來進行調(diào)整。例如:

          • 通過調(diào)整插件與配置實現(xiàn) Webpack 打包性能優(yōu)化后

          • 刪除腳手架構(gòu)建出來的部分功能

          • 項目架構(gòu)調(diào)整

          • 融合公司開發(fā)工具

          • ……

          總而言之,隨著業(yè)務(wù)發(fā)展,我們往往會沉淀出一套更“個性化”的業(yè)務(wù)方案。這時候我們最直接的做法就是開發(fā)出一個該方案的腳手架來,以便今后能復用這些最佳實踐與方案。

          1. 腳手架怎么工作?

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

          • 初始化,一般在這個時候會進行環(huán)境的初始化,做一些前置的檢查

          • 用戶輸入,例如用 vue-cli 的時候,它會“問”你很多配置選項

          • 生成配置文件

          • 生成項目結(jié)構(gòu),這是候可能會使用一個項目模版

          • 安裝依賴

          • 清理、校驗等收尾工作

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

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

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

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

          將腳手架開發(fā)類比為前端組件開發(fā),Yeoman 的角色就像是 React,是一個框架,尤其是定義了組件的生命周期函數(shù);而 generator 類似于你寫的一個 React 業(yè)務(wù)組件,根據(jù) React 的規(guī)則在各個生命周期中填代碼即可。

          Yeoman 內(nèi)置的“生命周期”方法執(zhí)行順序如下:

          1. initializing

          2. prompting

          3. default

          4. writing

          5. conflicts

          6. install

          7. end

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

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

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

          2. 開發(fā)一個自己的腳手架

          了解了一些腳手架的工作方式與 Yeoman 的基本概念,咱們就可以來創(chuàng)建一個屬于自己的腳手架。作為例子,這個腳手架的功能很簡單,它會為我們創(chuàng)建一個最簡版的基于 Webpack 的前端項目。最終腳手架使用效果如下:


          2.1. 準備一個項目模版

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

          第一個是模版內(nèi)變量的填充。

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

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

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

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

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

          回到我們「創(chuàng)建一個最簡版的基于 Webpack 的前端項目」的目標,我準備了一個項目模版,之后就會用它來作為腳手架生成的項目內(nèi)容。

          2.2. 創(chuàng)建 generator(yeoman-generator)

          創(chuàng)建 Yeoman 的 generator 需要遵循它的規(guī)則。

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

          這樣,當你通過

          npm?i?-g?yo

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

          yo?webpack-kickoff

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

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

          此外,你創(chuàng)建的 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 中的一個同名方法,你需要的就是在子類中重寫后所需的對應(yīng)方法。default階段則會執(zhí)行用戶定義的類方法。

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

          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 類中填充各種方法的實現(xiàn)細節(jié)了。

          2.3. 處理用戶交互

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

          可以使用 Inquirer.js 來實現(xiàn)。而 Yeoman 已經(jīng)幫我們集成好了,直接在 generator 里調(diào)用 this.prompt 即可。

          在用戶交互部分的需求也比較簡單,只需要詢問用戶所需創(chuàng)建的項目目錄名即可,隨后也會作為項目名。按照 Yeoman 的流程規(guī)范,我們將該部分代碼寫在 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();
          ????????});
          ????}
          ????//?……
          }

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

          2.4. 下載模版

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

          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()方法,該方法主要用于獲取路徑。不傳參時返回當前命令行運行的目錄;如果收到多個參數(shù),則會進行路徑的拼接。

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

          2.5. 模版文件拷貝

          項目模版下載完畢后,下面就可以將相關(guān)的目錄、文件拷貝到目標文件夾中。這些都可以在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()方法,它支持文件拷貝,同時還可以指定相應(yīng)的模版參數(shù),此外,如果出現(xiàn)重名覆蓋情況會在控制臺自動輸出相應(yīng)信息。

          最后,把下載與拷貝整合起來即可完成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. 依賴安裝

          到目前,腳手架已經(jīng)可以幫我們把項目開發(fā)所需的配置、目錄結(jié)構(gòu)、依賴清單都準備好了。這時候可以進一步幫開發(fā)人員將依賴安裝完畢,這樣腳手架創(chuàng)建項目完成后,開發(fā)人員就可以直接開發(fā)了。

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

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

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

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

          3. 使用腳手架 ?

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

          1. 直接發(fā)布到 npm,然后正常全局安裝

          2. 直接手動拷貝到全局 node_modules

          3. 使用npm link將某個目錄鏈接到全局

          依據(jù)2.2.節(jié)的內(nèi)容,咱們的 generator 名稱為 generator-webpack-kickoff。由于我的包已經(jīng)發(fā)到 npm 上了,所以要使用該腳手架可以運行如下指令:

          #?安裝一次即可
          npm?i?-g?yo
          npm?i?-g?generator-webpack-kickoff

          #?啟動腳手架
          yo?webpack-kickoff

          4. 優(yōu)化

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

          • 項目目錄的重名檢測,生成項目時,檢查是否目錄已存在,并提示警告

          • 項目模版的緩存。雖然我們使用 github 托管方式,但也可以考慮不必每次都重新下載,可以放一份本地緩存,然后每天或每周更新;

          • CLI 的優(yōu)化。完整版里還會包含一些更豐富的 CLI 使用,例如我們在動圖中看到的 loading 效果、頭尾顯示的信息面板等。這些工具包括

            • ora,用于創(chuàng)建 spinner,也就是上面所說的 loading 效果

            • chalk,用于打印彩色的信息

            • update-notifier,用于檢查包的線上版本與本地版本

            • beeper,可以“嗶”一下你,例如出錯的時候

            • boxen,創(chuàng)建頭尾的那個小“面板”

          • 版本檢查。上面提到可以用 update-notifier 來檢查版本。所以可以在 initializing 階段進行版本檢查,提示用戶更新腳手架。

          最后

          如果你喜歡探討技術(shù),或者對本文有任何的意見或建議,非常歡迎加魚頭微信好友一起探討,當然,魚頭也非常希望能跟你一起聊生活,聊愛好,談天說地。魚頭的微信號是:krisChans95 也可以掃碼關(guān)注公眾號,訂閱更多精彩內(nèi)容。


          瀏覽 83
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美日韩视频在线 | 久久精品国产99久久不卡 | 国产午夜精品久久久久久久 | 欧美黄色成人影片下载大全 | 91天天综合网 |