本地图片转成CDN图片
将本地图片上传到CDN服务器,然后本地图片替换为网络图片。这样我们就能去掉所有图片的体积, 大约有8M。显然这是一个巨大的改进效果。
而且图片是有缓存的,也就是图片缓存在本地后,app就不会在发起请求,那么就不会再销毁CDN资源。
下文会详细介绍如何将本地图片转成CDN图片。
如何替换CDN图片
我们需要写一个图片上传插件,将本地图片上传至CDN,并记录md5表,md5表如下所示。
js
{
"/src/images/cjbbanner.png": "4a8da6930cae6e4cae54d7bae3498fbc.png",
"/src/images/hbsbanner.png": "60f556b4993054e9db67b647a4f792ad.png",
"/src/images/ic_cjwt.png": "320854eccefc1b8a76bd1e6d8e7da98d.png",
"/src/images/ic_cp.png": "ac34f6c9c84b4779949851180bad6b09.png"
}
md5表key为图片的路径,值为图片的md5值,也是CDN服务器存放的文件名。这里我们假设存放到CDN的路径如下
js
https://mcdn.xxxxx.com/mdl/rn-xxxx
然后替换成第一步md5表中的值
css
<Image src={{uri: "https://mcdn.xxxxx.com/mdl/rn-xxxx/ac34f6c9c84b4779949851180bad6b09.png"}}/>
这一步,需要用到的技术有Babel插件,AST抽象语法树。参考Babel官网、AST抽象语法树、在线AST解析
Babel介绍
Babel 是一个编译器,将 ES6 语法编写的代码转换为向后兼容的 ES5 语法,以便能够运行在旧版本的浏览器,并且支持自定义插件,如成熟框架的插件@babel/preset-react,或你自己写的自定义插件。关于自定义插件你肯定需要知道AST抽象语法树。
AST抽象语法树介绍
首先我们的源代码编译成可执行代码,并不是一步生成的。而是源代码先解析成AST抽象语法树,然后再从AST抽象语法树转换成可执行代码。我们先看一下AST语法树长得什么样。
已知源代码如下:
ini
let a = require('/src/images/ic_cp.png');
let b = {uri: "https://mcdn.xxxx.com/mdl/rn-xxxx/ac34f6c9c84b4779949851180bad6b09.png" }
会解析成以下语法树
json
{
"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://mcdn.xxxx.com/mdl/rn-xxxx/ac34f6c9c84b4779949851180bad6b09.png"
}
}
]
}
}
]
}
]
}
}
可以看出AST语法树是一个个节点的树,我们可以替换节点以实现代码的替换。
实现细节
第一步:上传图片
遍历目标文件夹的所有图片,然后上传至CDN,代码如下
ini
const klaw = require('klaw');
const axios = require('axios');
const fs = require("fs");
const path = require("path");
// 记录
let record = {};
// 需要上传的图片
let uploadItems = [];
// 上传下标,按下标逐一上传
let uploadIndex = 0;
//遍历所有图片
klaw(path.resolve(process.cwd(), "./src"))
.on('data', (item) => {
if (/.(jpg|jpeg|png)$/.test(item.path)) {
uploadItems.push(filePath);
}
})
.on('end', async () => {
if (uploadItems.length > 0) {
uploadItem();
}
})
/**
* 上传单个文件
*/
function uploadItem() {
const filePath = uploadItems[uploadIndex];
let formData = new FormData()
formData.append("cdnPath", "https://mcdn.xxxx.com/mdl/rn-xxxx");
formData.append('projectName', "rn-xxxx");
formData.append('file', fs.createReadStream(filePath));
const uloadUrl = "https://nodejs.xxxx.com/file/upload-rn-image"
axios.post(uloadUrl, formData, { headers: formData.getHeaders() })
.then(response => {
// response.data = {data: { md5: "ac34f6c9c84b4779949851180bad6b09" }, message: { code: 0 }}
if (response && response.data && response.data.message.code == 0) {
const fileKey = filePath.replace(process.cwd(), "");
let fileExtension = filePath.substring(filePath.lastIndexOf('.'))
const recordValue = response.data.data.md5 + fileExtension;
// "/src/images/ic_cp.png": "ac34f6c9c84b4779949851180bad6b09.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,如下所示
css
{
"/src/images/cjbbanner.png": "4a8da6930cae6e4cae54d7bae3498fbc.png",
"/src/images/hbsbanner.png": "60f556b4993054e9db67b647a4f792ad.png",
"/src/images/ic_cjwt.png": "320854eccefc1b8a76bd1e6d8e7da98d.png",
"/src/images/ic_cp.png": "ac34f6c9c84b4779949851180bad6b09.png"
}
第二步:实现本地图片替换CDN图片
这一步实现将本地图片替换成CDN图片,要用到上一步生成的记录表upload-image-record.json,代码如下所示:
ini
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)) {
// upload-image-record.json 即上一步产生的md5表
const uploadRecordPath = p.resolve(process.cwd(), "./upload-image-record.json")
// foundPath = ac34f6c9c84b4779949851180bad6b09.png
// imagePath 为图片的路径,如/src/images/ic_cp.png,可以从path参数获取到,这里就不详细说明了
let foundPath = require(uploadRecordPath)[imagePath];
const uriValue = "https://mcdn.xxxx.com/mdl/rn-xxxx/" + foundPath;
const objectExpression = t.objectExpression([
t.objectProperty(
t.identifier('uri'),
t.stringLiteral(uriValue)
)
)];
// 替换节点,require('/src/images/ic_cp.png') 替换成
// {uri: https://mcdn.xxxx.com/mdl/rn-xxxx/ac34f6c9c84b4779949851180bad6b09.png}
path.replaceWith(objectExpression);
}
}
}
}
}
}
至此图片替换的操作大致就完成了.