我怎样才能让 webpack 嵌入我的 *.wasm 以便在 web worker 中使用?
How can I make webpack embed my *.wasm for use in a web worker?
我有一些使用 wasm-pack
和 wasm-bindgen
编译成 Web 程序集的 Rust 代码。我想从网络 worklet/worker 调用此代码。整个应用程序最终应该只是一个 *.js 文件,其他所有内容都内联。
这就是我想象中的构建过程:
- 使用
wasm-pack
将rust代码编译为*.wasm和*.js绑定(这一步工作得很好)
- 使用
webpack
构建一个独立的 *.js 文件,我可以将其加载为 worklet/worker。 *.wasm 必须包含在此文件中。 (此步骤失败)
- 再次使用
webpack
构建我的最终 app/package,内联第 2 步中的 worklet/worker 文件。(这一步工作正常)
我的问题出在第 2 步:我无法使 webpack
将 *.wasm 内联到 worklet/worker 文件中。
我在我的 webpack 配置中试过这个:
entry: {
worker: {
import: './src/worker.ts',
filename: '../lib/worker.js',
}
},
// ...
module: {
rules: [
// ...
{
test: /\.wasm$/,
// 1st option: type: 'webassembly/sync',
// 2nd option: type: 'asset/inline',
},
// ...
],
},
无论我做什么,webpack
总是发出两个文件,一个 worker.js
包含我的 worklet/worker 脚本本身,另一个 vendor_my_package_name_wasm_js.js
只包含*.wasm 及其绑定。显然,当将 worker.js
作为 web worker 加载时,它失败了——无法从 worker 作用域加载第二个文件。
我的目标是将所有内容都包含在 worker.js
中并且不发出单独的文件。但是我该怎么做呢?
编辑:记录解决方案的步骤:
Webpack 原生 wasm 加载似乎不允许内联 wasm 文件。我们可以尝试使用常规的原始加载器:
// in module.rules
{
test: /\.wasm$/,
loader: 'raw-loader',
},
这会导致以下错误:
ERROR in ./node_modules/my-module/my-wasm-file.wasm
Module parse failed: magic header not detected
File was processed with these loaders:
* ../../node_modules/raw-loader/dist/cjs.js
You may need an additional loader to handle the result of these loaders.
发生这种情况是因为仍然有一个隐含的默认规则在起作用。We can disable it by overwriting the default rules 只考虑 json
和 js
文件:
// in webpack.config.js
module: {
defaultRules: [
{
type: 'javascript/auto',
resolve: {},
},
{
test: /\.json$/i,
type: 'json',
},
],
rules: [
// ...
{
test: /\.wasm$/,
loader: 'raw-loader',
},
],
},
现在我们终于将 worker 捆绑到一个 *.js 文件中了!但是,在加载它时,我们最终会遇到此错误:
Uncaught ReferenceError: document is not defined
指向这段 webpack 生成的代码:
/* webpack/runtime/jsonp chunk loading */
/******/ (() => {
/******/ __webpack_require__.b = document.baseURI || self.location.href; // <<< error here
/******/
/******/ // object to store loaded and loading chunks
/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched
/******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded
/******/ var installedChunks = {
/******/ "myModuleName": 0
/******/ };
/******/
/******/ // no chunk on demand loading
/******/
/******/ // no prefetching
/******/
/******/ // no preloaded
/******/
/******/ // no HMR
/******/
/******/ // no HMR manifest
/******/
/******/ // no on chunks loaded
/******/
/******/ // no jsonp function
/******/ })();
出于某种原因,webpack 尝试支持动态加载内容 (?)。当使用 --target=web
CLI 参数时,我们可以将问题隔离到由 wasm-pack
作为 javascript 绑定的一部分生成的这段代码:
async function init(input) {
if (typeof input === 'undefined') {
input = new URL('my_wasm_file.wasm', import.meta.url);
}
const imports = {};
// ...
显然,必须生成 URL 的可能性使得 webpack 依赖 document
,这在工作范围内加载工作脚本时不可用。取消注释 new URL()
部分会使 document
引用从 webpack 输出中消失。
不知道从这里到哪里去。编写我自己的 wasm-loader?我为此工作了一段时间,base64 对 wasm 文件进行编码并将其内联为一个字符串——但随后我不得不大幅更改消费者代码以手动异步加载 wasm。这意味着我不能再使用 wasm-bindgen
绑定,因为它们依赖于上面显示的 URL
部分(使用 --target=web
时)或 webpack 5 的捆绑逻辑(使用 --target=bundler
) 我自己简单的 wasm-loader 尝试无法得到支持。
从本质上讲,这意味着我必须提供自己的 JS 绑定,这很不方便。
一定有更好的方法 - 对吧?
解决方案
- 构建 wasm 本身:
cargo build --target=wasm32/unknown/unknown
- 构建 JS-bindings:
wasm-bindgen --out-dir=dist --target=web --omit-default-module-path my-wasm-package.wasm
。
- 像这样在你的 worklet 脚本中使用 wasm:
import init, { /* other stuff */ } from 'my-wasm-package';
import wasmData from 'my-wasm-package/my-wasm-package.wasm';
const wasmPromise = init(wasmData);
- 使用此 webpack 配置将 worklet 脚本构建到一个 *.js 文件中:
// ...
module.exports = {
// ...
module: {
rules: [
{
test: /\.ts$/,
loader: 'ts-loader',
options: {
configFile: 'tsconfig.json',
},
},
{
test: /\.wasm$/,
type: "asset/inline",
},
],
},
};
为什么有效
我观察到的核心问题是 wasm-bindgen
生成的 JS 绑定包含 URL
关键字 - 这使得 webpack
以期望 [=17] 的方式初始化=] 待定义的对象。该对象在工作集范围内未定义,因此即使我们从未输入包含 URL
.
的代码段,初始化 webpack
也会崩溃
如果我们不使用 wasm-pack
一次性构建 wasm 和绑定,我们可以将额外的参数传递给 wasm-bindgen
- 主要是 --omit-default-module-path
参数,它删除了来自绑定的 URL
部分。现在 webpack
在初始化时不会引用 document
,我们可以不加修改地使用绑定。
从这里开始很简单:我们将 wasm 打包为 b64 编码资产,并将其传递给 JS 绑定附带的 init()
函数。
我有一些使用 wasm-pack
和 wasm-bindgen
编译成 Web 程序集的 Rust 代码。我想从网络 worklet/worker 调用此代码。整个应用程序最终应该只是一个 *.js 文件,其他所有内容都内联。
这就是我想象中的构建过程:
- 使用
wasm-pack
将rust代码编译为*.wasm和*.js绑定(这一步工作得很好) - 使用
webpack
构建一个独立的 *.js 文件,我可以将其加载为 worklet/worker。 *.wasm 必须包含在此文件中。 (此步骤失败) - 再次使用
webpack
构建我的最终 app/package,内联第 2 步中的 worklet/worker 文件。(这一步工作正常)
我的问题出在第 2 步:我无法使 webpack
将 *.wasm 内联到 worklet/worker 文件中。
我在我的 webpack 配置中试过这个:
entry: {
worker: {
import: './src/worker.ts',
filename: '../lib/worker.js',
}
},
// ...
module: {
rules: [
// ...
{
test: /\.wasm$/,
// 1st option: type: 'webassembly/sync',
// 2nd option: type: 'asset/inline',
},
// ...
],
},
无论我做什么,webpack
总是发出两个文件,一个 worker.js
包含我的 worklet/worker 脚本本身,另一个 vendor_my_package_name_wasm_js.js
只包含*.wasm 及其绑定。显然,当将 worker.js
作为 web worker 加载时,它失败了——无法从 worker 作用域加载第二个文件。
我的目标是将所有内容都包含在 worker.js
中并且不发出单独的文件。但是我该怎么做呢?
编辑:记录解决方案的步骤:
Webpack 原生 wasm 加载似乎不允许内联 wasm 文件。我们可以尝试使用常规的原始加载器:
// in module.rules
{
test: /\.wasm$/,
loader: 'raw-loader',
},
这会导致以下错误:
ERROR in ./node_modules/my-module/my-wasm-file.wasm
Module parse failed: magic header not detected
File was processed with these loaders:
* ../../node_modules/raw-loader/dist/cjs.js
You may need an additional loader to handle the result of these loaders.
发生这种情况是因为仍然有一个隐含的默认规则在起作用。We can disable it by overwriting the default rules 只考虑 json
和 js
文件:
// in webpack.config.js
module: {
defaultRules: [
{
type: 'javascript/auto',
resolve: {},
},
{
test: /\.json$/i,
type: 'json',
},
],
rules: [
// ...
{
test: /\.wasm$/,
loader: 'raw-loader',
},
],
},
现在我们终于将 worker 捆绑到一个 *.js 文件中了!但是,在加载它时,我们最终会遇到此错误:
Uncaught ReferenceError: document is not defined
指向这段 webpack 生成的代码:
/* webpack/runtime/jsonp chunk loading */
/******/ (() => {
/******/ __webpack_require__.b = document.baseURI || self.location.href; // <<< error here
/******/
/******/ // object to store loaded and loading chunks
/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched
/******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded
/******/ var installedChunks = {
/******/ "myModuleName": 0
/******/ };
/******/
/******/ // no chunk on demand loading
/******/
/******/ // no prefetching
/******/
/******/ // no preloaded
/******/
/******/ // no HMR
/******/
/******/ // no HMR manifest
/******/
/******/ // no on chunks loaded
/******/
/******/ // no jsonp function
/******/ })();
出于某种原因,webpack 尝试支持动态加载内容 (?)。当使用 --target=web
CLI 参数时,我们可以将问题隔离到由 wasm-pack
作为 javascript 绑定的一部分生成的这段代码:
async function init(input) {
if (typeof input === 'undefined') {
input = new URL('my_wasm_file.wasm', import.meta.url);
}
const imports = {};
// ...
显然,必须生成 URL 的可能性使得 webpack 依赖 document
,这在工作范围内加载工作脚本时不可用。取消注释 new URL()
部分会使 document
引用从 webpack 输出中消失。
不知道从这里到哪里去。编写我自己的 wasm-loader?我为此工作了一段时间,base64 对 wasm 文件进行编码并将其内联为一个字符串——但随后我不得不大幅更改消费者代码以手动异步加载 wasm。这意味着我不能再使用 wasm-bindgen
绑定,因为它们依赖于上面显示的 URL
部分(使用 --target=web
时)或 webpack 5 的捆绑逻辑(使用 --target=bundler
) 我自己简单的 wasm-loader 尝试无法得到支持。
从本质上讲,这意味着我必须提供自己的 JS 绑定,这很不方便。
一定有更好的方法 - 对吧?
解决方案
- 构建 wasm 本身:
cargo build --target=wasm32/unknown/unknown
- 构建 JS-bindings:
wasm-bindgen --out-dir=dist --target=web --omit-default-module-path my-wasm-package.wasm
。 - 像这样在你的 worklet 脚本中使用 wasm:
import init, { /* other stuff */ } from 'my-wasm-package';
import wasmData from 'my-wasm-package/my-wasm-package.wasm';
const wasmPromise = init(wasmData);
- 使用此 webpack 配置将 worklet 脚本构建到一个 *.js 文件中:
// ...
module.exports = {
// ...
module: {
rules: [
{
test: /\.ts$/,
loader: 'ts-loader',
options: {
configFile: 'tsconfig.json',
},
},
{
test: /\.wasm$/,
type: "asset/inline",
},
],
},
};
为什么有效
我观察到的核心问题是 wasm-bindgen
生成的 JS 绑定包含 URL
关键字 - 这使得 webpack
以期望 [=17] 的方式初始化=] 待定义的对象。该对象在工作集范围内未定义,因此即使我们从未输入包含 URL
.
webpack
也会崩溃
如果我们不使用 wasm-pack
一次性构建 wasm 和绑定,我们可以将额外的参数传递给 wasm-bindgen
- 主要是 --omit-default-module-path
参数,它删除了来自绑定的 URL
部分。现在 webpack
在初始化时不会引用 document
,我们可以不加修改地使用绑定。
从这里开始很简单:我们将 wasm 打包为 b64 编码资产,并将其传递给 JS 绑定附带的 init()
函数。