全网都找不到!小程序集成第三方 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脚本,后面有机会再另讲一篇。

小程序展示

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

相关推荐
卡戎-caryon1 小时前
【项目实践】boost 搜索引擎
linux·前端·网络·搜索引擎·boost·jieba·cpp-http
别催小唐敲代码3 小时前
解决跨域的4种方法
java·服务器·前端·json
溪饱鱼3 小时前
Nuxt3还能用吗?
前端·个人开发·seo
丨丨三戒丶4 小时前
layui轮播图根据设备宽度图片等比例,高度自适应
前端·javascript·layui
青茶3604 小时前
uniapp开发微信小程序时如何进行分包(新手图文)
微信小程序·小程序·uni-app
进取星辰4 小时前
20、数据可视化:魔镜报表——React 19 图表集成
前端·react.js·信息可视化
寧笙(Lycode)4 小时前
React实现B站评论Demo
前端·react.js·前端框架
24白菜头4 小时前
CSS学习笔记
前端·javascript·css·笔记·学习
蠢货爱好者5 小时前
Linux中web服务器的部署及优化
linux·服务器·前端
NightReader6 小时前
Google-chrome版本升级后sogou输入法不工作了
前端·chrome