0%

webpack 學習紀錄

文章目的

接觸前端到現在幾乎碰到的專案都會使用到前端的三大框架,框架中都會用到 webpack 的技術,但實際上 webpack 是什麼呢?
如果我今天不用框架開發,一樣能用 webpack 幫助我開發專案嗎?
這一直是我想了解的地方,利用此筆記記錄我學到的東西以方便日後複習。

webpack 目的

為什麼要使用 webpack?前端發展至今,開發上已經不是單純的寫 html、css、js 了,我們會在開發上利用許多衍生的語法來幫助我們開發,像是 es6、sass、pug 等等。
但瀏覽器根本壓根看不懂這些東西,那我們要怎麼把這些東西變成瀏覽器看得懂的語言呢?
這就是使用 webpack 的原因之一,webpack 可以幫助我們將這些衍生語法編譯成瀏覽器看得懂的語法,打包成一包並壓縮檔案大小,這麼方便還不用嗎?

建立 webpack 環境

在使用 webpack 前我們要先建立基本的環境,並做一些基本設定,讓我們後續使用更加方便。

創建 package.json

在我們的新專案中輸入 npm init 來安裝並填入一些訊息設定,若不想那麼麻煩也可以直接輸入 npm init -y

安裝 webpack

接下來安裝 webpack,輸入 npm install webpack webpack-cli --save-dev,安裝完後我們的專案會出現 node_modules 和 package-lock.json。

撰寫 webpack 設定檔

先創建兩個檔案,分別是 index.jswebpack.config.jswebpack.config.js 是 webpack 的設定檔,我們先來幫他做點設定:

1
2
3
4
5
6
7
8
9
10
11
const path = require('path');
module.exports = {
entry: './src/index.js',
// 進入點,所以檔案必須與此檔案有關聯才會被編譯
output: {
filename: 'index.bundle.js',
// 編譯檔案名稱
path: path.resolve(__dirname, 'dist')
// 編譯檔案的位置
}
};

我們可以看到在 entry 部分關連的檔案是 index.js,也就是我們剛剛建立的檔案,webpack 會以此檔案作為進入點來執行打包作業,因此往後需要用到 webpack 的檔案都要注入這個 index.js。
例如可以這樣寫:

1
2
import './src/all.scss'
import './src/all.js'

創建編譯指令

不論是開發或是打包發布,我們可以利用一些簡單的指令來方便我們操作,可以在 package.json 中做以下設定:

1
2
3
4
5
"scripts": {
"watch": "webpack --mode=development --watch",
"dev": "webpack --mode=development",
"build": "webpack --mode=production"
}

watch 的部分會即時監聽修改並編譯,建議只使用在開發上。

webpack 工具

webpack 工具可以幫助我們在打包過程中做更多自動化的事情,讓所有繁瑣的工作交給它來處理,介紹幾個實用的工具。

Babel

Babel 可以幫助我們將 js 高版本的語法轉譯成瀏覽器看得懂的 js 語法,像是 es6 等。
利用 npm install babel-loader @babel/core @babel/preset-env --save-dev 做安裝,安裝完成後我們在 webpack.config.js 做設定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module.exports = {
module: {
rules: [
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
}
}

css loader 與 sass loader

若需要編譯 css 會需要 css-loader 跟 style-loader 兩種 loader,若今天開發是使用 sass 的話就得多加一個 sass-loader
輸入以下指令做安裝:

1
2
npm install css-loader style-loader --save-dev
npm install sass-loader node-sass --save-dev

一樣在 webpack.config.js 引入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
module.exports = {
module: {
rules: [
// 編譯 css 檔案設定
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
// 編譯 scss 檔案設定
{
test: /\.s[ac]ss$/i,
use: [
'style-loader',
'css-loader',
'sass-loader'
]
}
]
}
}

到了這邊已經確實將 css 編譯引入了,編譯後的結果會存在 index.bundle.js 中,但如果今天想要將編譯結果獨立出來呢?不想將 css 相關編譯到 js 中,那就會需要利用到 mini-css-extract-plugin。
這時候我們就不需要 style-loader 了,style-loader 幫助我們將 css 編譯進 js 但我們要獨立出來,因此不需要了。
輸入 npm uninstall style-loader --save-dev 解除安裝,並輸入 npm install mini-css-extract-plugin --save-dev 安裝 mini-css-extract-plugin。
webpack.config.js 設定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); // 先引入

module.exports = {
module: {
rules: [
{ // css
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader']
},
{ // sass
test: /\.s[ac]ss$/i,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'sass-loader'
]
}
]
},
plugins: [new MiniCssExtractPlugin()]
};

設定完後去 build 就會發現 build 出來的檔案中 css 被獨立出來了。

補充
在 css 打包之後我們最常遇到的問題是圖片路徑引用不正確,我們在引入 background-image 時最常看到 url('../.....') 相對路徑的用法,但這時要注意的地方在於通常開發上會習慣把同一類型的檔案用一個資料夾做管理。
例如:js 有 js 資料夾,css 有 css 資料夾,img 有 img 資料夾。
這時會發生一件事,假如我們的 css 這樣寫 background-image: url('../img/...png'),在打包過後這樣的路徑其實會指定到 css 這個資料夾,但 img 應該是上一層才對啊,所以我們又要來調整 webpack.config.js 了。
我們可以針對 css 的編譯給他一個 publicPath

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
module.exports = {
module: {
rules: [
{
test: /\.s[ac]ss$/i,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../'
}
},
'css-loader',
'sass-loader'
]
}
]
}
}

這樣子編譯完成時,路徑就會自動地指定到上一層,**最後這部分要記得根據開發的專案資料夾結構,去修改 publicPath**。

html-webpack-plugin

在前面的 webpack 打包檔不管是 index.bundle.js 或是獨立出來的 css 檔案都會需要我們手動引入 html,但這會是一個麻煩點,開發者得要一個一個引入,難道不能讓 webpack 幫助我們處理這件事嗎?
當然可以~ 來看看 html-webpack-plugin 吧!
輸入 npm install html-webpack-plugin --save-dev 安裝。
針對 webpack.config.js 設定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 先引入

module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html', // 自動引入靜態 html 的目標文件
filename: 'index.html' // 打包完成後生成的檔案名稱
}),
// 若要引入複數個 html 可以有多個 HtmlWebpackPlugin
new HtmlWebpackPlugin({
template: './src/content.html',
filename: 'content.html'
})
]
};

完成之後當我們打包檔案,就會出現我們設定的 html 而且裡面就會自動引入我們打包的 js 跟 css 囉。
另外,html-webpack-plugin 預設會幫我們對 html 做壓縮,除非有些自定義需求,不然基本上不需要管它。

terser-webpack-plugin

接下來介紹的套件,可以幫助我們壓縮 js 檔,甚至可以針對選項做一些設定 參考
輸入 npm install terser-webpack-plugin --save-dev 安裝。

針對 webpack.config.js 設定:

1
2
3
4
5
6
7
8
9
10
const TerserPlugin = require('terser-webpack-plugin'); // 先引入

module.exports = {
optimization: {
minimize: true,
minimizer: [
new TerserPlugin()
]
}
};

這樣子,打包檔案我們就能得到壓縮的 js 了。

clean-webpack-plugin

若我們在 webpack 為打包的檔案設定命名時有加入 hash 的話,就會遇到我們每次打包時檔名都不同的情形發生,有些檔案是舊的根本用不到,但還留在資料夾內。
因此就需要在打包前先清空我們的目標資料夾,以確保內部檔案都是最新的,利用 clean-webpack-plugin 來幫助我們做這件事。
輸入 npm install --save-dev clean-webpack-plugin 安裝。

針對 webpack.config.js 設定:

1
2
3
4
5
6
7
const { CleanWebpackPlugin } = require('clean-webpack-plugin'); // 先引入

module.exports = {
plugins: [
new CleanWebpackPlugin()
]
};

設定完後每次打包 webpack 就會幫我們把目標資料夾清空了。

url-loader & file-loader

在開發網頁時,很容易會遇到靜態資源,如:圖片、字型等。
因此 webpack 打包時我們當然也需要將這些靜態資源打包,甚至能壓縮更好,這時就會需要用到 url-loader 跟 file-loader。
輸入以下指令來安裝這兩個套件:

1
2
npm install file-loader --save-dev
npm install url-loader --save-dev

file-loader 可以幫我們將靜態資源打包處理,url-loader 則是可以針對設定的檔案大小去比對靜態資源,若是低於設定大小就會轉變成 base64 提高之後載入的效能,若是高於設定值則將檔案丟給 file-loader 做處理。

針對 webpack.config.js 設定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module.exports = {
module: {
rules: [
{
test: /\.(png|jpe?g|gif)$/i // 驗證副檔名
use: [
{
loader: 'url-loader',
options: {
name: 'img/[name].[ext]', // 不做設定預設會是 hash + 副檔名
limlt: 10000 // 超過此限制,會將圖片轉成 base64 (byte)
}
}
]
}
]
}
};

接下來,來載入字體看看
將下載下來的字體放入對應的資料夾中,接著到 scss 以 @font-face 做 import:

1
2
3
4
@font-face {
font-family: 'NotoSansTC';
src: url('../font/NotoSansTC-Regular.otf') format('opentype');
}

一樣到 webpack.config.js 為 file-loader 做配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module.exports = {
module: {
rules: [
{
test: /\.(woff|woff2|eot|ttf|otf|)$/,
use: [
{
loader: 'file-loader',
options: {
name: 'font/[name].[ext]'
}
}
]
}
]
}
}

build 之後就會發現 font 的檔案也跟著打包進去了。

image-webpack-loader

每次開發的時候都會因為圖片檔案大小問題,都要事先壓縮圖片,這件事情我們一樣可以交給 webpack 來幫我們處理。
輸入 npm install image-webpack-loader --save-dev 安裝此套件
這個套件可以跟 url-loader 並用,接下來到 webpack.config.js 做設定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
module.exports = {
module: {
rules: [
{
test: /\.(png|jpe?g|gif)$/i,
use: [
{
loader: 'url-loader',
options: {
name: 'img/[name].[ext]',
limit: 200000
}
},
{
loader: 'image-webpack-loader'
}
]
}
}

接下來只要我們 build,webpack 就會自動幫我們壓縮圖片囉。

webpack dev server

既然我們已經對很多的事情透過 webpack 做自動化的處理,但是最重要的問題,我們的開發畫面呢?
我也必須要開發畫面會隨著我更新 code 的時候做即時的更新,這時我們就需要 webpack dev server
先執行此指令做安裝 npm install webpack-dev-server -D,安裝完成後,我們先來 package.json 做設定:

1
2
3
4
"scripts": {
"dev": "webpack-dev-server", // 在這裡加入 webpack-dev-server
"build": "webpack --mode=production"
}

現在已經有指令可以觸發 webpack dev server 了,接下來我們只要去 webpack.config.js 針對 webpack dev server 做設定即可:

1
2
3
4
devServer: {
contentBase: path.join(__dirname, 'dist'), // 指定 webpack dev server 以哪個資料夾為基礎作監聽。
compress: true
}

更多相關設定可以參考官網
最後我們只要根據在 package.json 設定的跑 npm run dev 就會啟動本地 localhost 監聽囉。

後記

講了那麼多,總算是對 webpack 有更深入的了解,接下來就是整理一個底層的 webpack core 方便以後不使用框架的專案開發了,希望真的能用在實戰上XD。
也感謝網路上寫相關教學文章的大大,讓我受益良多。


參考資料

webpack - 學習筆記
webpack 前端打包工具