帶你揭開自動化構(gòu)建的神秘面紗

?鏈接每一位開發(fā)者,讓編程更有趣兒!關(guān)注
前言:對于我們這些日常基于腳手架項目開發(fā),使用yarn/npm run start、yarn/npm run build等命令完成自動化構(gòu)建的開發(fā)者來說,重要的自動化構(gòu)建仿佛變成了前端的一個黑盒知識。但是,掌握前端工程的自動化構(gòu)建,是學(xué)習(xí)前端工程化以及進階高級前端所必不可缺少的部分。
通過這篇文章,我嘗試把我自己對自動化構(gòu)建知識體系的系統(tǒng)化認(rèn)識托盤而出,希望能夠?qū)﹂w下有所幫助。同樣,我更喜歡的是您能批判我的一些觀點或者指出我的一些問題,因為忠言逆耳利于行。
注意:文章的側(cè)重點是對自動化構(gòu)建知識的系統(tǒng)化探討,不會是對某一個具體工具使用上的面面俱到,畢竟那是官方文檔該做的事情。
對于自動化構(gòu)建知識體系的系統(tǒng)化認(rèn)識,從個人認(rèn)識出發(fā),我用腦圖做了以下整理:

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

這個項目很簡單,它只包含一個html文件,一個使用了ES6語法js文件以及一個使用了sass語法的樣式文件,接下來我們就用npm script來實現(xiàn)這個簡單項目的自動化構(gòu)建(也即開發(fā)環(huán)境構(gòu)建任務(wù)流和生產(chǎn)環(huán)境構(gòu)建任務(wù)流)。
簡單項目的自動化構(gòu)建就是npm script實現(xiàn)自動化構(gòu)建的使用場景。
與日常開發(fā)一樣,我們這里也把這個工程構(gòu)建分為兩個部分,即開發(fā)環(huán)境構(gòu)建和生產(chǎn)環(huán)境構(gòu)建。下面先講講開發(fā)環(huán)境構(gòu)建的實現(xiàn):
(一):開發(fā)環(huán)境構(gòu)建工程
通過上面我們對開發(fā)環(huán)境構(gòu)建任務(wù)流的認(rèn)識,我們先理一理在這個項目中,開發(fā)環(huán)境任務(wù)流至少應(yīng)該包含哪些任務(wù):
- 需要web開發(fā)服務(wù)器模擬生產(chǎn)環(huán)境以及實現(xiàn)源碼監(jiān)聽和自動刷新。
- 對于sass文件,需要實時sass轉(zhuǎn)換,并且監(jiān)聽源碼變化重啟開發(fā)服務(wù)器、刷新瀏覽器。
- 對于ES6文件,需要實時babel轉(zhuǎn)換,并且監(jiān)聽源碼變化重啟開發(fā)服務(wù)器、刷新瀏覽器。
- 對于html文件,需要監(jiān)聽源碼變化重啟開發(fā)服務(wù)器、刷新瀏覽器。
- ...
對于sass和ES6修改源代碼后的實時轉(zhuǎn)換,我們可以通過加上一個watch參數(shù)實現(xiàn)。而對于所有這些需要監(jiān)聽變化的文件,我們則統(tǒng)一放入temp文件夾下(角色好比如nginx和Tomcat的應(yīng)用存放目錄),而后讓web開發(fā)服務(wù)器監(jiān)聽這個temp文件夾下所有文件的變化,一旦變化即重啟并刷新瀏覽器。
好的經(jīng)過上面任務(wù)分析之后,我們可能會把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)建工程
通過上面我們對生產(chǎn)環(huán)境構(gòu)建任務(wù)流的認(rèn)識,我們先理一理在這個項目中,生產(chǎn)環(huán)境任務(wù)流應(yīng)該包含哪些任務(wù):
- 刪除上一次構(gòu)建結(jié)果任務(wù)
- 編譯任務(wù)
- html模板信息注入任務(wù)
- 文件壓縮任務(wù)
- ...
好的經(jīng)過上面任務(wù)分析之后,我們可能會把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"
}
上述代碼實現(xiàn)不全,按道理說,在生產(chǎn)環(huán)境下,至少需要做代碼的兼容以及壓縮。這時我們就需要找到對應(yīng)的工具庫或者自己實現(xiàn),另外對于壓縮而言至少需要在編譯之后完成,所以需要注意多個任務(wù)間的關(guān)系。思路很簡單,我偷個懶當(dāng)前就不花時間去實踐了,需要時再實現(xiàn)就行。
4.npm script構(gòu)建總結(jié)
在進入Gulp實現(xiàn)前端工程的自動化構(gòu)建之前,我覺得有必要再重申一點:在項目以及構(gòu)建需求不復(fù)雜時,npm scripts就可以滿足我們的構(gòu)建需求了,無需借助其它工具。
好的,下面進入gulp方式實現(xiàn)前端工程的自動化構(gòu)建。
文章都寫到這份上了,不值得你點個贊和關(guān)注鼓勵一下嗎?
三:實現(xiàn)前端工程的自動化構(gòu)建:gulp方式
有些看官可能不太了解gulp,這里我先簡單介紹一下它吧。
Gulp是一個基于流的自動化構(gòu)建工具,相比較于Grunt,它的構(gòu)建速度更快,任務(wù)編寫也更加簡單靈活。
安裝好gulp之后,使用gulp的流程也就基本是如下這樣:
- 首先需要在項目根目錄下創(chuàng)建一個Gulp入口文件gulpfile.js
- 然后在這個入口文件中通過暴露函數(shù)的方式注冊任務(wù)
- 最后以根目錄作為工作目錄執(zhí)行g(shù)ulp命令即可執(zhí)行注冊任務(wù)。
有了以上了解之后,下面我們就探討如何借助gulp這個工具來實現(xiàn)自動化構(gòu)建吧。
1.gulp完成各種任務(wù)調(diào)度
(1):實現(xiàn)同步任務(wù)和異步任務(wù)
對于新版本的Gulp來說,所有任務(wù)都是異步任務(wù),所以任務(wù)需要告訴Gulp什么時候執(zhí)行結(jié)束。以下是gulp異步任務(wù)實現(xiàn)的幾種方式(關(guān)注它們是如何通知Gulp異步任務(wù)結(jié)束的)。
//?方式1:調(diào)用done方法主動通知任務(wù)結(jié)束
exports.foo?=?done?=>?{
??console.log('foo?task?working~')
??done()?//?標(biāo)識任務(wù)執(zhí)行完成
}
//?方式2:返回Promise,通過它的resolve/reject方法通知任務(wù)結(jié)束
const?timeout?=?time?=>?{
??return?new?Promise(resolve?=>?{
????setTimeout(resolve,?time)
??})
}
//?方式3:返回讀取流對象,流完即自動通知任務(wù)結(jié)束
exports.stream?=?()?=>?{
??const?read?=?fs.createReadStream('yarn.lock')
??const?write?=?fs.createWriteStream('a.txt')
??read.pipe(write)
??return?read
}
//?更多方式
(2):實現(xiàn)并行任務(wù)和串行任務(wù)
并行任務(wù)和串行任務(wù)可以通過gulp提供的series(串行), parallel(并行)實現(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ù),或者對于我們的需求沒有較好的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)換過程
????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文件,分為開發(fā)環(huán)境構(gòu)建(develop任務(wù))和生產(chǎn)環(huán)境構(gòu)建(build任務(wù))。相對于理論認(rèn)識,工具的使用只是不同實現(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)致頁面不能及時更新
????.pipe(dest('temp'))
????.pipe(bs.reload({
??????stream:?true
????}))
}
//?串行編譯、模板解析
const?compile?=?parallel(style,?script,?page)
//?開發(fā)環(huán)境開發(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'
??????}
????}
??})
}
//?開發(fā)環(huán)境構(gòu)建流:編譯?+?啟動開發(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
}
四:其它方式實現(xiàn)前端工程的自動化構(gòu)建
在前端工程的自動化構(gòu)建發(fā)展歷程中,出現(xiàn)了很多的自動化工具。如grunt、gulp、webpack,包括現(xiàn)在正處在風(fēng)口浪尖的vite等等。webpack的相關(guān)內(nèi)容會在另外一篇文章中探討,對于其它的構(gòu)建工具以及過時的工具我覺得沒有現(xiàn)在學(xué)習(xí)的必要,具體生產(chǎn)需要使用時學(xué)習(xí)即可。
總的來說,工具很多,但是最重要的其實是對于自動化構(gòu)建本身的理論認(rèn)識,而對于自動化構(gòu)建的實現(xiàn)及其工具而言,我覺得,包括比較需要掌握的gulp和webpack,完全理解npm script方式是更重要的。
??? 最后
當(dāng)然也可以關(guān)注我的公眾號:「前端獵手」,或是添加我的微信(wKavin)私底下進行交流。
