Webpack是JavaScript的模組建置工具,運行在Node.js上,它可以將零散的JavaScript檔案用各式工具優化並打包起來,加快網頁的載入時間。Webpack也並不限於用在JavaScript上,舉凡網頁有用到的靜態資源(如JS、CSS、圖片檔等),甚至是HTML網頁,Webpack都有辦法打包。
Webpack官方網站:
官方網站上給的一張圖例,簡潔扼要地說明了Webpack的功用:
Webpack的Node.js環境
Webpack官方建議使用Node.js最新的LTS版本來運行Webpack。Windows的使用者可以直接到Node.js官方網站下載到Node.js的安裝檔,而Linux作業系統或是macOS的使用者可以參考這篇文章來安裝指定版本的Node.js。
建立Webpack專案
Webpack可以被加進現有的Node.js專案中,在終端機執行以下指令來安裝webpack
和webpack-cli
這兩個套件。
指令中的-D
參數即是--save-dev
。
接著在package.json
中加入新的腳本指令:
{
...
"scripts": {
...
"build:webpack": "webpack --mode production",
...
},
...
}
production
其實就是Webpack預設的模式,它會將NODE_ENV
設為production
,並且會自動啟用它自帶的一些相關外掛。如果需要的話也可以使用development
,它會將NODE_ENV
設為development
,也會自動啟用它自帶的一些相關外掛。
再來就是在Node.js專案根目錄中,新增Webpack的設定檔webpack.config.cjs
,內容可以初始如下:
module.exports = {
plugins: [
],
module: {
rules: [
]
},
};
使用npm指令打包Webpack專案
由於我們已經事先在package.json
中加入build:webpack
腳本指令了,因此可以直接使用npm run build:webpack
指令來執行它。
進入點(Entry Point)和輸出
Webpack會去執行某個或是某幾個JavaScript檔案作為進入點,預設的進入點只有一個,路徑為src/index.js
,且其輸出的JS檔案的預設路徑為dist/main.js
。搭配其它的載入器也可以使用非JavaScript作為進入點。
如果要修改進入點的路徑,要在webpack.config.cjs
設定檔中,加上entry
欄位,設定方式如下:
module.exports = {
...
entry: "./path/to/js-file",
...
};
例如:
module.exports = {
...
entry: "./src/lib.js",
...
};
至於進入點的JS程式寫法,稍候會再提到。
如果要修改輸出的JS檔案的路徑,要在webpack.config.cjs
設定檔中,加上output
欄位,設定方式如下:
module.exports = {
...
output: {
path: "/path/to/folder",
filename: "./path/to/file",
},
...
};
output
欄位中的path
欄位表示要儲存輸出檔(不限於JS檔)的目錄,注意這個路徑必須要是絕對路徑!而filename
欄位則是輸出的JS檔(其它類型的檔案會用其它方式設定)要儲存的路徑,注意這個路徑必須要是相對路徑,才能使輸出檔存在於path
欄位的絕對路徑下。當然,如果您想要讓輸出檔放置在Node.js專案的根目錄下的dist
目錄的話,可以不必設定path
欄位。
例如:
const path = require("node:path");
module.exports = {
...
output: {
path: path.join(__dirname, "output"),
filename: "./js/bundle.min.js",
},
...
};
以上的__dirname
即表示目前執行的JS檔案(此處即webpack.config.cjs
)的所在目錄之絕對路徑(此處即Node.js專案的根目錄)。
如果要設定多個進入點,entry
欄位和output
欄位的設定方式如下:
const path = require("node:path");
module.exports = {
...
entry: {
"name-1": "./path/to/js-file-1",
"name-2": "./path/to/js-file-2",
"name-3": "./path/to/non-js-file-2",
},
output: {
path: path.join(__dirname, "output"),
filename: "./js/[name].min.js",
},
...
};
在filename
欄位的字串中加入[name]
,可以讓Webpack自動用進入點的名稱來取代它。如果entry
欄位有設定多個進入點,但是沒有設定output
欄位的話,Webpack預設的output
欄位值如下:
const path = require("node:path");
module.exports = {
...
output: {
path: path.join(__dirname, "dist"),
filename: "./[name].js",
},
...
};
如果網頁的靜態資源有需要使用CDN(Content Delivery Network),output
欄位值可以再加上publicPath
欄位,來替輸出檔的網址連結加上前綴,如此一來在HTML或是CSS中有用到的靜態資源網址連結就可以被取代為CDN的網址連結。
例如:
const path = require("node:path");
module.exports = {
...
output: {
path: path.join(__dirname, "output"),
publicPath: "https://cdn.example.com/",
filename: "./js/bundle.min.js",
},
...
};
為了確保HTML或是CSS的版本和其連結到的靜態資源的版本是一樣的,我們可以在path
和publicPath
欄位值的字串中使用[fullhash]
,讓Webpack自動用專案的雜湊值來取代它。
例如:
const path = require("node:path");
module.exports = {
...
output: {
path: path.join(__dirname, "output", "[fullhash]"),
publicPath: "https://cdn.example.com/[fullhash]/",
filename: "./js/bundle.min.js",
},
...
};
在打包前先清空目錄
Webpack在打包專案前預設並不會先將輸出目錄清空,因此一不小心就會留有前次產生出來但現在已經沒有用到的檔案。若要讓Webpack將輸出目錄清空,可以將output
欄位的clean
屬性設為true
。
如下:
module.exports = {
...
output: {
...
clean: true,
...
},
...
};
打包JavaScript檔案
Babel
現在開發JavaScript程式大多是靠Node.js,Node.js能夠支援的ECMAScript版本很新(可以查看這個網頁),但是主流的網頁瀏覽器對於比較新的ECMAScript語法並沒有完全支援(可以查看這個網頁)。所以為了使Node.js開發的JavaScript程式能夠在各大網頁瀏覽器上正常運行,我們需要使用「Babel」這套工具來轉換JS語法。
在終端機執行以下指令來安裝babel-loader
、@babel/core
、@babel/register
和@babel/preset-env
這四個套件。
在webpack.config.cjs
設定檔中,於module
欄位的rules
陣列內,加入Babel的載入器(loader)規則。Webpack會去讀取進入點的JS檔案所require
或是import
的檔案,以及這些檔案再引用的檔案(例如被引用的JS檔案又去引用其它的JS檔案,或是被引用的SCSS檔案又去引用其它的SCSS檔案,又或是被引用的CSS檔案又去引用其它的圖片檔案),這些靜態資源的檔案路徑,除了JS檔案外,都必須要在rules
陣列中被匹配到,並指派正確的載入器來負責處理,否則Webpack就無法成功打包專案。
雖然JS檔可不必寫在rules
陣列中來匹配,但是如果我們要指定別的載入器就得要寫了。Babel的載入器規則寫法如下:
module.exports = {
...
module: {
rules: [
...
{
test: /\.js$/i,
use: {
loader: "babel-loader",
options: { presets: ["@babel/preset-env"] },
},
},
...
]
},
...
};
混淆與最小化JS程式
為了避免我們辛苦開發的JavaScript程式碼被輕易盜用,同時也希望Webpack打包出來的JS檔案可以愈小愈好,那就要替Webpack加上terser-webpack-plugin
外掛。Webpack預設就有啟用這個外掛,如果我們要更改它的設定值,就要手動在webpack.config.cjs
設定檔中將其require
進來,不過還要先執行以下指令將terser-webpack-plugin
加入devDependencies
:
在optimization
欄位中,其底下還有個minimizer
欄位,值為一個陣列,接著將terser-webpack-plugin
的實體加進minimizer
欄位的陣列中。
如下:
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
...
optimization: {
minimizer: [
new TerserPlugin({
extractComments: false,
terserOptions: { format: { comments: false } },
}),
]
},
...
};
以上設定可以讓npm run build:webpack
指令所輸出的JS檔案中完全沒有註解。
打包任意檔案
將檔案引用至JavaScript中
JavaScript原先的require
函數和import
關鍵字只能夠引用JavaScript程式,但是在Webpack的框架下,它們要引用什麼樣的類型的檔案都是可以的。因此如果要打包非JS檔案的話,只要在進入點的JS檔案中,加上如以下的程式即可:
import './path/to/file';
檔案載入器
前面有提到過,靜態資源的檔案路徑,除了JS檔案外,都必須要在webpack.config.cjs
設定檔中的rules
陣列內被匹配到,並指派正確的載入器來負責處理,否則Webpack就無法成功打包專案。
Webpack內建檔案載入器,稱為「資產模組」(Asset Module),分為如下幾種類型:
asset/resource
:在輸出目錄產生引用到的檔案並修改URL指到那個檔案。asset/inline
:將檔案內容轉成Data URI,嵌入至程式碼中。asset/source
:將檔案內容直接嵌入至程式碼中。asset
:根據檔案大小自動選擇要用asset/inline
還是asset/resource
來打包檔案。
在webpack.config.cjs
設定檔中,於module
欄位的rules
陣列內,加入資產規則。若是要載入如SVG、JPEG、PNG、GIF、WEBP等網頁常用的圖片格式,以及EOT、WOFF、WOFF2、TTF、OTF、SVG等網頁常用的字型格式的話,規則如下:
module.exports = {
...
module: {
rules: [
...
{
test: /\.(eot|woff|woff2|[ot]tf)$/,
type: "asset/resource",
generator: { filename: "fonts/[name][ext]" },
},
{
test: /.*font.*\.svg$/,
type: "asset/resource",
generator: { filename: "fonts/[name][ext]" },
},
{
test: /^(?!.*font).*\.svg$/,
type: "asset/resource",
generator: { filename: "images/[name][ext]" },
},
{
test: /\.(jpe?g|png|gif|webp)$/,
type: "asset/resource",
generator: { filename: "images/[name][ext]" },
},
...
]
},
...
};
如果想保留靜態資源檔案的檔名和副檔名,就如上面規則中,把generator
的filename
欄位值設為xxx/[name][ext]
。如果不設定filename
欄位,就會以雜湊值當作是輸出檔名,將檔案輸出到輸出目錄底下。
打包SCSS/CSS檔案
SCSS是用來產生CSS的程式,提供比CSS還要更方便的語法結構,也相容原本的CSS。若您原先只用CSS,在使用Webpack時改用SCSS,可以讓CSS變得容易維護許多。SCSS(Sassy CSS)是Sass提供的一種新格式,而Sass原本在用的舊格式也稱為Sass。
Sass/SCSS的官方網站:
為了能夠讓Webpack在打包時候去解析SCSS/CSS檔案,我們不能直接指派檔案載入器來處理SCSS/CSS檔案。
Sass/SCSS/CSS載入器
sass-loader
是一個能夠讀取Sass、SCSS和CSS檔案的載入器,檔案在被讀取後會被轉成CSS格式,可以在終端機執行以下指令來安裝。
在webpack.config.cjs
設定檔中,於module
欄位的rules
陣列內,加入Sass/SCSS/CSS載入器的規則。如下:
module.exports = {
...
module: {
rules: [
...
{
test: /\.(sa|sc|c)ss$/i,
use: ["sass-loader"],
},
...
]
},
...
};
在這邊的規則中,use
欄位值使用了陣列結構,表示要使用多個載入器。接下來繼續再介紹更多相關的載入器。
PostCSS載入器
postcss-loader
是一個能對CSS做前處理的載入器。autoprefixer
是postcss-loader
的外掛,可以自動添加所有網頁瀏覽器的CSS前綴。cssnano
也是postcss-loader
的外掛,可以最小化CSS。在終端機執行以下指令來安裝這些套件:
在webpack.config.cjs
設定檔中,於sass-loader
之上,加入PostCSS載入器。如下:
module.exports = {
...
module: {
rules: [
...
{
test: /\.(sa|sc|c)ss$/i,
use: [
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
require("autoprefixer"),
require("cssnano")({ preset: ["default", { discardComments: { removeAll: true } }] }),
],
},
},
},
"sass-loader",
]
},
...
]
},
...
};
關於autoprefixer
的作用,這邊再舉個例子。例如以下CSS語法:
.example {
display: grid;
transition: all .5s;
user-select: none;
background: linear-gradient(to bottom, white, black);
}
經過postcss-loader
和autoprefixer
處理後會變成:
.example {
display: grid;
-webkit-transition: all .5s;
transition: all .5s;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
background: -webkit-gradient(linear, left top, left bottom, from(white), to(black));
background: linear-gradient(to bottom, white, black);
}
如此一來就不必太擔心同一個CSS到不同的網頁瀏覽器上顯示出來會有很大的不同啦!
CSS載入器
css-loader
才是真正讀取CSS的載入器,它會去尋找CSS中使用@import
或是url()
引用的其它CSS檔案或是圖片檔案,將它們也丟給Webpack一起打包。在終端機執行以下指令來安裝這個套件:
在webpack.config.cjs
設定檔中,於postcss-loader
之上,加入CSS載入器。如下:
module.exports = {
...
module: {
rules: [
...
{
test: /\.(sa|sc|c)ss$/i,
use: [
"css-loader",
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
require("autoprefixer"),
require("cssnano")({ preset: ["default", { discardComments: { removeAll: true } }] }),
],
},
},
},
"sass-loader",
]
}
...
]
},
...
};
輸出CSS
光有CSS載入器的話會把CSS檔案包進JS檔案中一起輸出(若再加上style-loader
,就能讓CSS在JS執行之後被注入到HTML中,但筆者不太喜歡這樣的作法),如果要把CSS檔案從JS檔案中分離出來的話,我們還需要加上mini-css-extract-plugin
這個外掛,可以在終端機執行以下指令來安裝。
在webpack.config.cjs
設定檔中,將mini-css-extract-plugin
的實體加進plugins
欄位的陣列中。
如下:
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
...
plugins: [
...
new MiniCssExtractPlugin({
filename: "./path/to/css-file"
}),
...
],
...
};
filename
欄位用來設定CSS檔案的輸出路徑,其設定方式和JS檔案的filename
是一樣的。如果不設定filename
欄位,預設值會是./[name].css
。
接著也還需要在webpack.config.cjs
設定檔中,於postcss-loader
之上,加入mini-css-extract-plugin
提供的載入器。
如下:
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
...
module: {
rules: [
...
{
test: /\.(sa|sc|c)ss$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
require("autoprefixer"),
require("cssnano")({ preset: ["default", { discardComments: { removeAll: true } }] }),
],
},
},
},
"sass-loader",
]
}
...
]
},
...
};
移除無用的CSS
現在開發網頁很少自己從無到有把CSS寫出來,通常會直接套用如Bootstrap等的CSS框架,但是我們不太可能將CSS框架提供的CSS全都應用上,肯定會留有完全沒有被使用到CSS設定。這些沒有用到的CSS在沒有經過特殊處理的情況下也還是會被網頁瀏覽器給讀取,不僅會佔用伺服器流量,網頁的載入時間也會比較長。
為了移除沒有用到的CSS,我們需要安裝purgecss-webpack-plugin
外掛,以及方便用來取得檔案路徑的glob
套件。可以在終端機執行以下指令來安裝這些套件:
在webpack.config.cjs
設定檔中,將purgecss-webpack-plugin
的實體加進plugins
欄位的陣列中。
如下:
const path = require("node:path");
const glob = require("glob");
const { PurgeCSSPlugin } = require("purgecss-webpack-plugin");
const PATHS = { views: path.resolve(__dirname, "/path/to/html-or-templates-folder") };
module.exports = {
...
plugins: [
...
new PurgeCSSPlugin({
paths: glob.globSync(`${PATHS.views}/**/*`, {nodir: true}),
safelist() {
return {
standard: [],
deep: [],
greedy: [],
};
}
}),
...
],
...
};
以上程式中,用了PATHS
變數來儲存一些固定、可能會被重複使用的路徑。views
這個路徑是用來放置HTML網頁檔案或是HTML模板檔案的目錄。在purgecss-webpack-plugin
外掛的選項中,paths
欄位的值為多個路徑的字串陣列,一個路徑應指向一個純文字檔案,purgecss-webpack-plugin
會自動從這些路徑指到的多個純文字檔案中分析出現過的字詞,並記錄下來。如果CSS的元素名稱並沒有出現在這些字詞中,該元素就會被刪除,除非該名稱存在於白名單之中。
而purgecss-webpack-plugin
的白名單分為三種,用safelist
函數來設定,這三種會被同時使用。
首先是standard
,這個是很單純的名稱對應。例如:
return {
standard: ["example"],
deep: [],
greedy: [],
};
若輸入的CSS為:
.example {
width: 100%;
}
.example p {
font-size: 15px;
}
#example {
width: 100%;
}
.example-2 {
width: 100%;
}
經過白名單的檢查之後,只會剩下:
.example {
width: 100%;
}
#example {
width: 100%;
}
如果將白名單改為:
return {
standard: ["example", "p"],
deep: [],
greedy: [],
};
繼續使用以上的例子,最後它被保留的CSS元素則為:
.example {
width: 100%;
}
.example p {
font-size: 15px;
}
#example {
width: 100%;
}
再來是第二種白名單deep
,這個也是用來對應名稱,但是可以使用正規表示式來匹配元素名稱。例如:
return {
standard: [],
deep: [/^example/],
greedy: [],
};
繼續使用同樣的例子,最後它被保留的CSS元素為:
.example {
width: 100%;
}
#example {
width: 100%;
}
.example-2 {
width: 100%;
}
注意這邊是.example p
被刪除了!因為p
這個名稱並沒有被匹配到。
如果想要讓.example p
也被保留下來,可以使用第三種白名單greedy
,用這個白名單的正規表示式匹配成功的元素名稱,其後面的其它所有名稱都會被忽略。
例如把剛才的deep
白名單直接換成greedy
,繼續使用同樣的例子,最後例子中所有的CSS元素都可以被保留下來。
HTML網頁
Webpack的HTML網頁大致上可以分為兩種,一種是將主要的CSS語法甚至是HTML語法都寫進JavaScript中,而HTML檔案內只有撰寫如下的內容:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Title</title>
<!-- 可能會有簡單的CSS -->
</head>
<body>
<!-- 可能會有讀取畫面 -->
<script type="text/javascript" src="./path/to/bundle.min.js"></script>
</body>
</html>
當網頁瀏覽器開啟這個HTML網頁時,網頁瀏覽器就會下載JavaScript檔案來執行,接著將HTML和CSS注入到HTML網頁中,此時網頁才會顯示出真正的內容。
這種網頁通常會利用大量的JavaScript程式來完成畫面的操作流程,讓使用者在操作網頁時不太需要載入新網頁,但這種網頁在開啟的時候通常會有一個等待時間。這部份的用法可以參考這篇文章:
這篇文章要介紹的是第二種的網頁,就是傳統將HTML語法直接寫在HTML檔案內的作法。例如:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Title</title>
<script type="text/javascript" src="./path/to/bundle.min.js"></script>
<link href="./path/to/bundle.min.css" rel="stylesheet">
</head>
<body>
<main class="container">
<h1 class="mt-5">歡迎</h1>
<p class="lead">這是我用Webpack做的第一個網頁。</p>
<p>如果你也有興趣的話可以瀏覽<a href="https://magiclen.org/webpack/">這篇文章</a>。</p>
</main>
<footer class="footer">
<div class="container">
<span class="text-muted">沒有著作權,歡迎分享</span>
</div>
</footer>
</body>
</html>
其實這種網頁,HTML的部份也不太需要用Webpack來打包,不過放進Webpack框架中有個最大的好處,那就是可以讓它被最小化!
最小化HTML網頁
html-webpack-plugin
外掛可以幫助我們產生HTML檔案(如剛才提到的第一種網頁),或是將現有的HTML檔案最小化。可以在終端機執行以下指令來安裝這個套件:
在webpack.config.cjs
設定檔中,將html-webpack-plugin
的實體加進plugins
欄位的陣列中,有幾個HTML檔案就加幾個html-webpack-plugin
的實體。
如下:
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
...
plugins: [
...
new HtmlWebpackPlugin({
template: "./path/to/original-html-file",
filename: "./path/to/output-html-file",
minify: {
collapseBooleanAttributes: true,
collapseWhitespace: true,
removeAttributeQuotes: true,
removeComments: true,
removeEmptyAttributes: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
minifyCSS: true,
minifyJS: true,
sortAttributes: true,
useShortDoctype: true,
},
inject: false,
}),
...
]
...
};
filename
欄位用來設定HTML檔案的輸出路徑,其設定方式和JS檔案的filename
是一樣的。
如此一來,經過Webpack打包後就會得到最小化的HTML檔案。
把HTML檔案當作進入點來用
事實上有個html-loader
載入器,它可以去解析HTML中有連結用到的靜態資源網址,並且如css-loader
載入器一樣可以做到取代網址的動作。換句話說,利用它可以使HTML檔案也能夠作為進入點,去打包其有用到的靜態檔案。不過這個載入器的問題有點多,不去用它還比較省時間。
舉一些例子來說明常見問題
關於libraryTarget
現在Node.js專案中有以下幾個檔案。
export function greet(name) {
alert(`Hello, ${name}!`);
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Greeting</title>
<script src="./js/bundle.min.js"></script>
</head>
<body>
You should see an alert message.
<script>
greet("Webpack");
</script>
</body>
</html>
const TerserPlugin = require("terser-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
output: {
clean: true,
filename: "./js/bundle.min.js",
},
plugins: [
new HtmlWebpackPlugin({
template: "./views/index.html",
filename: "./index.html",
minify: {
collapseBooleanAttributes: true,
collapseWhitespace: true,
removeAttributeQuotes: true,
removeComments: true,
removeEmptyAttributes: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
minifyCSS: true,
minifyJS: true,
sortAttributes: true,
useShortDoctype: true,
},
inject: false,
}),
],
module: {
rules: [
{
test: /\.js$/i,
use: {
loader: "babel-loader",
options: { presets: ["@babel/preset-env"] },
},
},
],
},
optimization: {
minimizer: [
new TerserPlugin({
extractComments: false,
terserOptions: { format: { comments: false } },
}),
]
},
};
記得用以下指令安裝所需的套件:
在執行npm run build:webpack
之後,用網頁瀏覽器開啟dist/index.html
。我們預期會看到一個對話框,上面寫著Hello, Webpack
,然而實際上並沒有,而且網頁瀏覽器的主控台(console)還跳出錯誤訊息,說是greet
函數沒有定義。
這個其實是因為我們在webpack.config.cjs
的output
欄位中,並沒有去設定library
欄位的值,如果我們將library
欄位的值設為lib
,如下:
module.exports = {
...
output: {
...
library: "lib",
...
}
...
};
就可以將HTML語法改成以下這樣來呼叫到greet
函數。
...
<body>
...
<script>
lib.greet("Webpack");
</script>
</body>
...
為什麼會這樣?這是因為若我們沒有在webpack.config.cjs
的output
欄位中設定libraryTarget
欄位的值,這個值預設為var
,在var
模式下,用來設定全域變數的library
欄位的值若沒有設定,就無法正常使用了,因為沒有一個JS全域變數有儲存到我們的JS模組。
如果要將我們的JS模組指派給網頁瀏覽器下的window
物件來儲存的話,可以將libraryTarget
欄位的值改為window
,此時的library
欄位的值就代表我們的JS模組會存在window
物件的哪個欄位。例如:
module.exports = {
...
output: {
...
libraryTarget: "window",
library: "lib"
...
}
...
};
...
<body>
...
<script>
window.lib.greet("Webpack");
</script>
</body>
...
不過由於網頁中的JavaScript會自動去看沒有被定義的變數是否是window
物件的屬性,所以HTML中的JS程式其實也可以寫成最一開始的模樣:
...
<body>
...
<script>
lib.greet("Webpack");
</script>
</body>
...
若libraryTarget
欄位的值為window
,但沒有設定library
欄位的值的話,我們的JS模組內有被暴露(export)的項目會被塞給window
物件。如此一來就可以直接在網頁中呼叫greet
函數了!
...
<body>
...
<script>
greet("Webpack");
</script>
</body>
...
不過最多人喜歡使用的libraryTarget
欄位值應該是umd
,它除了有支援root(即window
或global
,要看webpack.config.cjs
的target
是設為Node.js還是網頁瀏覽器,預設為web
,即網頁瀏覽器)外,還支援AMD(Asynchronous Module Definition,用define
來引用模組;用return
來暴露)、CommonJS(用require
來引用模組;用module.exports
或exports
來暴露)等JS模組化的機制。
如果library
欄位值為lib
,它的JS輸出格式會像是這樣:
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object') // CommonJS
module.exports = factory();
else if(typeof define === 'function' && define.amd) // AMD
define([], factory);
else if(typeof exports === 'object') // CommonJS
exports["lib"] = factory();
else // root
root["lib"] = factory();
})(window /* or global */, ...);
如果library
欄位沒有設定,它的JS輸出格式會像是這樣:
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object') // CommonJS
module.exports = factory();
else if(typeof define === 'function' && define.amd) // AMD
define([], factory);
else { // root
var a = factory();
for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i];
}
})(window /* or global */, ...);
在Webpack中用Vue.js
Vue.js是一個非常方便、可在前端或後端運作的框架兼模板引擎。
現在Node.js專案中有以下幾個檔案。
import * as Vue from "vue";
const main = {
data() {
return { message: "Hello, Webpack!" };
},
mounted() {
alert("Mounted!");
},
};
export function init() {
Vue.createApp(main).mount("#main");
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Greeting</title>
<script src="./js/bundle.min.js"></script>
</head>
<body>
<div id="main">
{{message}}
</div>
<script>
init();
</script>
</body>
</html>
const TerserPlugin = require("terser-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
output: {
clean: true,
filename: "./js/bundle.min.js",
libraryTarget: "window",
},
plugins: [
new HtmlWebpackPlugin({
template: "./views/index.html",
filename: "./index.html",
minify: {
collapseBooleanAttributes: true,
collapseWhitespace: true,
removeAttributeQuotes: true,
removeComments: true,
removeEmptyAttributes: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
minifyCSS: true,
minifyJS: true,
sortAttributes: true,
useShortDoctype: true,
},
inject: false,
}),
],
module: {
rules: [
{
test: /\.js$/i,
use: {
loader: "babel-loader",
options: { presets: ["@babel/preset-env"] },
},
},
],
},
optimization: {
minimizer: [
new TerserPlugin({
extractComments: false,
terserOptions: { format: { comments: false } },
}),
]
},
};
記得用以下指令安裝所需的套件:
也記得要安裝Vue.js:
在執行npm run build:webpack
之後,用網頁瀏覽器開啟dist/index.html
。我們會看到一個對話框,上面寫著Mounted!
。
用Webpack打包Font Awesome
Font Awesome是非常多人用的Icon Font,但是它在Webpack下使用時有個地方需要注意,否則無法正常打包。
現在Node.js專案中有以下幾個檔案。
@import "~@fortawesome/fontawesome-free/scss/fontawesome";
@import "~@fortawesome/fontawesome-free/scss/solid";
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Apple</title>
<link href="./css/bundle.min.css" rel="stylesheet">
</head>
<body>
<i class="fas fa-apple-alt fa-10x"></i>
</body>
</html>
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./src/index.scss",
output: {
clean: true,
},
plugins: [
new HtmlWebpackPlugin({
template: "./views/index.html",
filename: "./index.html",
minify: {
collapseBooleanAttributes: true,
collapseWhitespace: true,
removeAttributeQuotes: true,
removeComments: true,
removeEmptyAttributes: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
minifyCSS: true,
minifyJS: true,
sortAttributes: true,
useShortDoctype: true,
},
inject: false,
}),
new MiniCssExtractPlugin({ filename: "./css/bundle.min.css" }),
],
module: {
rules: [
{
test: /\.(sa|sc|c)ss$/i,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
require("autoprefixer"),
require("cssnano")({ preset: ["default", { discardComments: { removeAll: true } }] }),
],
},
},
},
"sass-loader",
],
},
{
test: /\.(eot|woff|woff2|[ot]tf)$/,
type: "asset/resource",
generator: { filename: "fonts/[name][ext]" },
},
{
test: /.*font.*\.svg$/,
type: "asset/resource",
generator: { filename: "fonts/[name][ext]" },
},
],
},
};
記得用以下指令安裝所需的套件:
也記得要安裝Font Awesome:
但在執行npm run build:webpack
時,會出現如下的錯誤訊息:
這個是因為Sass/SCSS在使用@import
關鍵字引用其它Sass/SCSS檔案(不包含CSS檔案)時,貌似是直接拿目標檔案的內容來取代掉那行@import
關鍵字,所以如果目標檔案的內容有用到相對路徑就會出問題。解決的方法有兩種,第一種就是改成去@import
CSS檔案(如果有的話),如下:
@import "~@fortawesome/fontawesome-free/css/fontawesome.css";
@import "~@fortawesome/fontawesome-free/css/solid.css";
第二種就是去設定Sass/SCSS中儲存著路徑前綴的變數,以Font Awesome來說,這個變數為$fa-font-path
。如下:
$fa-font-path: "~@fortawesome/fontawesome-free/webfonts/";
@import "~@fortawesome/fontawesome-free/scss/fontawesome";
@import "~@fortawesome/fontawesome-free/scss/solid";
改寫之後npm run build:webpack
就可以正常被執行。用網頁瀏覽器開啟dist/index.html
,就可以看到大大的蘋果圖示。