阅读 857

如何分析及优化React Native中的JavaScript bundles

在使用react-native进行开发的过程中,也许会因为开发阶段的忙碌而忽略一些小细节,导致最后打包出来的bundle size不尽如人意。

本文将提出在JavaScript层面,优化react-native最终打包尺寸的几种方案。

裁剪具体模块的尺寸

首先是进行具体模块的剪裁,因为在开发阶段会忙于实现功能而忽略掉一些package的引用。

在这里我们需要用到对于react-native的bundle可视化工具,针对可视化的结果来进行具体模块/文件内容的优化。

bundle-analyzer

react-native中推荐使用react-native-bundle-visualizer进行bundle的查看。

执行如下命令进行安装:

yarn add --dev react-native-bundle-visualizer
复制代码

并运行它:

yarn run react-native-bundle-visualizer
复制代码

如果没有跑出具体内容,则需要手动添加入口文件。例如,--entry-file ./index.android.js。

执行结果如下图的官方介绍所示,会把node_modules和源文件中打包出来的代码尺寸都包含在内,可以清晰地看出哪些文件占用的空间比较大。

react-native-bundle-visualizer的原理是使用了source-map-explorer进行了Metro bundler的可视化输出。

  1. Metro: MetorReact Native官方的打包程序,会生成对应的bundle文件。
  2. Source-map-explorer: 在react中或者是使用webpack等工具打包出来的内容,都可以使用与source-map-explorer相关的一些打包分析工具进行可视化内容查看。

使用bundle分析工具,可以比较明显地辨识出哪些业务文件大小比较异常、需要进行优化,或者是引用了哪些Javascript库,导致bundle膨胀。

在实际的开发中,我们通常会发现如下两个package的尺寸很大,而且在代码中的运用也许不是很多,那么我们需要针对lodash和moment等库进行替换。

Momentjs、Lodashjs库的替换

在进行模块的替换之前,我们需要用到一个插件Import Cost

Import Cost衡量引入模块代价

VSCode插件 Import Cost 可以衡量当前引入文件大小。其他IDE也有该插件,或者可以直接从npm进行安装使用。Import Cost VSCode插件 - 从VSCode市场进行安装

第三方Import Cost IDE插件

该插件可以计算importrequire引用的模块大小,目前支持:

  • 默认import:
import Func from 'utils';
复制代码
  • 整个文件import:
import * as Utils from 'utils';
复制代码
  • 部分import:
import {Func} from 'utils';
复制代码
  • 别名方式的部分import:
import {orig as alias} from 'utils';
复制代码
  • 子模块import:
import Func from 'utils/Func';
复制代码
  • Require:
const Func = require('utils').Func;
复制代码
  • 支持JavascriptTypescript

moment存在的问题与解决

moment是一个常用的JavaScript日期处理类库,它支持多语言的日期格式化。

moment的核心代码只有52kb,但是包含了全世界语言的本地化文件,也就是说当你使用其中的功能时,也包含了很多你用不到的特性。

对应的解决方案是你可以通过npm安装moment-mini,该库非官方维护,但暴露了官方的moment-min.js作为npm模块开源使用。

或者你可以直接使用一些更为简洁的JavaScript日期格式化类库。

作为momentjs的替代方案,你可以使用以下3个类库,或者直接使用JavaScript的原生API来做日期国际化:

  1. luxon
  2. date-fns
  3. day.js
  4. JavaScript Internationalization API

如下对比来自You-Dont-Need-Momentjs

NameSize original/gzippedTree-shakingPopularity (stars)Methods richnessPatternTimezone SupportLocale
Moment.js329K/69.6KNo43.4kHighOOGood (moment-timezone)123
Luxon59.9K/17.2KNo9kHighOOGood (Intl)-
date-fns78.4k/13.4k without tree-shakingYes21.3kHighFunctionalGood (date-fns-tz)64
dayjs6.5k/2.6k without pluginsNo25.8kHighOONot yet130

如果不需要引入日期国际化,dayjs核心代码只有7.1k,可以作为momentjs的替代。

lodash存在的问题与解决

lodash是一个实用性非常高的JavaScript工具库,可以对array、object、string等值进行操作和检测等等,还具有一些非常实用的函数。

但lodash类库所占用的空间达到了71K,而且也存在很多你用不上的方法。实际上,我们在使用中或许只会用到非常少的几个函数。

官方虽然也提供了lodash-cli这样的工具,让使用方可以针对具体的某些函数进行打包,但官方是不推荐这种用法的,并且在新的版本中也取消了这样的部分模块打包方式。

官方推荐的方式是,在引用时指定对应的函数,这样最终打包时只会打包对应的函数。

如下所示,如果直接引用lodash,大小时71K。

import get from 'lodash' // 71K (gzipped: 24.7K)
复制代码

如果引用对应的函数,那么所需要的空间会大大减少。

import get from 'lodash/get' // 8.2K (gzipped: 2.5K)
复制代码

但lodash依然有很多存在依赖关系的内部函数需要一起打包进去。

如果你仅仅是使用到这个实用库类的部分工具函数,那么可以用一些体积更小的工具包进行优化,或者直接使用对应的原生实现方式进行替换。

You don't (may not) need Lodash/Underscore这个项目中,作者罗列了可以直接替代Lodash/Underscore库类中函数的原生代码实现。例如debounce函数。

Create a new function that calls func with thisArg and args.

function debounce(func, wait, immediate) {
  var timeout;
  return function() {
  	var context = this, args = arguments;
  	clearTimeout(timeout);
  	timeout = setTimeout(function() {
  		timeout = null;
  		if (!immediate) func.apply(context, args);
  	}, wait);
  	if (immediate && !timeout) func.apply(context, args);
  };
}

// Avoid costly calculations while the window size is in flux.
jQuery(window).on('resize', debounce(calculateLayout, 150));
复制代码

把项目中涉及到的工具库类函数直接用原生代码替换,不失为一个很好的解决方案。

替换不必要的模块

这里的替换掉不必要的组件/模块,更多地是从业务逻辑方面来说的

如果已经引用的库里面存在某些业务逻辑功能,或者有公用的组件已经实现了对应的功能,那么我们应该进行替换,删除掉多余的业务内代码。

同样的,检查下package.json文件中也许会存在未使用的包,或者是重复功能。

在开发阶段,也许会存在引用了某些库类,随着业务变化,又在具体逻辑中删除了引用,但未清除彻底,导致package中还有残余,却给bundle size带来了一定的负担。

也或者是同上面lodash和moment库,可以通过用一些更简单的库,或者自己实现几个常用功能来进行整个模块的替换。

总结如下:

  1. 使用现有组件替换不必要的组件/模块

  2. 优化package.json,删除不必要的node_modules模块,使用其他模块替代

优化base64图片大小

在优化代码的过程中发现,文件中存在base64过长的问题,甚至是base64出现在了一些不符合使用base64场景的地方。

base64适用场景

base64更适合出现在一些重复使用的背景图片,或者尺寸极小的ICON的情形,而一些较大的图片则适合使用PNG或者JPEG。

PNG是无损的,JPEG是有损的。如果不需要背景透明,那么把PNG转换为JPEG会更节省空间。

如何优化base64图片

  1. 剪裁图片大小

设计师给出的图片一般会比较大,而实际应用中不需要这么大的图片,可以适当地进行图片大小的裁剪。

可以使用在线的PS工具进行图片的裁剪,如果是MAC可以直接使用自带的软件进行图片大小的裁剪。

  1. 压缩图片质量

笔者一般会使用tinypng进行图片压缩,反复上传直到无法压缩为止。

VSCode中也有使用该网站进行快速压缩的插件。

该网站压缩效果比较好,一般来说肉眼看不出压缩前后差别。

经过以上两个步骤以后,base64的图片字节数会明显减少很多。如果字节数还是很大,那么应该考虑是否不适合使用base64进行展示。

总结

进行bundle分析后,可以明显地找出尺寸异常的文件或者模块,进行对应的优化,从大的层面上进行分析与尺寸优化:

  1. 根据bundle分析裁剪具体模块,结合Import Cost分析模块引用代价;
  2. 替换不合理的库类引用

粗粒度的优化后,剩下的有关逻辑的代码优化,就跟平时的编写有关。从小的层面上进行优化需要:

  1. 从逻辑上分析不必要存在的库类/模块引用;
  2. 编写逻辑代码时,需要更加注重保持代码行数的简洁;
  3. 提取常用功能为公用组件进行使用

参考

如何"有效"减少js包的体积