不是?全网都找不到案例?小程序集成 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 环境
相关推荐
天平3 小时前
油猴脚本创建webworker踩坑记录
前端·javascript·typescript
原则猫4 小时前
前端基础大厦
前端
陈随易5 小时前
编程语言级别的Skill市场,AI Agent 的未来形态
前端·后端·程序员
SoaringHeart6 小时前
Flutter进阶:基于 EasyRefresh 的下拉刷新封装 n_easy_refresh_mixin.dart
前端·flutter
IT_陈寒8 小时前
Vite的热更新突然不香了,排查三小时差点砸键盘
前端·人工智能·后端
子兮曰8 小时前
Agency-Agents 深度解析:400+ AI 专家的"梦之队"如何重塑开发工作流
前端·后端·vibecoding
竹林8189 小时前
用 The Graph 查询链上数据实战:从手搓 RPC 到 Subgraph,我的 NFT 项目数据加载快了 10 倍
前端·javascript
妙码生花9 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(十九):点选验证码代码逐行目检
前端·后端·go
Awu122710 小时前
⚡从零开发 Agent CLI(五)实现一个可治理、可扩展的工具系统
前端·人工智能·claude
咪库咪库咪10 小时前
Vue3-生命周期
前端