强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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使 stdos 模块作为全局对象
--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

扩展阅读