|
| 1 | +--- |
| 2 | +source-updated-at: 2025-05-29T18:05:49.000Z |
| 3 | +translation-updated-at: 2025-05-29T19:09:20.948Z |
| 4 | +title: Next.js 8 的 Webpack 記憶體優化 |
| 5 | +description: >- |
| 6 | + 近期推出的 Next.js 8 版本帶來了大幅的建置時期記憶體使用量降低。本篇部落格文章將探討我們如何協助社群優化 webpack。 |
| 7 | +author: |
| 8 | + - name: Connor Davis |
| 9 | + image: /static/team/connor.jpg |
| 10 | + - name: Tim Neutkens |
| 11 | + image: /static/team/tim.jpg |
| 12 | +date: 2019-02-19T14:00:00.000Z |
| 13 | +image: >- |
| 14 | + https://h8DxKfmAPhn8O0p3.public.blob.vercel-storage.com/static/blog/webpack-memory/twitter-card.png |
| 15 | +--- |
| 16 | + |
| 17 | +近期我們推出了 [Next.js 8](/blog/next-8)。這個版本包含了大幅降低建置時期記憶體使用量的改進。本篇部落格文章將探討我們如何協助社群優化 webpack。 |
| 18 | + |
| 19 | +Next.js 採用零配置設計,並建構於 [webpack](https://webpack.js.org/) 和 [Babel](https://babeljs.io) 等工具之上。其目標是讓您專注於最重要的部分:您的應用程式程式碼。 |
| 20 | + |
| 21 | +現代網頁應用程式通常由一個或多個頁面組成。例如首頁、部落格、儀表板或產品列表。 |
| 22 | + |
| 23 | +在 Next.js 中,這些頁面會成為專案根目錄下 `pages` 資料夾中的檔案。 |
| 24 | + |
| 25 | +例如:檔案 `pages/about.js` 會對應到網址 `/about`。 |
| 26 | + |
| 27 | +這個框架的關鍵設計限制之一,是必須同時適用於單一頁面和數千個頁面的情況。 |
| 28 | + |
| 29 | +在實作 [Serverless Next.js](/blog/next-8#serverless-nextjs) 時,我們很快發現對擁有數百個頁面的專案執行 `next build` 會導致高記憶體使用量。有時甚至會超過 Node.js 約 1.4 GB 的記憶體堆積限制。 |
| 30 | + |
| 31 | +我們開始使用 Chrome 開發者工具分析建置過程的記憶體使用情況。 |
| 32 | + |
| 33 | +在分析結果中,我們發現 webpack 會一次性分配 **548 MB** 的記憶體區塊。 |
| 34 | + |
| 35 | +記憶體分配量與頁面數量直接相關,意味著更多頁面會導致更高的記憶體使用量。 |
| 36 | + |
| 37 | + |
| 38 | + |
| 39 | +Chrome 開發者工具的記憶體分析器顯示一次性分配了 548 MB |
| 40 | + |
| 41 | +透過檢查記憶體分析堆疊追蹤,我們成功定位到導致記憶體分配激增的函式。 |
| 42 | + |
| 43 | +分配行為源自呼叫 [`source.source()` 方法](https://github.com/webpack/webpack/blob/v4.28.4/lib/Compiler.js#L334),該方法會生成結果檔案並將其儲存至記憶體中。 |
| 44 | + |
| 45 | +然而進一步查看呼叫 `source()` 方法的函式,可以發現 [`compilation.assets`](https://github.com/webpack/webpack/blob/v4.28.4/lib/Compiler.js#L316) 是使用 `asyncLib.forEach` 進行遍歷的。這意味著[提供的函式](https://github.com/webpack/webpack/blob/v4.28.4/lib/Compiler.js#L317)會同時對 `compilation.assets` 陣列中的每個檔案進行呼叫。 |
| 46 | + |
| 47 | +因此,這表示如果有 100 個頁面,且每個頁面都需要寫入磁碟,上述程式碼會嘗試同時寫入所有 100 個頁面,包括同時生成所有 100 個檔案。 |
| 48 | + |
| 49 | +解決此問題的方法是使用[信號量 (semaphore)](https://en.wikipedia.org/wiki/Semaphore_\(programming\)) 來限制並行寫入的數量。通常我們會使用 [async-sema](https://github.com/vercel/async-sema) 來實現,但在這個案例中,webpack 已經在 [neo-async](https://github.com/webpack/webpack/blob/v4.28.4/lib/Compiler.js#L8) 上提供了合適的方法: |
| 50 | + |
| 51 | +``` |
| 52 | +asyncLib.forEach(compilation.assets, (source, file, callback) => { |
| 53 | + // 等等 |
| 54 | +}); |
| 55 | +``` |
| 56 | + |
| 57 | +> 先前會對所有資源同時執行函式的程式碼 |
| 58 | +
|
| 59 | +``` |
| 60 | +asyncLib.forEachLimit(compilation.assets, 15, (source, file, callback) => { |
| 61 | + // 等等 |
| 62 | +}); |
| 63 | +``` |
| 64 | + |
| 65 | +> 新程式碼限制每次最多同時執行 15 個函式 |
| 66 | +
|
| 67 | +實作這個並行限制後,我們再次分析建置記憶體使用情況。可以看到記憶體分配被拆分為 **34 MB** 的小區塊。 |
| 68 | + |
| 69 | + |
| 70 | + |
| 71 | +分析器現在顯示隨時間分配 34 MB 的區塊 |
| 72 | + |
| 73 | +這個變更顯示了非常樂觀的結果,但在實際操作中,建置仍然會耗盡記憶體,因此我們持續進行分析和調查問題。 |
| 74 | + |
| 75 | +透過進一步檢查記憶體分析,我們注意到在呼叫 [`source.source()` 方法](https://github.com/webpack/webpack/blob/v4.28.4/lib/Compiler.js#L334) 後,記憶體並未被清理(垃圾回收)。 |
| 76 | + |
| 77 | +在 webpack 中,資源通常是 [Source 類別](https://github.com/webpack/webpack-sources)的實例。這些類別都實作了會生成檔案來源的 `source()` 方法。 |
| 78 | + |
| 79 | +分析顯示許多資源是 [`CachedSource`](https://github.com/webpack/webpack-sources#cachedsource) 的實例。`CachedSource` 的運作方式是當呼叫 `source()` 時,結果會被快取在記憶體中,直到資源被釋放。 |
| 80 | + |
| 81 | +檢查 Next.js 使用的 webpack 插件後,發現我們沒有在 webpack 寫入檔案後呼叫 `source()` 的插件,這意味著快取寫入值沒有任何好處。 |
| 82 | + |
| 83 | +在與 [Tobias Koppers](https://twitter.com/wSokra) [合作](https://github.com/webpack/webpack/pull/8609) 後,他[實作了一個名為 `output.futureEmitAssets` 的新選項](https://github.com/webpack/webpack/pull/8642),允許選擇啟用新的資源寫入行為。 |
| 84 | + |
| 85 | +採用這個新行為後,隨時間分配的區塊減少到 **_182 KB_**。 |
| 86 | + |
| 87 | + |
| 88 | + |
| 89 | +所有優化後,分析器顯示隨時間分配 184 KB 的區塊 |
| 90 | + |
| 91 | +[Next.js 8](/blog/next-8) 已經內建了所有這些優化。使用 Next.js 時無需進行任何變更。 |
| 92 | + |
| 93 | +這項優化是在 webpack 上實作的,意味著不僅是 Next.js 使用者,所有 webpack 使用者都將受益於這些優化。 |
| 94 | + |
| 95 | +我們將持續積極改進 Next.js 和 webpack 的記憶體使用與效能表現。 |
0 commit comments