Day25 - 如何在 Next.js 中正确地使用 lodash,使用 babel-plugin-import

前言

在前端通常会导入一些方便的 utility 函式库,以 lodash 来说,它是一个够帮我们处理各种资料的函式库,可以减少写一些比较琐碎的程序码,至今每周都将近 4000 万次下载。但是因为 lodash 的历史较为久远,在 2012 年就已经被开源,到现在已经接近快 10 年,可能因为各种历史因素,导致目前 lodash 并不是使用 es module。

lodash 下载次数

不是使用 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 (@next/bundle-analyzer**)**

在分析之前,先来介绍一款分析打包结果的视觉化套件 — webpack bundle analyzer,有了这套工具我们就可以清楚地从视觉化的图案知道每次打包後的档案大小,可以用来比较不同的  import  方式对 bundle size 会有什麽样的影响。

https://miro.medium.com/max/1400/0*UFykTIFSbESy4hsC.gif

在 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

分析用不同的方式 import lodash 的结果

第一种方式:如一般使用函式库的方式 named import lodash

不指定 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

第二种方式:指定 function 的路径

接下来,我们换一种  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/isEmpty

为什麽两种 import 的方式会导致 bundle size 不一样

我们首先要知道  lodash  是一个使用  UMD (Universal Module Definition)  的套件,这意味着  lodash  并不满足在 webpack 中的  tree-shaking  必须是 es module 的条件。所以第一种方法实际上会载入完整的  lodash ,最终导致 bundle size 莫名的巨大;而第二种方法就是只载入一个档案,再从档案中拿出我们需要的  isEmpty,如此一来就不用担心载入整包  lodash  的问题。

可是如果都要像第二种方法这样写 code 实际上有点麻烦,而且团队可能一开始没考虑到这个问题,程序码有很多地方都使用第一种方法 import lodash,改起来十分麻烦。

以下提供两种我认为比较简易的解法,可以用最少量的配置,达到降低 bundle size 的方法。

使用 lodash-es 而不是 lodash

这也是 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

使用 babel-plugin-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。

babel-plugin-import

结论

在这篇文章中我们了解了如何透过 webpack bundle analyzer 分析打包後的档案,并且透过这个工具看到不同  import lodash  方式对於 bundle size 的影响。

针对如何降低  lodash  被打包後的档案大小,本文提供两种方式,分别是使用  lodash-es  全域取代原本  lodash  的引用,或使用 babel-plugin-import 非侵入式的改动大量的程序码,而是在打包时处理,这两种方式都可以达到不把完整的 lodash 都打包进 bundle 的结果,就看各位如何选择罗!

Reference


<<:  Day 28 - 创意构想2 - 电子礼券存摺

>>:  30天学会 Python-Day24: 影像处理

那些被忽略但很好用的 Web API / CustomEvent

自己的事件自己决定。 网页最重要的两件事,资讯显示与使用者交互,而使用者交互在页面中所代表的行为就...

RISC-V: Memory Store 指令

昨天已经把 Memory Write 的功能做完了, 今天稍微轻松一点,就来完成 Memory St...

台湾游戏公司最新营收 | 第三季游戏公司营运「冻能」 | 蛤! 神逆转! 老大换人了 老二雄风不再

台湾游戏公司最新营收 | 第三季游戏公司营运「冻能」 | 蛤! 神逆转! 老大换人了 老二雄风不再...

Day10 - 基础篇总结 ,CI/CD 的功用为何 ?

如何实现一套基础开发流程? 在前面的章节里谈到了 GCP、Docker、Cloud Run 等技术,...

CSS float

前言 使用float可以让元素浮起来~原本block会撑满父元素的宽,现在不会发生了,变得有点像in...