小程序增量图片处理

背景

目前在本地编译时,需要开启一个本地 Server 来托管图片,然后使用自定义插件来替换代码内所有引用相对路径的图片,把相对地址改成本地server路径。

例如:

'/static/index/startWelfare.png' -> 'http://10.70.xxx.xxx:xxxx/index/startWelfare/btn.png'

现在目标是取消采用本地 Server,通过工程化方式把图片全替换成 CDN 地址,提高本地开发效率。

  • 对于之前已经发布上线的图片,图片已经上传到CDN,可以直接替换掉。
  • 新增的图片,通过脚本上传到团队 CDN,然后代码里自动替换成 CDN 地址。

设计

对于已有图片,维护一个映射文件 (本地图片 -> CDN地址)

这里分2个脚本来实现:

  1. 增量图片上传CDN
  2. 扫描components、pages和commonStyle目录,替换图片地址(可以通过映射文件中的记录查找,提高效率)

流程图

image.png

如何获取新增图片?
既然需要将新增的图片上传到CDN,首先就需要找出项目中新增的图片。
这里,我是通过 git status 去获取,然后用正则匹配出图片。

但是很快就遇到了问题:新文件夹下新文件 git status 识别不出来

解决方法是在 git status 命令加上 --untracked-files

const { exec } = require('child_process')
const util = require('util')
const execPromise = util.promisify(exec)

exec('git status --untracked-files', (err, std) => {
  if (err) {
    console.error(`error for exec git status ${err.message}`)
  }

  const newImgs = std.match(/src\/.*\.(png|jpg|jpeg)/g)
  console.log(newImgs)
})

举个例子,假如本次新增了2张图片:gift-logo.pngmain-header.png ,执行上述脚本后得到newImgs

image.png

如果没有新增图片,结束。
如果有新增图片,则读取缓存文件,判断新增图片是否已有缓存记录,如果都有缓存记录,说明这些图片已经上传过了,结束。

 if (!newImgs?.length) {
    return
  }

  const tempJson = readTempFile()
  const needUploadImgs = []

  for (const img of newImgs) {
    if (!tempJson[img]) {
      needUploadImgs.push(img)
    }
  }

  if (!needUploadImgs.length) {
    return
  }

通过命令上传图片到团队CDN。

上传返回结果里有图片的CDN地址,通过字符串操作获取,最终得到了 uploadMap (映射记录表),然后更新缓存文件。

  // ....上传

  // ...获取uploadMap
  
   // 筛选出本次新增的图片
  if (tempJson) {
    for (const img in uploadMap) {
      tempJson[img] = uploadMap[img]
    }
    // 写入缓存文件
    writeTempFile(tempJson)
  }

脚本执行后在项目根目录出现一个图片映射文件:


image.png

扫描源码,替换图片

首先读取缓存文件到内存,然后读取对应目录:

  const tempImgJson = readTempFile();  // tempImgJson 就是上图所示的json文件

  function readDir(dir) {
    fs.readdir(dir, function (err, files) {
      if (err) {
        console.error(err)
      }
      files.forEach(function (file) {
        replaceFile(path.resolve(dir, file))
      })
    })
  }

对于每个文件或目录,如果是文件则找出文件内符合正则的图片地址,替换成缓存记录中CDN地址。
如果是目录,则递归读取替换。

function replaceFile(filePath) {
  
  if (/*是文件*/) {
    fs.readFile(filePath, function (err, data) {
      // ......
      // 替换src和url中符合
      const newData = data
        .replace(/:?src="(.*?)"/g, ($0, $1) => {
          if ($1.includes('/static/')) {/*...*/}
        })
        .replace(/(url\((.*?)\))+/g, ($0, _, $2) => {
          if ($2.includes('@/static/')) {/*...*/}
        })

      if (/*发生变动*/) {
         // 写入文件
      }
    })
  } else if (/*是目录*/) {
    // 递归读目录下的文件
    readDir(filePath)
  }
}

通过分析代码,其中需要替换的代码集中在 .vue 和 .less 中。
而这两种文件又集中在 src/components 和 src/pages、src/commonStyle ,所以最终只需要替换这三个目录下的文件。


  // 替换components目录
  const componentsAbsolutePath = path.resolve(__dirname, '../src/components')
  readDir(componentsAbsolutePath)
  
  // 替换pages目录
  const pagesAbsolutePath = path.resolve(__dirname, '../src/pages')
  readDir(pagesAbsolutePath)

  // 替换commonStyle目录
  const commonStyleAbsolutePath = path.resolve(__dirname, '../src/commonStyle')
  readDir(commonStyleAbsolutePath)

脚本执行时机

在 package.json 中新增命令:

  "scripts" : {
        // ...
        "upload": "node ./script/upload"
   }

由研发自行判断是否需要上传,在合适阶段调用命令执行图片上传CDN及替换。

总结

新方案除了开发提效(6.6s -> 5.9s,编译时长大致提升10%)。

因为是用的Uniapp,需要先把源码编译成小程序代码,所以时间比直接使用原生小程序开发来说较长。

基于小程序的限制,目前只能发布一个小程序测试版本到小程序开发者平台。

假如有两个需求并行测试,那么其中一个需求只能由前端生成一个本地二维码,测试同学扫码测试。
这种做法结合之前本地Server托管图片的方案,就有一个存在的问题,就是前端必须时刻开启本地Server。
假如研发关掉本地Server或者切换分支(切换后分支的图片目录缺少了测试中分支的图片),那么二维码扫码之后小程序将无法访问到那些图片。

而采用新方案后,舍弃了本地Server,图片全部换成CDN,不管开发阶段还是生产阶段,保证了图片的一致性,一定程度上,也方便了测试。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容