跳至主要內容
Skip to content

Vue 打包與資源優化

Vue 專案的性能問題,不只發生在畫面更新時,也可能發生在使用者還沒看到畫面之前:bundle 太大、第三方庫太重、圖片字體太肥、快取策略錯誤、source map 暴露、chunk 拆得不合理,都會拖慢首屏與後續路由切換。

一句話回答:打包與資源優化的核心,是減少首屏必須下載與執行的資源,讓非必要程式碼延後載入,並透過壓縮、CDN、快取與依賴治理降低傳輸和解析成本;常見手段包含路由懶載入、組件懶載入、Tree Shaking、第三方庫按需導入、chunk 拆分、gzip/brotli、hash 檔名與 source map 策略。


一、 必考觀念

1. 打包優化解決什麼問題?

打包與資源優化不只是讓 build 變快,它更關心使用者端:

問題影響
JS 太大下載慢、解析慢、執行慢
CSS 太大下載慢、可能阻塞渲染
圖片太重LCP 慢、流量浪費
字體太重文字顯示延遲、可能造成版面跳動
chunk 拆分不合理首屏太重或切頁時卡
快取策略錯誤每次都重新下載,或使用者拿到舊資源
source map 處理不當產物過大或暴露原始碼

所以打包優化要回答三個問題:

text
少下載什麼?
晚下載什麼?
下載後能不能快取?

2. 不要只看檔案大小,也要看執行成本

同樣是 300KB,有些庫只是工具函數,有些庫會在載入後做大量初始化。

要同時看:

  • transfer size:網路傳輸大小。
  • parsed size:解壓後大小。
  • parse / compile time:瀏覽器解析 JS 的時間。
  • execute time:執行初始化的時間。
  • 是否阻塞首屏。

因此 CDN、gzip、brotli 可以降低傳輸成本,但不能消除 JS parse/execute 成本。

3. 先用工具分析,不要憑感覺猜

常用工具:

  • Chrome DevTools Network。
  • Chrome DevTools Coverage。
  • Lighthouse。
  • rollup-plugin-visualizer
  • webpack-bundle-analyzer。
  • Vite build report 或 Rollup 輸出。

要先知道:

  • 主包多大?
  • 哪些依賴最大?
  • 有沒有重複依賴?
  • 哪些頁面進了首屏 bundle?
  • 哪些 chunk 被過早下載?
  • gzip/brotli 後大小是多少?

沒有 bundle analyzer 的優化,很多時候只是在猜。


二、 分包與懶載入

1. 路由懶載入

Vue Router 最常見的分包方式是路由懶載入:

typescript
const routes = [
  {
    path: '/',
    component: () => import('@/views/HomePage.vue'),
  },
  {
    path: '/admin',
    component: () => import('@/views/AdminPage.vue'),
  },
]

這樣 AdminPage 不會進入首頁首屏必須下載的主包。

適合懶載入:

  • 後台管理頁。
  • 低頻功能頁。
  • 大型報表頁。
  • 使用者點進去才需要的頁面。

延伸讀:Vue 路由實現:Hash 路由與 History 路由原理

2. 組件懶載入

不是只有路由可以懶載入,重型組件也可以:

typescript
import { defineAsyncComponent } from 'vue'

const RichEditor = defineAsyncComponent(() => {
  return import('@/components/RichEditor.vue')
})

適合:

  • 富文本編輯器。
  • 圖表。
  • 地圖。
  • PDF 預覽。
  • 程式碼編輯器。
  • 非首屏 Modal。

如果某個 Modal 很少打開,就沒有必要一進頁面就把它的所有程式碼下載下來。

3. chunk 不是越碎越好

分包有副作用。

chunk 太大:

  • 首屏下載重。
  • 初次 parse / execute 慢。

chunk 太碎:

  • 請求數變多。
  • 路由切換可能等很多小檔。
  • 快取命中與載入順序更難控制。
  • HTTP/1.1 環境下更明顯。

比較好的原則是:

  • 首屏必要程式碼放首屏。
  • 低頻功能延後。
  • 大型第三方庫獨立出來。
  • 共用依賴穩定快取。
  • 不要為了拆而拆。

4. 預取與預載要克制

可以在使用者即將需要某個路由時預取 chunk。

例如:

  • hover 選單時預取。
  • 首屏空閒後預取高機率下一頁。
  • 登入後預取常用頁面。

但不要把所有路由都 prefetch。那會搶首屏頻寬,讓「懶載入」失去意義。


三、 Tree Shaking 與按需導入

1. Tree Shaking 是什麼?

Tree Shaking 是打包工具移除未使用程式碼的能力。

它通常更依賴 ESM:

typescript
export function add(a: number, b: number) {
  return a + b
}

export function multiply(a: number, b: number) {
  return a * b
}

如果只使用 add

typescript
import { add } from './math'

打包工具有機會移除未使用的 multiply

2. CommonJS 對 Tree Shaking 較不友好

CommonJS 是執行時模組系統,靜態分析比較困難。

javascript
const utils = require('./utils')

相比之下,ESM 的 import / export 是靜態結構,打包工具更容易分析哪些 export 沒被使用。

面試時可以說:

Tree Shaking 的效果和模組格式、打包工具、套件的 side effects 標記、實際 import 寫法都有關,不是只要開 build 就一定完全生效。

3. 工具庫按需導入

不理想:

typescript
import _ from 'lodash'

更好:

typescript
import debounce from 'lodash-es/debounce'

或使用支援 ESM 與按需導入的套件。

常見要注意的依賴:

  • 工具庫。
  • 日期庫。
  • UI library。
  • icon library。
  • 圖表庫。
  • 地圖 SDK。
  • 富文本編輯器。

4. UI library 與 icon 要避免全量引入

不理想:

typescript
import ElementPlus from 'element-plus'
app.use(ElementPlus)

如果專案只用少量組件,應考慮按需引入或自動導入。

icon 也一樣,不要為了幾個圖示把整包 icon 全打進來。

這類問題用 bundle analyzer 最容易看出來。


四、 第三方依賴治理

1. 找出真正的大依賴

常見大依賴:

  • 圖表庫。
  • 地圖 SDK。
  • 富文本編輯器。
  • PDF / Excel 解析。
  • 日期與時區處理。
  • 語法高亮。
  • 程式碼編輯器。

處理方式:

  • 是否能按需載入?
  • 是否能換更小的套件?
  • 是否能延後到使用者操作後載入?
  • 是否能放到獨立 chunk?
  • 是否真的需要這個功能?

2. 避免重複依賴

例如專案裡同時出現多個版本:

text
lodash@4.17.x
lodash-es@4.17.x
dayjs
moment

或 A 套件帶一份日期庫,B 套件又帶另一份。

這時可以檢查:

  • lockfile。
  • bundle analyzer。
  • package manager dedupe。
  • 是否能統一套件版本。
  • 是否能移除重疊功能。

3. 日期庫、圖表庫、編輯器要特別小心

例如 moment 常見問題是語系包與時區資料可能很大。

如果只是簡單格式化日期,可能不需要引入重型方案。可以考慮:

  • 原生 Intl.DateTimeFormat
  • dayjs。
  • date-fns。
  • 只引入必要 locale。

圖表與編輯器則通常適合動態載入:

typescript
async function openEditor() {
  const { createEditor } = await import('@/features/editor/createEditor')

  createEditor()
}

這樣使用者沒有打開編輯器時,就不用支付下載與初始化成本。


五、 傳輸、壓縮與快取

1. gzip 與 brotli 解決什麼?

gzip / brotli 解決的是傳輸大小。

text
原始 JS: 900KB
gzip 後: 260KB
brotli 後: 220KB

但瀏覽器下載後仍然要解壓、解析、編譯與執行 JS。

所以:

  • gzip/brotli 可以讓下載更快。
  • 不能解決 JS 執行太久。
  • 不能替代分包與依賴治理。

2. CDN 解決什麼?

CDN 主要解決:

  • 使用者離資源伺服器太遠。
  • 靜態資源下載慢。
  • 原站壓力過大。
  • 快取命中率不足。

適合放 CDN:

  • JS/CSS 靜態產物。
  • 圖片。
  • 字體。
  • 可公開快取的靜態資源。

但如果瓶頸是 API 慢或 JS 執行慢,CDN 只能解決其中一部分。

3. hash 檔名與快取策略

打包後常見產物:

text
assets/index.8f3a2c1.js
assets/vendor.91ab33d.css

檔名帶 hash 的好處是:內容變了,檔名也變。

典型快取策略:

text
index.html: no-cache 或短快取
assets/*.js: max-age=31536000, immutable
assets/*.css: max-age=31536000, immutable
assets/*.{png,jpg,webp,avif,woff2}: 長快取

為什麼 index.html 不適合長快取?

因為它是資源入口。如果它被長快取,使用者可能一直拿到舊的 JS/CSS 路徑。

4. 圖片與字體也是資源優化

圖片常常比 JS 更大。

優化方向:

  • 使用 WebP / AVIF。
  • 使用正確尺寸。
  • 非首屏圖片 lazy loading。
  • 首屏 LCP 圖片適度 preload 或提高 priority。
  • 圖片 CDN 動態裁切。
  • SVG icon 按需載入。

字體優化:

  • 只載入需要的字重。
  • 使用 font-display: swap
  • 子集化字體。
  • 避免過多 webfont。

六、 Vite 與 webpack 優化差異

1. Vite 的開發與生產不同

Vite 開發環境主要利用瀏覽器原生 ESM,並用 esbuild 做依賴預打包。

生產建置則使用 Rollup 打包。

所以 Vite 優化常見方向:

  • 檢查依賴預打包。
  • 使用 dynamic import。
  • Rollup manualChunks
  • 依賴按需導入。
  • 圖片與靜態資源處理。
  • bundle visualizer。

2. webpack 常見優化方向

webpack 專案常見方向:

  • splitChunks 拆公共依賴。
  • 持久化 cache。
  • loader 範圍縮小。
  • thread-loader。
  • babel-loader cache。
  • dynamic import。
  • externals。
  • webpack-bundle-analyzer。

要分清楚「建置速度」與「使用者載入速度」:

方向主要影響
cache / thread-loaderbuild 速度
splitChunks / dynamic import使用者載入與快取
Tree Shaking / minify產物體積
CDN / gzip / brotli傳輸速度

3. happypack 是舊時代方案

有些面試資料會提到 happypack。它是比較早期 webpack 多執行緒建置優化方案。

現在更常見的是:

  • webpack 5 cache。
  • thread-loader。
  • esbuild / swc 類工具。
  • Vite + esbuild。
  • Rollup chunk 拆分。

面試時可以說:

happypack 偏舊,現在如果是 webpack 5 會優先看持久化 cache、thread-loader 或更快的編譯器;如果是 Vite,則主要看 esbuild 預打包、Rollup 分包與依賴治理。


七、 Source Map 與安全

1. production 要不要開 source map?

答案不是絕對。

開 source map 的好處:

  • 線上錯誤能還原到原始碼位置。
  • 方便排查 stack trace。
  • 搭配 Sentry 等平台更好定位問題。

風險:

  • 可能暴露原始碼。
  • 產物與上傳流程更複雜。
  • 如果公開部署 .map,外部也能讀到更多程式細節。

2. 常見做法:上傳但不公開

成熟做法通常是:

text
build 產生 source map
-> 上傳到錯誤監控平台
-> 部署靜態資源時不公開 .map

這樣線上錯誤平台能還原 stack,但一般使用者不能直接下載 source map。

3. source map 也是效能與安全取捨

source map 不一定會被瀏覽器主動下載,但如果公開存在,仍有安全與資訊暴露問題。

面試時可以說:

production source map 不是不能開,而是要看錯誤追蹤需求與安全策略。常見做法是產生 source map 並上傳到監控平台,部署時不要公開暴露。


八、 追問題庫

Q1:主包 2MB,要怎麼排查?

先用 bundle analyzer 看主包由哪些模組組成。

接著檢查:

  • 路由是否懶載入。
  • 大型頁面是否進了首屏 bundle。
  • UI library 是否全量引入。
  • icon 是否全量引入。
  • 圖表、地圖、編輯器是否可以延後載入。
  • 是否有重複依賴。
  • 是否有未使用的 polyfill 或工具庫。
  • gzip/brotli 後大小是多少。

不要只說「開 gzip」,因為主包 2MB 往往也代表 parse/execute 成本很高。

Q2:路由懶載入後切頁變慢怎麼辦?

這是分包的典型取捨。

可以考慮:

  • 對高機率路由做預取。
  • hover 或 idle 時載入下一頁 chunk。
  • 把公共依賴抽成穩定 chunk。
  • 避免把單一路由拆成太多小 chunk。
  • 對重型組件加 loading 狀態。

懶載入減少首屏成本,但可能把成本延到切頁時,必須根據使用者路徑調整。

Q3:CDN 和 gzip 能不能解決 JS 執行慢?

不能完全解決。

CDN 和 gzip/brotli 主要降低下載時間。JS 下載後仍然要解壓、解析、編譯與執行。

如果瓶頸是 main thread long task,就要靠減少 JS、延後初始化、分包、Web Worker 或拆掉重型依賴。

Q4:Tree Shaking 為什麼沒有生效?

可能原因:

  • 套件使用 CommonJS。
  • import 寫法導致全量引入。
  • 套件 side effects 標記不正確。
  • 模組本身有頂層副作用。
  • 打包工具設定沒有啟用 production 優化。
  • 使用的庫不支援良好的 ESM 輸出。

Q5:source map 上線安全嗎?

要看怎麼上線。

如果公開部署 .map 檔,可能暴露原始碼細節。比較好的做法是產生 source map,上傳到錯誤監控平台,部署時不公開。


九、 實作題

題目:Vue SPA 首屏 JS 很大,你怎麼優化?

可以這樣回答:

  1. 先用 Network 看 JS transfer size、parsed size 與載入順序。
  2. 用 bundle analyzer 找出最大依賴與主包內容。
  3. 檢查路由是否懶載入,非首屏頁面不要進主包。
  4. 檢查重型組件是否可以用 defineAsyncComponent 延後載入。
  5. 檢查 UI library、icon、工具庫是否全量引入。
  6. 檢查圖表、地圖、編輯器是否能按需載入。
  7. 檢查是否有重複依賴或不必要 polyfill。
  8. 設定 gzip/brotli、CDN、hash 檔名與長快取。
  9. 檢查 source map 是否正確處理,不公開暴露。
  10. 優化後重新量測 FCP、LCP、JS parse/execute 與主包大小。

這題的重點是:先分析 bundle,再根據依賴和路由拆分處理,不是只背「懶載入、gzip、CDN」。


十、 資深視角

1. 打包優化要看使用者路徑

不是所有程式碼都要最小化到極致。

優先處理:

  • 首屏必須下載的資源。
  • 高流量路由。
  • 轉換關鍵頁。
  • 使用者高頻操作功能。
  • 低頻但很重的第三方模組。

如果某個功能只有少數人用,就更適合延後載入,而不是進主包。

2. 分包策略要兼顧快取

好的分包不只是檔案變小,也要讓快取穩定。

例如:

  • vendor chunk 變動少,可以長快取。
  • 業務頁面 chunk 變動較頻繁。
  • 首屏 chunk 控制在合理大小。
  • 公共依賴不要因為小改動就頻繁改 hash。

但也不要過度迷信 vendor chunk。大型依賴是否應該抽出,要看使用路由、更新頻率和實際載入瀑布圖。

3. 優化要避免把問題轉移

常見副作用:

  • 拆包後首屏變快,但切頁變慢。
  • 過度 prefetch 搶首屏頻寬。
  • 外部 CDN 掛掉影響可用性。
  • source map 處理不當暴露原始碼。
  • 動態載入太多造成 loading 體驗破碎。

資深回答要能說出:這個方案解決哪個瓶頸,又把成本移到哪裡。


總結

問題回答重點
打包優化解決什麼下載體積、解析執行、快取命中與首屏資源控制
怎麼減少首屏 JS路由懶載入、組件懶載入、第三方庫按需導入
Tree Shaking 關鍵ESM、side effects、import 寫法、production build
第三方依賴怎麼治理bundle analyzer、重複依賴、重型庫延後載入
傳輸怎麼優化gzip/brotli、CDN、hash 檔名、長快取
source map 怎麼處理可產生並上傳監控平台,但不一定公開部署

一句完整的面試回答可以是:

Vue 打包與資源優化我會先用 Network 和 bundle analyzer 找出首屏主包大小、最大依賴和載入瀑布圖。常見做法是路由懶載入、重型組件用 dynamic import 或 defineAsyncComponent 延後載入,UI library、icon、工具庫按需導入,避免圖表、地圖、編輯器進入主包。接著會檢查 Tree Shaking 是否生效、是否有重複依賴,再配合 gzip/brotli、CDN、hash 檔名和長快取降低傳輸成本。source map 則依錯誤追蹤需求產生並上傳監控平台,但通常不公開暴露。最後要重新量測首屏、切頁與 JS parse/execute,避免只是把成本從首屏轉移到別的地方。

延伸閱讀