背景
在开发微信小程序的过程中,随着业务逻辑日渐庞大之后,突显了一些问题。
首先我们发现在 dev mode 时,本地包大小已经达到了 4m+,这种情况下,已经无法在 dev mode 使用真机调试了。
其次此时,小程序 build 后也有 1.8M 左右。而且后续还有相当多的业务需求需要开发,包大小肯定会更大。
这时候就想要优化小程序包大小。下面分享一下我的定位过程和解决思路。尽管我们使用 uni-app 开发,但思路是通用的,希望能给大家一些帮助吧。
如何减小包大小
代码分析
首先分析包大在哪儿了。
打开本地代码目录查看文件大小。可以发现 common/vendor.js 和 page,components 中 js 占了大部分。
在 build 编译模式下,代码压缩已经启用了,需要思考别的优化方式。这时候可以使用webpack-bundle-analyzer
插件。它可以帮助分析 vendor.js 中都有哪些 js 模块,哪些模块比较大,以便我们进一步优化代码
通过这个插件,发现了下面两个问题。
问题一: uni-app 自定义组件模式编译 tree shaking 无效
如果不是使用 uni-app 开发可以跳过这一段
通过代码分析发现有些模块应该被 tree shaking 但却被打包进来了。基本确定是 tree shaking 没有生效。
同样是 webpack4 + babel7。在不使用 uni-app,直接使用 vue-cli create 项目的前提下,tree shaking 是没有问题的。而使用 uni-app 去新建项目,tree shaking 却无效。
排查 babel 配置时发现是由于 uni-app 在创建项目的时候,设置了 modules: 'commonjs'导致。修改后,demo 的 tree shaking ok。但是回到项目里一编译,又出错了。继续定位发现是 uni-app 自定义组件模式编译问题。目前uni-app已经修复了我提的bug,虽然还未正式发布。
当然你不使用 uni-app 自定义组件模式编译也可以解决,uni-app 还支持template模板模式
,但是会有一些开发差异和性能差距,有兴趣可以看下这篇文章
问题二:部分库不支持 tree shaking
有些库(比如 lodash)本身并没有使用 import/export,所以 webpack 并不能对它们 tree shaking。这些库我们可以分情况优化。
首先可以找下网上是否有库对应的 esm 版本可以替代,如 lodash-es。
其次可以从代码分析中看出,如果库的每个模块都在不同文件中,入口文件只是一个统一入口,那么我们就可以通过修改写法按需加载,如
import add from "lodash/add";
import Button from 'ant-design-vue/lib/button';
我们也可以使用babel-plugin-import
插件针对那些库统一实现按需加载,它的本质是在编译时统一按配置修改加载路径,不需要自己手动去修改代码。
最后如果都不行,那要么接受,要么自己重写为社区做贡献~
规范模块开发
为了免除无法 tree shaking 的烦恼,我们在开发 npm 模块的时候也需要遵循一定的规范,从而减少模块打包后的大小。
同时支持 commonjs 和 es module
我们的模块需要同时支持 commonjs 和 es module。这样才能既满足 commonjs 开发的用户,又支持 tree shaking。
如何实现呢?如果你的代码是 typescript,以@sentry/browser 为例,可以在编译时编译 cjs 和 esm 两种规范代码,如下
// package.json"build": "run-s build:dist build:esm build:bundle","build:bundle": "rollup --config","build:dist": "tsc -p tsconfig.build.json","build:esm": "tsc -p tsconfig.esm.json",
然后在 package.json 中指定两个入口以及无副作用标识
"main": "dist/index.js", "module": "esm/index.js", "sideEffects": false,
这样当 webpack 解析模块(解析规则),就会按需优先解析 esm 目录。并且当识别到无副作用时进行 tree shaking。
如果你的代码本身就是 es6,你也可以这样
"module": "src/index.js",
第三方自定义组件
如果使用了第三方微信自定义组件,由于引用是在 json 文件,所以 webpack 在编译时并不能通过 entry 分析到相关文件,因此不会对其进行编译、压缩等。这时候就需要我们自己处理。而且由于 webpack 不处理,tree shaking 自然也无法支持,因此建议尽量避免这种方式引用组件。
分包
小程序分包也是一种常规的优化方案。
通过分析后,可以将一些较大的页面划分为子包。如果有单页依赖第三方自定义组件,而且第三方组件还挺大,也可以考虑将该页面划分为子包。也因此尽量避免将第三方自定义组件放在 globalStyle,不然没法将它放到子包去。
大图不要打包
小程序中的大图,尽量避免打包进来,应该放到 CDN 通过 url 加载。我们的做法是在开发时加载本地图片,在 CI/CD 环节自动化发布图片,并改写地址。
如何解决真机调试问题
首先还是查看编译后的文件,发现common/vendor.js
巨大,足有 1.5M。其次pages
和components
也有 1.4M,而这其中占了 js 的大小又占了绝大部分。
为什么 js 文件这么大呢?主要是因为在 dev mode 默认并没有压缩,当然也没有 tree shaking。
我的选择是修改编译配置,在 dev mode 压缩 js 代码。本地代码减少到了 2M。预览大小则是减少到了 1.4M。参考配置如下:
// vue.config.js
configureWebpack: () => { if (isDev && isMp) { return {
optimization: {
minimize: true,
},
}
}
}
这看上去并不是个好方案,但确实简单有效。也考虑过分包,但分包并不能解决 common/vendor.js 巨大的问题,预览时包还是很大。如果有其它好的办法也欢迎留言~
能介绍一下在 CI/CD 环节自动化发布图片,并改写地址这块是怎么操作的吗?