為什麼網頁原始碼看起來都像亂碼#

打開大型網站的「檢視原始碼」,常會看到擠成一團、變數名只剩單字母的 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:原始檔的路徑陣列。
  • sourceRootsources 的根目錄。
  • 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 預設值處理即可。

原文出處#