简介

由于随着项目业务的增加,导致包越来越大,大概有10M,每次发热更下载都比较久,而且会消耗较多的下载资源,在分析包内容的时候,发现图片占据了约80%的体积,因此提出优化图片的方案。

分析包内容

首先我们先看下包内容构成,以安卓包为例,如下所示

|-- dist 10MB|-- drawable-hdpi 8K|-- drawable-mdpi 8.2MB |-- drawable-xhdpi 8K|-- drawable-xxhdpi 8K|-- index.android.bundle 1.8MB

从上图可以看出,图片跟bundle文件大概各占据了80%,优化掉了图片,可以减少80%的体积,相当可观的优化,当然bundle文件也是大头,但是这不是本篇文章要介绍的。

解决方案

压缩图片

我们用tinypng 进行批量压缩drawable-mdpi(共678张)下的所有图片,压缩完重新打包,结果如下

|—— dist  6.5MB

优化体积为 (10 - 6.5) = 3.5MB,可以看到图片体积减少了一部分,但包还是挺大的。

本地图片转成OSS图片

另外一种思路为将本地图片上传到OSS,然后本地图片替换为网络图片。这样我们就能去掉所有图片的体积, 大约有8M。显然这是一个巨大的改进效果。

而且图片是有缓存的,也就是图片缓存在本地后,app就不会在发起请求,那么就不会消耗OSS资源。

下文会详细介绍如何将本地图片转成OSS图片。

实现思路

第一步 上传本地图片

我们需要将本地图片上传至阿里云OSS,假设我们现在有以下目录下的几张图片:

|-- src|-- images|-- a.png|-- b.png|-- pages|-- images|-- c.png

考虑到图片有可能重复的可能,文件名取文件md5值。上传成功后记录文件映射表,映射表为json格式,如下所示

{"src/images/a.png": "4a8da6930cae6e4cae54d7bae3498fbc.png","src/images/b.png": "d21c595c585f11d8bc24bdc5dc0c9b18.png","src/pages/images/c.png": "674303925428601900ed2e58fdc7cbb6.png"
}

表key为图片的路径,值为md5值,假设阿里云地址为https://my.oss-cn-beijing.aliyuncs.com (下文会多次用到这个地址)。

我们随便挑md5表中的一个图片,如src/images/a.png,那么这张图对应的OSS地址如下

https://my.oss-cn-beijing.aliyuncs.com/4a8da6930cae6e4cae54d7bae3498fbc.png

第二步 本地图片替换成OSS图片

将本地图片代码,替换成引用OSS图片。比如有如下代码

<Image src={require('/src/images/a.png')}/>

然后替换成第一步md5表中的值

<Image src={{uri: "https://my.oss-cn-beijing.aliyuncs.com/4a8da6930cae6e4cae54d7bae3498fbc.png"}}/>

这一步需要用到的技术有Babel插件及AST抽象树。

AST抽象语法树介绍

首先我们的源代码编译成可执行代码,并不是一步生成的。而是源代码先解析成AST抽象语法树,然后再从AST抽象语法树转换成可执行代码。我们先看一下AST语法树长得什么样。

已知源代码如下:

let a = require('/src/images/a.png');
let b = {uri: "https://my.oss-cn-beijing.aliyuncs.com/4a8da6930cae6e4cae54d7bae3498fbc.png" }

会解析成以下语法树

{"type": "File","program": {"type": "Program","body": [{"type": "VariableDeclaration","declarations": [{"id": {"type": "Identifier","name": "a"},"init": {"type": "CallExpression","callee": {"type": "Identifier","loc": {"identifierName": "require"},"name": "require"},"arguments": [{"type": "StringLiteral","value": "/src/images/ic_cp.png"}]}}]},{"type": "VariableDeclaration","declarations": [{"id": {"type": "Identifier","name": "b"},"init": {"type": "ObjectExpression","properties": [{"type": "ObjectProperty","key": {"type": "Identifier","name": "uri"},"value": {"type": "StringLiteral","value": "https://my.oss-cn-beijing.aliyuncs.com/4a8da6930cae6e4cae54d7bae3498fbc.png"}}]}}]}]}
}

语法树相对简单,就不逐行介绍了,可以使用在线AST语法树对应,从上我们可以看出AST语法树是一个个节点的树,我们可以替换节点以实现代码的替换。

实现细节

第一步:上传图片

遍历src下的所有图片,然后上传至OSS( 如何上传OSS,参考上传本地文件),代码如下

const klaw = require('klaw');
const fs = require("fs");
const path = require("path");
let OSS = require('ali-oss');
const crypto = require('crypto');// 记录
let record = {};
// 需要上传的图片
let uploadItems = [];
// 上传下标,按下标逐一上传
let uploadIndex = 0;let client = new OSS({// yourRegion填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。region: 'oss-cn-beijing',// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。accessKeyId: '你的keyId',accessKeySecret: '你的secret',// 填写Bucket名称。bucket: '你的bucket'
});//遍历所有图片
klaw(path.resolve(process.cwd(), "./src")).on('data', (item) => {if (/\.(jpg|jpeg|png)$/.test(item.path)) {const filePath = item.path;uploadItems.push(filePath);}}).on('end', async () => {if (uploadItems.length > 0) {uploadItem();}})/*** 上传单个文件*/
async function uploadItem() {const filePath = uploadItems[uploadIndex];let fileExtension = filePath.substring(filePath.lastIndexOf('.'))// fileKey = /src/images/a.pngconst fileKey = filePath.replace(process.cwd(), "");const buffer = fs.readFileSync(filePath);const hash = crypto.createHash('md5');hash.update(buffer);const md5 = hash.digest('hex');// recordValue = 4a8da6930cae6e4cae54d7bae3498fbc.pngconst recordValue = md5 + fileExtension;// 填写OSS文件完整路径和本地文件的完整路径。OSS文件完整路径中不能包含Bucket名称。// 如果本地文件的完整路径中未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。const result = await client.put(recordValue, path.normalize(filePath));if(result && result.res && result.res.statusCode == 200){//  "/src/images/a.png": "4a8da6930cae6e4cae54d7bae3498fbc.png",record[fileKey] = recordValue;}uploadIndex++;if (uploadIndex < uploadItems.length) {uploadItem();} else {const uploadRecordPath =  path.resolve(process.cwd(), "./upload-image-record.json")fs.writeFileSync(uploadRecordPath, JSON.stringify(record, null, "\t"));}}

上传成功后,会生成上传记录文件upload-image-record.json,如下所示

{"src/images/a.png": "4a8da6930cae6e4cae54d7bae3498fbc.png","src/images/b.png": "d21c595c585f11d8bc24bdc5dc0c9b18.png","src/pages/images/c.png": "674303925428601900ed2e58fdc7cbb6.png"
}

第二步:替换本地文件为OSS图片

这一步实现将本地图片替换成OSS图片,要用到上一步生成的记录表upload-image-record.json,代码如下所示:

const t = require('@babel/types');
const p = require('path');module.exports = function () {return {visitor: {CallExpression(path, ref = { opts: {} }) {const node = path.node;if (node.callee.type === 'Identifier' && node.callee.name === 'require') {const value = node.arguments[0].value;if (typeof (value) === 'string' && /\.(jpg|jpeg|png)$/.test(value)) {/*** 以假设存在项目,/Users/zhangsan/rn-app/index.js代码为例,/Users/zhangsan/rn-app为项目在我电脑的绝对地址class HelloWorld extends React.Component {render() {return (<View style={styles.container}><Image source={require('./src/images/a.png')}/></View>);}}*/// upload-image-record.json 即上一步产生的md5表const uploadRecordPath = p.resolve(process.cwd(), "./upload-image-record.json")// filename = /Users/zhangsan/rn-app/index.jslet filename = path.hub.file.opts.filename;// fileDir = /Users/zhangsan/rn-applet fileDir = filename.substring(0, filename.lastIndexOf("/") + 1);// imagePath = /Users/zhangsan/rn-app/src/images/ic_bjx.pnglet imagePath = pathP.resolve(fileDir, value);// rootPath = /Users/zhangsan/rn-applet rootPath = path.hub.file.opts.root;// imagePath = /src/images/a.pngimagePath = imagePath.replace(rootPath, "");// foundPath = 4a8da6930cae6e4cae54d7bae3498fbc.pnglet foundPath = require(uploadRecordPath)[imagePath];const uriValue = "https://my.oss-cn-beijing.aliyuncs.com/" + foundPath;const objectExpression = t.objectExpression([t.objectProperty(t.identifier('uri'),t.stringLiteral(uriValue))]);// 替换节点,require('/src/images/a.png') 替换成// {uri: https://my.oss-cn-beijing.aliyuncs.com/4a8da6930cae6e4cae54d7bae3498fbc.png}path.replaceWith(objectExpression);}}}}}
}

至此图片替换的操作就完成了,再看下包大小

包大小
优化前 10MB
优化后 2MB

至此已完成体积优化,当然有些细节没处理,可以参考源码。

React Native包体积优化之图片优化相关推荐

  1. 前端每周清单第 34 期:Vue 现状盘点与 3.0 展望,React 代码迁移与优化,图片优化详论

    新闻热点 国内国外,前端最新动态 Microsoft 宣发面向 iOS 与 Android 平台的 Microsoft Edge:为了保证 Windows 用户各平台使用体验的一致性,Microsof ...

  2. 前端每周清单第 34 期:Vue 现状盘点与 3.0 展望,React 代码迁移与优化,图片优化详论... 1

    前端每周清单第 34 期:Vue 现状盘点与 3.0 展望,React 代码迁移与优化,图片优化详论 作者:王下邀月熊 编辑:徐川 前端每周清单专注前端领域内容,以对外文资料的搜集为主,帮助开发者了解 ...

  3. 前端性能优化(图片优化)

    从输入URL到页面加载完成的过程:首先通过DNS(域名解析)把URL解析为对应的IP地址,然后与该IP地址确定的服务器建立起TCP网络连接.随后向服务器发送HTTP请求,服务器处理完HTTP请求后把目 ...

  4. 前端性能优化之图片优化

    图片优化的价值 为什么要做图片优化?图片优化的收益有多大? Google官方的最佳实践中关于图片优化有下面这样一段描述: 对于网页来说,在所下载的字节数中,图片往往会占很大比例.因此,优化图片通常可以 ...

  5. Web 性能优化: 图片优化让网站大小减少 62%

    摘要: 压缩各种格式的图片. 原文:Web 性能优化: 图片优化让网站大小减少 62% 作者:前端小智 Fundebug经授权转载,版权归原作者所有. 这是 Web 性能优化的第二篇,上一篇在下面看点 ...

  6. 网站SEO优化之图片优化方法

    网站SEO优化之图片优化方法 网站中图片的优化是很多站长都比较容易忽略的细节,我们知道,对于搜索引擎目前的技术来讲,是无法识别图片的内容信息的,但为了提升用户体验,我们在进行文章或者网站布局时,必须进 ...

  7. 前端每周清单第 34 期:Vue 现状盘点与 3.0 展望,React 代码迁移与优化,图片优化详论...

    作者:王下邀月熊 编辑:徐川 前端每周清单专注前端领域内容,以对外文资料的搜集为主,帮助开发者了解一周前端热点:分为新闻热点.开发教程.工程实践.深度阅读.开源项目.巅峰人生等栏目.欢迎关注[前端之巅 ...

  8. Android:最全面详细的性能优化攻略(含内存优化、内存泄漏、绘制优化、布局优化、图片优化、APK优化、多线程优化、列表优化等)

    前言:佛教中有一句话:初学者的心态,拥有初学者心态是件了不起的事情.真正的大师永远怀有一颗学徒的心. 一.概述 在Android中,性能优化是细分领域中最难且也是知识面涉及最深和最广的方向之一. 更快 ...

  9. react native 包学不包会系列--认识react native

    react native 是由Facebook推出,基于JavaScript框架和React库来提高多平台开发效率的一门语言.很好地填补了跨平台开发的空缺,推出之后也是收到很多开发者的关注,目前使用的 ...

最新文章

  1. VirtualBox 启动时提示“获取 VirtualBox COM 对象失败”的解决
  2. vue 怎么样不重复往数组里插入数据_Vue.js在数组中插入重复数据的实现代码分享...
  3. how to add the language things at the idiscover
  4. Qt UDP 广播简单示例
  5. 前端开发面试题总结之——CSS3
  6. python装饰器编程_Python编程中装饰器的使用示例解析
  7. 如何运用Reflection转化DynamicObject和Generic集合为DataTable
  8. 知道经纬度坐标怎么计算两点间距离_【我的时间拣屎】亚里士多德:地球是圆的,我计算了地球的圆周...
  9. excel空值读不到java里_第一列中的空值是否阻止Pentaho Spoon中的Excel文件导入?
  10. photon四种同步方式_Map 四种同步方式的性能比较
  11. 外汇交易系统(自动化/程序化/量化/EA)
  12. 最新Latex安装详细教程
  13. Ctrl 键失效或者 Ctrl + D键失效,不灵
  14. Jamie Zawinski访谈:C++之恶
  15. python 爬取整部漫画(简单的图片爬取)
  16. macbook如何使用visual studio code进行c语言编程
  17. cmd中直接使用pip安装python模块、包
  18. 2021第五届蓝帽杯初赛部分题目wp
  19. 指令集入选重庆市工业和信息化领域“揭榜挂帅”项目榜单
  20. 科研必备工具篇(持续更新)

热门文章

  1. pytorch神经网络训练及测试流程代码
  2. Python 缺省参数,可变参数
  3. vue 拦截器使用
  4. 【Linux】磁盘挂载
  5. 责任链模式在复杂数据处理场景中的实战
  6. 汉诺塔递归问题,递归思路详解
  7. redhat安装Xvfb
  8. 最新版Arch系Linux中Manjaro Linux 的安装和使用,常用软件以及Docker安装
  9. 五个维度着手MySQL的优化
  10. python怎样导入游戏库_如何安装python的游戏模块pygame