不是?全网都找不到案例?小程序集成 gifsicle wasm

💐 前言

这是继上一篇《全网都找不到!小程序集成第三方 WASM npm 包》后的第二篇。在上一篇中讲述了小程序集成第三方 npm 包@jsquash/jpeg实现 JPEG 图片压缩,而这一次则是探讨小程序中如何集成并实现 GIF 图片压缩。这一系列的共同之处都是小程序集成使用 WASM。使用 WASM 并不是很难的一件事,因为有很多开源的项目可供参考。真正难的是在小程序这个平台下如何使用,这也是我写这一篇所要分享的内容,这是在全网都找不到的。才疏学浅,也因此希望能够抛砖引玉。

🌈 需求&选型

前段时间我开发了一个图像压缩小程序------图像压缩暗房,它不仅能够完成图像压缩,还能够缩小图像尺寸、进行格式转换。当然,这其中最重要的一点是所有功能完完全全的本地运行。

↓这是图像压缩暗房的界面,包括设计也都是我独立完成的。

回到需求背景中来,就是我的这个小程序需要实现 GIF 压缩。

那么先来分析一下,可以实现的路径:

  • 服务端,搭建 NodeJS 环境用shrap完成
  • 客户端,使用支持浏览器运行的 WebAssembly 技术实现

因为我买不起服务器所以只能选择使用本地 WASM 技术实现。

接下来就是要找一个适合的第三方 WASM 包,这个包必须满足几点:

  • 能够完成 GIF 压缩、尺寸调整
  • 基于 WebAssembly 技术开发
  • 支持浏览器环境运行

经过一番调研,我选择了gifsicle-wasm-browser,之后又从一个shot-easy-website开源项目中参考如何使用gifsicle-wasm-browser

🍭集成 gifsicle wasm 的各种坑

坑1:小程序只允许引入JS文件

首先在项目根目录新建一个wasm目录,用来放 gifsicle 的.wasm文件和胶水.js文件。

接下来在业务需要用到的地方引入GifWasmModule.js

javascript 复制代码
import { gifsicle } from "../wasm/GifWasmModule";

此时会报错。

看一下报错源码位置,这个问题原因在于小程序中只允许引入 JS 文件。

javascript 复制代码
import gifWasmBinaryFile from "./gif.wasm?url";

export const gifsicle = (function () {
  let wasmUrl = gifWasmBinaryFile;
  //...
}

找一下用到gifWasmBinaryFile的地方,直接修改为绝对路径,也就是小程序要求的"代码包内路径"。

javascript 复制代码
// import gifWasmBinaryFile from "./gif.wasm?url";

export const gifsicle = (function () {
  let wasmUrl = "/wasm/gif.wasm";
  //...
}

坑2:小程序有自己的WXWebAssembly

继续重新编译,又有新的问题,说是不支持 WASM。

看一下打印这句警告的位置,发现是 WebAssembly 这个对象判断出了问题。

这是因为小程序自己定义了一个WXWebAssembly

直接在最顶部声明,让WebAssembly指向WXWebAssembly

javascript 复制代码
var WebAssembly = WXWebAssembly;

坑3:小程序只能加载本地 wasm

重新编译,还是有问题。

WebAssembly.RuntimeError is not a constructor,看字面意思是小程序的WXWebAssemblyRuntimeError没有提供构造函数。这里不用管因为有实际抛出异常的代码才会走到这里。

从抛出异常的调用栈中定位问题。

报错在getBinary()方法里,很明显就是获取 WASM 二进制文件的。通常获取 WASM 二进制文件要么通过网络请求获取,要么就加载本地的。

javascript 复制代码
function getBinary() {
  try {
    if (wasmBinary) {
      return new Uint8Array(wasmBinary);
    }
    if (readBinary) {
      return readBinary(wasmBinaryFile);
    } else {
      throw "both async and sync fetching of the wasm failed";
    }
  } catch (err) {
    abort(err);
  }
}

直接查找WebAssembly.instantiate的所在方法,因为在小程序中只能通过WebAssembly.instantiate方法完成加载本地 wasm。

从这里的方法看,结合上面的异常调用栈,可以判断是在getBinaryPromise()这里面抛出异常,所以没走到WebAssembly.instantiate(binary, info)

javascript 复制代码
function instantiateArrayBuffer(receiver) {
  return getBinaryPromise()
    .then(function (binary) {
      return WebAssembly.instantiate(binary, info);
    })
    .then(receiver, function (reason) {
      err("failed to asynchronously prepare wasm: " + reason);
      abort(reason);
    });
}

解决方法就是跳过getBinaryPromise(),直接调用WebAssembly.instantiate

因为我们前面声明了var WebAssembly = WXWebAssembly,所以这里实际就是WXWebAssembly.instantiate,第一个参数要传代码包内路径的.wasm文件。

javascript 复制代码
function instantiateArrayBuffer(receiver) {
  // return getBinaryPromise()
  //   .then(function (binary) {
  //     return WebAssembly.instantiate(binary, info);
  //   })
  return WXWebAssembly.instantiate('/wasm/gif.wasm', info)
    .then(receiver, function (reason) {
      err("failed to asynchronously prepare wasm: " + reason);
      abort(reason);
    });
}

坑3:小程序真机调试才告诉你bug!

这是小程序开发中历史悠久的一个大烂坑!真机调试了才告诉你出了哪些 bug。

这里的报错是说"是个终端"?挺奇怪的。应该说程序判断现在处于一个终端环境,但正确情况下我们是在 Web 环境。

定位到4146行,由于报错信息有限也没有调用栈,只能看出来是gifsicle_c函数中报错。

通过调试,最终发现确实是gifsicle_c函数中环境的判断出现错误。在小程序中是没有window这个对象的。

javascript 复制代码
ENVIRONMENT_IS_WEB = typeof window === "object"

修改一下这个判断,让它能够支持小程序环境。

javascript 复制代码
ENVIRONMENT_IS_WEB = typeof window === "object" || typeof wx === "object";

至此,就成功在小程序中集成 gifsicle wasm 了。

🎄结语

终于,又一次完成了在小程序集成 wasm。通过这几次集成@jsquash/jpeggifsicle-wasm-browser的历经过程来归纳出一些要点:

  1. node_modules源码文件拷贝到miniprogram_npm
  2. 将源码中的.wasm和相应的胶水.js放到项目根目录的wasm
  3. 胶水文件中不能直接引入.wasm、不能使用import.meta.url动态获取文件路径
  4. 原生的WebAssembly对象要改用WXWebAssembly
  5. 加载.wasm只能使用WXWebAssembly.instantiate方法
  6. 检查输入输出的环境判断,在小程序中也属于 Web 环境
相关推荐
rocky1912 分钟前
谷歌浏览器插件 录制动态 DOM 元素
前端
谁还不是一个打工人5 分钟前
css解决边框四个角有颜色
前端·css
海晨忆1 小时前
【Vue】v-if和v-show的区别
前端·javascript·vue.js·v-show·v-if
1024小神1 小时前
在GitHub action中使用添加项目中配置文件的值为环境变量
前端·javascript
齐尹秦1 小时前
CSS 列表样式学习笔记
前端
Mnxj2 小时前
渐变边框设计
前端
用户7678797737322 小时前
由Umi升级到Next方案
前端·next.js
快乐的小前端2 小时前
TypeScript基础一
前端
北凉温华2 小时前
UniApp项目中的多服务环境配置与跨域代理实现
前端
源柒2 小时前
Vue3与Vite构建高性能记账应用 - LedgerX架构解析
前端