【问题标题】:How can I make webpack embed my *.wasm for use in a web worker?如何让 webpack 嵌入我的 *.wasm 以在 web worker 中使用?
【发布时间】:2022-03-30 15:38:36
【问题描述】:

我有一些使用 wasm-packwasm-bindgen 编译为 Web 程序集的 rust 代码。我想从 web worklet/worker 调用这段代码。整个应用程序最终应该只是一个 *.js 文件,其他所有内容都内联。

这是我想象的构建过程的样子:

  1. 使用 wasm-pack 将 rust 代码编译为 *.wasm 和 *.js 绑定(此步骤工作正常)
  2. 使用webpack 构建一个独立的*.js 文件,我可以将其作为工作集/工作人员加载。 *.wasm 必须包含在此文件中。 (此步骤失败)
  3. 再次使用 webpack 构建我的最终应用程序/包,内联步骤 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 文件。我们可以尝试使用常规的 raw-loader:

// 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 仅考虑 jsonjs 文件:

// 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?我为此工作了一段时间,对 wasm 文件进行 base64 编码并将其内联为字符串 - 但随后我必须大幅更改使用者代码以手动异步加载 wasm。这意味着我不能再使用wasm-bindgen 绑定,因为它们依赖于上面显示的URL 部分(使用--target=web 时)或webpack 5 的捆绑逻辑(使用--target=bundler 时),我可以我自己的简单 wasm-loader 尝试没有得到支持。 本质上这意味着我必须提供自己的 JS 绑定,这很不方便。

一定有更好的方法——对吧?

【问题讨论】:

  • 我可以将 js 和 wasm 捆绑到一个文件中,不幸的是它在运行时不能正常工作。你开启了 web 组装的 webpack 实验了吗?
  • webpack 的 web 组装实验只将 wasm 复制到输出目录。我希望将 wasm 嵌入到输出 *.js 文件中。

标签: webpack webassembly webpack-5 wasm-bindgen wasm-pack


【解决方案1】:

解决办法

  1. 自行构建 wasm:cargo build --target=wasm32/unknown/unknown
  2. 构建 JS 绑定:wasm-bindgen --out-dir=dist --target=web --omit-default-module-path my-wasm-package.wasm
  3. 像这样在工作集脚本中使用 wasm:
import init, { /* other stuff */ } from 'my-wasm-package';
import wasmData from 'my-wasm-package/my-wasm-package.wasm';

const wasmPromise = init(wasmData);
  1. 使用此 webpack 配置将工作集脚本构建到单个 *.js 文件中:
// ...

module.exports = {

    // ...

    module: {
        rules: [
            {
                test: /\.ts$/,
                loader: 'ts-loader',
                options: {
                    configFile: 'tsconfig.json',
                },
            },
            {
                test: /\.wasm$/,
                type: "asset/inline",
            },
        ],
    },
};

为什么会起作用

我观察到的核心问题是wasm-bindgen 生成的JS 绑定包含URL 关键字——这使得webpack 以这样一种方式初始化,即它期望定义document 对象。该对象在工作集范围内未定义,因此即使我们从未进入包含 URL 的代码部分,初始化 webpack 也会崩溃。

如果我们不使用 wasm-pack 一次性构建 wasm 和绑定,我们可以将其他参数传递给 wasm-bindgen - 主要是 --omit-default-module-path 参数,它从绑定。现在webpack 在初始化时不会引用document,我们可以使用绑定而无需修改。

从这里开始很简单:我们将 wasm 捆绑为 b64 编码资产,并将其传递给 JS 绑定附带的 init() 函数。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-04-15
    • 2019-05-26
    • 2020-12-15
    • 1970-01-01
    • 1970-01-01
    • 2018-11-02
    • 2019-09-01
    • 2021-09-27
    相关资源
    最近更新 更多