在前端通常會導入一些方便的 utility 函式庫,以 lodash
來說,它是一個夠幫我們處理各種資料的函式庫,可以減少寫一些比較瑣碎的程式碼,至今每週都將近 4000 萬次下載。但是因為 lodash
的歷史較為久遠,在 2012 年就已經被開源,到現在已經接近快 10 年,可能因為各種歷史因素,導致目前 lodash
並不是使用 es module。
不是使用 es module 的套件就會面臨一個問題「webpack 沒有辦法發揮 tree shaking 的功能」,因為 webpack 的 tree shaking 只能使用在符合 es module 規範的程式碼。所以,在沒有 tree shaking 的情況下,就有可能會把很多沒用到的函式庫一起打包進 bundle 中。而在使用 lodash
時就要特別注意 import 的方式,不同的方式就會導致不一樣的結果。
接下來,我們就來看看, 用不同的方式 import lodash 會有什麼不一樣的結果,在 Next.js 裡面要怎麼優化呢?
我們需要一個乾淨的 Next.js 環境測試 用不同的方式 import lodash 會有什麼不一樣的結果,使用以下指定建立一個新的專案:
yarn create next-app analyze-import-lodash
建立完後進入專案資料夾,安裝我們需要的 lodash
:
yarn add lodash
在分析之前,先來介紹一款分析打包結果的視覺化套件 — webpack bundle analyzer,有了這套工具我們就可以清楚地從視覺化的圖案知道每次打包後的檔案大小,可以用來比較不同的 import
方式對 bundle size 會有什麼樣的影響。
在 Next.js 中使用的不是原始的 webpack-bundle-analyzer
,而是官方包裝過一層的 @next/bundle-analyzer
,因為在 Next.js 中設定 webpack 的方式不太一樣,所以為了減少設定的流程,官方另外開源了這個套件,專門在 Next.js 中使用。我們使用以下指令安裝這個套件:
yarn add -D @next/bundle-analyzer
接著,我們修改 next.config.js
的設定:
const withBundleAnalyzer = require("@next/bundle-analyzer")({
enabled: process.env.ANALYZE === "true",
});
module.exports = withBundleAnalyzer({
reactStrictMode: true,
});
如果想要分析打包後的結果,可以運行以下指令,在執行完後就會打開像是上圖的兩個網頁,分別為 客戶端與伺服器端的打包後的程式碼:
ANALYZE=true yarn build
不指定 function 路徑也就像我們平常使用 named exports 的 module 一樣,我們稍微修改 pages/index.js
的程式碼,在這個頁面中使用 isEmpty
判斷 title
是否為空的程式碼:
import { isEmpty } from "lodash";
const Home = ({ title }) => (
<div>{isEmpty(title) ? "Title is Empty!!" : title}</div>
);
export default Home;
在各位讀者還沒看到結果之前,心裡想的只有幾行程式碼的應用,bundle size 應該不會太大才對。如果是這樣想的話,接下來看到的應該會讓你大吃一驚。
接著,我們用 webpack bundle analyzer 看看使用這種 import 方式的 bundle size 為多少。
![lodash 原始 bundle size]](https://i.imgur.com/TQ1sjoI.png)
很誇張的是,明明只有用到 isEmpty
這個 function,結果 lodash 打包 Next.js 後的檔案大小卻足足有 531KB, 不禁讓人懷疑 [isEmpty](https://github.com/lodash/lodash/blob/master/isEmpty.js)
是多麽偉大的 function ?,做了包山包海的事情。
isEmpty 的原始碼: lodash/isEmpty.js
接下來,我們換一種 import
的方式,看看對 bundle size 會有什麼影響:
import isEmpty from "lodash/isEmpty";
const Home = ({ title }) => (
<div>{isEmpty(title) ? "Title is Empty!!" : title}</div>
);
export default Home;
修改完後,再次執行 ANALYZE=true yarn build
,讓 webpack-bundle-analyzer
分析打包後的結果。天哪,換一種方式結果讓 lodash
打包後的大小足足少了 22 倍,這是什麼魔法?
我們首先要知道 lodash
是一個使用 UMD (Universal Module Definition) 的套件,這意味著 lodash
並不滿足在 webpack 中的 tree-shaking 必須是 es module 的條件。所以第一種方法實際上會載入完整的 lodash
,最終導致 bundle size 莫名的巨大;而第二種方法就是只載入一個檔案,再從檔案中拿出我們需要的 isEmpty
,如此一來就不用擔心載入整包 lodash
的問題。
可是如果都要像第二種方法這樣寫 code 實際上有點麻煩,而且團隊可能一開始沒考慮到這個問題,程式碼有很多地方都使用第一種方法 import lodash,改起來十分麻煩。
以下提供兩種我認為比較簡易的解法,可以用最少量的配置,達到降低 bundle size 的方法。
這也是 lodash 的 GitHub 提到的作法,lodash 的 GitHub 中寫道:「Looking for Lodash modules written in ES6 or smaller bundle sizes? Check out lodash-es.」,所以第一種解法便是改用 lodash-es
:
// 下載 lodash-es
yarn add lodash-es
// 修改程式碼 lodash 的引用,變成使用 lodash-es
import { isEmpty } from 'lodash-es';
你可以看到 bundle size 順利地從 531KB 降低到 24KB 左右,與上面提到的第二種 import 的方法有異曲同工之妙。
如果你不想動到大量的程式碼,上面使用 lodash-es
意味者必須全域取代 lodash
的引用,其實有另一個解法是使用 babel 的插件,讓 babel 幫我們從第一種 import
的方式改成第二種。
首先,安裝 babel-plugin-import 這個插件:
yarn add -D babel-plugin-import
然後修改 .babelrc
中的設定:
再用 webpack bundle analyzer 看看打包後的檔案大小,可以看到檔案打小與第二種 import
的方式一樣都是 24.31KB,同樣成功地降低 bundle size。
在這篇文章中我們了解了如何透過 webpack bundle analyzer 分析打包後的檔案,並且透過這個工具看到不同 import lodash
方式對於 bundle size 的影響。
針對如何降低 lodash
被打包後的檔案大小,本文提供兩種方式,分別是使用 lodash-es
全域取代原本 lodash
的引用,或使用 babel-plugin-import 非侵入式的改動大量的程式碼,而是在打包時處理,這兩種方式都可以達到不把完整的 lodash 都打包進 bundle 的結果,就看各位如何選擇囉!