全网都找不到!小程序集成第三方 WASM npm 包

前言

这又是一次被小程序折磨的故事!距我上一次被小程序折磨还是在上一次(去年9月做微信头像加国旗小程序的时候)。但这是我第二次做小程序,就让我再次遇到这么个大难题,就是在小程序中用 WASM,准确来讲是集成一个第三方 npm 包@jsquash/jpeg,而这个依赖包是一个基于 WebAssembly ,支持浏览器端运行的第三方 JPEG 编解码库。

业务&背景

这次我开发的小程序是一个图像压缩工具------图像压缩暗房,它不仅能够完成图像压缩,还能够缩小图像尺寸、进行格式转换。当然,这其中最重要的一点是所有功能完完全全的本地运行。因为没钱买服务器,也才如此困难重重,有钱还是放服务端运行吧。

如果有服务器,可以搭建 NodeJS 环境用shrap,而如果要在浏览器环境实现则需要自己找其它的第三方依赖了,比如@jsquash/jpeg,它是jSquash中的一个包,而jSquash则是基于谷歌的Squoosh开源项目而来。因此我的小程序初期就直接尝试用@jsquash/jpeg来实现功能了。但从一开始就是重重磨难。所遇到的问题搜遍全网无一案例。

第一难:引入@jquash/jpeg

首先来安装@jsquash/jpeg

shell 复制代码
npm install @jsquash/jpeg

之后在小程序中构建npm,到这里没有任何问题。

接着根据官方文档的示例引入@jquash/jpeg

javascript 复制代码
import decode, { init as initJpegDecode } from '@jsquash/jpeg/decode';
import encode, { init as initJpegEncode } from '@jsquash/jpeg/encode';

这时就报错了:模块没有被定义?这看得我很奇怪,根据这个信息,必然是微信在构建 npm 的时候出了差错(当然这是我排查了半天后得出的结论)。

好,来对比一下node_modules中的源码和miniprogram_npm,发现是因为encode.jsdecode.js都没有被编译。(实际这一看就只有2个文件躺在miniprogram_npm里,匪夷所思)

在编译后的index.js最底部有注释,即这些文件被认为外部依赖,所以没被编译。

而为什么没有被编译,后面会提到。

javascript 复制代码
//miniprogram-npm-outsideDeps=["./codec/enc/mozjpeg_enc.js","./codec/dec/mozjpeg_dec.js"]
//# sourceMappingURL=index.js.map

此时需要手动将源码中的相关的文件和整个codec复制到miniprogram_npm

@jsquash/jpeg 中 REAMDE 也有提到:

Note: You will need to either manually include the wasm files from the codec directory or use a bundler like WebPack or Rollup to include them in your app/server.

第二难:禁用import.meta

接着前面,清除小程序缓存并重新编译,依然报错,这次的错误是不能在模块外使用import.meta

这个错误在miniprogram_npm/@jsquash/jpeg/codec/enc/mozjpeg_enc.js

实际上这就是codec中的 JS 文件没被构建编译的原因。在小程序中不能使用的东西,就直接不给编译了(悄悄地啥都不说,让你自己找)。

看一下源码中import.meta.url的作用,其实就是动态确定脚本文件路径。

直接把文件中所有的import.meta.url修改为实际值:

javascript 复制代码
// var _scriptDir = import.meta.url;
var _scriptDir = '/miniprogram_npm/@jsquash/jpeg/codec/enc/mozjpeg_enc.js';

用不到的就注释:

javascript 复制代码
/*if(import.meta.url===undefined){import.meta.url="https://localhost"}*/

下面这里没有改成/miniprogram_npm/@jsquash/jpeg/codec/enc/mozjpeg_enc.wasm,而是把.wasm文件另外放到项目根目录wasm中。这是因为小程序限制禁止访问miniprogram_npm,后面会遇到。

javascript 复制代码
// wasmBinaryFile=new URL("mozjpeg_enc.wasm",import.meta.url).href
wasmBinaryFile="/wasm/mozjpeg_enc.wasm"

之后,miniprogram_npm/@jsquash/jpeg/codec/dec/mozjpeg_dec.js也一样修改。

第三难:禁用WebAssembly

接着就需要初始化@jsquash/jpeg,也就是加载 WASM。

javascript 复制代码
initJpegEncode('/miniprogram_npm/@jsquash/jpeg/codec/enc/mozjpeg_enc.wasm')

这时遇到了WebAssembly没有被定义的报错。

定位一下报错的代码位置,在initEmscriptenModule方法中:

javascript 复制代码
export function initEmscriptenModule(moduleFactory, wasmModule, moduleOptionOverrides = {}) {
    let instantiateWasm;
    if (wasmModule) {
        instantiateWasm = (imports, callback) => {
            const instance = new WebAssembly.Instance(wasmModule, imports);
            callback(instance);
            return instance.exports;
        };
    }
    return moduleFactory({
        // Just to be safe, don't automatically invoke any wasm functions
        noInitialRun: true,
        instantiateWasm,
        ...moduleOptionOverrides,
    });
}

经过排查,这是由于微信小程序自己定义了一个WXWebAssembly(这也要干预?虽然不明白但感觉手脚不是很干净,改了参数但是其他啥也没讲,连个例子都没有)。

首先,这里的重点是WXWebAssembly.instantiatepath指向一个代码包内路径的.wasm文件。也就是前面提到的.wasm文件要单独放到项目根目录wasm中。放在miniprogram_npm是没用的,因为小程序限制禁止访问。

↓你敢尝试就敢给你报错

修改一下初始化方法中的代码,用微信的WXWebAssembly.instantiate

javascript 复制代码
export function initEmscriptenModule(moduleFactory, wasmModule, moduleOptionOverrides = {}) {
    let instantiateWasm;
    if (wasmModule) {
        instantiateWasm = async (imports, callback) => {
            const result = await WXWebAssembly.instantiate(wasmModule, imports);
            const instance = result.instance;
            callback(instance);
            return instance.exports;
        };
    }
    return moduleFactory({
        // Just to be safe, don't automatically invoke any wasm functions
        noInitialRun: true,
        instantiateWasm,
        ...moduleOptionOverrides,
    });
}

当然,这还不够。因为胶水文件mozjpeg_enc.js中还有很多原生使用WebAssembly对象的地方。我这里直接取巧全局替换了。

javascript 复制代码
var WebAssembly = WXWebAssembly;
// var _scriptDir = import.meta.url;
var _scriptDir = '/miniprogram_npm/@jsquash/jpeg/codec/enc/mozjpeg_enc.js';

最后,我们的初始化入口方法参数直接传.wasm文件的代码包内路径。

javascript 复制代码
initJpegEncode('/wasm/mozjpeg_enc.wasm')

后续

突破三重难关,总算是能够使用@jquash/jpeg了。然而实际用下来发现,@jquash/jpeg的图片压缩效率实在是太低了,竟然要几十秒,这与我预想中的相差太远了,也是用户所不能容忍的。我本以为是哪里的参数设置不对,但是在谷歌官方的Squoosh应用中发现也是如此。因此我直接改用Canvas实现jpeg图片的处理了,而其他的gifpng格式处理则使用另外的wasm脚本,后面有机会再另讲一篇。

小程序展示

最后展示一下我这个"图像压缩暗房"小程序,设计都是我自己。

相关推荐
开心小老虎1 小时前
threeJs实现裸眼3D小狗
前端·3d·threejs
龙井>_<2 小时前
vue3使用keep-alive缓存组件与踩坑日记
前端·vue.js·缓存
Captaincc2 小时前
OpenAI以API的形式发布了三 个新模型:GPT-4.1、GPT-4.1 mini 和 GPT-4.1 nano
前端·openai
A-Kamen4 小时前
Webpack vs Vite:深度对比与实战示例,如何选择最佳构建工具?
前端·webpack·node.js
codingandsleeping4 小时前
OPTIONS 预检请求
前端·网络协议·浏览器
程序饲养员5 小时前
ReactRouter7.5: NavLink 和 Link 的区别是什么?
前端·javascript·react.js
小小小小宇6 小时前
CSS 层叠上下文总结
前端
拉不动的猪6 小时前
设计模式之------命令模式
前端·javascript·面试
Json____6 小时前
springboot框架集成websocket依赖实现物联网设备、前端网页实时通信!
前端·spring boot·websocket·实时通信
uhakadotcom6 小时前
Bun vs Node.js:何时选择 Bun?
前端·javascript·面试