為什麼網頁原始碼看起來都像亂碼#
打開大型網站的「檢視原始碼」,常會看到擠成一團、變數名只剩單字母的 JavaScript。這並不是工程師故意刁難讀者,而是部署前對程式碼做過兩道處理:最小化(Minify)與混淆(Uglify)。這兩個動作雖然方向略有差異,目的卻一致——讓送到瀏覽器的檔案盡可能小、載入盡可能快。
Minify 與 Uglify 的差別#
兩者常被合稱,但概念並不完全相同。
- 最小化(Minify):在不影響程式行為的前提下,移除多餘字元,例如空白、換行、註解、可省略的分號等。
- 混淆(Uglify):在最小化的基礎上更進一步,把長變數名、函式名與參數替換為短字元,甚至調整邏輯順序,讓檔案更小,同時也讓他人較難直接閱讀或抄襲。
換句話說,Minify 著重在「壓縮體積」,Uglify 則包含了壓縮與「降低可讀性」兩個層面。
三劍客都能 Minify#
很多人以為這套處理只適用於 JavaScript,其實 HTML、CSS、JavaScript 三者都能做最小化:
- JavaScript 透過 UglifyJS、Terser、esbuild 等工具處理。
- HTML 可透過 HTML Minifier 移除註解與空白。
- CSS 可透過 CSS Minifier 壓縮選擇器、合併規則。
效果都是相同的方向:讓檔案在不影響語意的前提下變小。
用打包工具自動化#
實際開發中不會手動把每個檔案丟進線上服務。主流的做法是讓 bundler(如 Webpack、Vite、esbuild、Rollup)在打包流程中自動執行最小化。
Webpack 的設定大致如下:
// webpack.config.js
const path = require("path");
const webpack = require("webpack");
module.exports = {
entry: ["./src/index"],
output: {
path: path.join(__dirname, "dist"),
filename: "bundle.js",
},
plugins: [
new webpack.optimize.UglifyJsPlugin({
compressor: { warnings: false },
}),
],
};像 create-react-app、Next.js、Vite 等現代框架或樣板,預設就已經把 Minify 與 Uglify 放進 production build,多數情況下不需要額外設定。
Source Map:保留 debug 能力#
最小化後的程式碼幾乎無法閱讀,發生錯誤時自然也很難對應回原始檔。為了解決這個問題,2011 年提出了 Source Map 規範。
Source Map 是一個記錄「原始碼與壓縮後程式碼之間對應關係」的檔案。當瀏覽器開啟 DevTools 時,會載入這個檔案,把錯誤堆疊與斷點還原到開發者熟悉的版本。
一份 Source Map 通常包含這些欄位:
version:Source Map 的版本。file:壓縮後檔案的名稱。sources:原始檔的路徑陣列。sourceRoot:sources的根目錄。names:原始檔中變數與函式名稱。mappings:以 Base64 VLQ 編碼的對應關係字串。
這個格式不需要人工閱讀,瀏覽器看得懂即可。
透過 UglifyJS 產生 Source Map#
除了 bundler,也可以直接用 CLI 工具產生:
uglifyjs input.js -o script.min.js \
--source-map script.js.map \
--source-map-root http://example.com/js -c -m壓縮後的檔案尾端會出現一行註解,告訴瀏覽器到哪裡載入 Source Map:
//# sourceMappingURL=/path/to/script.js.map
也可以改用 HTTP 標頭 X-SourceMap 指定路徑。最後在瀏覽器 DevTools 設定中確認 JavaScript 與 CSS 的 Source Map 功能有開啟即可。
因為瀏覽器只在 DevTools 開啟時才下載 Source Map,所以對一般使用者的載入效能不會造成影響,只是 build time 會稍微變長。
Benchmark 帶來的直觀感受#
只說「會變小」沒什麼說服力,看數字最直接。社群上有 minification-benchmarks ↗ 專門比較各家工具的效果。以 React 為例,原始大小約 72.1 KB,經過主流 Minifier 壓縮後可以縮到一半以下,這還沒算上後續的 Gzip 壓縮。
如果想體驗速度差異,可以試試 esbuild:
import esbuild from "esbuild";
export default async ({ code }) => {
const minified = await esbuild.transform(code, {
minify: true,
sourcemap: false,
legalComments: "none",
});
return minified.code;
};esbuild 在打包與壓縮速度上的優勢非常明顯,跑過一次就會有感。
小結#
Code Minify 與 Uglify 是現代 web 開發中幾乎不需要思考就會啟用的優化。重點不在於懂得手動執行哪個工具,而在於理解兩件事:
- 為什麼要做:減少傳輸體積,直接縮短載入時間。
- 如何維持 debug 體驗:靠 Source Map 把錯誤資訊還原到原始檔。
把這兩件事釐清,剩下的就交給 bundler 預設值處理即可。