ReactNative包优化之本地图片

本地图片转成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);
              }
          }
      }

    }
  }  
}

至此图片替换的操作大致就完成了.

相关推荐
Senar2 小时前
如何判断浏览器是否开启硬件加速
前端·javascript·数据可视化
HtwHUAT2 小时前
实验四 Java图形界面与事件处理
开发语言·前端·python
利刃之灵2 小时前
01-初识前端
前端
codingandsleeping2 小时前
一个简易版无缝轮播图的实现思路
前端·javascript·css
天天扭码2 小时前
一分钟解决 | 高频面试算法题——最大子数组之和
前端·算法·面试
全宝3 小时前
🌏【cesium系列】01.vue3+vite集成Cesium
前端·gis·cesium
拉不动的猪3 小时前
简单回顾下插槽透传
前端·javascript·面试
烛阴4 小时前
Fragment Shader--一行代码让屏幕瞬间变黄
前端·webgl
爱吃鱼的锅包肉4 小时前
Flutter路由模块化管理方案
前端·javascript·flutter
风清扬雨4 小时前
Vue3具名插槽用法全解——从零到一的详细指南
前端·javascript·vue.js