QuickJS 嵌入式 JavaScript 引擎完全教程 / 02 - 安装与编译
安装与编译
2.1 获取源码
QuickJS 没有官方的包管理器分发,需要从源码编译。
从 Bellard 官网下载
# 下载最新发布版(以 2024-01-13 版本为例)
wget https://bellard.org/quickjs/quickjs-2024-01-13.tar.xz
tar xf quickjs-2024-01-13.tar.xz
cd quickjs-2024-01-13
从 GitHub 克隆
# 社区维护的 GitHub 镜像(更新更频繁)
git clone https://github.com/nicoretti/quickjs.git
cd quickjs
目录结构
quickjs/
├── quickjs.c # 核心引擎实现
├── quickjs.h # C API 头文件
├── quickjs-libc.c # 标准库实现(os, std 模块)
├── quickjs-libc.h # 标准库头文件
├── qjs.c # qjs 命令行解释器入口
├── qjsc.c # qjsc 字节码编译器入口
├── quickjs-opcode.h # 字节码操作码定义
├── libregexp.c # 正则表达式引擎
├── libunicode.c # Unicode 支持
├── cutils.c / cutils.h # C 工具函数
├── Makefile # 构建脚本
├── run-test262.c # Test262 测试运行器
├── test262.conf # Test262 测试配置
├── doc/ # 文档
│ └── quickjs.pdf # 官方 PDF 文档
└── tests/ # 测试文件
├── test_builtin.js
├── test_language.js
└── ...
2.2 编译构建
Linux / macOS 编译
# 进入源码目录
cd quickjs
# 使用默认配置编译(静态链接,优化级别 -O2)
make
# 编译完成后会生成以下文件:
# qjs - JavaScript 解释器
# qjsc - 字节码编译器
# qjscalc - 用 QuickJS 自身实现的计算器
# libquickjs.a - 静态库(用于嵌入)
Makefile 常用选项
# 查看所有可用的 make 目标
make help
# 常用目标
make # 默认构建
make qjs # 仅构建 qjs 解释器
make qjsc # 仅构建 qjsc 编译器
make test # 运行测试套件
make test262 # 运行 ECMAScript Test262
make clean # 清理构建文件
make install # 安装到 /usr/local(需要 root)
自定义编译选项
# 自定义安装路径
make install PREFIX=/opt/quickjs
# 启用地址消毒器(调试用)
CFLAGS="-fsanitize=address -g" LDFLAGS="-fsanitize=address" make
# 启用 ASAN + UBSAN(全面检测)
CFLAGS="-fsanitize=address,undefined -g" LDFLAGS="-fsanitize=address,undefined" make
# 最小体积编译
CFLAGS="-Os -ffunction-sections -fdata-sections" LDFLAGS="-Wl,--gc-sections" make
Windows 编译
使用 MSYS2 / MinGW
# 在 MSYS2 MinGW64 终端中
pacman -S mingw-w64-x86_64-gcc make git
cd quickjs
make
使用 WSL
# 在 WSL Ubuntu 中
sudo apt install gcc make git
cd quickjs
make
使用 CMake(社区维护)
# 部分 GitHub 镜像提供 CMake 支持
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j$(nproc)
2.3 qjs 解释器
qjs 是 QuickJS 的命令行交互式解释器(REPL),类似于 Node.js 的 node 命令。
基本使用
# 启动 REPL 交互模式
./qjs
# 执行脚本文件
./qjs script.js
# 执行单行表达式
./qjs -e 'console.log("Hello, QuickJS!")'
# 带参数执行脚本
./qjs script.js arg1 arg2
命令行选项
| 选项 | 说明 |
|---|---|
-e EXPR | 执行指定的 JavaScript 表达式 |
-i | 执行脚本后进入 REPL |
-m | 以 ES Module 模式加载脚本 |
--std | 使 std 和 os 模块作为全局对象 |
--script | 强制使用脚本模式(非模块) |
--module | 强制使用模块模式 |
-q | 安静模式,不显示版本信息 |
--memory-limit N | 设置内存限制(字节) |
--stack-size N | 设置栈大小(字节) |
--opcode-count N | 设置最大操作码执行数 |
REPL 交互示例
$ ./qjs
QuickJS - Type "\h" for help
qjs > 1 + 2
3
qjs > function hello(name) { return `Hello, ${name}!`; }
undefined
qjs > hello("World")
"Hello, World!"
qjs > [1,2,3].map(x => x * 2)
[2,4,6]
qjs > let {readFile} = await import("std")
undefined
qjs > \q # 退出 REPL
运行脚本文件
// hello.js
import * as os from "os";
import * as std from "std";
console.log("QuickJS Version:", scriptArgs[0] || "unknown");
console.log("Platform:", os.platform);
console.log("Arch:", os.arch);
// 读取命令行参数
for (let i = 0; i < scriptArgs.length; i++) {
console.log(`Arg ${i}: ${scriptArgs[i]}`);
}
./qjs hello.js --version
2.4 qjsc 字节码编译器
qjsc 可以将 JavaScript 源码编译为字节码,甚至生成 C 源文件以便嵌入到应用程序中。
编译为字节码
# 将 JS 文件编译为字节码(.qjsc 文件)
./qjsc -o hello.qjsc hello.js
# 执行字节码(比解析源码更快)
./qjs hello.qjsc
生成 C 嵌入代码
# 生成 C 字节数组,可直接编译进 C 程序
./qjsc -c -o hello_bytecode.h -m hello.js
# 生成完整的 C 主程序
./qjsc -o hello_c -m hello.js
# 包含多个模块
./qjsc -c -o modules.h \
-m module_a.js \
-m module_b.js \
main.js
qjsc 命令选项
| 选项 | 说明 |
|---|---|
-o FILE | 输出文件名 |
-c | 输出 C 源文件(字节数组) |
-m | 以模块模式编译 |
-M MODULE_NAME | 指定模块名称 |
-N MODULE_NAME | 设置 C 变量名 |
-p PREFIX | 设置函数前缀名 |
-D DEFINENAME | 定义预处理宏 |
-e | 输出独立可执行文件 |
-flto | 使用链接时优化(LTO) |
-fbignum | 启用 BigDecimal/BigInt 支持 |
字节码编译工作流
# 步骤 1:编写 JavaScript 模块
cat > app.js << 'EOF'
import { processData } from "./processor.js";
const input = [1, 2, 3, 4, 5];
const result = processData(input);
console.log("Result:", result);
EOF
cat > processor.js << 'EOF'
export function processData(arr) {
return arr.map(x => x ** 2).filter(x => x > 4);
}
EOF
# 步骤 2:编译为字节码
./qjsc -c -o app_bytecode.h -m app.js -m processor.js
# 步骤 3:在 C 项目中使用
# #include "app_bytecode.h"
# 然后加载并执行字节码(详见第 04 章)
2.5 语言绑定
C++ 绑定 (quickjspp)
quickjspp 提供了类型安全的 C++ 封装。
// quickjspp 基本示例
#include "quickjspp.hpp"
#include <iostream>
int main() {
qjs::Runtime rt;
qjs::Context ctx(rt);
// 直接在 C++ 中执行 JavaScript
auto result = ctx.eval(R"(
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
fibonacci(10);
)");
std::cout << "Result: " << (int)result << std::endl; // 55
// 注册 C++ 函数到 JavaScript
ctx.global().add("nativeAdd", [](int a, int b) {
return a + b;
});
auto sum = ctx.eval("nativeAdd(3, 4)");
std::cout << "Sum: " << (int)sum << std::endl; // 7
return 0;
}
Go 绑定
// go-quickjs 使用示例
package main
import (
"fmt"
quickjs "github.com/nicoretti/go-quickjs"
)
func main() {
rt := quickjs.NewRuntime()
defer rt.Free()
ctx := rt.NewContext()
defer ctx.Free()
result, err := ctx.Eval(`2 ** 10`)
if err != nil {
panic(err)
}
fmt.Println("2^10 =", result) // 1024
}
Rust 绑定
// quickjs-rs 使用示例
use quickjs_rs::{Runtime, Context};
fn main() {
let rt = Runtime::new().unwrap();
let ctx = Context::new(&rt).unwrap();
let result = ctx.eval_global("1 + 2 + 3").unwrap();
println!("Result: {:?}", result); // Int(6)
}
2.6 FFI(外部函数接口)
QuickJS 内置了对 C 函数的 FFI 支持,可以直接从 JavaScript 调用动态库中的 C 函数。
FFI 基本用法
// ffi_example.js
import * as os from "os";
// 加载 C 标准库中的函数
const libc = os.ffi;
// 调用 libc 的 time() 函数
const timeFunc = libc.bind("time", "number", ["pointer"]);
const currentTime = timeFunc(null);
console.log("Current timestamp:", currentTime);
// 调用 libc 的 strlen()
const strlenFunc = libc.bind("strlen", "number", ["string"]);
console.log("Length:", strlenFunc("Hello QuickJS!")); // 14
FFI 类型映射
| JavaScript 类型 | C 类型 | 说明 |
|---|---|---|
"number" | double / int | 数值类型 |
"string" | const char* | 字符串 |
"pointer" | void* | 指针 |
"bool" | int | 布尔值 |
"void" | void | 无返回值 |
注意: FFI 功能在不同版本中可能有差异,部分功能需要编译时启用。在生产环境中使用 FFI 需要格外注意类型安全和内存管理。
2.7 WebAssembly 构建
通过 Emscripten 可以将 QuickJS 编译为 WebAssembly,在浏览器或 Node.js 中运行。
编译步骤
# 1. 安装 Emscripten
git clone https://github.com/nicoretti/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh
# 2. 编译 QuickJS 为 WASM
cd quickjs
emmake make \
CC=emcc \
CFLAGS="-O3 -s WASM=1" \
EXE=.js
# 3. 生成的文件
# qjs.wasm - WebAssembly 模块
# qjs.js - JavaScript 胶水代码
在浏览器中使用
<!DOCTYPE html>
<html>
<head>
<title>QuickJS in Browser</title>
</head>
<body>
<div id="output"></div>
<script src="qjs.js"></script>
<script>
Module.onRuntimeInitialized = function() {
// QuickJS WASM 已加载,可以执行 JS 代码
const output = document.getElementById('output');
output.textContent = 'QuickJS loaded in WASM!';
};
</script>
</body>
</html>
使用 quickjs-emscripten 库
npm install quickjs-emscripten
import { getQuickJS } from "quickjs-emscripten";
async function main() {
const QuickJS = await getQuickJS();
const vm = QuickJS.newContext();
// 执行 JavaScript 代码
const result = vm.evalCode(`
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
fibonacci(10);
`);
if (result.error) {
console.log("Error:", vm.dump(result.error));
result.error.dispose();
} else {
console.log("Result:", vm.dump(result.value));
result.value.dispose();
}
vm.dispose();
}
main(); // Output: Result: 55
2.8 验证安装
安装完成后,可以通过以下方式验证:
# 1. 检查版本
./qjs --version 2>/dev/null || ./qjs -e 'console.log("QuickJS is working")'
# 2. 运行 ES2023 特性测试
./qjs -e '
// ES2023 Array methods
const arr = [1, 2, 3, 4, 5];
console.log("findLast:", arr.findLast(x => x % 2 === 0)); // 4
// async/await
async function test() {
const result = await Promise.resolve(42);
console.log("Promise:", result); // 42
}
test();
// Class with private fields
class Counter {
#count = 0;
increment() { this.#count++; }
get value() { return this.#count; }
}
const c = new Counter();
c.increment();
c.increment();
console.log("Private field:", c.value); // 2
'
# 3. 运行内置测试
make test
2.9 常见问题
编译错误
| 问题 | 解决方案 |
|---|---|
gcc: command not found | 安装 GCC:apt install gcc (Ubuntu) 或 xcode-select --install (macOS) |
fatal error: quickjs.h: No such file | 确保在 QuickJS 源码目录中执行编译 |
undefined reference to clock_gettime | 添加链接选项:LDFLAGS=-lrt |
implicit declaration of function | 确认 GCC 版本 ≥ 4.8 |
运行时错误
# 内存不足
./qjs --memory-limit 16777216 script.js # 限制 16MB
# 栈溢出
./qjs --stack-size 1048576 script.js # 限制 1MB 栈
# 字节码不兼容
# 不同版本的 QuickJS 字节码可能不兼容,需要重新编译
2.10 本章小结
| 要点 | 说明 |
|---|---|
| 源码获取 | 从 Bellard 官网或 GitHub 镜像下载 |
| 编译方式 | make 一条命令即可完成 |
| qjs | 命行解释器,支持 REPL 和脚本执行 |
| qjsc | 字节码编译器,可生成 C 嵌入代码 |
| 语言绑定 | C++、Go、Rust 等均有社区实现 |
| FFI | 内置 C 函数接口调用支持 |
| WebAssembly | 通过 Emscripten 编译为 WASM |
扩展阅读
- QuickJS Makefile 源码 — 了解编译细节
- Emscripten 文档 — WebAssembly 编译
- quickjs-emscripten — 浏览器端 QuickJS
- quickjspp — C++ 绑定