大家好,我是农村程序员,独立开发者,前端之虎陈随易,我的个人网站是
https://chensuiyi.me
这是我的
《改变世界的编程语言MoonBit》系列文章,将自己学习和理解 MoonBit 的过程分享给大家,希望能带来参考和帮助。
全部文章可以前往 MoonBit 开发网
https://moonbit.edgeone.app或https://moonbit.pages.dev查看,我坚信,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) -> Unit
fn 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 API
js_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 {
// 使用系统 API
native_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 如何在前端开发中用起来,敬请期待!
4538

被折叠的 条评论
为什么被折叠?



