在 React 中为大文件(超过 4kb)加载 WASM 模块的正确方法
Proper way to load WASM module in React for big files (more than 4kb)
我对如何使用 wasm 编译器在 React 代码中加载 C++ 函数感到困惑。
我的C++由两个文件组成,编译后得到一个160kb的wasm文件。这是我目前用于编译的命令(运行 on 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
然后我将 lss 和 wasm 文件一起复制到我的 React 代码中的同一文件夹中。
src
- utils
- wasm
lss.js
lss.wasm
但是,每当我尝试在另一个文件中导入 lss.js 时,我的应用程序就会因一堆未定义的表达式而崩溃。
我的js文件
import * as lss from '../wasm/lss'
./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
...
我还尝试通过在编译时添加 SIDE_MODULE=1 标志来生成独立的 wasm 文件。
// My util function to load wasm file to js
const loadWebAssembly = (filename, imports = {}) =>
WebAssembly
.instantiateStreaming(fetch(filename), imports)
.then(({instance}) => instance.exports);
// wasm file is now in my static folder, in public/
const lss = loadWebAssembly('wasm/lss.wasm')
// splitIntoCommonSequences is the C++ function I try to call
.then(module => Promise.resolve(module.splitIntoCommonSequences));
export {lss};
但是我又遇到了一个错误。
WebAssembly.instantiate(): Import #0 module="env" error: module is not an object or function
我试图弄清楚如何为我的情况声明一个正确的导入对象,但没有成功。他们有更好的解决方案吗?我怎么知道要在我的导入对象中放入什么?
谢谢!
所以我发现他们需要执行更多步骤才能让 WASM 二进制文件与 React 一起工作,特别是如果你想将它导入 React es6 模块(而不是从 public 文件夹)。
我不认为这是一个通用的解决方案,只是对我有用的解决方案(而且似乎在大多数情况下都有效)。
编译器标志
这是我现在使用的 em++ 构建命令:
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
不确定所有的选项,它们可能是在这里做的一些优化。重要的是:
- Bind:编译器显然有必要在命名 js 模块时覆盖 C++ 名称重整(默认情况下,C++ 编译器将更改所有变量名称,就像 react 对 css 模块)。
- EXPORT_ES6: 会稍微改变生成的胶水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
/path/to/ 可以是文件夹内的任何路径,甚至是根目录。
ADAPT GLUE JS
最后,为了修复错误,我编辑了生成的 lss.mjs 文件:
- 在文件顶部添加
/* eslint-disable */
,以避免 React 语法错误。
- 将
var _scriptDir = import.meta.url;
替换为 var _scriptDir = '/path/to/lss.wasm';
,相对于 public 文件夹。不确定它对以下步骤是否有用,但 React 只会因 import.meta 语法而崩溃。
- 将
scriptDirectory = self.location.href;
替换为 scriptDirectory = window.self.location.href;
,因为 React es6 函数未绑定到 window。
- 删除以下块:
var dataURIPrefix = "data:application/octet-stream;base64,";
function isDataURI(filename) {
return String.prototype.startsWith ? filename.startsWith(dataURIPrefix) : filename.indexOf(dataURIPrefix) === 0;
}
是编译器用来自动定位二进制文件的,我自己处理了
- 将
var wasmBinaryFile = "lss.wasm";
替换为 const wasmBinaryFile = '/path/to/lss.wasm';
('/' 将指向 public 文件夹)。
- 删除:
if (!isDataURI(wasmBinaryFile)) {
wasmBinaryFile = locateFile(wasmBinaryFile);
}
- 删除:
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);
}
}
- 将
getBinaryPromise()
函数替换为以下函数:
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);
});
- 下面几行,替换
if (!wasmBinary && typeof WebAssembly.instantiateStreaming === "function" && !isDataURI(wasmBinaryFile) && typeof fetch === "function")
和
if (!wasmBinary && typeof WebAssembly.instantiateStreaming === "function" && typeof fetch === "function")
这只是删除了 isDataURI 条件,因为我们 'trust' 我们给二进制文件的路径。
现在使用您的二进制代码
这就是我管理它所需要的全部。现在,在我的任何反应文件中,我都可以像这样使用我的 c++ 函数:
anyfile.js
import LongerSubSequenceGlue from 'path/to/lss.mjs';
// Avoid main function running if you just want to use another function
const lssModule = LongerSubSequenceGlue({
noInitialRun: true,
noExitRuntime: true
});
//...
lssModule.my_cpp_function(...my_params);
在 Chrome、Firefox 和 Safari 上表现出色,稍后将进行更多测试。
我对如何使用 wasm 编译器在 React 代码中加载 C++ 函数感到困惑。
我的C++由两个文件组成,编译后得到一个160kb的wasm文件。这是我目前用于编译的命令(运行 on 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
然后我将 lss 和 wasm 文件一起复制到我的 React 代码中的同一文件夹中。
src
- utils
- wasm
lss.js
lss.wasm
但是,每当我尝试在另一个文件中导入 lss.js 时,我的应用程序就会因一堆未定义的表达式而崩溃。
我的js文件
import * as lss from '../wasm/lss'
./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
...
我还尝试通过在编译时添加 SIDE_MODULE=1 标志来生成独立的 wasm 文件。
// My util function to load wasm file to js
const loadWebAssembly = (filename, imports = {}) =>
WebAssembly
.instantiateStreaming(fetch(filename), imports)
.then(({instance}) => instance.exports);
// wasm file is now in my static folder, in public/
const lss = loadWebAssembly('wasm/lss.wasm')
// splitIntoCommonSequences is the C++ function I try to call
.then(module => Promise.resolve(module.splitIntoCommonSequences));
export {lss};
但是我又遇到了一个错误。
WebAssembly.instantiate(): Import #0 module="env" error: module is not an object or function
我试图弄清楚如何为我的情况声明一个正确的导入对象,但没有成功。他们有更好的解决方案吗?我怎么知道要在我的导入对象中放入什么?
谢谢!
所以我发现他们需要执行更多步骤才能让 WASM 二进制文件与 React 一起工作,特别是如果你想将它导入 React es6 模块(而不是从 public 文件夹)。
我不认为这是一个通用的解决方案,只是对我有用的解决方案(而且似乎在大多数情况下都有效)。
编译器标志
这是我现在使用的 em++ 构建命令:
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
不确定所有的选项,它们可能是在这里做的一些优化。重要的是:
- Bind:编译器显然有必要在命名 js 模块时覆盖 C++ 名称重整(默认情况下,C++ 编译器将更改所有变量名称,就像 react 对 css 模块)。
- EXPORT_ES6: 会稍微改变生成的胶水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
/path/to/ 可以是文件夹内的任何路径,甚至是根目录。
ADAPT GLUE JS
最后,为了修复错误,我编辑了生成的 lss.mjs 文件:
- 在文件顶部添加
/* eslint-disable */
,以避免 React 语法错误。 - 将
var _scriptDir = import.meta.url;
替换为var _scriptDir = '/path/to/lss.wasm';
,相对于 public 文件夹。不确定它对以下步骤是否有用,但 React 只会因 import.meta 语法而崩溃。 - 将
scriptDirectory = self.location.href;
替换为scriptDirectory = window.self.location.href;
,因为 React es6 函数未绑定到 window。 - 删除以下块:
var dataURIPrefix = "data:application/octet-stream;base64,";
function isDataURI(filename) {
return String.prototype.startsWith ? filename.startsWith(dataURIPrefix) : filename.indexOf(dataURIPrefix) === 0;
}
是编译器用来自动定位二进制文件的,我自己处理了
- 将
var wasmBinaryFile = "lss.wasm";
替换为const wasmBinaryFile = '/path/to/lss.wasm';
('/' 将指向 public 文件夹)。 - 删除:
if (!isDataURI(wasmBinaryFile)) {
wasmBinaryFile = locateFile(wasmBinaryFile);
}
- 删除:
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);
}
}
- 将
getBinaryPromise()
函数替换为以下函数:
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);
});
- 下面几行,替换
if (!wasmBinary && typeof WebAssembly.instantiateStreaming === "function" && !isDataURI(wasmBinaryFile) && typeof fetch === "function")
和
if (!wasmBinary && typeof WebAssembly.instantiateStreaming === "function" && typeof fetch === "function")
这只是删除了 isDataURI 条件,因为我们 'trust' 我们给二进制文件的路径。
现在使用您的二进制代码
这就是我管理它所需要的全部。现在,在我的任何反应文件中,我都可以像这样使用我的 c++ 函数:
anyfile.js
import LongerSubSequenceGlue from 'path/to/lss.mjs';
// Avoid main function running if you just want to use another function
const lssModule = LongerSubSequenceGlue({
noInitialRun: true,
noExitRuntime: true
});
//...
lssModule.my_cpp_function(...my_params);
在 Chrome、Firefox 和 Safari 上表现出色,稍后将进行更多测试。