MeNav 采用模块化架构,将代码按职责拆分为独立的、可维护的模块。整体分为三个主要部分:
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 # 工具函数
将 YAML 配置文件转换为静态 HTML 网站。
generator/main.js在浏览器中提供用户交互功能和扩展 API。
提供 window.MeNav API,供浏览器扩展使用:
运行时代码通过 scripts/build-runtime.js 打包:
src/runtime/index.jsdist/script.js(IIFE 格式,单文件)为 Handlebars 模板提供辅助函数。
定义:一个模块应该只负责一件事情,只有一个改变的理由。
判断方法:
示例:
✅ 好的拆分:
// 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 行时:
当前状态:
page-data.js、search-engine.jsloader.js、validator.js、resolver.jsindex.jsloadConfig、validateConfigget、set、load、validate、preparepreparePageData、assignCategorySlugspage-data/、search-engine/config/、cache/、html/// 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,
};
生成端(构建期代码):
src/generator/
├── cache/ # 缓存处理
├── config/ # 配置管理
├── html/ # HTML 生成
├── template/ # 模板引擎
└── utils/ # 工具函数
运行时(浏览器代码):
src/runtime/
├── app/ # 应用逻辑
├── menav/ # 扩展 API
└── nested/ # 嵌套书签
// src/generator/config/new-module.js
/**
* 模块描述
* @param {Object} param - 参数说明
* @returns {Object} 返回值说明
*/
function newFunction(param) {
// 实现逻辑
}
module.exports = {
newFunction,
};
// src/generator/config/index.js
const { newFunction } = require('./new-module');
module.exports = {
// ... 其他导出
newFunction,
};
// test/new-module.test.js
const { newFunction } = require('../src/generator/config/new-module');
test('newFunction 应该...', () => {
const result = newFunction(input);
expect(result).toBe(expected);
});
npm test # 运行所有测试
npm run build # 验证构建
npm test # 运行所有测试
npm run lint # 代码检查
npm run format:check # 格式检查
A: 问自己三个问题:
A: 拆分步骤:
A: 三种方法:
A: 当满足以下条件时: