改变世界的编程语言MoonBit:配置系统介绍(下)
大家好,我是农村程序员,独立开发者,前端之虎陈随易,我的个人网站是
https://chensuiyi.me
这是我的
《改变世界的编程语言MoonBit》系列文章,将自己学习和理解 MoonBit 的过程分享给大家,希望能带来参考和帮助。
全部文章可以前往 MoonBit 开发网
https://moonbit.edgeone.app或https://moonbit.pages.dev查看,我坚信,MoonBit 将会改变世界。
往期文章
- 改变世界的编程语言 MoonBit:背景知识速览
- 改变世界的编程语言 MoonBit:项目文件详解
- 改变世界的编程语言 MoonBit:配置系统介绍(上)
上篇文章,主要对 MoonBit 的moon.mod.json(模块配置)进行了详细讲解,本文则对moon.pkg.json(包配置)进行介绍和讲解。
MoonBit 包配置概述
按照 MoonBit 的约定,一个目录下如果有 moon.pkg.json 文件,那么这个目录就是一个包(Package)。
如果说模块(Module)是一个项目的整体配置,那么包(Package)就是项目中某个具体功能单元的配置。一个模块可以包含多个包,每个包负责不同的功能。
类比理解:模块就像一本书,而包就是书中的各个章节。每个章节有自己的内容和特点,但都属于同一本书。
完整配置示例
MoonBit 完整的包配置参数(moon.pkg.json)如下:
{"is-main": false,"import": ["moonbitlang/quickcheck",{"path": "moonbitlang/x/encoding","alias": "lib","value": ["encode"]}],"test-import": [{"path": "moonbitlang/core/test","alias": "test"}],"wbtest-import": [],"link": {"native": {"cc": "/usr/bin/gcc13","cc-flags": "-DMOONBIT","cc-link-flags": "-s"},"js": {"exports": ["hello"],"format": "esm"},"wasm": {"export-memory-name": "memory","import-memory": {"module": "env","name": "memory"},"exports": ["hello", "foo:bar"]},"wasm-gc": {"exports": ["hello", "foo:bar"],"use-js-builtin-string": true,"imported-string-constants": "_"}},"native-stub": ["file1.c", "file2.c"],"implement": "moonbitlang/core/abort","overrides": ["moonbitlang/dummy_abort/abort_show_msg"],"pre-build": [{"input": "a.txt","output": "a.mbt","command": ":embed -i $input -o $output"}],"targets": {"only_js.mbt": ["js"],"only_wasm.mbt": ["wasm"],"only_wasm_gc.mbt": ["wasm-gc"],"all_wasm.mbt": ["wasm", "wasm-gc"],"not_js.mbt": ["not", "js"],"only_debug.mbt": ["debug"],"js_and_release.mbt": ["and", ["js"], ["release"]],"js_or_wasm.mbt": ["js", "wasm"],"wasm_release_or_js_debug.mbt": ["or", ["and", "wasm", "release"], ["and", "js", "debug"]]},"virtual": {"has-default": true},"warn-list": "-2-4+31","alert-list": "-test_import_all+deprecated-warn"
}
下面我们逐个详细讲解每个配置项。
is-main 配置说明
is-main 用来标识这个包是否是程序的入口包,就像一本书的第一章,是读者开始阅读的地方。
配置值:
true:表示这是主包,包含程序的入口点(main函数)false:表示这是普通包,提供功能但不是程序入口
通俗理解:
在一个应用程序中,必须有一个起点,告诉计算机"从这里开始执行"。is-main: true 就是在说:“嘿,我就是那个起点!”
典型使用场景:
场景一:命令行工具
你开发了一个命令行工具,需要一个入口来接收用户的命令和参数。
{"is-main": true,"import": ["moonbitlang/core/builtin"]
}
配套的 main.mbt 文件:
fn main {println("Hello, MoonBit!")
}
场景二:库包
你开发了一个工具库,提供各种实用函数,但它本身不需要运行。
{"is-main": false,"import": []
}
这个包提供的函数将被其他包导入使用。
注意事项:
⚠️ 根据实测,一个模块中可以有多个 is-main: true 的包,可以生成多个可执行文件。
⚠️ 如果设置了 is-main: true,那么包中必须有 main 函数,否则会报错。
import 配置说明
import 就是告诉 MoonBit:“我需要使用其他包的功能”。就像做菜时,你需要的各种调料和食材。
配置方式:
方式一:简单导入
{"import": ["moonbitlang/core/builtin", "moonbitlang/quickcheck"]
}
直接列出需要导入的包的完整路径。
方式二:带别名导入
{"import": [{"path": "moonbitlang/x/encoding","alias": "enc"}]
}
给导入的包起个简短的别名,使用时更方便。
方式三:选择性导入
{"import": [{"path": "moonbitlang/x/encoding","alias": "lib","value": ["encode", "decode"]}]
}
只导入包中的特定功能,而不是全部。
在代码中使用:
导入后,在代码中通过 @别名 或 @包名 来使用:
// 使用简单导入的包
let result = @quickcheck.run(test)// 使用别名导入的包
let encoded = @enc.encode(data)// 使用选择性导入
let value = @lib.encode("hello")
实际应用场景:
场景一:开发 Web 应用
需要 HTTP 客户端、JSON 处理等功能:
{"import": [{ "path": "moonbitlang/x/http", "alias": "http" },{ "path": "moonbitlang/x/json", "alias": "json" }]
}
场景二:数据处理工具
只需要特定的编解码功能:
{"import": [{"path": "moonbitlang/x/encoding","alias": "encoding","value": ["base64_encode", "base64_decode"]}]
}
依赖关系:
| 配置位置 | 配置内容 | 作用 |
|---|---|---|
| moon.mod.json | deps | 声明模块级别的依赖(需要谁) |
| moon.pkg.json | import | 在包中实际使用依赖(用了谁) |
| 关系 | deps 是前提条件 | import 是具体使用 |
| 类比 | 买菜(准备食材) | 做菜(使用食材) |
| 必要性 | 必须先在 deps 声明 | 才能在 import 使用 |
重要提示:
✅ 使用 moon add <包名> 命令会自动更新 moon.mod.json 的 deps
✅ 然后需要手动在 moon.pkg.json 的 import 中添加要使用的包
✅ 别名可以避免包名过长,提高代码可读性
test-import 配置说明
test-import 是专门用于黑盒测试的导入配置。就像质检员从外部检查产品质量,不关心内部实现。
什么是黑盒测试?
黑盒测试只能测试包的公开接口(pub 标记的函数、类型等),不能访问包的内部实现细节。就像用户使用软件,只能看到界面和功能,看不到背后的代码。
配置方式:
{"test-import": [{ "path": "moonbitlang/core/test", "alias": "test" }, "moonbitlang/quickcheck"]
}
使用场景:
在测试文件(_test.mbt 结尾)中编写测试:
// math_test.mbt - 黑盒测试
test "add function" {let result = @math.add(1, 2) // 只能使用 pub 函数@test.assert_eq(result, 3)
}
实际应用场景:
场景一:测试 API 接口
测试一个 HTTP 客户端库的公开 API:
{"test-import": [{ "path": "moonbitlang/core/test", "alias": "test" },{ "path": "moonbitlang/x/http", "alias": "http" }]
}
test "http get request" {let response = @http.get("https://api.example.com")@test.assert_true(response.status == 200)
}
场景二:测试工具函数库
测试字符串处理库的公开函数:
{"test-import": [{ "path": "moonbitlang/core/test", "alias": "test" },{ "path": "username/stringutils", "alias": "str" }]
}
test "capitalize function" {let result = @str.capitalize("hello")@test.assert_eq(result, "Hello")
}
测试类型对比:
| 对比项 | test-import(黑盒测试) | wbtest-import(白盒测试) |
|---|---|---|
| 访问权限 | 只能测试 pub 公开内容 | 可以测试包内所有内容 |
| 测试角度 | 外部用户视角 | 开发者视角 |
| 文件命名 | *_test.mbt | *_wbtest.mbt |
| 适用场景 | API 接口测试、用户功能验收测试 | 内部函数测试、边界条件详细测试 |
| 类比 | 用户试用产品 | 工程师检查零件 |
注意事项:
⚠️ 黑盒测试文件通常放在单独的测试目录中
⚠️ 测试导入的包不会被打包到最终的程序中,只用于开发阶段
⚠️ 推荐使用测试框架(如 moonbitlang/core/test)来组织测试用例
wbtest-import 配置说明
wbtest-import 是专门用于白盒测试的导入配置。就像工程师打开机器检查内部零件,可以看到和测试所有细节。
什么是白盒测试?
白盒测试可以访问包的所有内容,包括私有函数、内部变量等。这让开发者能够深入测试每个细节,确保内部逻辑正确。
配置方式:
{"wbtest-import": [{ "path": "moonbitlang/core/test", "alias": "test" }, "moonbitlang/x/debug"]
}
使用场景:
在白盒测试文件(_wbtest.mbt 结尾)中编写测试:
// math_wbtest.mbt - 白盒测试
test "internal helper function" {// 可以测试私有函数let result = internal_calculate(5, 3)@test.assert_eq(result, 8)
}
实际应用场景:
场景一:测试算法实现
测试一个排序算法的内部辅助函数:
{"wbtest-import": [{ "path": "moonbitlang/core/test", "alias": "test" }]
}
// sort_wbtest.mbt
test "partition function" {// partition 是内部函数,黑盒测试无法访问let arr = [3, 1, 4, 1, 5]let pivot_index = partition(arr, 0, 4)@test.assert_true(pivot_index >= 0 && pivot_index <= 4)
}
场景二:测试边界条件
深入测试内部状态管理:
{"wbtest-import": [{ "path": "moonbitlang/core/test", "alias": "test" },{ "path": "moonbitlang/x/debug", "alias": "debug" }]
}
// cache_wbtest.mbt
test "cache internal state" {let cache = new_cache()// 测试内部的缓存状态@test.assert_eq(cache.size, 0)@test.assert_eq(cache.capacity, 100)// 使用调试工具检查内部结构@debug.inspect(cache)
}
场景三:测试错误处理
测试内部错误处理逻辑:
// error_wbtest.mbt
test "internal error handling" {// 直接调用内部错误处理函数let error = create_error("test", 404)@test.assert_eq(error.code, 404)@test.assert_eq(error.message, "test")
}
黑盒测试 vs 白盒测试:
| 特性 | 黑盒测试(test-import) | 白盒测试(wbtest-import) |
|---|---|---|
| 测试对象 | 公开 API(pub 内容) | 所有内容(包括私有) |
| 测试目的 | 验证功能是否符合预期 | 验证实现细节和内部逻辑 |
| 测试者 | 可以是任何使用者 | 通常是开发者本人 |
| 测试深度 | 浅层(只看行为) | 深层(看实现) |
| 维护成本 | 低(API 变化少) | 高(实现可能经常变) |
| 文件名 | *_test.mbt | *_wbtest.mbt |
| 适用场景 | 用户验收、回归测试 | 单元测试、性能测试 |
| 类比 | 试驾汽车(只关心驾驶体验) | 检查引擎(关心内部运作) |
| 优点 | 测试稳定,不受内部实现影响 | 测试全面,覆盖所有代码路径 |
| 缺点 | 无法测试内部逻辑 | 维护成本高,实现变化需更新 |
| 推荐度 | ⭐⭐⭐⭐⭐(优先使用) | ⭐⭐⭐(按需使用) |
最佳实践:
- 优先编写黑盒测试:关注用户最关心的功能
- 补充白盒测试:针对复杂逻辑和边界情况
- 保持平衡:不要过度依赖白盒测试,避免测试和实现耦合太紧
- 定期重构:当白盒测试频繁失败时,考虑重构实现或测试
测试策略建议:
总测试量 = 70% 黑盒测试 + 30% 白盒测试黑盒测试:✅ API 接口测试✅ 用户场景测试✅ 集成测试白盒测试:✅ 复杂算法内部逻辑✅ 边界条件和异常处理✅ 性能关键代码路径
link 配置说明
link 配置用于指定不同编译目标的链接选项。就像给不同平台的用户准备不同的安装包。
MoonBit 支持多个编译目标:
- native:编译为本地机器码(最快)
- js:编译为 JavaScript(用于浏览器和 Node.js)
- wasm:编译为 WebAssembly(跨平台,高性能)
- wasm-gc:带垃圾回收的 WebAssembly(更现代)
native 链接配置
用于配置编译为本地代码时的 C 编译器选项。
配置示例:
{"link": {"native": {"cc": "/usr/bin/gcc13","cc-flags": "-DMOONBIT -O2","cc-link-flags": "-lm -lpthread"}}
}
配置项说明:
| 配置项 | 说明 | 示例 |
|---|---|---|
cc | 指定 C 编译器路径 | "/usr/bin/gcc13" |
cc-flags | C 编译器编译选项 | "-O2 -g -DDEBUG" |
cc-link-flags | C 链接器选项 | "-lm -lpthread -static" |
stub-cc | 编译 stub 文件的编译器 | "/usr/bin/clang" |
使用场景:
场景一:高性能计算工具
需要调用 C 数学库,进行大量数值计算:
{"link": {"native": {"cc": "gcc","cc-link-flags": "-lm"}}
}
场景二:系统工具开发
需要特定的编译优化和静态链接:
{"link": {"native": {"cc": "/usr/bin/gcc","cc-flags": "-O3 -march=native","cc-link-flags": "-static"}}
}
js 链接配置
用于配置编译为 JavaScript 时的选项。
配置示例:
{"link": {"js": {"exports": ["add", "multiply", "calculate"],"format": "esm"}}
}
配置项说明:
| 配置项 | 说明 | 可选值 | 示例 |
|---|---|---|---|
exports | 导出给 JS 调用的函数列表 | 函数名数组 | ["add", "subtract"] |
format | JavaScript 模块格式 | esm, cjs, iife | "esm" |
模块格式说明:
| 格式 | 全称 | 适用场景 | 导入方式 |
|---|---|---|---|
esm | ES Module(推荐) | 现代浏览器、Deno、Node.js | import { add } from './math.js' |
cjs | CommonJS | 老版本 Node.js | const { add } = require('./math') |
iife | Immediately Invoked Function Expression | 直接在浏览器中使用 | <script src="math.js"></script> |
使用场景:
场景一:开发 npm 包
{"link": {"js": {"exports": ["encrypt", "decrypt", "hash"],"format": "esm"}}
}
在 Node.js 中使用:
import { encrypt, decrypt } from 'my-crypto-lib';const encrypted = encrypt('secret message');
const decrypted = decrypt(encrypted);
场景二:浏览器计算库
{"link": {"js": {"exports": ["calculate", "analyze"],"format": "iife"}}
}
在 HTML 中使用:
<!DOCTYPE html>
<html><head><script src="calculator.js"></script></head><body><script>const result = calculate(10, 20);console.log(result);</script></body>
</html>
wasm 链接配置
用于配置编译为传统 WebAssembly 时的选项。
配置示例:
{"link": {"wasm": {"exports": ["add", "multiply:math_multiply"],"export-memory-name": "memory","heap-start-address": 1024,"import-memory": {"module": "env","name": "memory"}}}
}
配置项说明:
| 配置项 | 说明 | 示例 |
|---|---|---|
exports | 导出的函数(可重命名) | ["add", "foo:bar"] |
export-memory-name | 导出的内存名称 | "memory" |
heap-start-address | 堆起始地址 | 1024 |
import-memory | 从宿主环境导入的内存配置 | {"module": "env", "name": "memory"} |
函数重命名:
{"exports": ["add", // 导出为 add"multiply:mul", // MoonBit 中的 multiply 导出为 mul"divide:math_div" // MoonBit 中的 divide 导出为 math_div]
}
使用场景:
场景一:嵌入到 Web 应用
{"link": {"wasm": {"exports": ["process_image", "apply_filter"],"export-memory-name": "memory"}}
}
在 JavaScript 中调用:
const wasmModule = await WebAssembly.instantiateStreaming(fetch('image_processor.wasm'));const result = wasmModule.instance.exports.process_image(imageData);
场景二:共享内存场景
当需要与宿主环境共享内存时:
{"link": {"wasm": {"exports": ["calculate"],"import-memory": {"module": "env","name": "memory"}}}
}
wasm-gc 链接配置
用于配置编译为带垃圾回收的 WebAssembly。
配置示例:
{"link": {"wasm-gc": {"exports": ["process", "transform"],"use-js-builtin-string": true,"imported-string-constants": "_"}}
}
配置项说明:
| 配置项 | 说明 | 示例 |
|---|---|---|
exports | 导出的函数列表 | ["add"] |
use-js-builtin-string | 使用 JavaScript 原生字符串 | true |
imported-string-constants | 字符串常量导入命名空间 | "_" |
use-js-builtin-string 说明:
| 设置 | 字符串处理方式 | 性能 | 兼容性 |
|---|---|---|---|
true | 使用 JS 原生字符串(推荐) | 更快 | 需要新浏览器 |
false | 使用 Wasm 内部字符串表示 | 较慢 | 更好的兼容性 |
使用场景:
场景一:现代 Web 应用
利用最新的 Wasm-GC 特性:
{"link": {"wasm-gc": {"exports": ["render", "update"],"use-js-builtin-string": true,"imported-string-constants": "strings"}}
}
场景二:高性能字符串处理
大量字符串操作时,使用 JS 字符串可以显著提升性能:
{"link": {"wasm-gc": {"exports": ["parse_json", "stringify"],"use-js-builtin-string": true}}
}
多目标配置
可以同时配置多个目标:
{"link": {"native": {"cc": "gcc","cc-link-flags": "-lm"},"js": {"exports": ["calculate"],"format": "esm"},"wasm": {"exports": ["calculate"]},"wasm-gc": {"exports": ["calculate"],"use-js-builtin-string": true}}
}
编译目标选择建议:
| 目标 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
native | 命令行工具、系统程序 | 最快、无运行时依赖 | 平台相关 |
js | Node.js 服务、前端应用 | 生态丰富、易于调试 | 性能较低 |
wasm | 浏览器应用、跨平台 | 跨平台、性能好 | 调试相对困难 |
wasm-gc | 现代浏览器、复杂应用 | 性能最佳、内存管理好 | 浏览器兼容性要求高 |
native-stub 配置说明
native-stub 用于指定 C 语言的"桩文件"(stub files)。就像给 MoonBit 提供一些 C 语言的"帮手"。
什么是 Stub 文件?
当你需要在 MoonBit 中调用 C 语言的功能时(比如操作系统 API、第三方 C 库),就需要编写一些 C 代码作为"桥梁",这些 C 代码文件就叫 stub 文件。
配置示例:
{"native-stub": ["ffi_helpers.c", "system_calls.c"]
}
使用场景:
场景一:调用系统 API
需要调用 POSIX 系统调用:
ffi_helpers.c:
#include <unistd.h>
#include <stdio.h>// 提供给 MoonBit 调用的 C 函数
int get_process_id() {return getpid();
}void print_message(const char* msg) {printf("%s\n", msg);
}
moon.pkg.json:
{"native-stub": ["ffi_helpers.c"],"link": {"native": {"cc": "gcc"}}
}
MoonBit 代码:
// 声明外部 C 函数
extern "C" fn get_process_id() -> Int
extern "C" fn print_message(msg: String) -> Unitfn main {let pid = get_process_id()println("Process ID: \{pid}")print_message("Hello from C!")
}
场景二:调用第三方 C 库
使用 SQLite 数据库:
sqlite_wrapper.c:
#include <sqlite3.h>// 包装 SQLite 函数供 MoonBit 使用
sqlite3* open_database(const char* filename) {sqlite3* db;sqlite3_open(filename, &db);return db;
}void close_database(sqlite3* db) {sqlite3_close(db);
}
moon.pkg.json:
{"native-stub": ["sqlite_wrapper.c"],"link": {"native": {"cc": "gcc","cc-link-flags": "-lsqlite3"}}
}
注意事项:
⚠️ stub 文件只在编译到 native 目标时有效
⚠️ 需要确保 C 编译器能找到相关的头文件和库文件
⚠️ 使用第三方 C 库时,需要在 cc-link-flags 中添加相应的链接选项
Stub 文件的作用:
MoonBit 代码↓ (调用)
extern 声明↓ (通过)
Stub C 文件 ←→ 系统 API / 第三方 C 库↓ (返回)
MoonBit 代码
implement 和 overrides 配置说明
这两个配置与 MoonBit 的虚拟包(Virtual Package)机制相关,用于实现接口和覆盖默认实现。
什么是虚拟包?
虚拟包就像是一个"合同"或"接口规范",定义了一组函数签名,但不提供具体实现。不同的包可以提供不同的实现。
类比理解:
- 虚拟包:像电源插座标准(220V、两孔、三孔等)
- 实现包:像具体的电器插头(手机充电器、笔记本充电器等)
- 好处:只要遵循标准,任何电器都能用
implement 配置
implement 用于声明当前包实现了某个虚拟包。
配置示例:
{"implement": "moonbitlang/core/abort"
}
使用场景:
场景一:实现日志接口
虚拟包定义(logger.mbti):
// moonbitlang/core/logger 的接口定义
pub fn log(level: String, message: String) -> Unit
pub fn error(message: String) -> Unit
pub fn info(message: String) -> Unit
实现包 1:控制台日志
{"implement": "moonbitlang/core/logger"
}
// console_logger/top.mbt
pub fn log(level: String, message: String) -> Unit {println("[\{level}] \{message}")
}pub fn error(message: String) -> Unit {log("ERROR", message)
}pub fn info(message: String) -> Unit {log("INFO", message)
}
实现包 2:文件日志
{"implement": "moonbitlang/core/logger"
}
// file_logger/top.mbt
pub fn log(level: String, message: String) -> Unit {// 写入文件write_to_file("app.log", "[\{level}] \{message}")
}pub fn error(message: String) -> Unit {log("ERROR", message)
}pub fn info(message: String) -> Unit {log("INFO", message)
}
场景二:跨平台适配
为不同平台提供不同实现:
虚拟包(moonbitlang/core/os):
pub fn get_env(name: String) -> Option[String]
pub fn set_env(name: String, value: String) -> Unit
Windows 实现:
{"implement": "moonbitlang/core/os"
}
// os_windows/top.mbt
pub fn get_env(name: String) -> Option[String] {// Windows 特定实现windows_get_env(name)
}
Linux 实现:
{"implement": "moonbitlang/core/os"
}
// os_linux/top.mbt
pub fn get_env(name: String) -> Option[String] {// Linux 特定实现linux_get_env(name)
}
overrides 配置
overrides 用于覆盖虚拟包的默认实现,选择使用特定的实现。
配置示例:
{"overrides": ["myproject/custom_logger/console"]
}
使用场景:
场景:切换日志实现
项目默认使用简单的控制台日志,生产环境需要切换到文件日志:
开发环境配置:
{"import": ["moonbitlang/core/logger"],"overrides": ["myproject/logger/console"]
}
生产环境配置:
{"import": ["moonbitlang/core/logger"],"overrides": ["myproject/logger/file"]
}
应用代码保持不变:
fn main {// 使用统一的接口,具体实现由配置决定@logger.info("Application started")@logger.error("An error occurred")
}
virtual 配置
virtual 用于声明当前包是一个虚拟包。
配置示例:
{"virtual": {"has-default": true}
}
has-default 说明:
| 值 | 含义 | 使用场景 |
|---|---|---|
true | 虚拟包提供默认实现 | 大多数情况下可以直接使用 |
false | 必须由其他包提供实现 | 强制用户选择特定实现 |
完整示例:
1. 定义虚拟包(storage.mbti):
// moonbitlang/core/storage
pub fn save(key: String, value: String) -> Unit
pub fn load(key: String) -> Option[String]
pub fn delete(key: String) -> Unit
2. 虚拟包配置:
{"virtual": {"has-default": true}
}
3. 提供默认实现(storage/top.mbt):
// 简单的内存存储作为默认实现
let storage: Map[String, String] = Map::new()pub fn save(key: String, value: String) -> Unit {storage.set(key, value)
}pub fn load(key: String) -> Option[String] {storage.get(key)
}pub fn delete(key: String) -> Unit {storage.remove(key)
}
4. 提供 Redis 实现(storage_redis/top.mbt):
{"implement": "moonbitlang/core/storage"
}
pub fn save(key: String, value: String) -> Unit {redis_set(key, value)
}pub fn load(key: String) -> Option[String] {redis_get(key)
}pub fn delete(key: String) -> Unit {redis_del(key)
}
5. 使用存储:
// 使用默认实现(内存存储)
fn use_default_storage {@storage.save("name", "Alice")let value = @storage.load("name")
}// 或者在配置中覆盖为 Redis 实现
// moon.pkg.json:
// {
// "overrides": ["myproject/storage_redis"]
// }
虚拟包机制的优势:
| 优势 | 说明 | 举例 |
|---|---|---|
| 解耦 | 接口和实现分离 | 日志、存储、网络请求等 |
| 可替换 | 轻松切换不同实现 | 开发环境用 Mock,生产用真实 API |
| 可测试 | 测试时使用测试专用实现 | 使用内存数据库代替真实数据库 |
| 跨平台 | 为不同平台提供特定实现 | Windows/Linux/macOS 特定功能 |
| 渐进式开发 | 先用简单实现,后续优化 | 先用内存缓存,后续加 Redis |
pre-build 配置说明
pre-build 用于在正式编译前执行一些预处理任务。就像做饭前的备菜工作。
配置格式:
{"pre-build": [{"input": "data.txt","output": "data.mbt","command": ":embed -i $input -o $output"}]
}
配置项说明:
| 字段 | 说明 | 示例 |
|---|---|---|
input | 输入文件路径 | "config.txt", "template.html" |
output | 输出的 MoonBit 文件 | "config.mbt", "template.mbt" |
command | 执行的命令 | ":embed -i $input -o $output" |
内置命令::embed
:embed 命令可以将文本文件嵌入到 MoonBit 代码中。
基本用法:
{"pre-build": [{"input": "readme.txt","output": "readme.mbt","command": ":embed -i $input -o $output"}]
}
输入文件(readme.txt):
Welcome to MoonBit!
This is a sample text file.
生成的代码(readme.mbt):
let resource : String =#|Welcome to MoonBit!#|This is a sample text file.#|
使用场景
场景一:嵌入配置文件
将 JSON 配置文件嵌入到代码中:
config.json:
{"api_url": "https://api.example.com","timeout": 5000,"retry": 3
}
moon.pkg.json:
{"pre-build": [{"input": "config.json","output": "config_data.mbt","command": ":embed -i $input -o $output"}]
}
使用生成的配置:
// 引用生成的 config_data.mbt
fn get_config() -> String {// resource 变量由 :embed 命令自动生成resource
}fn main {let config_str = get_config()// 解析 JSON 配置并使用let config = parse_json(config_str)println("API URL: \{config.api_url}")
}
场景二:嵌入 HTML 模板
Web 应用中嵌入 HTML 模板:
template.html:
<!DOCTYPE html>
<html><head><title>{{title}}</title></head><body><h1>{{heading}}</h1><p>{{content}}</p></body>
</html>
moon.pkg.json:
{"pre-build": [{"input": "template.html","output": "template.mbt","command": ":embed -i $input -o $output"}]
}
使用模板:
fn render_page(title: String, heading: String, content: String) -> String {// resource 是嵌入的 HTML 模板let html = resource.replace("{{title}}", title).replace("{{heading}}", heading).replace("{{content}}", content)html
}fn main {let page = render_page("Home", "Welcome", "This is the home page")println(page)
}
场景三:嵌入多个资源文件
{"pre-build": [{"input": "help.txt","output": "help_text.mbt","command": ":embed -i $input -o $output"},{"input": "version.txt","output": "version_info.mbt","command": ":embed -i $input -o $output"},{"input": "license.txt","output": "license_text.mbt","command": ":embed -i $input -o $output"}]
}
场景四:嵌入 SQL 查询
数据库应用中嵌入 SQL 语句:
queries.sql:
SELECT * FROM users WHERE age > 18;
SELECT name, email FROM customers ORDER BY created_at DESC;
moon.pkg.json:
{"pre-build": [{"input": "queries.sql","output": "queries.mbt","command": ":embed -i $input -o $output"}]
}
自定义变量名:
默认生成的变量名是 resource,可以通过 --name 参数自定义:
{"pre-build": [{"input": "config.json","output": "config.mbt","command": ":embed -i $input -o $output --name app_config"}]
}
生成的代码:
let app_config : String =#|{"api_url": "https://api.example.com"}
pre-build 的优势:
| 优势 | 说明 | 举例 |
|---|---|---|
| 类型安全 | 文件内容编译时即可用,避免运行时读取 | 无需处理文件不存在错误 |
| 性能更好 | 无需运行时 I/O 操作 | 直接使用字符串常量 |
| 部署简单 | 资源打包进二进制,无需额外文件 | 单文件部署,不丢失资源 |
| 版本一致 | 资源和代码版本绑定 | 避免资源文件版本不匹配 |
注意事项:
⚠️ input 路径相对于包目录
⚠️ output 文件会自动生成,不要手动编辑
⚠️ 适合嵌入小型文本文件,大文件建议运行时加载
⚠️ 每次修改输入文件后需要重新编译
targets 配置说明
targets 用于条件编译,让特定的文件只在特定条件下编译。就像根据天气选择不同的衣服。
配置格式:
{"targets": {"filename.mbt": ["条件"]}
}
基本条件
按编译目标:
{"targets": {"only_js.mbt": ["js"],"only_wasm.mbt": ["wasm"],"only_wasm_gc.mbt": ["wasm-gc"],"only_native.mbt": ["native"]}
}
按优化级别:
{"targets": {"only_debug.mbt": ["debug"],"only_release.mbt": ["release"]}
}
组合条件
多个条件(OR 关系):
{"targets": {"all_wasm.mbt": ["wasm", "wasm-gc"],"js_or_wasm.mbt": ["js", "wasm"]}
}
意思是:all_wasm.mbt 在编译到 wasm 或 wasm-gc 时都会被包含。
AND 逻辑:
{"targets": {"js_and_release.mbt": ["and", ["js"], ["release"]]}
}
意思是:只有在编译到 js 且 是 release 模式时才包含。
OR 逻辑:
{"targets": {"wasm_release_or_js_debug.mbt": ["or", ["and", "wasm", "release"], ["and", "js", "debug"]]}
}
意思是:在 (wasm + release) 或 (js + debug) 时包含。
NOT 逻辑:
{"targets": {"not_js.mbt": ["not", "js"]}
}
意思是:除了 js 目标,其他目标都包含这个文件。
使用场景
场景一:平台特定代码
为不同平台提供不同实现:
项目结构:
src/├── common.mbt # 通用代码├── js_api.mbt # JavaScript 专用├── wasm_api.mbt # WebAssembly 专用└── native_api.mbt # 原生平台专用
moon.pkg.json:
{"targets": {"js_api.mbt": ["js"],"wasm_api.mbt": ["wasm", "wasm-gc"],"native_api.mbt": ["native"]}
}
js_api.mbt:
// 只在编译到 JS 时使用
pub fn fetch_data(url: String) -> String {// 使用 fetch APIjs_fetch(url)
}
wasm_api.mbt:
// 只在编译到 Wasm 时使用
pub fn fetch_data(url: String) -> String {// 使用 Wasm 的方式wasm_http_get(url)
}
native_api.mbt:
// 只在编译到 Native 时使用
pub fn fetch_data(url: String) -> String {// 使用系统 APInative_http_request(url)
}
common.mbt:
// 所有平台共用
fn main {let data = fetch_data("https://api.example.com")println(data)
}
场景二:调试和生产环境
开发时包含调试代码,发布时自动排除:
{"targets": {"debug_utils.mbt": ["debug"],"production_config.mbt": ["release"]}
}
debug_utils.mbt:
pub fn log_debug(message: String) -> Unit {println("[DEBUG] \{message}")
}pub fn assert_valid(value: Int) -> Unit {if value < 0 {panic("Invalid value: \{value}")}
}
production_config.mbt:
pub fn log_debug(message: String) -> Unit {// 生产环境不输出调试信息
}pub fn assert_valid(value: Int) -> Unit {// 生产环境跳过验证,提升性能
}
场景三:特性开关
根据编译配置启用不同功能:
{"targets": {"feature_experimental.mbt": ["and", ["js"], ["debug"]],"feature_stable.mbt": ["release"]}
}
feature_experimental.mbt(实验性功能):
pub fn new_algorithm(data: Array[Int]) -> Int {// 新的、未充分测试的算法experimental_sort(data)
}
feature_stable.mbt(稳定功能):
pub fn new_algorithm(data: Array[Int]) -> Int {// 经过验证的稳定算法stable_sort(data)
}
场景四:性能优化
针对不同目标提供优化版本:
{"targets": {"optimized_wasm.mbt": ["and", ["wasm-gc"], ["release"]],"generic.mbt": ["not", ["and", ["wasm-gc"], ["release"]]]}
}
optimized_wasm.mbt:
// Wasm-GC 发布版的高度优化版本
pub fn process_large_array(arr: Array[Int]) -> Int {// 使用 SIMD 等 Wasm 特性wasm_simd_process(arr)
}
generic.mbt:
// 通用版本
pub fn process_large_array(arr: Array[Int]) -> Int {// 标准实现arr.fold_left(fn(acc, x) { acc + x }, 0)
}
条件编译对比表
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
targets 文件级别 | 清晰,易于管理 | 粒度较粗 | 平台特定模块 |
#[cfg] 函数级别 | 粒度细,灵活 | 代码可能显得凌乱 | 个别函数的平台差异 |
| 虚拟包 | 完全解耦,可替换 | 需要额外配置 | 大型跨平台项目 |
最佳实践:
- 优先使用
targets:大块平台特定代码放在单独文件中 - 补充使用
#[cfg]:少量平台差异在函数级别处理 - 保持一致性:同一项目使用统一的条件编译策略
- 文档说明:在 README 中说明哪些文件是条件编译的
virtual 配置说明
前面在 implement 和 overrides 部分已经详细介绍过,这里简要总结。
virtual 用于声明当前包是一个虚拟包(接口包)。
配置格式:
{"virtual": {"has-default": true}
}
has-default 说明:
| 值 | 含义 | 适用场景 |
|---|---|---|
true | 虚拟包提供默认实现 | 提供开箱即用的实现 |
false | 必须由其他包提供实现 | 强制用户选择实现 |
典型用途:
- 定义抽象接口(如日志、存储、网络)
- 提供可替换的实现
- 支持跨平台开发
详细内容请参考前面的 implement 和 overrides 配置说明 章节。
warn-list 和 alert-list 配置说明
这两个配置用于控制编译器的警告和提示信息。在上篇文章的模块配置中已经详细介绍过,包配置中的用法完全一致。
warn-list 简要回顾
控制编译器预设的警告:
{"warn-list": "-2-4+31"
}
-2:关闭"未使用变量"警告-4:关闭"未使用抽象类型"警告+31:开启"未使用可选参数"警告
alert-list 简要回顾
控制自定义的警告:
{"alert-list": "-deprecated+unsafe"
}
-deprecated:关闭"废弃"警告+unsafe:开启"不安全"警告
配置级别优先级:
包级别配置 (moon.pkg.json) > 模块级别配置 (moon.mod.json)
如果在包配置中设置了 warn-list,会覆盖模块配置中的设置。
使用建议:
- 模块级别:设置项目整体的警告策略
- 包级别:针对特殊包调整警告(如测试包、第三方适配包)
详细内容请参考上篇文章的 warn-list 配置说明 和 alert-list 配置说明 章节。
总结
本文详细介绍了 MoonBit 的包配置文件 moon.pkg.json 的各个配置项:
核心配置:
is-main:标识入口包import:导入依赖包test-import和wbtest-import:测试专用导入
编译链接:
link:多目标编译配置(native, js, wasm, wasm-gc)native-stub:C 语言桥接文件targets:条件编译配置
高级特性:
implement和overrides:虚拟包机制virtual:定义虚拟包pre-build:预编译处理
代码质量:
warn-list:控制编译警告alert-list:控制自定义提示
通过合理配置这些选项,你可以:
✅ 构建跨平台应用
✅ 优化编译产物
✅ 实现模块化架构
✅ 提升开发体验
✅ 保证代码质量
下一篇文章将开始介绍 MoonBit 如何在前端开发中用起来,敬请期待!
