這篇文章要來講如何用 webpack 蓋出一個 SPA 的前端打包程序。
這些事情在 Vue-CLI 裡面只要一個 serve/build 就搞定了,但是如果要自己用 webpack 做出類似的打包該怎麼做?
如果你對 webpack 不熟悉,可以到 webpack 教學文件 做到「起步」的步驟,大概就會有點概念了。
開發環境
先說明一下我的開發語言:
- Vue
- scss
- HTML
- JS6+
Vue-CLI 做了哪些打包?
來列一下 Vue-CLI 支援什麼:
- 打包 .vue 檔
- 可以在 .vue 檔中使用預處理語言 (scss/pug)
- 用 babel 轉譯
- CSS 自動前綴(墜) (autoprefix)
- 壓縮圖片變成 base64 格式
- 自動網頁重載 Hot Module Reload (HMR)
- CSS + JS sourcemap
以上就是這篇文章要實現的功能。
設置 webpack 環境
首先當然要有 webpack,所以基本款是安裝 webpack webpack-cli
,假裝專業一點加入 webpack-merge
,可以用來切分 production
跟 devlopment
環境。
1 | npm install --save-dev webpack webpack-cli webpack-merge |
接著在根目錄建立 webpack 用的設定檔
webpack.common.js
(共通設定)webpack.prod.js
(production 設定)webpack.dev.js
(devlopment 設定)
1 | // webpack.common.js |
1 | // webpack.dev.js |
1 | // webpack.prod.js |
好,好長啊orz。
好,接下來設定好 npm script,我們就可以在 command line 快速打包。
1 | // package.json |
好,這麼一來,兩個環境的進入點都是 ./src/index.js,所以請新增一個 src 資料夾,底下開一個 index.js 做個樣子。
// 目前的目錄結構
build/
src/
index.js
webpack.common.js
webpack.dev.js
webpack.prod.js
vue-loader 處理 .vue 檔
好,離上市集資已經跨了一大步了。
Webpack 簡單來說就是用一堆 loader 去處理依賴圖(dependencies graph)上的檔案。每種檔案都會有相對應的 loader,要有loader,webpack才知道要怎麼處理這個檔案。vue-loader 就是用來處理 .vue 檔的。
首先來把以下東西裝進去:npm install --save-dev vue-loader vue-template-compiler
npm install --save vue
vue-loader
跟 vue-template-compiler
就是拿來 load .vue 檔的。vue-loader
會使用 vue-template-compiler
先對 .vue 檔做預處理。
注意一下 vue-template-compiler
要跟你的 vue
版本相同,否則會出錯。
安裝完後,可以到 package.json 去確認兩個的版本是不是一樣的。
好,接下來要設定 webpack,把 loader 裝上去。到 webpack.common.js
設定:
1 |
|
rules 的部分就是指定對 .vue 檔使用 vue-loader
。VueLoaderPlugin
的作用,引用一下官網解釋
这个插件是必须的! 它的职责是将你定义过的其它规则复制并应用到 .vue 文件里相应语言的块。例如,如果你有一条匹配 /.js$/ 的规则,那么它会应用到 .vue 文件里的 script 块。
也就是說,你如果有對其他檔案或是語言使用 loader 去處理的話,VueLoaderPlugin會使在 .vue 檔中的相應語言也被處理。
所以,威~~什麼在 .vue 檔裡面的各種語言會被處理呢?就是因為有 VueLoaderPlugin把其他 loader 套進去的喔。
這麼一來,webpack就設定好惹。
接著我們來用.vue檔寫個 halloween 吧。在 src 目錄下開一個 App.vue
,然後寫個內容:
1 |
|
接著接著,在 src 目錄下,寫個 index.js:
1 |
|
接著接著接著,
在根目錄的 build 目錄下開一個 app.html,在他的 body 裡面:
1 |
|
好了,準備完成,接著來打個包:
npm run build
然後你就會看到 app.bundle.js 降臨在 build 資料夾中。然後打開 app.html,你就會看到 halloween
的字。
這表示什麼?
表示我們打包成功了QWQ。
處理 css
接下來處理CSS。
套用官網的教學範例,需要 style-loader
跟 css-loader
。但是在 vue-loader 中,有個已經安裝好的 vue-style-loader
,跟 style-loader
的效果相同。所以我們只要裝 css-loader
就好:
npm install --save-dev css-loader
根據 vue-style-loader
的說法,這個 loader 跟style-loader
不同,可以支援一些 SSR 的需求。有興趣的人可以參考 vue-style-loader。
有 loader 之後呢?裝上去啊哈哈哈哈:
1 | // ./webpack.common.js |
然後到 APP.vue 裡面加入一個 style
1 | <style> |
然後然後重新打包 npm run watch
,打包完後,對網頁重新整理,就會發現 h1 變成紅色,他的樣式在 head 裡的 style 標籤裡面。
處理 scss
承接上面處理 css 的部分,處理 scss 還需要其他套件 node-sass
和 sass-loader
。
npm install --save-dev node-sass sass-loader
大家都知道 sass/scss 是需要編譯器的,node-sass 就是一個用 C++ 寫出來的編譯器。而地方的 sass-loader 需要 node-sass 來滿足他編譯 scss/sass的需求。
1 | // ./webpack.common.js |
注意上面的 1 跟 2 兩個步驟都做完。
然後改一下 App.vue 的 style 的部分:
1 | <style lang="scss"> <!-- 注意: 要加個 lang="scss" --> |
然後重新打包 npm run watch
,打包完,打開app.html,就發現字變成藍色的,表示 scss 打包成功啦哈哈哈哈哈。
自動前綴 postcss-loader + autoprefixer
打到這裡覺得好累,
相信你要是能看到這裡一定也覺得很累 orz
但是,接下來,要來加上自動前綴的功能惹~
npm install --save-dev postcss-loader autoprefixer
postcss 是一個 css 的後處理器,而 sass 是前處理器。
而 autoprefixer 是 postcss 的一個 plugin,顧名思義就是可以幫css自動加上前綴字。
同樣的,把他裝上去:
1 | // ./webpack.common.js |
接著來隨便寫一個應該需要 prefix 的 CSS 屬性
1 | <style lang="scss"> |
然後就可以打包了~ 還沒啊啊啊
要加上前綴字,需要知道對象的瀏覽器版本,我們需要在 根目錄 加一個檔案 .browserslistrc
,裡面加上對應的瀏覽器資訊。
> 1%
last 2 versions
其實這個設定也可以寫在 loader 上,只是其他套件(Babel)也會用到這個.browserslistrc
的資訊,所以把他獨立出來,整個專案對瀏覽器的支援才會統一。
加上去了吼?來,打個包。
打開網頁用開發者工具看,你會看到 transform 前面多一個有前綴字的屬性。
(這篇文章是在 2020/4 寫的,這時候會出現 -webkit-transform 的屬性)
postcss 還有其他有趣的功能,有興趣的人可以研究研究。
加上 CSS sourcemap
最後,來幫CSS 加上 sourcemap。
1 | // ./webpack.common.js |
把 css-loader
、postcss-loader
、sass-loader
的 options 都加上 sourceMap,然後設定成 true
,就會生成 sourceMap 了。
如果這時候你重新打包,再開啟網頁,你就會看到樣式上面有標註來源。
根據CSS-loader
webpack 官方文件說法:
They are not enabled by default because they expose a runtime overhead and increase in bundle size (JS source maps do not).
這個功能是預設不開啟的,因為會增加 bundle file 的大小,而且會讓打包速度變慢……
所以好像應該要把這兩個環境設定切開。
這邊說明一下,我想不到一個看起來很專業的切分法(比如說使用 webpack merge 去處理)
其實也可以這樣寫:
sourceMap : process.env.NODE_ENV !== "production"
其實這樣就解決了。
但是這個解法總給我一種:「既然這樣,為什麼要切分 dev / prod 兩個檔案?」的疑問。而且,如果我今天只從這兩個檔案的層級去看環境差異的話,我就可能會漏掉 sourceMap 的環節。
所以底下我想了一個不是那麼成熟的解法:
其實 merge 這東西就是在合併一般的 javascript Object 而已,所以可以像是修改物件一樣,在 prod/dev 兩個檔案中做出不同的修改:
首先,把 webpack.common 的選項包到一個 function 中,讓 sourceMap 變成一個參數。
1 |
|
接著分別設定dev/prod兩個檔案,對 common 函式傳入不同的參數。
1 | // webpack.dev.js |
1 | // webpack.prod.js |
不知道還有沒有更好的解法?
加上 JS sourceMap
都說到 sourceMap ,也順便來加一下 JS 的 sourceMap 吧。
其實這個 Webpack 有透過 devtool 選項做支援,你可以選擇不同的 devtool 生成自己想要的 sourceMap 格式。
跟 CSS 不同的是,他似乎不會大量增加打包後檔案的容量。
官方建議兩個模式下都加入 JS sourceMap 。因為 production 跟 devlopment 兩個環境的需求不同,所以要使用不同的 sourceMap。
所以就分別對 prod 跟 dev 兩個做不同的設定囉:
1 | // webpack.dev.js |
1 | // webpack.prod.js |
好,接著來測試一下,到 ./src/index.js 裡面:
1 |
|
然後打包之後,打開開發者工具的 console,就會看到 console.log
的位置在 index.js 的某行。
簡單多了 QQ
babel-loader 轉譯 javascript
終於要進到 babel 了。(我都念 babel)
npm install --save-dev babel-loader @babel/core @babel/preset-env
首先是 @babel/core
跟 @babel/preset-env
這兩個 babel 主要功能套餐,接著是 babel-loader
用來對接 webpack。
我們把 loader 裝上去:
1 | // webpack.common.js |
說明一下:
options 的部分會稍後說明,如果你有自己偏好的 babel 設定可以跳過。
exclude 選項指定了 babel 不要處理的檔案,通常會不處理node_module的檔案,但是 vue-loader 官方給了個建議設定,如上方範例,可以確保 node_modules 中的 .vue 檔被轉譯。
至於 babel 會用到的 browser 設定,會直接套用根目錄的 .browserslistrc,嘿嘿嘿,前面把他獨立成一個檔案有道理,這邊不用重新寫一次。
來講一下 option 的部分:
主要要講的是 transform-runtime,他主要是 babel 用來解決轉譯後的 code 多處重複的問題,可以把轉譯後的 code 縮小。
[@babel/plugin-transform-runtime]https://babeljs.io/docs/en/babel-plugin-transform-runtime
如果你跟我一樣就是死都要用 polyfill ,那來裝一下吧:
npm install --save-dev @babel/plugin-transform-runtime
以及另外要在 production 環境使用的:
npm install --save @babel/runtime-corejs3 @babel/runtime
然後 loader 的部分直接使用上方的設定,就行了。
自動重載 Hot Module Reload (HMR)
效果是:你不用自己按重新整理! orz
根據 webpack 官方文件,基本的基本上有兩種做法,
- webpack-dev-server
- webpack-dev-middleware + webpack-hot-middleware (伺服器用)
webpack-dev-server
npm install --save-dev webpack-dev-server
然後到 webpack.dev.js
1 | // webpack.dev.js |
接著到 package.json 中設定指令:
1 | "scripts": { |
好,這麼一來只要在 command line 上執行 npm start
就會開啟頁面。並且在每次修改依賴圖上的檔案時,就會自動刷新網頁囉。
webpack-dev-middleware + webpack-hot-middleware (伺服器用)
如果你是用像是 express 的後端框架,這兩個東西就是拿來當 middleware 插入的。只有 dev-middleware 的話,只會在伺服器開啟時打包一次,但是再加上 hot-middleware 的話,就可以在依賴圖上的檔案變化時刷新網頁。
npm install --save-dev webpack-dev-middleware webpack-hot-middleware
接著在 webpack.dev.js 中:
1 | const webpack = require('webpack') // 新增 |
接著在你設定中間層檔案上加入 middleware,比如我是使用 express,加在最前面:
1 | // app.js |
最後,啟動 server ,你就會看到 webpack 執行打包,並且在更新依賴圖上的檔案時,重新打包+刷新網頁。
壓縮圖片變成 base64 格式
這邊要用的是在官方文件也有的 file-loader & url-loader。實際上做到壓縮這件事的是 url-loader。而 file-loader 可以把依賴圖上出現的對應檔案通通拉到 build 目錄中。
npm install --save-dev url-loader file-loader
接著設定 loader,可以只設定 url-loader。( 實戰 Webpack 的 file-loader 和 url-loader - 《Chris 技術筆記》)
1 | rules: [ |
好,接著來測試一下,在 App.vue 中
1 | <template> |
1 | .bg{ |
最後,重新打包,打開 app.html,看一下開發者工具,就會發現他被壓成 base64 了。
結語
哈哈哈哈,搞到最後好像在 webpack 教學啊~~
參考資料: