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);
              }
          }
      }

    }
  }  
}

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

相关推荐
乌夷7 分钟前
axios结合AbortController取消文件上传
开发语言·前端·javascript
晓晓莺歌30 分钟前
图片的require问题
前端
码农黛兮_461 小时前
CSS3 基础知识、原理及与CSS的区别
前端·css·css3
水银嘻嘻1 小时前
web 自动化之 Unittest 四大组件
运维·前端·自动化
(((φ(◎ロ◎;)φ)))牵丝戏安1 小时前
根据输入的数据渲染柱形图
前端·css·css3·js
wuyijysx2 小时前
JavaScript grammar
前端·javascript
溪饱鱼2 小时前
第6章: SEO与交互指标
服务器·前端·microsoft
咔_2 小时前
LinkedList详解(源码分析)
前端
逍遥德3 小时前
CSS可以继承的样式汇总
前端·css·ui
读心悦3 小时前
CSS3 选择器完全指南:从基础到高级的元素定位技术
前端·css·css3