在 React 中为大文件(超过 4kb)加载 WASM 模块的正确方法

Kaw*_*aLo 7 emscripten reactjs webassembly

我被困在如何使用 wasm 编译器在 React 代码中加载 C++ 函数。

我的 C++ 由两个文件组成,编译后生成一个 160kb 的 wasm 文件。这是我目前用于编译的命令(在 macOS 上运行)。

em++ ../main.cpp ../stringFormat.cpp -s WASM=1 -s EXPORT_ALL=1 -s MODULARIZE=1 -O3 --closure 1 -o lss.js -std=c++11
Run Code Online (Sandbox Code Playgroud)

然后我将 lss 和 wasm 文件一起复制到我的 React 代码中,在同一个文件夹中。

src
  - utils
    - wasm
      lss.js
      lss.wasm
Run Code Online (Sandbox Code Playgroud)

但是,每当我尝试在另一个文件中导入 lss.js 时,我的应用程序都会因一堆未定义的表达式而崩溃。

我的js文件

import * as lss from '../wasm/lss'
Run Code Online (Sandbox Code Playgroud)
./src/utils/wasm/lss.js
  Line 10:6:     Expected an assignment or function call and instead saw an expression  no-unused-expressions
  Line 11:69:    Expected an assignment or function call and instead saw an expression  no-unused-expressions
  Line 11:117:   'read' is not defined                                                  no-undef
  Line 11:197:   'readbuffer' is not defined                                            no-undef
  Line 11:214:   'read' is not defined                                                  no-undef
  Line 11:336:   'quit' is not defined                                                  no-undef
  Line 11:367:   Unexpected use of 'print'                                              no-restricted-globals
  Line 11:430:   Unexpected use of 'print'                                              no-restricted-globals
  Line 11:493:   'printErr' is not defined                                              no-undef
  Line 12:1:     Unexpected use of 'print'                                              no-restricted-globals
  Line 12:22:    Expected an assignment or function call and instead saw an expression  no-unused-expressions
  Line 12:26:    Unexpected use of 'self'                                               no-restricted-globals
  Line 14:307:   Expected an assignment or function call and instead saw an expression  no-unused-expressions
  Line 23:174:   Expected an assignment or function call and instead saw an expression  no-unused-expressions
  Line 29:10:    Expected an assignment or function call and instead saw an expression  no-unused-expressions
  Line 29:152:   'readline' is not defined                                              no-undef
  Line 29:260:   Expected an assignment or function call and instead saw an expression  no-unused-expressions
  Line 29:350:   Expected an assignment or function call and instead saw an expression  no-unused-expressions
  Line 29:433:   Expected an assignment or function call and instead saw an expression  no-unused-expressions
  Line 30:19:    Expected an assignment or function call and instead saw an expression  no-unused-expressions
...
Run Code Online (Sandbox Code Playgroud)

我还尝试通过在编译时添加SIDE_MODULE=1标志来生成独立的 wasm 文件。

./src/utils/wasm/lss.js
  Line 10:6:     Expected an assignment or function call and instead saw an expression  no-unused-expressions
  Line 11:69:    Expected an assignment or function call and instead saw an expression  no-unused-expressions
  Line 11:117:   'read' is not defined                                                  no-undef
  Line 11:197:   'readbuffer' is not defined                                            no-undef
  Line 11:214:   'read' is not defined                                                  no-undef
  Line 11:336:   'quit' is not defined                                                  no-undef
  Line 11:367:   Unexpected use of 'print'                                              no-restricted-globals
  Line 11:430:   Unexpected use of 'print'                                              no-restricted-globals
  Line 11:493:   'printErr' is not defined                                              no-undef
  Line 12:1:     Unexpected use of 'print'                                              no-restricted-globals
  Line 12:22:    Expected an assignment or function call and instead saw an expression  no-unused-expressions
  Line 12:26:    Unexpected use of 'self'                                               no-restricted-globals
  Line 14:307:   Expected an assignment or function call and instead saw an expression  no-unused-expressions
  Line 23:174:   Expected an assignment or function call and instead saw an expression  no-unused-expressions
  Line 29:10:    Expected an assignment or function call and instead saw an expression  no-unused-expressions
  Line 29:152:   'readline' is not defined                                              no-undef
  Line 29:260:   Expected an assignment or function call and instead saw an expression  no-unused-expressions
  Line 29:350:   Expected an assignment or function call and instead saw an expression  no-unused-expressions
  Line 29:433:   Expected an assignment or function call and instead saw an expression  no-unused-expressions
  Line 30:19:    Expected an assignment or function call and instead saw an expression  no-unused-expressions
...
Run Code Online (Sandbox Code Playgroud)

但后来我又犯了一个错误。

WebAssembly.instantiate(): Import #0 module="env" error: module is not an object or function
Run Code Online (Sandbox Code Playgroud)

我试图弄清楚如何为我的情况声明一个正确的导入对象,但没有成功。他们有更好的解决方案吗?我怎么知道要在我的导入对象中放入什么?

谢谢 !

Kaw*_*aLo 5

所以我发现他们有更多的步骤可以让 WASM 二进制文件与 React 一起工作,特别是如果你想将它导入到 React es6 模块中(而不是从公共文件夹中)。

我不认为这是通用的解决方案,只是对我有用的解决方案(并且似乎在大多数情况下都有效)。

编译器标志

这是我现在使用的 em++ build 命令:

em++ ../main.cpp ../stringFormat.cpp \
      -Os -g1 \
      -s WASM=1 \
      -s MALLOC=emmalloc \
      -s ALLOW_MEMORY_GROWTH=1 \
      -s EXPORT_ES6=1 \
      -s MODULARIZE=1 \
      -s 'EXPORT_NAME="LongerSubSequence"' \
      -s 'ENVIRONMENT="web"' \
      --bind \
      -o lss.mjs \
      -std=c++11 || exit 1
Run Code Online (Sandbox Code Playgroud)

不确定所有选项,它们可能是这里要做的一些优化。重要的是:

  • 绑定:显然,编译器在命名 js 模块时必须覆盖 C++ 名称修改(默认情况下,C++ 编译器将更改所有变量名称,就像 react 对 css 模块所做的那样)。
  • EXPORT_ES6 : 会稍微改变生成的glue js 语法,以便它可以以es6 方式导入。
  • MODULARIZE:导出通用模块下的所有函数(以EXPORT_NAME标志命名),这样您就可以将 js 作为模块导入并在您的 js 代码中调用 Module.My_CPP_Function 。

  • g1:保持生成的胶水代码足够可读以管理下一步。

添加文件以进行反应

该过程生成两个文件:lss.mjs 和 lss.wasm。它们在 React 的项目树中是这样的:

My_React_Project
  |_public
  | |_/path/to/lss.wasm
  |
  |_src
    |_/path/to/lss.mjs
Run Code Online (Sandbox Code Playgroud)

/path/to/ 可以是文件夹内的任何路径,甚至是根目录。

适应胶水JS

最后,为了修复错误,我编辑了生成的 lss.mjs 文件:

  1. 添加/* eslint-disable */在文件顶部,以避免 React 语法错误。
  2. 替换var _scriptDir = import.meta.url;var _scriptDir = '/path/to/lss.wasm';, 相对于公用文件夹。不确定它对以下步骤是否有用,但 React 只会因 import.meta 语法而崩溃。
  3. 替换scriptDirectory = self.location.href;scriptDirectory = window.self.location.href;,因为 React es6 函数没有绑定到窗口。
  4. 删除以下块:
My_React_Project
  |_public
  | |_/path/to/lss.wasm
  |
  |_src
    |_/path/to/lss.mjs
Run Code Online (Sandbox Code Playgroud)

编译器使用它来自动定位二进制文​​件,但我自己处理了它。

  1. 替换var wasmBinaryFile = "lss.wasm";const wasmBinaryFile = '/path/to/lss.wasm';('/' 将指向公​​用文件夹)。
  2. 消除:
var dataURIPrefix = "data:application/octet-stream;base64,";

function isDataURI(filename) {
 return String.prototype.startsWith ? filename.startsWith(dataURIPrefix) : filename.indexOf(dataURIPrefix) === 0;
}
Run Code Online (Sandbox Code Playgroud)
  1. 消除:
if (!isDataURI(wasmBinaryFile)) {
 wasmBinaryFile = locateFile(wasmBinaryFile);
}
Run Code Online (Sandbox Code Playgroud)
  1. 将该getBinaryPromise()函数替换为以下函数:
function getBinary() {
 try {
  if (wasmBinary) {
   return new Uint8Array(wasmBinary);
  }
  if (readBinary) {
   return readBinary(wasmBinaryFile);
  } else {
   throw "both async and sync fetching of the wasm failed";
  }
 } catch (err) {
  abort(err);
 }
}
Run Code Online (Sandbox Code Playgroud)
  1. 下面几行,替换
const getBinaryPromise = () => new Promise((resolve, reject) => {
 fetch(wasmBinaryFile, { credentials: 'same-origin' })
   .then(
     response => {
      if (!response['ok']) {
       throw "failed to load wasm binary file at '" + wasmBinaryFile + "'";
      }
      return response['arrayBuffer']();
     }
   )
   .then(resolve)
   .catch(reject);
});
Run Code Online (Sandbox Code Playgroud)

if (!wasmBinary && typeof WebAssembly.instantiateStreaming === "function" && !isDataURI(wasmBinaryFile) && typeof fetch === "function")
Run Code Online (Sandbox Code Playgroud)

这只是删除 isDataURI 条件,因为我们“信任”我们提供给二进制文件的路径。

现在使用您的二进制代码

这就是我管理它所需要的全部。现在,在我的任何反应文件中,我都可以像这样使用我的 c++ 函数:

任何文件.js

if (!wasmBinary && typeof WebAssembly.instantiateStreaming === "function" && typeof fetch === "function")
Run Code Online (Sandbox Code Playgroud)

在 Chrome、Firefox 和 Safari 上效果很好,稍后会进行更多测试。