min

MeNav 源代码目录

目录

架构概述

MeNav 采用模块化架构,将代码按职责拆分为独立的、可维护的模块。整体分为三个主要部分:

  1. 生成端(generator):构建期代码,负责将配置转换为静态 HTML
  2. 运行时(runtime):浏览器端代码,负责用户交互和动态功能
  3. 辅助函数(helpers):Handlebars 模板辅助函数

架构原则

目录结构

src/
├── generator.js              # 生成端薄入口(14行)
├── generator/                # 生成端实现
│   ├── main.js              # 主流程控制(301行)
│   ├── cache/               # 缓存处理
│   │   ├── articles.js      # 文章缓存(159行)
│   │   └── projects.js      # 项目缓存(135行)
│   ├── config/              # 配置管理
│   │   ├── index.js         # 配置入口(61行)
│   │   ├── loader.js        # 配置加载(89行)
│   │   ├── validator.js     # 配置验证(90行)
│   │   ├── resolver.js      # 配置解析(146行)
│   │   └── slugs.js         # Slug 生成(43行)
│   ├── html/                # HTML 生成
│   │   ├── 404.js           # 404 页面(94行)
│   │   ├── components.js    # 组件生成(208行)
│   │   ├── fonts.js         # 字体处理(155行)
│   │   └── page-data.js     # 页面数据准备(161行)
│   ├── template/            # 模板引擎
│   │   └── engine.js        # Handlebars 引擎(120行)
│   └── utils/               # 工具函数
│       ├── html.js          # HTML 工具(17行)
│       ├── pageMeta.js      # 页面元信息(101行)
│       └── sites.js         # 站点处理(35行)
├── runtime/                 # 运行时实现
│   ├── index.js             # 运行时入口(18行)
│   ├── shared.js            # 共享工具(142行)
│   ├── tooltip.js           # 提示框(115行)
│   ├── app/                 # 应用逻辑
│   │   ├── index.js         # 应用入口(112行)
│   │   ├── routing.js       # 路由管理(403行)
│   │   ├── search.js        # 搜索功能(440行)
│   │   ├── searchEngines.js # 搜索引擎(25行)
│   │   ├── ui.js            # UI 交互(181行)
│   │   └── search/
│   │       └── highlight.js # 搜索高亮(90行)
│   ├── menav/               # 扩展 API
│   │   ├── index.js         # API 入口(70行)
│   │   ├── addElement.js    # 添加元素(351行)
│   │   ├── updateElement.js # 更新元素(166行)
│   │   ├── removeElement.js # 删除元素(26行)
│   │   ├── getAllElements.js# 获取元素(11行)
│   │   ├── getConfig.js     # 获取配置(25行)
│   │   └── events.js        # 事件系统(34行)
│   └── nested/              # 嵌套书签
│       └── index.js         # 嵌套功能(230行)
└── helpers/                 # Handlebars 辅助函数
    ├── index.js             # 辅助函数注册
    ├── formatters.js        # 格式化函数
    ├── conditions.js        # 条件判断
    └── utils.js             # 工具函数

生成端(generator)

生成端职责

将 YAML 配置文件转换为静态 HTML 网站。

生成端核心模块

config/ - 配置管理

html/ - HTML 生成

cache/ - 缓存处理

template/ - 模板引擎

utils/ - 工具函数

生成端入口文件

运行时(runtime)

运行时职责

在浏览器中提供用户交互功能和扩展 API。

运行时核心模块

app/ - 应用逻辑

menav/ - 扩展 API

提供 window.MeNav API,供浏览器扩展使用:

nested/ - 嵌套书签

运行时入口文件

构建流程

运行时代码通过 scripts/build-runtime.js 打包:

辅助函数(helpers)

辅助函数职责

为 Handlebars 模板提供辅助函数。

辅助函数模块

模块化开发规范

职责单一性原则

定义:一个模块应该只负责一件事情,只有一个改变的理由。

判断方法

  1. 能用一句话描述模块职责
  2. 只有一个理由会修改这个模块
  3. 函数/变量名清晰反映职责

示例

好的拆分

// config/loader.js - 只负责加载配置
function loadModularConfig(dirPath) { /* ... */ }

// config/validator.js - 只负责验证配置
function validateConfig(config) { /* ... */ }

// config/resolver.js - 只负责解析配置
function prepareRenderData(config) { /* ... */ }

不好的拆分

// config.js - 职责混杂
function processConfig(config) {
  // 加载、验证、解析、转换... 400 行代码
}

文件大小规范

目标

超过 500 行时

  1. 检查是否职责混杂
  2. 拆分为多个子模块
  3. 提取可复用的工具函数

当前状态

命名规范

文件命名

函数命名

目录命名

依赖管理

导入规范

// 1. Node 内置模块
const fs = require('node:fs');
const path = require('node:path');

// 2. 第三方依赖
const yaml = require('js-yaml');

// 3. 项目内部模块(相对路径)
const { loadConfig } = require('./config');
const { renderTemplate } = require('./template/engine');

导出规范

// 单个导出
module.exports = function initSearch(state, dom) { /* ... */ };

// 多个导出
module.exports = {
  loadConfig,
  validateConfig,
  prepareRenderData,
};

循环依赖

开发指南

添加新模块

1. 确定模块位置

生成端(构建期代码):

src/generator/
├── cache/      # 缓存处理
├── config/     # 配置管理
├── html/       # HTML 生成
├── template/   # 模板引擎
└── utils/      # 工具函数

运行时(浏览器代码):

src/runtime/
├── app/        # 应用逻辑
├── menav/      # 扩展 API
└── nested/     # 嵌套书签

2. 创建模块文件

// src/generator/config/new-module.js

/**
 * 模块描述
 * @param {Object} param - 参数说明
 * @returns {Object} 返回值说明
 */
function newFunction(param) {
  // 实现逻辑
}

module.exports = {
  newFunction,
};

3. 更新入口文件

// src/generator/config/index.js
const { newFunction } = require('./new-module');

module.exports = {
  // ... 其他导出
  newFunction,
};

4. 添加测试

// test/new-module.test.js
const { newFunction } = require('../src/generator/config/new-module');

test('newFunction 应该...', () => {
  const result = newFunction(input);
  expect(result).toBe(expected);
});

修改现有模块

1. 理解模块职责

2. 保持职责单一

3. 运行测试

npm test                    # 运行所有测试
npm run build              # 验证构建

4. 更新文档

测试要求

单元测试

集成测试

测试命令

npm test                    # 运行所有测试
npm run lint               # 代码检查
npm run format:check       # 格式检查

最佳实践

1. 先读后写

2. 小步迭代

3. 保持一致性

4. 文档同步

5. 测试先行

常见问题

Q: 如何判断代码应该放在哪个模块?

A: 问自己三个问题:

  1. 这段代码的职责是什么?(加载/验证/解析/渲染…)
  2. 它属于哪个功能域?(配置/缓存/HTML/模板…)
  3. 它在构建期还是运行时执行?(generator/runtime)

Q: 模块太大了怎么办?

A: 拆分步骤:

  1. 识别模块中的不同职责
  2. 为每个职责创建独立模块
  3. 更新入口文件的导入/导出
  4. 运行测试验证

Q: 如何避免循环依赖?

A: 三种方法:

  1. 提取共享代码到独立模块
  2. 使用依赖注入
  3. 重新设计模块边界

Q: 什么时候应该创建新目录?

A: 当满足以下条件时:

  1. 有 3 个以上相关模块
  2. 这些模块属于同一功能域
  3. 需要独立的入口文件(index.js)

参考资料