帶你揭開(kāi)前端自動(dòng)化構(gòu)建的神秘面紗
- 本文已獲得原作者的獨(dú)家授權(quán),有想轉(zhuǎn)載的朋友們可以在后臺(tái)聯(lián)系我申請(qǐng)開(kāi)白哦!
- PS:歡迎掘友們向我投稿哦,被采用的文章還可以送你掘金精美周邊!
摸魚(yú)醬的文章聲明:內(nèi)容保證原創(chuàng),純技術(shù)干貨分享交流,不打廣告不吹牛逼。
前言:對(duì)于我們這些日常基于腳手架項(xiàng)目開(kāi)發(fā),使用yarn/npm run start、yarn/npm run build等命令完成自動(dòng)化構(gòu)建的開(kāi)發(fā)者來(lái)說(shuō),重要的自動(dòng)化構(gòu)建仿佛變成了前端的一個(gè)黑盒知識(shí)。但是,掌握前端工程的自動(dòng)化構(gòu)建,是學(xué)習(xí)前端工程化以及進(jìn)階高級(jí)前端所必不可缺少的部分。
通過(guò)這篇文章,我嘗試把我自己對(duì)自動(dòng)化構(gòu)建知識(shí)體系的系統(tǒng)化認(rèn)識(shí)托盤而出,希望能夠?qū)﹂w下有所幫助。同樣,我更喜歡的是您能批判我的一些觀點(diǎn)或者指出我的一些問(wèn)題,因?yàn)橹已阅娑谛小?/p>
注意:文章的側(cè)重點(diǎn)是對(duì)自動(dòng)化構(gòu)建知識(shí)的系統(tǒng)化探討,不會(huì)是對(duì)某一個(gè)具體工具使用上的面面俱到,畢竟那是官方文檔該做的事情。
對(duì)于自動(dòng)化構(gòu)建知識(shí)體系的系統(tǒng)化認(rèn)識(shí),從個(gè)人認(rèn)識(shí)出發(fā),我用腦圖做了以下整理:

接下來(lái)的行文,我都會(huì)圍繞這副腦圖展開(kāi),如果您有興趣繼續(xù)往下看下去,我希望您能在這幅圖上停留多一些時(shí)間。
好地,按照上述腦圖中的邏輯,接下來(lái)我會(huì)分成以下幾個(gè)部分來(lái)展開(kāi)探討本文。
- 理解前端工程的自動(dòng)化構(gòu)建
- 實(shí)現(xiàn)前端工程的自動(dòng)化構(gòu)建:npm script方式
- 實(shí)現(xiàn)前端工程的自動(dòng)化構(gòu)建:gulp方式
- 其它方式實(shí)現(xiàn)前端工程的自動(dòng)化構(gòu)建
好的,理清楚行文思路之后,進(jìn)入第一點(diǎn),理解前端工程的自動(dòng)化構(gòu)建。
一:理解前端工程的自動(dòng)化構(gòu)建
1.先理解一下為什么會(huì)出現(xiàn)這玩意
簡(jiǎn)單來(lái)說(shuō),隨著前端需求和項(xiàng)目的日益復(fù)雜,出于提高開(kāi)發(fā)效率、用戶體驗(yàn)以及其它工程上的需要,我們通常會(huì)借助很多更高階的語(yǔ)法(如es6、ts、less等)或者服務(wù)(如web server)等來(lái)幫助我們更快更好的開(kāi)發(fā)、調(diào)試、增強(qiáng)一個(gè)前端工程。但是,這會(huì)導(dǎo)致一個(gè)問(wèn)題,就是我們寫的代碼會(huì)離瀏覽器或node可解析運(yùn)行的代碼越來(lái)越遠(yuǎn)。
為了解決這個(gè)問(wèn)題,前端工程構(gòu)建的概念就逐漸豐富完整了起來(lái)。也就是說(shuō),前端工程構(gòu)建就是指前端項(xiàng)目從源代碼到一個(gè)能按需運(yùn)行(開(kāi)發(fā)環(huán)境、生產(chǎn)環(huán)境)的前端工程所需要做的所有事情。由于前端工程的構(gòu)建過(guò)程中會(huì)包含很多任務(wù)并且工程需要頻繁構(gòu)建,所以按照任何簡(jiǎn)單機(jī)械的重復(fù)勞動(dòng)都應(yīng)該讓機(jī)器去完成的思想,我們應(yīng)該自動(dòng)化去完成工程的構(gòu)建,提高構(gòu)建效率。
2.從這玩意的具體實(shí)踐角度來(lái)再理解一次
前面提到了前端工程自動(dòng)化構(gòu)建的本質(zhì),但是這個(gè)理解離我們具體實(shí)踐它還是有點(diǎn)遠(yuǎn),下面再說(shuō)說(shuō)我對(duì)自動(dòng)化構(gòu)建實(shí)踐上的理解吧。
我認(rèn)為,前端工程構(gòu)建的具體實(shí)踐形式就是一個(gè)任務(wù)流,完成了這個(gè)任務(wù)流中的所有任務(wù)即完成了前端工程構(gòu)建。而自動(dòng)化構(gòu)建,也就是不用手動(dòng)的執(zhí)行這個(gè)任務(wù)流中的一個(gè)個(gè)任務(wù)。
好的,經(jīng)過(guò)上面兩點(diǎn)講解,我覺(jué)得我已經(jīng)把我對(duì)它的所有理解都已經(jīng)傾囊相授了。
為了引導(dǎo)接下來(lái)對(duì)自動(dòng)化構(gòu)建具體實(shí)現(xiàn)的講解,下面我們?cè)購(gòu)?strong>自動(dòng)化構(gòu)建就是完成一個(gè)任務(wù)流這個(gè)實(shí)踐角度理解來(lái)展開(kāi)細(xì)致探討,也就是以下這兩點(diǎn),即:
- 理解構(gòu)建任務(wù)流中的任務(wù)
- 理解構(gòu)建任務(wù)流
3.理解構(gòu)建任務(wù)流中的任務(wù)
對(duì)比于JavaScript的函數(shù),個(gè)人對(duì)任務(wù)是這么分類的:
- 單任務(wù):同步任務(wù)和異步任務(wù)
- 多任務(wù):并行任務(wù)、串行任務(wù)
同步任務(wù)和異步任務(wù)無(wú)須解釋,這里說(shuō)說(shuō)并行任務(wù)和串行任務(wù)。任務(wù)并行可以用于縮短多個(gè)任務(wù)的執(zhí)行時(shí)間。因?yàn)閚ode是單線程執(zhí)行的,我認(rèn)為它并不能縮短多個(gè)同步任務(wù)并行的執(zhí)行時(shí)間,但是構(gòu)建過(guò)程中的任務(wù)通常都會(huì)涉及到IO流,所以大部分任務(wù)都是異步任務(wù),IO任務(wù)并行可以避免IO阻塞線程執(zhí)行,進(jìn)而縮短多個(gè)任務(wù)的執(zhí)行時(shí)間。
而任務(wù)串行可以保證任務(wù)之間的有序執(zhí)行,比如在開(kāi)發(fā)環(huán)境下,我們肯定要先執(zhí)行編譯任務(wù),然后才能執(zhí)行啟動(dòng)開(kāi)發(fā)服務(wù)器的任務(wù)。
理解了構(gòu)建過(guò)程中的任務(wù)之后,下面再列舉一些日常開(kāi)發(fā)的構(gòu)建過(guò)程中,我們所常見(jiàn)到的任務(wù)。
前端構(gòu)建過(guò)程中的常見(jiàn)任務(wù)
| 任務(wù)名 | 任務(wù)職責(zé) |
|---|---|
| Eslint檢查 | 統(tǒng)一代碼風(fēng)格 |
| babel轉(zhuǎn)換 | ES6 -> ES5 |
| typescript轉(zhuǎn)換 | Ts -> Js |
| sass轉(zhuǎn)換 | sass -> css |
| less轉(zhuǎn)換 | less -> css |
| html、css、js壓縮 | .html -> .min.html、.css->.min.css、.js->.min.js |
| web server | 開(kāi)發(fā)服務(wù)器 |
| js、css兼容 | 兼容不同瀏覽器 |
| ... | ... |
除了上述表格中列舉的任務(wù)之外,在不同的項(xiàng)目不同的場(chǎng)景中還會(huì)有不同的構(gòu)建任務(wù),這里就不再一一贅述了。上面說(shuō)到構(gòu)建其實(shí)就是完成一個(gè)任務(wù)流,在理解和認(rèn)識(shí)了常見(jiàn)任務(wù)之后,接下來(lái)我們理解一下前端工程當(dāng)中的任務(wù)流。
4.理解構(gòu)建任務(wù)流
任務(wù)流的理解可不只是多個(gè)任務(wù)這么簡(jiǎn)單,任務(wù)流是要為目的服務(wù)的,就好比生產(chǎn)一個(gè)產(chǎn)品,必須完整跑完生產(chǎn)流水線一樣。所以我們這里得從構(gòu)建目的的角度來(lái)理解任務(wù)流。
前端構(gòu)建是為前端工程服務(wù)的,而前端工程又是為用戶服務(wù)的。對(duì)應(yīng)于開(kāi)發(fā)環(huán)境和生產(chǎn)環(huán)境,前端工程可以分為開(kāi)發(fā)環(huán)境工程和生產(chǎn)環(huán)境工程,其中開(kāi)發(fā)環(huán)境工程為開(kāi)發(fā)者服務(wù),生產(chǎn)環(huán)境工程為用戶服務(wù)。
滿足工程使用者的需求是我們構(gòu)建工程的終極目的,所以有必要投其所好,根據(jù)工程的使用者不同,完成他所需要的的一連串任務(wù),也就是任務(wù)流。這時(shí)可以根據(jù)構(gòu)建后工程的目標(biāo)使用者來(lái)劃分,把任務(wù)流分為開(kāi)發(fā)環(huán)境構(gòu)建任務(wù)流和生產(chǎn)環(huán)境構(gòu)建任務(wù)流兩種。這兩點(diǎn)認(rèn)識(shí)很重要,所以我們把它兩單獨(dú)拎出來(lái)講解。
我們先來(lái)理解一下開(kāi)發(fā)環(huán)境的構(gòu)建任務(wù)流。
5.理解開(kāi)發(fā)環(huán)境的構(gòu)建任務(wù)流
開(kāi)發(fā)環(huán)境構(gòu)建任務(wù)流構(gòu)建后的工程是為開(kāi)發(fā)者服務(wù)的。開(kāi)發(fā)者需要開(kāi)發(fā)調(diào)試代碼,所以開(kāi)發(fā)環(huán)境任務(wù)流構(gòu)建的工程需要實(shí)現(xiàn)以下功能:
| 功能項(xiàng) | 包含任務(wù) |
|---|---|
| 語(yǔ)法檢查 | Eslint |
| 語(yǔ)法轉(zhuǎn)換 | ES6 -> ES5、Sass -> less、Ts->Js等等 |
| 模擬生產(chǎn)環(huán)境 | web開(kāi)發(fā)服務(wù)器:devServer |
| 易于調(diào)試 | sourceMap |
| ... | ... |
開(kāi)發(fā)者需要不斷修改代碼查看效果,所以除了滿足功能之外,還需要加快構(gòu)建速度并且自動(dòng)刷新,以保證良好的使用體驗(yàn)。
| 優(yōu)化方式 | 實(shí)現(xiàn)方案 |
|---|---|
| 加快構(gòu)建速度 | devServer熱模塊替換 |
| 自動(dòng)刷新 | devServer 監(jiān)聽(tīng)源代碼 |
| ... | ... |
關(guān)于web開(kāi)發(fā)服務(wù)器devServer
使用web開(kāi)發(fā)服務(wù)器可以模擬像使用nginx、tomcat等服務(wù)器軟件一樣的線上環(huán)境,它在功能以及配置上都與nginx以及tomcat類似, 最簡(jiǎn)單的配置就是指明資源路徑baseUrl以及服務(wù)啟動(dòng)ip和端口port即可。在開(kāi)發(fā)環(huán)境啟動(dòng)本地服務(wù)時(shí),配置代理可以在符合同源策略的情況下解決跨域問(wèn)題。
開(kāi)發(fā)服務(wù)器除了可以模擬線上環(huán)境之外,更加強(qiáng)大的一點(diǎn)是它可以監(jiān)聽(tīng)源代碼,實(shí)現(xiàn)熱部署和自動(dòng)刷新功能!
好的,下面我們?cè)賮?lái)理解一下生產(chǎn)環(huán)境的構(gòu)建任務(wù)流。
5.理解生產(chǎn)環(huán)境的構(gòu)建任務(wù)流
生產(chǎn)環(huán)境構(gòu)建任務(wù)流構(gòu)建后的工程是為用戶服務(wù)的。與開(kāi)發(fā)環(huán)境相比,它也需要語(yǔ)法檢查以及編譯功能,但不需要考慮修改以及調(diào)試代碼的問(wèn)題,它關(guān)注的是瀏覽器兼容以及運(yùn)行速度等問(wèn)題。
| 功能項(xiàng) | 包含任務(wù) |
|---|---|
| 語(yǔ)法檢查 | Eslint |
| 語(yǔ)法轉(zhuǎn)換 | ES6 -> ES5、Sass -> less、Ts->Js等等 |
| 語(yǔ)法兼容 | 不同瀏覽器的js、css語(yǔ)法兼容 |
| 下載速度 | 資源壓縮與合并 |
| ... | ... |
生產(chǎn)環(huán)境的優(yōu)化除了資源的下載速度之外,還可以從很多方面入手,下面是其中的一些方面以及實(shí)現(xiàn)方案。
| 優(yōu)化方面 | 實(shí)現(xiàn)方式 |
|---|---|
| 下載優(yōu)化 | treeshaking、代碼分割、懶加載 |
| 運(yùn)行優(yōu)化 | 代碼上優(yōu)化性能 |
| 離線訪問(wèn) | pwa技術(shù) |
| ... | ... |
終于把任務(wù)以及任務(wù)流淺顯粗陋的講完了,接下來(lái)我們先是使用npm scripts來(lái)實(shí)現(xiàn)簡(jiǎn)單項(xiàng)目的自動(dòng)化構(gòu)建,而后學(xué)習(xí)一下Gulp工具如何實(shí)現(xiàn)復(fù)雜項(xiàng)目的自動(dòng)化構(gòu)建。
抱歉,文章那么長(zhǎng),讓你看累了、倦了。但你要知道,我他喵的寫文章更累啊,堅(jiān)持一下,然后點(diǎn)個(gè)贊和關(guān)注吧( ̄▽ ̄)~■干杯□~( ̄▽ ̄)。
二:實(shí)現(xiàn)前端工程的自動(dòng)化構(gòu)建:npm script方式
前面說(shuō)到,完成前端工程構(gòu)建也就是完成任務(wù)流。任務(wù)流由任務(wù)組成,而任務(wù)又由腳本代碼實(shí)現(xiàn)。
對(duì)于任務(wù)的調(diào)用,我們?cè)诙x好任務(wù)腳本或者安裝好所需的cli模塊之后,我們只需在package.json的scripts選項(xiàng)中配置一條script,就可以方便地調(diào)用任務(wù)腳本或者cli模塊。
cli模塊提供了腳本命令,可以使用npm/npx/yarn運(yùn)行該模塊所提供的腳本。
這里不得不提一下node_modules/.bin文件夾,我們?cè)陧?xiàng)目中安裝的cli模塊都會(huì)有一個(gè)cmd文件出現(xiàn)在這里。當(dāng)我們?cè)陧?xiàng)目中需要調(diào)用這些cli模塊時(shí),只需yarn/npx cli模塊名的方式就可以很方便的調(diào)用這些cli模塊。
對(duì)于任務(wù)流的調(diào)用,我們則可以借助一些可以幫助任務(wù)組合(并行和串行)的庫(kù),而后在npm script中配置一條組合任務(wù),調(diào)用它以啟動(dòng)構(gòu)建任務(wù)流。
好的,通過(guò)上面的分析之后,我們接下來(lái)展開(kāi)講述一下npm scripts如何實(shí)現(xiàn)任務(wù)以及任務(wù)流的構(gòu)建。
1.單任務(wù)注冊(cè)調(diào)用示例
下面是sass轉(zhuǎn)換和ES6轉(zhuǎn)換的兩個(gè)單任務(wù)示例:
- 注冊(cè)任務(wù):package.json中配置script
"scripts": {
"sass": "sass scss/main.scss css/style.css",
"es6": "babel es6 --out-dir es5",
},
"devDependencies": {
"@babel/cli": "^7.12.8",
"@babel/core": "^7.12.9",
"sass": "^1.29.0"
}
- 調(diào)用任務(wù):
# sass轉(zhuǎn)換
yarn sass
# es6轉(zhuǎn)換
yarn es6
- 調(diào)用原理追溯
最基本的腳本調(diào)用也就是用某個(gè)命令(如node)去執(zhí)行一個(gè)文件,這里直接使用yarn就可以觸發(fā)script中的任務(wù)難免會(huì)讓人有點(diǎn)疑惑。下面是我為您整理的任務(wù)調(diào)用追溯,理解它有時(shí)候能幫助你定位解決一些問(wèn)題。
sass轉(zhuǎn)換任務(wù)追溯:yarn sass(觸發(fā)任務(wù)) -> sass scss/main.scss css/style.css -> node_modules/.bin/sass.cmd -> node_modules/sass/sass.js -> ...code
es6轉(zhuǎn)換任務(wù)追溯:yarn es6(觸發(fā)任務(wù)) -> babel es6 --out-dir es5 -> node_modules/.bin/babel.cmd -> node_modules/@babel\cli\bin\babel.js -> node_modules/@babel\cli\lib\index.js -> ...code
2.簡(jiǎn)單任務(wù)流構(gòu)建示例
這里需要再重申一點(diǎn),任務(wù)流不等同于任務(wù)組合,它與構(gòu)建目的有關(guān)。這里我們假設(shè)我們前端工程的構(gòu)建目的就只是sass轉(zhuǎn)換和es6轉(zhuǎn)換,那么我們以如下形式實(shí)現(xiàn)自動(dòng)化構(gòu)建。
- 注冊(cè)任務(wù):package.json中配置script
注意:對(duì)于任務(wù)流中的任務(wù)組合我們這里通過(guò)npm-run-all這個(gè)庫(kù)來(lái)幫助我們實(shí)現(xiàn),這個(gè)庫(kù)提供了兩個(gè)cmd文件,nun-p.cmd實(shí)現(xiàn)任務(wù)的并行,nun-s.cmd實(shí)現(xiàn)任務(wù)的串行。
"scripts": {
"sass": "sass scss/main.scss css/style.css",
"es6": "babel es6 --out-dir es5",
"build": "run-p sass es6"
},
"devDependencies": {
"npm-run-all": "^4.1.5",
"@babel/cli": "^7.12.8",
"@babel/core": "^7.12.9",
"sass": "^1.29.0"
}
- 調(diào)用任務(wù):
yarn build
3.具體工程構(gòu)建示例
下圖即是我們想要構(gòu)建的簡(jiǎn)單前端項(xiàng)目:

這個(gè)項(xiàng)目很簡(jiǎn)單,它只包含一個(gè)html文件,一個(gè)使用了ES6語(yǔ)法js文件以及一個(gè)使用了sass語(yǔ)法的樣式文件,接下來(lái)我們就用npm script來(lái)實(shí)現(xiàn)這個(gè)簡(jiǎn)單項(xiàng)目的自動(dòng)化構(gòu)建(也即開(kāi)發(fā)環(huán)境構(gòu)建任務(wù)流和生產(chǎn)環(huán)境構(gòu)建任務(wù)流)。
簡(jiǎn)單項(xiàng)目的自動(dòng)化構(gòu)建就是npm script實(shí)現(xiàn)自動(dòng)化構(gòu)建的使用場(chǎng)景。
與日常開(kāi)發(fā)一樣,我們這里也把這個(gè)工程構(gòu)建分為兩個(gè)部分,即開(kāi)發(fā)環(huán)境構(gòu)建和生產(chǎn)環(huán)境構(gòu)建。下面先講講開(kāi)發(fā)環(huán)境構(gòu)建的實(shí)現(xiàn):
(一):開(kāi)發(fā)環(huán)境構(gòu)建工程
通過(guò)上面我們對(duì)開(kāi)發(fā)環(huán)境構(gòu)建任務(wù)流的認(rèn)識(shí),我們先理一理在這個(gè)項(xiàng)目中,開(kāi)發(fā)環(huán)境任務(wù)流至少應(yīng)該包含哪些任務(wù):
- 需要web開(kāi)發(fā)服務(wù)器模擬生產(chǎn)環(huán)境以及實(shí)現(xiàn)源碼監(jiān)聽(tīng)和自動(dòng)刷新。
- 對(duì)于sass文件,需要實(shí)時(shí)sass轉(zhuǎn)換,并且監(jiān)聽(tīng)源碼變化重啟開(kāi)發(fā)服務(wù)器、刷新瀏覽器。
- 對(duì)于ES6文件,需要實(shí)時(shí)babel轉(zhuǎn)換,并且監(jiān)聽(tīng)源碼變化重啟開(kāi)發(fā)服務(wù)器、刷新瀏覽器。
- 對(duì)于html文件,需要監(jiān)聽(tīng)源碼變化重啟開(kāi)發(fā)服務(wù)器、刷新瀏覽器。
- ...
對(duì)于sass和ES6修改源代碼后的實(shí)時(shí)轉(zhuǎn)換,我們可以通過(guò)加上一個(gè)watch參數(shù)實(shí)現(xiàn)。而對(duì)于所有這些需要監(jiān)聽(tīng)變化的文件,我們則統(tǒng)一放入temp文件夾下(角色好比如nginx和Tomcat的應(yīng)用存放目錄),而后讓web開(kāi)發(fā)服務(wù)器監(jiān)聽(tīng)這個(gè)temp文件夾下所有文件的變化,一旦變化即重啟并刷新瀏覽器。
好的經(jīng)過(guò)上面任務(wù)分析之后,我們可能會(huì)把package.json的scripts以及devDependencies寫成如下樣子(核心關(guān)注scripts中的start命令):
"scripts": {
"sassDev": "sass scss/main.scss temp/css/style.css --watch",
"babelDev": "babel es6/script.js --out-dir temp/es5/script.js --watch",
"copyHtmlDev": "copyfiles index.html temp",
"serve": "browser-sync temp --files \"temp\"",
"start": "run-p sassDev babelDev copyHtmlDev serve"
},
"devDependencies": {
"@babel/cli": "^7.12.8",
"@babel/core": "^7.12.9",
"browser-sync": "^2.26.13",
"copyfiles": "^2.4.1",
"npm-run-all": "^4.1.5",
"sass": "^1.29.0"
}
(二):生產(chǎn)環(huán)境構(gòu)建工程
通過(guò)上面我們對(duì)生產(chǎn)環(huán)境構(gòu)建任務(wù)流的認(rèn)識(shí),我們先理一理在這個(gè)項(xiàng)目中,生產(chǎn)環(huán)境任務(wù)流應(yīng)該包含哪些任務(wù):
- 刪除上一次構(gòu)建結(jié)果任務(wù)
- 編譯任務(wù)
- html模板信息注入任務(wù)
- 文件壓縮任務(wù)
- ...
好的經(jīng)過(guò)上面任務(wù)分析之后,我們可能會(huì)把package.json的scripts以及devDependencies寫成如下樣子(核心關(guān)注scripts中的build命令):
"scripts": {
"sass": "sass scss/main.scss dist/css/style.css",
"babel": "babel es6 --out-dir dist/es5",
"copyHtml": "copyfiles index.html dist",
"build": "run-p sass babel copyHtml"
},
"devDependencies": {
"@babel/cli": "^7.12.8",
"@babel/core": "^7.12.9",
"browser-sync": "^2.26.13",
"copyfiles": "^2.4.1",
"npm-run-all": "^4.1.5",
"sass": "^1.29.0"
}
上述代碼實(shí)現(xiàn)不全,按道理說(shuō),在生產(chǎn)環(huán)境下,至少需要做代碼的兼容以及壓縮。這時(shí)我們就需要找到對(duì)應(yīng)的工具庫(kù)或者自己實(shí)現(xiàn),另外對(duì)于壓縮而言至少需要在編譯之后完成,所以需要注意多個(gè)任務(wù)間的關(guān)系。思路很簡(jiǎn)單,我偷個(gè)懶當(dāng)前就不花時(shí)間去實(shí)踐了,需要時(shí)再實(shí)現(xiàn)就行。
4.npm script構(gòu)建總結(jié)
在進(jìn)入Gulp實(shí)現(xiàn)前端工程的自動(dòng)化構(gòu)建之前,我覺(jué)得有必要再重申一點(diǎn):在項(xiàng)目以及構(gòu)建需求不復(fù)雜時(shí),npm scripts就可以滿足我們的構(gòu)建需求了,無(wú)需借助其它工具。
好的,下面進(jìn)入gulp方式實(shí)現(xiàn)前端工程的自動(dòng)化構(gòu)建。
文章都寫到這份上了,不值得你點(diǎn)個(gè)贊和關(guān)注鼓勵(lì)一下嗎?
三:實(shí)現(xiàn)前端工程的自動(dòng)化構(gòu)建:gulp方式
有些看官可能不太了解gulp,這里我先簡(jiǎn)單介紹一下它吧。
Gulp是一個(gè)基于流的自動(dòng)化構(gòu)建工具,相比較于Grunt,它的構(gòu)建速度更快,任務(wù)編寫也更加簡(jiǎn)單靈活。
安裝好gulp之后,使用gulp的流程也就基本是如下這樣:
- 首先需要在項(xiàng)目根目錄下創(chuàng)建一個(gè)Gulp入口文件gulpfile.js
- 然后在這個(gè)入口文件中通過(guò)暴露函數(shù)的方式注冊(cè)任務(wù)
- 最后以根目錄作為工作目錄執(zhí)行g(shù)ulp命令即可執(zhí)行注冊(cè)任務(wù)。
有了以上了解之后,下面我們就探討如何借助gulp這個(gè)工具來(lái)實(shí)現(xiàn)自動(dòng)化構(gòu)建吧。
1.gulp完成各種任務(wù)調(diào)度
(1):實(shí)現(xiàn)同步任務(wù)和異步任務(wù)
對(duì)于新版本的Gulp來(lái)說(shuō),所有任務(wù)都是異步任務(wù),所以任務(wù)需要告訴Gulp什么時(shí)候執(zhí)行結(jié)束。以下是gulp異步任務(wù)實(shí)現(xiàn)的幾種方式(關(guān)注它們是如何通知Gulp異步任務(wù)結(jié)束的)。
//?方式1:調(diào)用done方法主動(dòng)通知任務(wù)結(jié)束
exports.foo?=?done?=>?{
??console.log('foo?task?working~')
??done()?//?標(biāo)識(shí)任務(wù)執(zhí)行完成
}
//?方式2:返回Promise,通過(guò)它的resolve/reject方法通知任務(wù)結(jié)束
const?timeout?=?time?=>?{
??return?new?Promise(resolve?=>?{
????setTimeout(resolve,?time)
??})
}
//?方式3:返回讀取流對(duì)象,流完即自動(dòng)通知任務(wù)結(jié)束
exports.stream?=?()?=>?{
??const?read?=?fs.createReadStream('yarn.lock')
??const?write?=?fs.createWriteStream('a.txt')
??read.pipe(write)
??return?read
}
//?更多方式
(2):實(shí)現(xiàn)并行任務(wù)和串行任務(wù)
并行任務(wù)和串行任務(wù)可以通過(guò)gulp提供的series(串行), parallel(并行)實(shí)現(xiàn)。
const?{?series,?parallel?}?=?require('gulp');
const?task1?=?done?=>?{
??setTimeout(()?=>?{
????console.log('task1?working~');
????done();
??},?1000)
}
const?task2?=?done?=>?{
??setTimeout(()?=>?{
????console.log('task2?working~');
????done();
??},?1000)??
}
exports.bar?=?parallel(task1,?task2);?//?并行任務(wù)bar
exports.foo?=?series(task1,?task2);?//?串行任務(wù)foo
(3): gulp插件任務(wù)
Gulp生態(tài)中有很多成熟的gulp任務(wù)插件,使用它們可以很好地提高效率,如以下示例:
const?{?src,?dest?}?=?require('gulp');
const?cleanCSS?=?require('gulp-clean-css');
const?rename?=?require('gulp-rename');
exports.default?=?()?=>?{
??return?src('src/*.css')
????.pipe(cleanCSS())
????.pipe(rename({?extname:?'.min.css'?}))
????.pipe(dest('dist'))
}
(4): 自定義gulp任務(wù)
如果需要定制任務(wù),或者對(duì)于我們的需求沒(méi)有較好的gulp插件,那么我們就需要自定義任務(wù),如下示例:
const?fs?=?require('fs')
const?{?Transform?}?=?require('stream')
exports.default?=?()?=>?{
??const?readStream?=?fs.createReadStream('normalize.css');
??const?writeStream?=?fs.createWriteStream('normalize.min.css');
??//?文件轉(zhuǎn)換流
??const?transformStream?=?new?Transform({
????//?核心轉(zhuǎn)換過(guò)程
????transform:?(chunk,?encoding,?callback)?=>?{
??????const?input?=?chunk.toString();
??????const?output?=?input.replace(/\s+/g,?'').replace(/\/\*.+?\*\//g,?'');
??????callback(null,?output);
????}
??})
??return?readStream
????.pipe(transformStream)?//?轉(zhuǎn)換
????.pipe(writeStream)?//?寫入
}
2.gulp完成構(gòu)建任務(wù)流
如下gulpfile文件,分為開(kāi)發(fā)環(huán)境構(gòu)建(develop任務(wù))和生產(chǎn)環(huán)境構(gòu)建(build任務(wù))。相對(duì)于理論認(rèn)識(shí),工具的使用只是不同實(shí)現(xiàn)方式,這里就不多贅述了,具體的邏輯可以看下面代碼中的注釋,我為您寫的很清楚了哦。
const?{
??src,
??dest,
??parallel,
??series,
??watch
}?=?require('gulp')
const?del?=?require('del')
const?browserSync?=?require('browser-sync')
const?loadPlugins?=?require('gulp-load-plugins')
const?plugins?=?loadPlugins()
const?bs?=?browserSync.create()
const?data?=?{
??menus:?[{
??????name:?'Home',
??????icon:?'aperture',
??????link:?'index.html'
????},
????{
??????name:?'Features',
??????link:?'features.html'
????},
????{
??????name:?'About',
??????link:?'about.html'
????},
????{
??????name:?'Contact',
??????link:?'#',
??????children:?[{
??????????name:?'Twitter',
??????????link:?'https://twitter.com/w_zce'
????????},
????????{
??????????name:?'About',
??????????link:?'https://weibo.com/zceme'
????????},
????????{
??????????name:?'divider'
????????},
????????{
??????????name:?'About',
??????????link:?'https://github.com/zce'
????????}
??????]
????}
??],
??pkg:?require('./package.json'),
??date:?new?Date()
}
//?css編譯??src?=>?temp
const?style?=?()?=>?{
??return?src('src/assets/styles/*.scss',?{
??????base:?'src'
????})
????.pipe(plugins.sass({
??????outputStyle:?'expanded'
????}))
????.pipe(dest('temp'))
????.pipe(bs.reload({
??????stream:?true
????}))
}
//?js編譯???src?=>?temp
const?script?=?()?=>?{
??return?src('src/assets/scripts/*.js',?{
??????base:?'src'
????})
????.pipe(plugins.babel({
??????presets:?['@babel/preset-env']
????}))
????.pipe(dest('temp'))
????.pipe(bs.reload({
??????stream:?true
????}))
}
//?html模板解析?????src?=>?temp
const?page?=?()?=>?{
??return?src('src/*.html',?{
??????base:?'src'
????})
????.pipe(plugins.swig({
??????data,
??????defaults:?{
????????cache:?false
??????}
????}))?//?防止模板緩存導(dǎo)致頁(yè)面不能及時(shí)更新
????.pipe(dest('temp'))
????.pipe(bs.reload({
??????stream:?true
????}))
}
//?串行編譯、模板解析
const?compile?=?parallel(style,?script,?page)
//?開(kāi)發(fā)環(huán)境開(kāi)發(fā)服務(wù)器
const?serve?=?()?=>?{
??watch('src/assets/styles/*.scss',?style)
??watch('src/assets/scripts/*.js',?script)
??watch('src/*.html',?page)
??//?watch('src/assets/images/**',?image)
??//?watch('src/assets/fonts/**',?font)
??//?watch('public/**',?extra)
??watch([
????'src/assets/images/**',
????'src/assets/fonts/**',
????'public/**'
??],?bs.reload)
??bs.init({
????notify:?false,
????port:?2080,
????//?open:?false,
????//?files:?'dist/**',
????server:?{
??????baseDir:?['temp',?'src',?'public'],
??????routes:?{
????????'/node_modules':?'node_modules'
??????}
????}
??})
}
//?開(kāi)發(fā)環(huán)境構(gòu)建流:編譯?+?啟動(dòng)開(kāi)發(fā)服務(wù)器??? src => temp
const?develop?=?series(compile,?serve)
//?生產(chǎn)環(huán)境下清空文件夾
const?clean?=?()?=>?{
??return?del(['dist',?'temp'])
}
//?生產(chǎn)環(huán)境js、css、html壓縮后構(gòu)建??temp?=>?dist
const?useref?=?()?=>?{
??return?src('temp/*.html',?{
??????base:?'temp'
????})
????.pipe(plugins.useref({
??????searchPath:?['temp',?'.']
????}))
????//?html?js?css
????.pipe(plugins.if(/\.js$/,?plugins.uglify()))
????.pipe(plugins.if(/\.css$/,?plugins.cleanCss()))
????.pipe(plugins.if(/\.html$/,?plugins.htmlmin({
??????collapseWhitespace:?true,
??????minifyCSS:?true,
??????minifyJS:?true
????})))
????.pipe(dest('dist'))
}
//?生產(chǎn)環(huán)境圖片壓縮后構(gòu)建???src?=>?dist
const?image?=?()?=>?{
??return?src('src/assets/images/**',?{
??????base:?'src'
????})
????.pipe(plugins.imagemin())
????.pipe(dest('dist'))
}
//?生產(chǎn)環(huán)境字體壓縮后構(gòu)建???src?=>?dist
const?font?=?()?=>?{
??return?src('src/assets/fonts/**',?{
??????base:?'src'
????})
????.pipe(plugins.imagemin())
????.pipe(dest('dist'))
}
//?生產(chǎn)環(huán)境靜態(tài)資源構(gòu)建
const?extra?=?()?=>?{
??return?src('public/**',?{
??????base:?'public'
????})
????.pipe(dest('dist'))
}
//?上線之前執(zhí)行的任務(wù)???src?=>?(temp?=>)?=>?dist?
const?build?=?series(
??clean,
??parallel(
????series(compile,?useref),
????image,
????font,
????extra
??)
)
module.exports?=?{
??clean,
??build,
??develop
}
四:其它方式實(shí)現(xiàn)前端工程的自動(dòng)化構(gòu)建
在前端工程的自動(dòng)化構(gòu)建發(fā)展歷程中,出現(xiàn)了很多的自動(dòng)化工具。如grunt、gulp、webpack,包括現(xiàn)在正處在風(fēng)口浪尖的vite等等。webpack的相關(guān)內(nèi)容會(huì)在另外一篇文章中探討,對(duì)于其它的構(gòu)建工具以及過(guò)時(shí)的工具我覺(jué)得沒(méi)有現(xiàn)在學(xué)習(xí)的必要,具體生產(chǎn)需要使用時(shí)學(xué)習(xí)即可。
總的來(lái)說(shuō),工具很多,但是最重要的其實(shí)是對(duì)于自動(dòng)化構(gòu)建本身的理論認(rèn)識(shí),而對(duì)于自動(dòng)化構(gòu)建的實(shí)現(xiàn)及其工具而言,我覺(jué)得,包括比較需要掌握的gulp和webpack,完全理解npm script方式是更重要的。
最后
如果你覺(jué)得這篇內(nèi)容對(duì)你挺有啟發(fā),我想邀請(qǐng)你幫我三個(gè)小忙:
點(diǎn)個(gè)「在看」,讓更多的人也能看到這篇內(nèi)容(喜歡不點(diǎn)在看,都是耍流氓 -_-)
歡迎加我微信「huab119」拉你進(jìn)技術(shù)群,長(zhǎng)期交流學(xué)習(xí)...
關(guān)注公眾號(hào)「前端勸退師」,持續(xù)為你推送精選好文,也可以加我為好友,隨時(shí)聊騷。
