在 web 开发场景,减小代码体积虽然是性能优化的一个方向,还没到锱铢必较的程度。可是在小程序场景,因为代码包上传阶段限制了主包 2M 和总包 16M(近期微信官方正在内测将总包上限调整至 20M )的尺寸,超过就会面临没法发版的风险,代码包体积的优化就变得特别重要了。
京喜小程序首页做为微信购物的大入口,承载大量流量,功能复杂模块众多,又要与其余核心业务和公共组件共享 2M 的主包空间,所以代码包瘦身的工做在持续不断进行,不然没法知足业务的快速增加。本文将结合以往优化策略与最近一次的瘦身实践,分享小程序代码瘦身的经验与思考。css
京喜首页项目是一个优化良好的项目,对于常见的优化措施,已经有过很好的实践,就让咱们咱们先回顾一下有哪些常见的优化策略吧:git
此外,京喜首页团队还针对 Taro 开发场景进行了以下优化:github
在开始正式介绍瘦身实践以前,咱们先来明确一下代码包体积的衡量标准和统计方式吧。web
小程序上传代码以代码包尺寸为准,所谓 2M 的限制,就是指该尺寸不能超过 2048KB。npm
从信息传输角度来讲,Gzip 等压缩工具能够进行不少信息化编码优化,所以一些内容重复是能够容忍的,可是因为咱们的目标是为了解决小程序上传限制,就只有对代码包尺寸锱铢必较了。json
在开发者工具-详情-基本信息-上次预览或上次上传,能够查看到最近一次的代码包体积,本文接下来所介绍的优化都是以缩小这个体积为目的。小程序
可是代码上传生成模板速度很慢,若是每次都要根据这里的数据来统计体积变化,效率过低了。微信小程序
在未改动项目配置的状况下,咱们就能够间接以代码目录的文件体积大小做为变化参照。怎么方便的统计文件体积呢?这里我用了tree-cli,利用它提供的参数,能够输出具有尺寸统计和排序功能的代码文件清单:浏览器
npm install -g tree-cli // 目标目录 cd target-directory // 输出文件为 size-analysis.md tree -s --sort=size -o size-analysis.md
清单内容格式以下:sass
. ├── [ 1000] index.js ├── [ 500] index.wxss ├── [ 500] index.wxml ├── [ 500] index.json ├── [ 4000] components │ ├── [ 4000] child │ │ ├── [ 1000] index.js │ │ ├── [ 1000] index.wxml │ │ ├── [ 1000] index.wxss └── └── └── [ 1000] index.json 6500 bytes used in 2 directories, 8 files
前面说到京喜首页优化措施都作的很好了,下面即将分享的是一些不那么常见的优化方式,优化空间有大有小,想要优化小程序代码包,建议先尽可能完成前文提到的优化方案,这样得到的收益最明显,而后再来看接下来提到的这些方式吧~
小程序文档内关于继承样式的说明为:继承样式,如font
,color
, 会从组件外继承到组件内。
分析项目现状,咱们一般会把字体定义放在公共 css 文件内,随着页面或组件引入公共 css,font 也将被重复引入,能够经过改造,把 font 的定义仅放在 app.wxss 内,取消组件和页面的引入,能够达到减小总体代码包体积的目的。
关于这一项首页项目体积减小1%,预估整个项目还有 20kb 左右的 font 定义可清理。
若是有全局的颜色定义,也能够进行相似的优化。
做为 web 开发者,对 -webkit- 这种前缀必定不陌生,为了适配不一样浏览器内核,一般咱们会在编译阶段使用 autoprefixer 进行样式的自动补全。
而小程序开发者工具也提供了样式补全的能力:详情-本地设置-能够勾选「上传代码时样式自动补全」
这个补全和咱们在编译时作的有什么不一样吗?
关键在于它实现的时机:若是是本地模板上传前,那么应该和咱们编译的补全效果同样;若是是在上传模板后,也许能够借此减掉补全内容所占的尺寸。
结合小程序代码包传递过程和样式补全时机,大概有如下3种状况:
阶段一补全:
阶段二:
或者是阶段三:
为了验证猜测,来作一个实验吧,比较「 项目编译不补全样式+开发者工具设置样式补全」 vs「 项目编译补全样式+开发者工具不设置样式补全」,模板体积统计以下:
可见前者比后者少了 58kb,这说明,开发者工具提供的样式补全不是在阶段一作的,否则模板体积应该和咱们本身作的编译补全基本一致。
那么,就能够愉快的去掉编译补全,使用小程序开发者工具提供的能力了。
不过这样改动会出现一个小问题,开发者工具内的样式是未经补全处理的,个别样式会有点问题,测试就发现 mask-border-source 无效,而相应真机由于已添加样式补全没有问题。为了避免出现预览误会,建议给这种还没有支持的样式手动写上 -webkit- 前缀,保证开发和真机表现一致。
sass/less 等工具使得 css 的编写变得更加流畅,函数和变量的引入也让 css 有了一点工程化的意味。可是你有没有观察过 sass 的编译实现呢?
// a.scss,做为被引用方 .banner { // 样式定义 color: red; } $COLOR = red // 变量定义(函数定义相似) // b.scss,做为使用方 @import 'a.scss'; .banner_wrapper { background: white; color:$COLOR; }
关注b.sass的编译后:
// a.scss的引用消失了,内容被整合到文件内 .banner { // a.scss内的样式定义会被拷贝进来 color: red; } .banner_wrapper { background: white; color:red; //变量定义会被按值替换 }
这里出现的问题是:咱们是否须要.banner
被拷贝进来呢
为了不多引入不须要的样式定义,有如下几个方向:
而在小程序场景,wxss 语法支持 @import,实现了极弱版的模块化,使得咱们能够再加一个角度解决上面的问题:
京喜首页项目使用 Taro 开发,须要适配 H5/微信小程序/QQ小程序等多端场景,利用 Taro 提供的环境变量能力,能够在方法内部实现多端差别处理,好比下面这段:
init(){ if(process.env.TARO_ENV === 'weapp'{ // 微信小程序逻辑 this.initWeapp() }else if(process.env.TARO_ENV === 'h5'){ // H5页面逻辑 this.initH5() } } initWeapp(){...} initH5(){...}
小程序端打包后代码:
init(){ this.initWeapp() } initWeapp(){...} initH5(){...}
可是,环境变量方式没办法处理 initH5 这种方法定义,致使也被打包进来了。
所以,咱们须要更强大的差别打包:京喜首页利用内部的 wxa-cli 工具提供的条件编译能力,经过注释段落标记,圈注出多端内容,实现了代码片断层面的差别打包,细节以下:
init(){ if(process.env.TARO_ENV === 'weapp'{ // 微信小程序逻辑 this.initWeapp() }else if(process.env.TARO_ENV === 'h5'){ // H5页面逻辑 this.initH5() } } initWeapp(){...} /* wxa if:type=='h5' */ 标记h5端代码开始位置 initH5(){...} /* /wxa */ 标记注释结束位置
打包后代码:
init(){ // weapp内 this.initWeapp() } initWeapp(){...}
initH5 消失了,代码更瘦了
为了调试方便,你的项目内有没有打很长的 log,相似于这种:
console.log('==============xx接口异常============')
通过测试,首页代码文件内有 5KB 的内容是 log 语句,能够试着优化一下:
有没有一样的逻辑需求,能够用更短更优雅的写法来实现呢?
关于代码分析是个很复杂的话题,暂时列一个结论相对明确的写法吧
function format(list){ let result = [] list.forEach(item => { const { a, b, c: f, d, e, } = item result.push({ a, b, f, d, e, }) }) return result
能够利用 lodash 的 pick 方法改写成:
import { pick } from 'lodash/pick' function format(list){ return list.map(item=>({ ...pick(item,'a','b','d','e'), f:item.c }))
京喜首页项目因为 H5 端混搭老项目,为了不类名冲突,采用了形如block-name__element--modifier
的 bem 命名规则。在开发中进一步发现,一些相似 navbar-content__item
的常见命名偶尔撞车,为了不冲突,类名就越写越长,而小程序代码包的尺寸影响也在悄悄增大。
为了解决命名冲突的问题,将类名 hash 化是个好办法,css-modules 就是个成熟的插件,能够经过配置规则,对样式名编译出「文件名+内容相关」的独特化 hash。
可是研究下它的实现,会发现对代码尺寸的影响不容乐观,看一个编译后例子:
import style from './index.module.map.scss.js' //js文件,增长一句jsMap的引入 <view className={style.banner}></view> // wxml文件,每处类名都比原类名增长了`style.`的引用 .hash { xx } // wxss文件, 类名被hash化,减小的具体尺寸为:原类名-hash module.exports = { banner : hash } // 新增了一个map文件,实现原名与hash名的映射,增长的具体尺寸为:原类名+hash
计算总体内容变化:
style.
可见引入 css-modules 会致使总体代码尺寸增长。
会不会以为这个新增的 map 文件的做用特别熟悉呢?
在咱们压缩 js 文件时,会有一个 sourceMap 文件,它保留了原始命名和代码位置,能够方便定位和 debug。
css-modules 实现的 map 文件,在我看来做用和 sourceMap 的命名索引差很少,对于代码逻辑来讲,除了保持原类名的引用信息,它好像也没什么用了,在尺寸敏感场景,就能够考虑去掉 map 文件,仍是上文的示例,若是能够实现成这样就行了:
// import style from './index.module.map.scss.js' js文件取消map的引入 // wxml文件 <view className="hash"></view> // 对style.banner进行求值并替换 // wxss文件 .hash { xx } // 这里不变 module.exports = { banner : hash } // 删掉不要
网上遍寻没有相关的处理,只能本身造轮子开搞了。
因为当前主要目的是对小程序代码瘦身,H5 端文件处理和小程序也有一些差别,因此暂时只对小程序场景造了插件,取名 weapp-css-modules ,github 地址在这里:https://github.com/o2team/wea...
大概思路是:
若是是只开发小程序端,能够借此实现小程序样式命名相关的代码瘦身,而对于 Taro 开发的多端场景,还能够同时解决 h5 端的命名冲突问题。
仍是上面的例子,下面是 weapp-css-modules 编译后效果:
// js文件 let style = {} // 不引用map,加入对不规范引入style的兼容 // wxml文件 <view className="a"></view> // 对style.banner进行求值并替换,加入单字母编排 // wxss文件 .a { xx } // 由于小程序组件样式隔离,因此能够最短化类名 module.exports = { banner : hash } // 删掉不要
京喜首页项目经过改造组件采用 css-modules 写法,加上 weapp-css-modules 编译,代码相对尺寸减小了 10%,仍是颇有效果的,感兴趣的同窗能够试用一下。
关于代码瘦身,想提一下信息学中熵的概念:熵反映信息的无序程度,一段信息无序程度越低,它的熵值越低,可被压缩的空间越大;无序程度越高,熵值越高,可被压缩的空间越小。而数据压缩或者是代码瘦身的过程,就是经过优化信息存储方式以逼近它真实的熵值。
从这个角度来讲:
看起来最很差归类的是「良好的编码策略」,它是在编码阶段对信息的梳理和整合,也算凝练有效信息吧。
以上就是京喜首页项目此次代码瘦身的主要方式了,除此以外的删除不用文件、整合公共文件这些体力活,我就再也不啰嗦了。经过以上方式,京喜首页代码在本来优化良好的基础上,实现了再次减重30%的目标,但愿能给小程序开发者们带来有价值的信息和思考。
参考资料
[1] CSS Modules: https://github.com/css-module...
[2] Tree-Cli: https://github.com/MrRaindrop...
[3] 小程序工程化探索: https://mp.weixin.qq.com/s/_N...
[4] 微信小程序 限制2M的瘦身技巧与方法详解: https://blog.csdn.net/wlanye/...
欢迎关注凹凸实验室博客:aotu.io
或者关注凹凸实验室公众号(AOTULabs),不定时推送文章。