名称: 模块系统 描述: 设计模块系统的专家技能,包括解析算法、导入/导出机制、可见性控制、命名空间管理和循环依赖处理。 允许工具: 读取、写入、编辑、Bash、Glob、Grep
模块系统技能
为编程语言设计和实现模块系统,支持解析、加载、可见性和依赖管理。
能力
- 设计模块/导入/导出语法
- 实现模块解析算法
- 处理循环模块依赖
- 实现可见性/访问控制
- 设计命名空间管理系统
- 支持模块别名和重新导出
- 实现惰性/按需模块加载
- 设计包/箱系统集成
使用场景
在以下情况下调用此技能:
- 为新语言设计模块系统
- 实现模块解析算法
- 处理复杂依赖图
- 构建可见性和访问控制系统
- 与包管理器集成
输入参数
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
| 模块风格 | 字符串 | 是 | 风格 (es6, commonjs, rust, ml) |
| 解析策略 | 字符串 | 是 | 解析方式 (node, rust, python, custom) |
| 功能特性 | 数组 | 否 | 要实施的功能 |
| 循环处理 | 字符串 | 否 | 如何处理循环 (error, lazy, tarjan) |
| 可见性 | 对象 | 否 | 可见性模型配置 |
功能选项
{
"功能特性": [
"导入-导出",
"重新导出",
"命名空间别名",
"选择性导入",
"默认导出",
"惰性加载",
"循环检测",
"可见性控制",
"内联模块",
"包集成"
]
}
输出结构
模块系统/
├── 语法/
│ ├── 导入.语法 # 导入语句语法
│ ├── 导出.语法 # 导出语句语法
│ └── 模块声明.语法 # 模块声明语法
├── 解析/
│ ├── 解析器.ts # 主解析算法
│ ├── 模块图.ts # 依赖图
│ ├── 路径解析器.ts # 路径解析
│ └── 缓存.ts # 模块缓存
├── 加载/
│ ├── 加载器.ts # 模块加载器
│ ├── 惰性加载器.ts # 惰性加载支持
│ └── 并行加载器.ts # 并行加载
├── 可见性/
│ ├── 访问控制.ts # 可见性检查
│ └── 命名空间.ts # 命名空间管理
├── 分析/
│ ├── 循环检测器.ts # 循环依赖检测
│ └── 依赖分析器.ts # 依赖分析
└── 测试/
├── 解析测试.ts
├── 循环测试.ts
└── 可见性测试.ts
模块系统类型
ES6风格模块
// 导入语法
import 默认导出 from '模块';
import { 命名导出, 另一个 as 别名 } from '模块';
import * as 命名空间 from '模块';
// 导出语法
export const 值 = 42;
export function 函数() {}
export default class 我的类 {}
export { 名称, 其他 as 重命名 };
export * from '其他模块';
// 实现
interface ES模块 {
默认导出?: any;
命名导出: Map<string, any>;
重新导出: 重新导出[];
}
interface 导入标识符 {
类型: 'default' | 'named' | 'namespace';
导入的: string;
本地的: string;
}
Rust风格模块
// 模块声明
mod 我的模块; // 从文件加载
mod 内联 { ... } // 内联模块
// Use语句
use crate::模块::项目;
use super::父级::*;
use self::子级::事物;
use 外部箱::某物;
// 可见性
pub struct 公开;
pub(crate) struct 箱内可见;
pub(super) struct 父级可见;
struct 私有; // 默认
// 实现
interface Rust模块 {
名称: string;
路径: 模块路径;
可见性: 可见性;
项目: Map<string, 模块项目>;
子模块: Map<string, Rust模块>;
}
类型 可见性 =
| { 类型: 'private' }
| { 类型: 'public' }
| { 类型: 'restricted'; 路径: 模块路径 };
ML风格模块
(* 模块签名 *)
module type 栈 = sig
type 'a t
val 空 : 'a t
val 推入 : 'a -> 'a t -> 'a t
val 弹出 : 'a t -> ('a * 'a t) option
end
(* 模块实现 *)
module 列表栈 : 栈 = struct
type 'a t = 'a list
let 空 = []
let 推入 x s = x :: s
let 弹出 = function
| [] -> None
| x :: xs -> Some (x, xs)
end
(* 函子 *)
module 制作集合 (排序: 排序) : 集合 = struct
(* ... 使用排序.比较的实现 *)
end
解析算法
Node.js风格解析
interface Node解析器 {
解析模块(标识符: string, 来源: string): string | null;
}
function node解析(标识符: string, 来源目录: string): string | null {
// 1. 如果是核心模块,直接返回
if (是核心模块(标识符)) return 标识符;
// 2. 如果以'/'或'./'开头,解析相对路径
if (标识符.startsWith('/') || 标识符.startsWith('./') ||
标识符.startsWith('../')) {
return 解析相对路径(标识符, 来源目录);
}
// 3. 否则,向上遍历node_modules
let 目录 = 来源目录;
while (目录 !== '/') {
const 候选 = path.join(目录, 'node_modules', 标识符);
const 解析结果 = 解析包(候选);
if (解析结果) return 解析结果;
目录 = path.dirname(目录);
}
return null;
}
function 解析包(包路径: string): string | null {
// 检查package.json的exports/main
const 包配置 = 读取包配置(包路径);
if (包配置?.exports) {
return 解析导出(包路径, 包配置.exports);
}
if (包配置?.main) {
return path.join(包路径, 包配置.main);
}
// 默认使用index.js
return path.join(包路径, 'index.js');
}
Rust风格解析
interface Rust解析器 {
解析Use(use路径: Use路径, 当前模块: 模块路径): 解析项目;
}
function rust解析(use路径: Use路径, 当前: 模块路径): 解析项目 {
const [第一个, ...其余] = use路径.段;
// 确定起始点
let 起始模块: Rust模块;
if (第一个 === 'crate') {
起始模块 = 获取箱根();
} else if (第一个 === 'super') {
起始模块 = 获取父模块(当前);
} else if (第一个 === 'self') {
起始模块 = 获取当前模块(当前);
} else if (是外部箱(第一个)) {
起始模块 = 获取外部箱(第一个);
} else {
// 从当前模块作用域开始
起始模块 = 获取当前模块(当前);
其余.unshift(第一个);
}
// 解析路径段
let 当前项目: 模块项目 = 起始模块;
for (const 段 of 其余) {
当前项目 = 解析段(当前项目, 段);
检查可见性(当前项目, 当前);
}
return 当前项目;
}
循环依赖处理
// Tarjan算法用于SCC检测
function 查找循环(图: 模块图): 模块循环[] {
const 索引 = new Map<模块, number>();
const 低链接 = new Map<模块, number>();
const 在栈上 = new Set<模块>();
const 栈: 模块[] = [];
const 强连通分量: 模块[][] = [];
let 当前索引 = 0;
function 强连接(模块: 模块): void {
索引.set(模块, 当前索引);
低链接.set(模块, 当前索引);
当前索引++;
栈.push(模块);
在栈上.add(模块);
for (const 依赖 of 模块.依赖项) {
if (!索引.has(依赖)) {
强连接(依赖);
低链接.set(模块, Math.min(低链接.get(模块)!, 低链接.get(依赖)!));
} else if (在栈上.has(依赖)) {
低链接.set(模块, Math.min(低链接.get(模块)!, 索引.get(依赖)!));
}
}
if (低链接.get(模块) === 索引.get(模块)) {
const 强连通分量: 模块[] = [];
let w: 模块;
do {
w = 栈.pop()!;
在栈上.delete(w);
强连通分量.push(w);
} while (w !== 模块);
if (强连通分量.length > 1) {
强连通分量.push(强连通分量);
}
}
}
for (const 模块 of 图.模块) {
if (!索引.has(模块)) {
强连接(模块);
}
}
return 强连通分量.map(模块 => ({ 模块, 边: 查找循环边(模块) }));
}
可见性控制
interface 可见性检查器 {
可访问(项目: 模块项目, 来自模块: 模块路径): boolean;
}
function 检查可见性(
项目: 模块项目,
来自模块: 模块路径,
项目模块: 模块路径
): boolean {
switch (项目.可见性.类型) {
case 'public':
return true;
case 'private':
return 是相同模块(来自模块, 项目模块);
case 'crate':
return 是相同箱(来自模块, 项目模块);
case 'super':
return 是父级或相同(获取父级(项目模块), 来自模块);
case 'restricted':
return 是后代(来自模块, 项目.可见性.路径);
default:
return false;
}
}
惰性加载
interface 惰性模块 {
路径: string;
已加载: boolean;
导出: Map<string, any> | null;
加载中: Promise<void> | null;
}
class 惰性模块加载器 {
private 模块 = new Map<string, 惰性模块>();
async 导入(标识符: string): Promise<any> {
const 解析结果 = this.解析(标识符);
let 模块 = this.模块.get(解析结果);
if (!模块) {
模块 = {
路径: 解析结果,
已加载: false,
导出: null,
加载中: null
};
this.模块.set(解析结果, 模块);
}
if (模块.已加载) {
return 模块.导出;
}
if (模块.加载中) {
await 模块.加载中;
return 模块.导出;
}
模块.加载中 = this.加载模块(模块);
await 模块.加载中;
return 模块.导出;
}
private async 加载模块(模块: 惰性模块): Promise<void> {
const 源代码 = await 读取文件(模块.路径);
const 编译结果 = 编译(源代码);
模块.导出 = await 执行(编译结果);
模块.已加载 = true;
}
}
工作流程
- 设计模块语法 - 导入、导出、模块声明
- 实现解析 - 路径解析算法
- 构建依赖图 - 跟踪模块依赖
- 检测循环 - 查找并报告循环依赖
- 实现可见性 - 访问控制检查
- 添加惰性加载 - 按需模块加载
- 集成包管理 - 包管理器支持
- 生成测试 - 解析、循环、可见性
最佳实践
- 解析与加载的清晰分离
- 已解析模块的高效缓存
- 信息丰富的循环检测错误消息
- 一致的可见性语义
- 支持同步和异步加载
- IDE支持的增量解析
参考资料
- ES模块规范: https://tc39.es/ecma262/#sec-modules
- Node.js模块解析: https://nodejs.org/api/modules.html
- Rust模块系统: https://doc.rust-lang.org/reference/items/modules.html
- OCaml模块系统: https://ocaml.org/docs/modules
目标流程
- 模块系统设计.js
- 语义分析.js
- 解释器实现.js
- LSP服务器实现.js