当前位置: 首页 > news >正文

改变世界的编程语言MoonBit:配置系统介绍(下)

大家好,我是农村程序员,独立开发者,前端之虎陈随易,我的个人网站是 https://chensuiyi.me

这是我的 《改变世界的编程语言MoonBit》 系列文章,将自己学习和理解 MoonBit 的过程分享给大家,希望能带来参考和帮助。

全部文章可以前往 MoonBit 开发网 https://moonbit.edgeone.apphttps://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.jsondeps声明模块级别的依赖(需要谁)
moon.pkg.jsonimport在包中实际使用依赖(用了谁)
关系deps 是前提条件import 是具体使用
类比买菜(准备食材)做菜(使用食材)
必要性必须先在 deps 声明才能在 import 使用

重要提示

✅ 使用 moon add <包名> 命令会自动更新 moon.mod.jsondeps

✅ 然后需要手动在 moon.pkg.jsonimport 中添加要使用的包

✅ 别名可以避免包名过长,提高代码可读性

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
适用场景用户验收、回归测试单元测试、性能测试
类比试驾汽车(只关心驾驶体验)检查引擎(关心内部运作)
优点测试稳定,不受内部实现影响测试全面,覆盖所有代码路径
缺点无法测试内部逻辑维护成本高,实现变化需更新
推荐度⭐⭐⭐⭐⭐(优先使用)⭐⭐⭐(按需使用)

最佳实践

  1. 优先编写黑盒测试:关注用户最关心的功能
  2. 补充白盒测试:针对复杂逻辑和边界情况
  3. 保持平衡:不要过度依赖白盒测试,避免测试和实现耦合太紧
  4. 定期重构:当白盒测试频繁失败时,考虑重构实现或测试

测试策略建议

总测试量 = 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-flagsC 编译器编译选项"-O2 -g -DDEBUG"
cc-link-flagsC 链接器选项"-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"]
formatJavaScript 模块格式esm, cjs, iife"esm"

模块格式说明

格式全称适用场景导入方式
esmES Module(推荐)现代浏览器、Deno、Node.jsimport { add } from './math.js'
cjsCommonJS老版本 Node.jsconst { add } = require('./math')
iifeImmediately 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命令行工具、系统程序最快、无运行时依赖平台相关
jsNode.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] 函数级别粒度细,灵活代码可能显得凌乱个别函数的平台差异
虚拟包完全解耦,可替换需要额外配置大型跨平台项目

最佳实践

  1. 优先使用 targets:大块平台特定代码放在单独文件中
  2. 补充使用 #[cfg]:少量平台差异在函数级别处理
  3. 保持一致性:同一项目使用统一的条件编译策略
  4. 文档说明:在 README 中说明哪些文件是条件编译的

virtual 配置说明

前面在 implementoverrides 部分已经详细介绍过,这里简要总结。

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-importwbtest-import:测试专用导入

编译链接

  • link:多目标编译配置(native, js, wasm, wasm-gc)
  • native-stub:C 语言桥接文件
  • targets:条件编译配置

高级特性

  • implementoverrides:虚拟包机制
  • virtual:定义虚拟包
  • pre-build:预编译处理

代码质量

  • warn-list:控制编译警告
  • alert-list:控制自定义提示

通过合理配置这些选项,你可以:

✅ 构建跨平台应用

✅ 优化编译产物

✅ 实现模块化架构

✅ 提升开发体验

✅ 保证代码质量

下一篇文章将开始介绍 MoonBit 如何在前端开发中用起来,敬请期待!

http://www.dtcms.com/a/552983.html

相关文章:

  • mip网站推广普通话宣传周活动方案
  • EL(F)K日志分析系统
  • 算法题——图论
  • AutoCAD开发:主流语言与实用插件精选
  • 余姚响应式网站建设做个网站应该怎么做
  • Docker 日志管理实战:轻松掌控容器输出
  • 移动端h5适配方案
  • 【雅思备考】雅思写作笔记
  • 亚马逊产品备案网站建设要求域名不变修改网站怎么做
  • 6-3〔O҉S҉C҉P҉ ◈ 研记〕❘ 客户端攻击▸通过宏文件实现反向shell
  • Python 实现 Excel 连续数据分组求平均值
  • 小红书获取笔记详情API接口运用指南
  • SQL 自连接详解:当数据表需要与自己对话(组织层级实战)
  • AI代码开发宝库系列:Text2SQL技术入门
  • 网站充值链接怎么做三亚做网站推广
  • 在Azure webapp中搭建 基于chroma的 RAG agent
  • 【春秋云境】CVE-2024-38856 Apache OFbiz从未授权到RCE
  • 货拉拉用户画像基于 Apache Doris 的数据模型设计与实践
  • JAR 包中替换依赖jar的正确姿势(Windows 环境)
  • linux驱动开发之pr_warn和pr_warning
  • Keil(MDK-ARM)和 STM32CubeIDE对比
  • Linux上使用Docker安装MinIO指南
  • Maven 依赖冲突:解决 jar 包版本不一致的 3 种方法
  • android集成react native组件踩坑笔记(Activity局部展示RN的组件)
  • 多语言网站思路十大h5页面制作工具
  • 汽车之家网站系统是什么做的防爆玻璃门网站建设
  • k8s——services资源+pod详解1
  • 基于深度学习的医疗器械分类编码映射系统:实现篇
  • [人工智能-大模型-122]:模型层 - RNN是通过神经元还是通过张量时间记录状态信息?时间状态信息是如何被更新的?
  • React 18.x 学习计划 - 第六天:React路由和导航