导入与导出
Lux 的导入导出是编译期模块系统,不是 Lua 运行时 require。编译器会先解析完整
模块图,再由目标后端生成加载代码。对 GMod 后端来说,导入通常会变成对编译器生成
的模块注册表读取,而不是直接调用 require。
导入形式
命名导入会从目标模块的导出表中取出公开名,并绑定到当前 part 的局部变量。
命名导入和别名
import { arr } from "@lux/std"
import { dict as tableDict } from "@lux/std"
local names = arr.map(players, (player) => player:Nick())
local merged = tableDict.merge(defaults, overrides)
命名空间导入绑定的是整个导出表。
命名空间导入
import * as std from "@lux/std"
local names = std.arr.map(players, (player) => player:Nick())
副作用导入不创建本地绑定,只保证目标模块进入模块图并按顺序执行。
副作用导入
import "@lux/ui/register_defaults"
宏导入只在编译期可用,不会进入运行时模块图。
宏导入
import macro { dbg } from "@lux/macros"
import macro * as gmodMacros from "@lux/gmod/macros"
路径解析
相对路径引用项目模块,以 @ 开头的路径引用包。
项目模块和包模块
import { formatItem } from "../inventory"
import { theme } from "./theme"
import { valid } from "@lux/gmod"
实际模块 id 由项目 source_root、当前模块路径、包根和后端规则共同决定。文档中的
Lua 只展示形态,不要求手写这些 id。
导入绑定是不可变的
导入后得到的是当前 part 的局部绑定,并且不可重新赋值。
导入绑定不可赋值
import { arr } from "@lux/std"
arr = {}
如果要在多个 part 使用同一个包,每个 part 自己写导入。导入不会自动变成模块级
共享绑定。
导出是公开名映射
顶层声明默认私有。导出不是“取消 private”,而是从内部绑定到外部 API 名称的显式
映射。
导出同名公开 API
local player_inventory = {}
export { player_inventory }
外部模块可以导入这个公开名。
导入公开名
import { player_inventory } from "../inventory"
使用别名时,公开名发生变化。export { public_name = local_binding } 的左侧是外部
可见名称,右侧是模块内部绑定。
Lua 风格导出别名
local player_inventory = {}
export { p_inv = player_inventory }
外部只能使用公开名 p_inv,不能再导入内部名 player_inventory。
导入别名后的公开名
import { p_inv } from "../inventory"
import { p_inv as inventory } from "../inventory"
Lux 也支持接近常见模块系统的别名方向:
as 风格导出别名
export { player_inventory as p_inv }
两种写法等价:
export { public_name = local_binding }
export { local_binding as public_name }
文档和团队规范推荐固定使用一种风格,避免同一项目中混用导致误读。
声明时导出
可以在声明时导出简单名字。export fn 先创建普通模块私有绑定,再把它映射到同名
公开 API。
声明时导出
export fn createInventory(owner) {
{ owner = owner, items = {} }
}
export const maxSlots = 64
GLua 风格的点号/冒号声明不能用 export fn,因为它不是模块私有绑定,而是对已有
表路径的副作用声明。
不能导出方法路径声明
export fn PANEL:Paint(w, h) {}
运行域导出
导出可以带运行域。导出的运行域决定外部模块在哪些产物中能看见这个 API。
运行域导出
server fn grantItem(player, item) {
inventory[player:SteamID64()] = item
}
client fn openPanel() {
vgui.Create("DFrame")
}
shared fn itemName(item) =
item.name ?? "Unknown"
export server { grantItem }
export client { openPanel }
export shared { itemName }
声明时也可以写运行域导出。
声明时运行域导出
export server fn grantItem(player, item) {
inventory[player:SteamID64()] = item
}
export client fn openPanel() {
vgui.Create("DFrame")
}
如果导出没有显式运行域,默认使用绑定的有效运行域。sv_ 文件中的 export fn
默认是服务端导出;cl_ 文件中默认是客户端导出;共享文件中默认是共享导出。
只能收窄,不能扩大
导出的运行域必须是绑定可用运行域的子集。
server-only 不能导出成 shared
server fn grantItem(player, item) {
inventory[player:SteamID64()] = item
}
export shared { grantItem }
export client { grantItem }
export server { grantItem }
共享绑定可以收窄导出。这表示函数本身可在共享代码中使用,但对外只作为服务端 API
暴露。
shared 绑定可收窄导出
shared fn normalizeName(value) =
string.Trim(value ?? "")
export server { normalizeName }
导出限制
MVP 规则:
- 只能导出模块作用域绑定。
- 不能导出从其他模块导入的绑定。
- 公开名参与重复导出检查。
- 运行域导出不能扩大绑定的实际可用运行域。
- 未通过公开名导出的内部名保持私有。
不能直接转导入绑定。
不能转导入绑定
import { arr } from "@lux/std"
export { arr }
需要转发 API 时,先定义自己的包装。
用包装函数转发 API
import { arr } from "@lux/std"
fn mapItems(items, callback) =
arr.map(items, callback)
export { mapItems }
public all
public all 是显式选择“全部公开”的模块级配置,适合非常小的工具模块。
public all
public all
fn clamp(value, min, max) {
value < min then min else value > max then max else value
}
fn lerp(t, from, to) {
from + (to - from) * t
}
大型模块不要依赖 public all。默认私有、按名称导出更利于维护 API 边界。