摘要
这里就不在介绍 WebAssembly、Quickjs 是什么了,我觉得前人的介绍都特别棒.在这里主要是想和大家交流交流,怎么有效的去玩这项技术!
Safe - wasm - jsvm
仓库地址: github.com/1160007652/…
如何调用Quickjs,实现eval执行js代码.
#include <emscripten.h>
#include <string.h>
#include "../lib/quickjs-2020-03-16/quickjs.h"
#ifndef EM_PORT_API
# if defined(__EMSCRIPTEN__)
# if defined(__cplusplus)
# define EM_PORT_API(rettype) extern "C" rettype EMSCRIPTEN_KEEPALIVE
# else
# define EM_PORT_API(rettype) rettype EMSCRIPTEN_KEEPALIVE
# endif
# else
# if defined(__cplusplus)
# define EM_PORT_API(rettype) extern "C" rettype
# else
# define EM_PORT_API(rettype) rettype
# endif
# endif
#endif
// 导出 api 供 js 调用
EM_PORT_API(const char*) eval(const char* str) {
// 运行时环境
JSRuntime* runtime = JS_NewRuntime();
// 获取运行上下文
JSContext* ctx = JS_NewContext(runtime);
// 在此上下文中eval 执行js 代码
JSValue result = JS_Eval(ctx, str, strlen(str), "<evalScript>", JS_EVAL_TYPE_GLOBAL);
// 异常情况
if (JS_IsException(result)) {
JSValue realException = JS_GetException(ctx);
return JS_ToCString(ctx, realException);
}
JSValue json = JS_JSONStringify(ctx, result, JS_UNDEFINED, JS_UNDEFINED);
// 释放内存
JS_FreeValue(ctx, result);
return JS_ToCString(ctx, json);
}
wasm编译脚本
#!/bin/bash
set -e
# 编译quickjs 根目录
export QUICKJSHOME="./lib/quickjs-2020-03-16"
export inputFile="./src-wasm/main.cpp ${QUICKJSHOME}/quickjs.c ${QUICKJSHOME}/cutils.c ${QUICKJSHOME}/libregexp.c ${QUICKJSHOME}/libbf.c ${QUICKJSHOME}/libunicode.c"
# export inputFile="./src-wasm/main.cpp"
export outputFile="./public/build/main-wasm.js"
export OPTIMIZE="-DCONFIG_VERSION=\"1.0.0\" -lm -Oz -Wall -Werror --llvm-lto 1 -fno-exceptions"
export LDFLAGS="${OPTIMIZE}"
export CFLAGS="${OPTIMIZE}"
export CXXFLAGS="${OPTIMIZE}"
echo -e "\033[?25l" # 隐藏光标
echo -e "\033[44;37m ============================================= \033[0m"
echo ''
echo -e "\033[32m[ wasm ] 开始构建 \033[0m"
echo -e "\033[1A"
(
# Compile C/C++ code
docker run --rm -it -v `pwd`:/src apiaryio/emcc emcc \
${inputFile} \
${OPTIMIZE} \
-s MALLOC=emmalloc \
-s STRICT=1 \
-s EXPORT_ES6=0 \
-s SINGLE_FILE=1 \
-s INLINING_LIMIT=0 \
-s NO\_FILESYSTEM=1 \
-s ALLOW_MEMORY_GROWTH=1 \
-s ASSERTIONS=1 \
-s NO_EXIT_RUNTIME=0 \
-s WASM=1 \
-s ENVIRONMENT="web" \
-s EXPORT_NAME='SafeJSModule' \
-s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' \
--memory-init-file 0 \
--bind \
-o ${outputFile}
)
# -s MALLOC=emmalloc
# -s FILESYSTEM=0
# -s STRICT=1 删除对所有已弃用的构建选项的支持。这可确保您的代码以向前兼容的方式构建。
# -s ALLOW_MEMORY_GROWTH=1 允许内存在必要时自动增长
# -s EXPORT_ES6=1 将JavaScript代码转换为ES6模块,其默认导出适用于任何捆绑器
# -s FILESYSTEM=0 是否启用文件系统
# --cpuprofiler cpu 分析器
# --memoryprofiler 内存 分析器
# --threadprofiler 多线程 分析器
echo -e "\033[32m[ wasm ] 构建完成 \033[0m"
echo -e "\033[32m[ Path ] ${outputFile} \033[0m"
echo -e "\033[32m[ wasm ] Gzip压缩 \033[0m"
echo -e "\033[1A"
(
gzip -n -c ${outputFile} > ${outputFile}.gz
)
echo -e "\033[32m[ Path ] ${outputFile}.gz \033[0m"
echo ''
echo -e "\033[44;37m ============================================= \033[0m"
echo -e "\033[?25h" # 显示光标
asm编译脚本
#!/bin/bash
set -e
# 编译quickjs 根目录
export QUICKJSHOME="./lib/quickjs-2020-03-16"
export inputFile="./src-wasm/main.cpp ${QUICKJSHOME}/quickjs.c ${QUICKJSHOME}/cutils.c ${QUICKJSHOME}/libregexp.c ${QUICKJSHOME}/libbf.c ${QUICKJSHOME}/libunicode.c"
export outputFile="./public/build/main-asm.js"
export OPTIMIZE="-DCONFIG_VERSION=\"1.0.0\" -lm -Oz -Wall -Werror --llvm-lto 1 -fno-exceptions"
export LDFLAGS="${OPTIMIZE}"
export CFLAGS="${OPTIMIZE}"
export CXXFLAGS="${OPTIMIZE}"
echo -e "\033[?25l" # 隐藏光标
echo -e "\033[44;37m ============================================= \033[0m"
echo ''
echo -e "\033[32m[ asm ] 开始构建 \033[0m"
echo -e "\033[1A"
(
# Compile C/C++ code
docker run --rm -it -v `pwd`:/src apiaryio/emcc emcc \
${inputFile} \
${OPTIMIZE} \
-s MALLOC=emmalloc \
-s EXPORT_ES6=0 \
-s SINGLE_FILE=1 \
-s INLINING_LIMIT=0 \
-s NO\_FILESYSTEM=1 \
-s ASSERTIONS=1 \
-s NO_EXIT_RUNTIME=1 \
-s WASM=0 \
-s ENVIRONMENT="web" \
-s EXPORT_NAME='SafeJSModule' \
-s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' \
--memory-init-file 0 \
--bind \
-o ${outputFile}
)
# -s MODULARIZE=1
# -s ENVIRONMENT="web"
# -s STRICT=1 删除对所有已弃用的构建选项的支持。这可确保您的代码以向前兼容的方式构建。
# -s ALLOW_MEMORY_GROWTH=1 允许内存在必要时自动增长
# -s EXPORT_ES6=1 将JavaScript代码转换为ES6模块,其默认导出适用于任何捆绑器
# -s FILESYSTEM=0 是否启用文件系统
echo -e "\033[32m[ asm ] 构建完成 \033[0m"
echo -e "\033[32m[ Path ] ${outputFile} \033[0m"
echo -e "\033[32m[ asm ] Gzip压缩 \033[0m"
echo -e "\033[1A"
(
gzip -n -c ${outputFile} > ${outputFile}.gz
)
echo -e "\033[32m[ Path ] ${outputFile}.gz \033[0m"
echo ''
echo -e "\033[44;37m ============================================= \033[0m"
echo -e "\033[?25h" # 显示光标
如何调用脚本
因为需要启动服务,调用脚本所以初始化了一个npm 环境.
"scripts": {
"server": "http-server ./public -p 8088 -g --cors -o",
"asm": "./config/asm_build.sh",
"wasm": "./config/wasm_build.sh",
"test": "echo \"Error: no test specified\" && exit 1"
},
启动server服务,测试结果
npm run server
编译quickjs 为wasm 模块
npm run wasm
编译quickjs 为asm 模块
npm run asm
优化考虑
- 因为有些工程,是引入了 polyfill 库的,所以在编译wasm时,去除polyfill的注入, 由调用者在外部提供运行环境.
loadScript("//cdn.bootcss.com/babel-polyfill/7.4.4/polyfill.min.js", function(){
loadScript("../build/main-asm.js");
});
- 由于现在的运行环境都会启用Gzip进行压缩,所以打包的时候,直接编译出两种结果,一种是为压缩的,一种是压缩的.
运行效果
wasm
asm
心得
1. 如何区分模块,避免与前端中的其他模块名产生冲突.
-s EXPORT_NAME='SafeJSModule'
EXPORT_NAME, 得作用就是为了编译出来的模块名
2. 如何区分编译出来的模块是wasm、还是asm?
-s WASM=1
WASM, { 1: wasm 模块,0: asm 模块 }
3. 如何指导编译出来的模块运行宿主环境.
-s ENVIRONMENT="web"
ENVIRONMENT, 得作用就是为了设置宿主环境的