
Univer 命名规范深度解析文件结构、DI Token、插件名、命令 ID 与 Locale Key 命名空间体系【免费下载链接】univerUniver is a full-stack framework for creating and editing spreadsheets / word processor / presentation on both web and server.项目地址: https://gitcode.com/GitHub_Trending/un/univer本文以 Univer 仓库中的 docs/NAMING_CONVENTION.md 为主体完整覆盖该规范定义的文件/目录命名、接口前缀、依赖注入 Token、插件名与资源 Key、命令 ID 格式以及 Locale Key 的命名空间约束等全部规则并结合仓库中可验证的真实源码示例如ILogService、SHEET_CONDITIONAL_FORMATTING_PLUGIN、set-selection-frozen命令等逐条印证这些约定在实际代码中的落地方式。读完后你可以在为 Univer 贡献插件或修改既有模块时让新增的文件、Token、命令与多语言 Key 一次性符合社区规范避免 PR 因命名问题被打回。为什么 Univer 需要一套强制命名规范Univer 是一个用于在 Web 和服务器端创建/编辑电子表格、文字处理与演示文稿的全栈框架仓库按packages/拆分为数十个 npm 包如sheets、sheets-filter、sheets-conditional-formatting等包之间通过依赖注入、命令模式、插件机制相互协作。在这种多包 DI 的架构下命名一致性直接决定代码的可检索性按 Token 字符串全局搜索即可定位服务注册点按命令 ID 即可定位功能入口。因此 docs/NAMING_CONVENTION.md 明确以确保代码质量与一致性为目标约定了从文件命名到资源 Key 的完整规则。以下逐条讲解并给出仓库中的真实证据。文件与目录命名kebab-case、复数目录与约定类型后缀基本规则原文档对 Files Folders 给出三条硬性规则文件名与目录名一律使用 kebab-case短横线小写唯一例外是包含 React 组件的文件该文件应使用 PascalCase// ✅ src/ components/ my-component/ my-util.ts MyComponent.tsx // src/ components/ myComponent/ myUtil.ts my-component.tsx目录名用复数文件名用单数。例如services/util.ts正确service/utils.ts错误// ✅ src/ services/ util.ts // src/ service/ utils.ts优先使用约定的类型后缀.service、.controller、.menu、.command、.mutation、.operation确有必要时可自造新类型名但要注意不要造得太多// ✅ src/ services/ log.service.ts user.service.ts controllers/ log.controller.ts user.controller.ts仓库中的真实落地从源码结构看这条规范在packages/core中被严格执行packages/core/src/services/目录下就是按xxx.service.ts形式组织的服务文件例如 log.service.ts、command.service.ts、config.service.ts、context.service.ts全部符合复数目录 单数文件名 .service后缀的组合。各功能包如packages/sheets-conditional-formatting的src/下同样按services/、commands/、controllers/、menu/分目录组织与规范一一对应。接口命名强制 I 前缀规范对 TypeScript 接口只有一条规则以大写I开头。// ✅ export interface IMyInterface {} // export interface MyInterface {}在仓库中可以观察到该规则的一致性贯穿所有包ILogService、ISomeCommandParams、IResolveCommentCommandParams等均以I开头。这一点在后续命令命名一节中的ICommandISomeCommandParams模式里还会再次出现——接口、参数接口、DI Token 三者都以I前缀区分于普通值这是 Univer 代码库中一眼可辨的风格特征。依赖注入 TokencreateIdentifier 与命名空间化字符串规则定义原文档规定当需要定义依赖注入DIToken 时必须遵循以下模板export const IYourServiceOrControllerName createIdentifierIYourServiceOrControllerName(package-name.your-service-or-controller-name.(service|controller));即常量名与类型同名且带I前缀Token 字符串采用包名.服务名.service|controller的命名空间化格式// ✅ export const ILogService createIdentifierILogService(core.log.service); // export const ILogService createIdentifierILogService(log-service);Token 字符串的本质作用是在 DI 容器中作为唯一注册键——createIdentifier生成的标识符在injector.add()/injector.get()时用于定位服务实例。使用包名.类型.角色格式可以避免不同包中的同名服务如多个包都有log.service相互覆盖也能通过全局搜索字符串快速找到注册点与注入点。仓库中的实现createIdentifier本身来自 DI 框架wendellhu/redi由 packages/core/src/common/di.ts 统一 re-export连同Injector、Inject、Optional、Self、Many等装饰器与工具供所有包从univerjs/core引入使用。仓库中的真实示例是日志服务// packages/core/src/services/log/log.service.ts 第 45 行 export const ILogService createIdentifierILogService(univer.log);可以看到实际代码同样采用命名空间前缀 角色的 Token 字符串此处命名空间为univer。需要说明的是原文档示例中写的是core.log.service而当前仓库中该 Token 实际值为univer.log二者体现了同一约定Token 字符串携带包级命名空间、避免裸名但具体取值以当前仓库实际代码为准。插件命名常量格式、类名前缀与资源 Key插件Plugin是 Univer 的功能扩展单元原文档对插件的命名给出了三层约束。插件名字符串BUSINESS_TYPE_PLUGIN_NAME_PLUGIN插件名常量必须是全大写、下划线分隔、以_PLUGIN结尾// ✅ export const SHEET_CONDITIONAL_FORMATTING_PLUGIN SHEET_CONDITIONAL_FORMATTING_PLUGIN; // export const SHEET_CONDITIONAL_FORMATTING_PLUGIN SHEET_CONDITIONAL_FORMATTING; // export const SHEET_CONDITIONAL_FORMATTING_PLUGIN sheet-conditional-formatting-plugin;仓库中的条件格式插件正是照此实现的const.ts 第 19 行定义了该常量随后在 plugin.ts 第 48 行通过static override pluginName SHEET_CONDITIONAL_FORMATTING_PLUGIN;将其挂到插件类上——注意常量名、字符串值、类属性三处保持完全一致。插件类PascalCase 且以Univer前缀开头// ✅ export class UniverFilterPlugin extends Plugin {} // export class FilterPlugin extends Plugin {}所有业务插件类如UniverFilterPlugin、条件格式插件等均继承 core 提供的Plugin基类并以Univer开头这样在多包聚合的入口文件中插件类名与宿主框架自身的类不会产生命名歧义。资源 Key与插件名完全相同原文档还规定Resource key should be identical to the corresponding plugins name即插件的资源加载 Key 必须与插件名字符串相同。这一约定使插件注册与资源加载可以共用同一个字符串标识减少一份映射表在仓库中可通过resource-loader相关服务packages/core/src/services/resource-loader/看到资源 Key 的解析流程而各插件包在测试与注册处传递的 Key 正是形如SHEET_CONDITIONAL_FORMATTING_PLUGIN的字符串例如 create-test-bed.ts 第 122 行。命令命名business-type.command-type.command-name三段式 ID模板与规则Univer 大量交互逻辑撤销重做、命令执行、权限校验都围绕ICommand展开。原文档给出命令定义的标准形态export interface ISomeCommandParams { // Define the parameters here } export const SomeCommand ICommandISomeCommandParams { id: business-type.command-type.command-name, };命令 ID 是三段点分字符串第一段business-type业务域名称必须与所属包/模块名一致若命令是通用general purpose的则用所属插件名作为第一段第二段command-type固定为command第三段command-name具体动作使用 kebab-case 单数形式。正确与错误示例// ✅ export const SetSelectionFrozenCommand: ICommandISetSelectionFrozenCommandParams { id: sheet.command.set-selection-frozen, // note this should be in single format } // —— 业务域用了复数 sheets export const SetSelectionFrozenCommand: ICommandISetSelectionFrozenCommandParams { id: sheets.command.set-selection-frozen, } // —— ID 直接用了类名 export const SetSelectionFrozenCommand: ICommandISetSelectionFrozenCommandParams { id: SetSelectionFrozenCommand, }对于通用命令business-type取插件名例如评论解析命令export const ResolveCommentCommand: ICommandIResolveCommentCommandParams { id: thread-comment.command.resolve-comment, }仓库中的真实命令这条规范在仓库中可以直接找到对应实现冻结选区命令定义于 set-frozen.command.ts 第 38 行ID 为sheet.command.set-selection-frozen严格符合三段式且业务域使用单数sheet。命令执行入口 command.service.ts 第 70 行的 JSDoc 示例中也以该 ID 作为文档示例说明这是被认可的标准格式。命令 ID 之所以要严格区分sheet与sheets、禁止复数是因为它同时是运行时路由键命令分发、监听如公式/渲染对特定命令 ID 的响应、以及命令历史与权限系统都按字符串精确匹配一个复数差异就会导致监听失效且无任何编译期报错。ID 属性命名id或Id原文档对对象中的 ID 字段只有一条要求一律使用 pascal case 的id或Id。即禁止ID、uuid拼成Id之外的其他大小写变体在类型定义中表示某某的标识的字段命名xxxId如unitId、sheetId这类驼峰形式而裸的标识字段写作id。这条规则配合前几节的命令 ID、Token 字符串一起保证了所有标识类字符串在代码库中呈现统一的检索形态。Locale Key 命名空间体系包名即根 Key多语言i18n是 Univer 命名规范中篇幅最大、约束最细的部分因为它直接影响运行时localeService.t(...)的解析成败。原文档将 Locale Key 规则拆为五个小节逐一继承并讲解如下。语法点分路径首段必须是包命名空间Locale key 是一个点分路径第一段是包命名空间package namespace且必须与 npm 包名完全一致// ✅ find-replace.button.confirm sheets-filter-ui.permission.filterErr // button.confirm // missing package namespace hyperLink.message.refError // namespace does not match package name包命名空间对照表每个包恰好拥有一个命名空间且必须等于包名去掉univerjs/前缀。原文档给出的对照表PackageNamespaceuniverjs/find-replacefind-replaceuniverjs/sheets-filtersheets-filteruniverjs/sheets-filter-uisheets-filter-uiuniverjs/sheets-hyper-linksheets-hyper-linkuniverjs/sheets-hyper-link-uisheets-hyper-link-ui特别地同一功能的 core 层与 UI 层必须使用不同命名空间。例如sheets-filter与sheets-filter-ui绝不能共用sheets-filter这个命名空间——两个包各自拥有packages/sheets-filter/src/locale/与packages/sheets-filter-ui/src/locale/下的独立 locale 文件命名空间隔离后任意 key 字符串都能唯一回溯到某个包。禁止跨包引用任何包都不得引用首段属于其他包命名空间的 key// In univerjs/sheets-filter // references a key from the UI package localeService.t(sheets-filter-ui.panel.empty) // In univerjs/sheets-filter-ui // references a key from a common/utility package localeService.t(sheets-ui.permission.dialog.filterErr) // ✅ sink the key into your own locale file localeService.t(sheets-filter-ui.permission.filterErr)违反这条规则的典型诱因是复用——UI 包想直接引用sheets-ui基础表格 UI 包里已存在的文案但规范明确要求下沉把需要的 key 复制/定义到本包自己的 locale 文件中用自己的命名空间前缀。仓库中 replace.command.ts 第 46–47 行的用法即为正面示例localeService.t(find-replace.button.cancel)、localeService.t(find-replace.button.confirm)univerjs/find-replace包只引用自己命名空间下的 keyfind-replace.menu.ts 第 31 行同样以find-replace.toolbar作为 tooltip key。命名空间必须是唯一根 Key每个包导出的 locale 对象必须恰好包含一个根 key——即包命名空间本身所有翻译文案嵌套在它之下// const locale { button: { confirm: OK }, // unrelated top-level key find-replace: { toolbar: ... }, }; // ✅ const locale { find-replace: { toolbar: ..., button: { confirm: OK }, // nested under the package namespace }, };这一约束保证了合并mergelocale 对象时的行为可预测注册阶段只需以包名为 key 挂载整棵子树运行时t(find-replace.button.confirm)沿路径下钻即可命中。若允许多个顶层 key 并存同名顶层 key 在不同语言文件间合并时会产生难以排查的覆盖问题。禁止 common/shared 公共文案桶允许重复规范最后明确不要在工具包中创建common、shared之类的公共命名空间如button.*、permission.dialog.*供其他包消费。每个需要某条文案的包都在自己的命名空间下自行定义// defined in univerjs/sheets-ui for others to reuse permission.dialog.filterErr // ✅ defined locally in univerjs/sheets-filter-ui sheets-filter-ui.permission.filterErr // ✅ defined locally in univerjs/sheets-hyper-link-ui sheets-hyper-link-ui.permission.hyperLinkErr原文档最后一句点明取舍Duplication across namespaces is acceptable跨命名空间的文案重复是可以接受的。这与禁止跨包引用是一对一体两面牺牲少量文本重复的存储成本换取命名空间的完全自治——key 字符串可以无歧义地定位到唯一包、唯一 locale 文件任何包的本地化工作都不会被其他包的 key 变动波及。总结一张速查表对象规则仓库示例文件/目录kebab-caseReact 组件文件 PascalCase目录复数、文件单数packages/core/src/services/log/log.service.ts类型后缀.service/.controller/.menu/.command/.mutation/.operationpackages/core/src/services/command/command.service.ts接口大写I前缀ILogServiceDI TokencreateIdentifier 命名空间化字符串log.service.ts插件名BUSINESS_TYPE_PLUGIN_NAME_PLUGIN全大写下划线const.ts插件类PascalCase Univer前缀继承Pluginplugin.ts资源 Key与插件名字符串相同SHEET_CONDITIONAL_FORMATTING_PLUGIN命令 IDbusiness-type.command.name业务域单数set-frozen.command.tsID 字段id或Idpascal case各类型定义Locale Key点分路径首段 包名单一根 key禁止跨包引用无公共桶允许重复replace.command.ts这些规则并非风格偏好而是与 Univer 的 DI 容器、命令分发与 locale 合并机制强绑定的运行时契约Token 字符串、命令 ID、Locale Key 都是靠字符串精确匹配工作的键。遵循 docs/NAMING_CONVENTION.md 的约定新增功能在合入多包体系后才能被正确地注入、分发与本地化。【免费下载链接】univerUniver is a full-stack framework for creating and editing spreadsheets / word processor / presentation on both web and server.项目地址: https://gitcode.com/GitHub_Trending/un/univer创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考