Webpack是JavaScript的模組建置工具,運行在Node.js上,它可以將零散的JavaScript檔案用各式工具優化並打包起來,加快網頁的載入時間。Webpack也並不限於用在JavaScript上,它除了還能打包網頁有用到的靜態資源(如JS、CSS、圖片檔等)外,也還能透過HTML模板來產生HTML網頁。
本篇文章不會介紹用Webpack打包檔案的方式,如果您還不熟悉這部份的話,請先閱讀這篇文章:
Webpack的HTML網頁大致上可以分為兩種,一種是傳統將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打包。
一種是將主要的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注入(inject)到HTML網頁中,此時網頁才會顯示出真正的內容。本篇文章將會介紹多種不同的HTML模板引擎來應用這種方式產生出HTML網頁。
Handlebars
Handlebars是個HTML模板引擎,被廣泛用於JavaScript生態圈,由於它相容於原本的HTML,而且又多了模板引用機制(partial),因此非常適合拿來切版。
Handlebars的官方網站:
在這篇文章介紹過的Webpack基本用法,這邊就不再詳細說明了。現在就讓我們把Handlebars模板引擎加進Webpack專案吧!
Handlebars 載入器
為了能夠讓Webpack打包並處理Handlebars的模板檔案,我們需要使用handlebars-loader
這個Handlebars載入器。可以在終端機執行以下指令來安裝:
在webpack.config.cjs
設定檔中,於module
欄位的rules
陣列內,加入Handlebars載入器的規則。若是要載入副檔名為.hbs
或.handlebars
的檔案的話,規則如下:
module.exports = {
...
module: {
rules: [
...
{
test: /\.(hbs|handlebars)$/i,
use: "handlebars-loader",
}
...
]
}
...
};
加入html-webpack-plugin
外掛
html-webpack-plugin
外掛可以幫助我們產生HTML檔案,或是將現有的HTML檔案最小化。這個在先前的文章中也有介紹過。
可以在終端機執行以下指令來安裝html-webpack-plugin
:
在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,
})
...
]
...
};
至於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網頁後,會去讀取並執行./path/to/bundle.min.js
,而這個JavaScript程式內就有我們用Handlebars產生出來的HTML語法,其會被JavaScript程式注入到現有的HTML網頁中。
HTML注入
我們必須要自己撰寫HTML注入的JavaScript程式。最基本的方式就是替網頁瀏覽器的document
加上DOMContentLoaded
事件,並在此事件的回呼(callback)函數中去建立或搜尋要修改的元素,然後修改其擁有的innerHTML
欄位值。
例如要將某個Handlebars的模板檔案編譯出來的HTML語法注入到HTML的<body>
中,Webpack的JavaScript進入點可以這樣寫:
import template from "./path/to/handlebars-file";
document.addEventListener("DOMContentLoaded", () => {
document.body.innerHTML = template();
});
例如要在HTML的<body>
中新增一個id為main
的<div>
,並且把某個Handlebars的模板檔案編譯出來的HTML語法注入到這個<div>
中,Webpack的JavaScript進入點可以這樣寫:
import template from "./path/to/handlebars-file";
document.addEventListener("DOMContentLoaded", () => {
const divMain = document.createElement("div");
divMain.id = "main";
divMain.innerText = template();
document.body.appendChild(divMain);
});
Handlebars的上下文(context)
如果Handlebars的模板有挖空格的話,例如:
Hello, {{something}}!
我們可以在JavaScript程式中,將Handlebars的上下文物件,作為參數傳給引用Handlebars的模板檔案後所得到的函數。如下:
import template from "../views/index.hbs";
document.addEventListener("DOMContentLoaded", () => {
const context = { something: "world" };
document.body.innerHTML = template(context);
});
更改模板引用(partial)的目錄
如果要使用Handlebars的模板引用機制,預設的partial模板搜尋路徑是在使用這個partial模板的Handlebars模板的同一個目錄下。由於許多人會把partial模板分開來存放,若要修改partial模板的存放目錄,可以在webpack.config.cjs
設定檔的Handlebars載入器規則中,添加query
欄位,這個欄位值是一個物件,該物件的partialDirs
欄位可以用來設定partial模板的多個存放目錄。
如下:
const path = require("node:path");
module.exports = {
...
module: {
rules: [
...
{
test: /\.(hbs|handlebars)$/i,
use: {
loader: "handlebars-loader",
options: { query: { partialDirs: [path.join(__dirname, "views", "partials")] } },
},
}
...
]
}
...
};
自訂Handlebars的Helper
handlebars-loader
提供了Handlebars的Helper的自動引用機制,並將Helper模組化。如果想使用自訂的Helper,可以在webpack.config.cjs
設定檔的Handlebars載入器規則中,添加query
欄位,這個欄位值是一個物件,該物件的helperDirs
欄位可以用來設定Helper模組的多個存放目錄。
如下:
const path = require("node:path");
module.exports = {
...
module: {
rules: [
...
{
test: /\.(hbs|handlebars)$/i,
use: {
loader: "handlebars-loader",
options: { helperDirs: [path.join(__dirname, "views", "helpers")] },
},
}
...
]
}
...
};
只要存在於Helper目錄中的JavaScript模組,都會被自動引用。Helper模組的實作方式如下:
import Handlebars from "handlebars/dist/handlebars.runtime";
export default function link(text, url) {
text = Handlebars.escapeExpression(text);
url = Handlebars.escapeExpression(url);
return new Handlebars.SafeString(`<a href="${url}">${text}</a>`);
}
如果將以上模組存成link.js
,則這個Helper的名稱為link
。這個Helper可以用來將一個文字和一個網址組成擁有超連結的HTML文字。
呼叫方式如下:
My website is {{{link 'MagicLen' 'https://magiclen.org'}}}.
React
React用的JSX是一種將JavaScript程式和HTML語法結合在一起的模板,非常適合用於大型專案。
React的官方網站:
我們也可以讓Webpack直接支援JSX。不過在這篇文章介紹過的Webpack基本用法,這邊就不再詳細說明了。現在就讓我們把JSX加進Webpack專案吧!
替Babel加上@babel/preset-react
套件
在終端機執行以下指令來安裝@babel/preset-react
套件:
修改webpack.config.cjs
設定檔中的Babel載入器的規則,如下:
module.exports = {
...
module: {
rules: [
...
{
test: /\.jsx?$/i,
use: {
loader: "babel-loader",
options: {
presets: [
"@babel/preset-env",
"@babel/preset-react",
],
},
},
}
...
]
}
...
};
如此一來就可以直接在.js
檔案中撰寫JSX了,或者也可以用.jsx
檔案。
安裝React套件
為了要能夠在JSX中使用到React的模組,我們還需要安裝react
和react-dom
這兩個套件。指令如下:
加入html-webpack-plugin
外掛
html-webpack-plugin
外掛的加入方式可以參考文章上面的Handlebars部份。
至於HTML的話,React似乎比較習慣弄一個頂層的<div>
(id通常會叫作root
或是app
)來操作底下的DOM,而不是直接去控制<body>
。
所以HTML網頁可以寫成如下這樣:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Title</title>
</head>
<body>
<div id="root"></div>
<script type="text/javascript" src="./path/to/bundle.min.js"></script>
</body>
</html>
JSX的Hello World
我們可以直接在Webpack的進入點試寫一個JSX的Hello World網頁,程式碼如下:
import React from "react";
import ReactDOM from "react-dom";
ReactDOM.render(<h1>Hello, world!</h1>, document.getElementById("root"));