我怎样才能让webpack嵌入我的 *.wasm,以便在web worker中使用?

eqzww0vc  于 2022-12-27  发布在  Webpack
关注(0)|答案(3)|浏览(502)

我有一些使用wasm-packwasm-bindgen编译成web汇编的rust代码。我想从一个web worklet/worker调用这些代码。整个应用程序最终应该只是一个单独的 *.js文件,其他的都是内联的。
下面是我想象中的构建过程:
1.使用wasm-pack将rust代码编译为 *.wasm和 *.js绑定(这一步工作正常)
1.使用webpack构建一个独立的 .js文件,我可以将其作为worklet/worker加载。.wasm必须包含在此文件中。(此步骤失败)
1.再次使用webpack构建我的最终应用程序/包,内联步骤2中的worklet/worker文件(这一步运行良好)。
我的问题在第二步:我无法使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.

发生这种情况是因为仍然有一个隐式默认规则起作用。我们可以通过覆盖默认规则来禁用它,只考虑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试图支持动态加载内容(?)。我们可以将问题隔离到wasm-pack在使用--target=web CLI参数时作为javascript绑定的一部分生成的这段代码:

async function init(input) {
    if (typeof input === 'undefined') {
        input = new URL('my_wasm_file.wasm', import.meta.url);
    }
    const imports = {};
    // ...

显然,必须生成URL的可能性使得webpack依赖于document,而document在工作者作用域中加载工作者脚本时不可用。取消注解new URL()部分会使document引用从webpack输出中消失。
不知道该怎么做。写我自己的wasm加载程序?我写了一段时间,base64对wasm文件进行编码,并将其作为字符串进行内联-但随后我必须大幅更改使用者代码,以手动异步加载wasm。这意味着我不能再使用wasm-bindgen绑定,因为它们依赖于上面所示的URL部分(当使用--target=web时)或者webpack 5的绑定逻辑(当使用--target=bundler时),我无法从我自己的简单wasm加载器尝试中获得支持。本质上,这意味着我必须提供自己的JS绑定,这很不方便。
一定有更好的办法-对吧?

qyswt5oh

qyswt5oh1#

解决方案

1.构建WASM本身:第一个月
1.构建JS绑定:wasm-bindgen --out-dir=dist --target=web --omit-default-module-path my-wasm-package.wasm.
1.在工作面板脚本中使用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配置将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初始化时期望定义document对象。该对象在worklet作用域中未定义,因此初始化webpack会崩溃,即使我们从未进入包含URL的代码段。
如果我们不使用wasm-pack一次性构建wasm和绑定,我们可以传递额外的参数给wasm-bindgen-主要是--omit-default-module-path参数,它从绑定中删除包含URL的部分。现在webpack在初始化时不会引用document,我们可以不加修改地使用绑定。
从这里开始很简单:我们将wasm绑定为b64编码资产,并将其传递给JS绑定附带的init()函数。

noj0wjuj

noj0wjuj2#

我可以得到公认的解决方案,作为我的构建过程的一部分,所以我这样做:
https://github.com/SonOfLilit/vscode-web-wasm-rust
它涉及到编写一个webpack WASM Loader插件(并修补webpack以使其成为可能,PR提交)来使用此加载程序:

/******/    /* webpack/runtime/wasm loading */
/******/    (() => {
/******/        __webpack_require__.v = (exports, wasmModuleId, wasmModuleHash, importsObj) => {
/******/            var vscode = require('vscode');
/******/            var wasmPath = __webpack_require__.p + wasmModuleHash + '.module.wasm';
/******/            var req = vscode.workspace.fs.readFile(vscode.Uri.file(wasmPath));;
/******/            return req
/******/                .then((bytes) => (WebAssembly.instantiate(bytes, importsObj)))
/******/                .then((res) => (Object.assign(exports, res.instance.exports)));
/******/        };
/******/    })();

以及在尝试导入WASM之前在activate中设置__webpack_public_path__

export function activate(context: vscode.ExtensionContext) {
  __webpack_public_path__ =
    context.extensionUri.toString().replace("file:///", "") + "/dist/web/";

  let disposable = vscode.commands.registerCommand(
    "vscode-rust-web.helloWorld",
    () => {
      require("rust-wasm").then((rust: any) => {
        vscode.window.showInformationMessage(rust.greet());
      });
    }
  );

  context.subscriptions.push(disposable);
}
axr492tv

axr492tv3#

accepted answer非常适合与问题的上下文完全匹配的情况(您希望将自己的Rust代码 * 构建到WASM模块中,然后 * 在worker中使用它 *)。我在稍微不同的上下文中遇到了完全相同的错误消息,但我认为我的发现可能对这里有所帮助。
我的背景是:我有一个WASM依赖项是由 * 其他人 * 构建的,所以改变用于构建JS绑定的标志不太方便(例如wasm-bindgen--omit-default-module-path)。在我的WASM中,甚至不是从Rust构建的,但它的JS绑定具有与问题中描述的相同的URL标记。

    • 免责声明:我既不是WASM也不是网络包Maven,所以我不知道下面的方法的全部含义。在我的背景下,它没有引起意想不到的副作用,但我不相信这是肯定的事情。**

解决方案是在webpack的config中为entry指定一个baseUri属性:

entry: {
    worker: {
        import: './src/worker.ts',
        filename: '../lib/worker.js',
        baseUri: 'my:worker.ts',
    }
},

在撰写本文时,该属性似乎没有出现在官方文档中,而是contributed
这会将webpack生成的代码更改为

/******/    /* webpack/runtime/jsonp chunk loading */
/******/    (() => {
/******/        __webpack_require__.b = "my:worker.ts";
/******/        // ... rest remains the same as in the question's description ...
/******/    })();

这将删除对document的引用并避免运行时错误。

相关问题