news 2026/9/7 14:28:12

Joplin 编码规范实战:CLAUDE.md 指南、ESLint 自动化与源码级工程约定

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Joplin 编码规范实战:CLAUDE.md 指南、ESLint 自动化与源码级工程约定

Joplin 编码规范实战:CLAUDE.md 指南、ESLint 自动化与源码级工程约定

【免费下载链接】joplinJoplin - the privacy-focused note taking app with sync capabilities for Windows, macOS, Linux, Android and iOS.项目地址: https://gitcode.com/GitHub_Trending/jo/joplin

Joplin 仓库根目录的CLAUDE.md是一份面向 AI 编码助手与人类贡献者的简明工程守则(Joplin Guidelines),它把项目最核心的代码风格、测试、样式与工具链约定浓缩为一个“快速参考”。本篇以该文档为骨架,逐条解读其规则,并结合仓库中的eslint配置、pre-commit 钩子、cspell拼写检查与 coding_style.md 等配套文档,说明每条规则背后的自动化支撑与源码级落地方式,帮助你在提交任何修改前做到与项目惯例完全一致。

快速参考(Quick Reference)总览

CLAUDE.md 的第一部分列出了 18 条高频规则,覆盖格式、类型、注释、测试、工具链与文档六大方面。下面按主题分组逐条展开。

格式与 TypeScript 类型

  • 制表符缩进:整个仓库统一使用 Tab 缩进,而非空格。
  • 字符串使用单引号:与多数 JS 项目偏好双引号不同,Joplin 规定字符串一律使用单引号。
  • 使用规范的 TypeScript 类型,避免any:这是硬性要求,any会绕过类型检查,削弱整个 monorepo 的类型安全。
  • 不要标注可推断的类型:当 TypeScript 能从上下文推断出类型时(例如const x = 'foo'推断为string),不要显式写出返回类型或const类型;只有在 TS 会推断出any时才需要显式标注。这一点与 coding_style.md 中“Don't set the type when it can be inferred”一节完全对应,后者指出 ESLint 的no-inferable-types规则只覆盖简单类型,函数调用等场景仍需自觉避免冗余标注。
  • Markdown 文件不要硬换行:段落内不手动折行,交给渲染器自动换行;只有在真正的段落或列表分隔处才插入换行符。

注释与代码复用

  • 注释只用//,不要写 JSDoc 语法:项目不采用/** ... */块注释风格。
  • 默认不写注释:仅在“为什么这样做”不明显时才加注释(例如 workaround、隐藏约束、微妙不变量),且尽量控制在一两行;永远不要解释代码“做了什么”——变量名和函数名已经承担了这一职责。
  • 复制大段代码时必须声明:如果你复制了一块可观的代码,需在复制处上方加注释,注明这是重复代码,并给出原始位置的文件/路径引用,以便日后同步维护。

Jest 测试约定

  • 每个测试文件只允许一个顶层describe():保持文件结构单一,便于浏览与定位。
  • 聚焦核心行为与边界情况:不要为每个细枝末节添加琐碎测试,测试应覆盖关键行为与 edge case。
  • 避免测试中的重复代码:当同一逻辑用不同输入测试时,使用test.each或共享 helper,而不是复制相似的测试块。

这与 coding_style.md 的“Test units”一节互为补充:该项目还主张避免 mock 对象与 spy——优先测试真实输入输出(例如真实写临时文件、真实建库写行),只在别无选择时才使用jest.spyOn/mockImplementation,因为过度打桩实际上是在测“假实现”。

工具链与架构约束

  • 新增 TypeScript 文件后运行yarn updateIgnored(在仓库根目录执行):这条规则背后是一个自动生成机制,下文“自动化落地”一节会展开。
  • 遇到 cSpell 未知单词时按 readme/dev/spellcheck.md 的规定处理:加入词表或使用cSpell:disable注释块,详见后文。
  • 编译 TypeScript 使用yarn tsc;只做类型检查不产出文件时用yarn tsc --noEmit:package.json 中tsc脚本实际是yarn workspaces foreach --worktree --parallel --verbose --interlaced run tsc,即按 workspace 并行分发到各个packages/*子包中执行各自的tsc,这与项目的 monorepo 结构("workspaces": ["packages/*"])一致。
  • SQL 查询只允许出现在 models 中(packages/lib/models目录):这是 Joplin 的核心架构约束——packages/lib是可被 CLI、桌面端(Electron)、移动端(React Native)三端共用的数据层,所有数据访问必须收敛到packages/lib/models/下的模型类(如BaseItem.tsNote.ts等),禁止在 service、命令或其他任意位置直接拼接 SQL,从而保证三端数据库驱动(SQLite / better-sqlite / 移动端存储)下的行为一致。

桌面端样式规范(Styling)

CLAUDE.md 的“Styling (desktop app)”一节规定:

  • 桌面端只使用 RSCSS + SCSS 文件组织样式,禁止内联style={{...}}和 styled-components
  • 例外:内联style只用于“真正的逐实例动态值”(例如计算出来的宽度、坐标位置);
  • 主题色一律使用var(--joplin-*)CSS 变量,而不是在 JS 中读取主题对象再传颜色值。

这些规则的完整依据在 readme/dev/spec/desktop_styling.md。该文档交代了桌面端样式方案的三次演进:直接内联 style(缺乏灵活性、无法被自定义样式表覆盖)→ styled-components(需要为每种样式建组件、且存在若干样式失效的 bug)→当前的 SCSS + 普通 CSS 类

工程上,应用样式表由构建packages/app-desktop/style.scss生成:新建组件时应同时新建一个样式文件(如MyComponent/style.scss)并在根style.scss中引入,全局样式放根 main.scss;每次构建会把style.scss编译为最终的style.css打进应用。

类名组织遵循RSCSS 约定,需要记住的要点:

  • 组件类名至少两个词,如.search-form
  • 组件内元素类名只用一个词,如.fieldtextinput
  • 始终使用>子代选择器,防止样式在嵌套组件间“渗透”。

例如:

<form class="order-form"> <input class="firstname" type="text"/> <div class="slider-box"> Select quantity: <div class="knob"></div> </div> </form>
.order-form > firstname, .order-form > lastname { padding: 10px; }

若某个元素(如slider-box)既是独立组件又是父组件的子元素,不要写.order-form > .slider-box,而是给它额外命名(如quantityslider)后选择.order-form > .quantityslider,目标是“精准命中标定的元素,不多不少”。

自动化落地:pre-commit 钩子、lint-staged 与 cSpell

CLAUDE.md 末尾指向 readme/dev/coding_style.md,其开篇说明了这些规范的执行机制:编码风格主要由运行eslint的 pre-commit 钩子强制,该钩子在任一应用目录执行yarn install时自动安装;若钩子缺失,在仓库根目录重新执行yarn install即可恢复。

仓库中可验证的对应物:

  • .husky/pre-commit 的内容只有一行corepack yarn lint-staged,即提交前对暂存文件运行 lint-staged;
  • 根目录 lint-staged.config.js 定义了对待提交文件执行检查的具体命令;
  • package.json 的postinstall脚本为husky && gulp build,解释了“yarn install之后钩子即生效”的原因;
  • 手动运行 lint 的方式为yarn linter ./(即eslint --fix --quiet);CI 使用yarn linter-cieslint --quiet,不带--fix,保证失败即报红)。

coding_style.md 还给出了新增 ESLint 规则的标准流程:把规则加进 ESLint 配置后,老代码往往会大面积报错,此时二选一——文件不多且改动简单就直接逐一修复(首选);或者运行yarn linter-interactive ./,交互式为存量违规行逐条添加eslint-disable-next-line注释,并统一标注 “Old code before rule was applied” 以便日后检索。这样存量代码保持不动、新代码强制遵守规则。

yarn updateIgnored背后的生成机制

CLAUDE.md 要求“新增.ts文件后运行yarn updateIgnored”,其实现是 packages/tools/gulp/tasks/updateIgnoredTypeScriptBuild.js:

  1. 以 glob 扫描全仓库的**/*.ts**/*.tsx(排除node_modules、各包构建产物、测试夹具、packages/serverpackages/utils等目录,并过滤掉.d.ts);
  2. 为每个.ts文件推导对应编译产物.js的文件名;
  3. 用正则定位.gitignore.ignore.eslint中由# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD标记的自动生成分区,把最新列表整块替换进去。

原理是:TypeScript 编译会产出.js文件,这些本地产物既不能进版本库也不能参与 lint,所以每次源码文件增删后都要重新生成这两处忽略清单——yarn updateIgnored(对应node packages/tools/gulp/tasks/updateIgnoredTypeScriptBuildRun.js)就是把这一机械步骤自动化。

拼写检查(cSpell)

CLAUDE.md 引用了 readme/dev/spellcheck.md:仓库中的 Markdown 与 TypeScript 文件会被 cspell.json 配置的 CSpell 自动检查。实操要点:

  • 全量检查:yarn spellcheck --all;单文件:yarn spellcheck /path/to/file
  • pre-commit 钩子会自动检查新提交文件中的拼写;
  • 忽略词两种途径:追加到packages/tools/cspell/下的词表文件(每个词表不超过约 400 词,超出会导致 CSpell 加载失败,需另建词表并在cspell.json中注册),或对含大量不可忽略词的代码块使用// cSpell:disable/// cSpell:enable(Markdown 中为<!-- cSpell:disable -->);更推荐优先用词表以免污染代码;
  • 也可在cspell.json中用ignore属性按路径或正则整体跳过(例如忽略 changelog 中的 GitHub 用户名)。

深入配套文档:coding_style.md 的关键细则

CLAUDE.md 的快速参考是“摘要层”,readme/dev/coding_style.md 是“细则层”,两者必须结合阅读。以下几组细则对实际写代码影响最大。

文件命名与导入

  • 导出多个东西的文件用camelCase.ts仅当文件包含单一类且为默认导出时才用PascalCase.ts;共享类型定义放types.tsfooTypes.ts
  • 导出成员与导入成员保持相同的命名大小写(文件、函数、导入名一致);
  • 只导入需要的成员(利于 tree shaking):import { writeFile } from 'fs-extra'优于import * as fs
  • TypeScript 文件中优先import而非require,以便享受类型检查;老包没有类型声明时才退回require()
  • 避免内联类型,类型应单独定义以便复用:
type Config = Record<string, Knex.Config>; const config: Config = { /* ... */ };

变量与函数

  • 新代码中的常量用camelCase(不用全大写SNAKE_CASE);
  • 变量声明尽量贴近其首次使用处(避免“声明在最顶部却隔了很远才用”);
  • 优先const而非let;优先箭头函数() => {}而非function() {}——不用this便于日后把类组件重构为 React Hooks,且对this的误用会被 TypeScript 直接报错;
  • 尽量避免默认参数与可选字段:所有参数都必填时,重构代码编译器会自动帮你发现漏传。

安全:转义用户内容(Escape variables)

这是细则文档中篇幅最大的安全章节,核心原则:永远不要假设输入安全,即使你认为自己控制了输入;且尽可能晚地转义——应用内部保持原始数据,只在边界处解码/编码,避免双重转义。按插入位置选择手段:

插入位置手段
JS 脚本字符串JSON.stringify(data)
HTML 字符串htmlentities(来自 packages/utils/html.ts),如htmlentities(content)
URL 查询参数encodeURIComponent;完整 URL 用encodeURI
Markdownpackages/lib/markdownUtils.ts 提供escapeTableCell()escapeInlineCode()escapeTitleText()escapeLinkUrl()

文档还提出了“Make wrong code look wrong”命名法:给已转义变量加后缀(如userContentHtml),让“把未转义变量插进 HTML”这件事在代码审阅中一眼可见、一眼可疑。

数据库约定

  • 表名与列名一律snake_case
  • 所有列NOT NULL,可带默认值,避免查询中同时处理NULL/0/ 空串;
  • 默认值要克制——多数情况应要求调用方显式传值;
  • 枚举值用 integer 列 + TypeScript enum表达,不用数据库内置 enum(迁移困难);
  • 布尔语义优先tinyint(1)而非bool(SQLite/MySQL 中布尔本就不是独立类型)。

React 与 GitHub Actions

  • 新组件一律函数组件 + Hooks,不写extends Component类组件;长逻辑抽成自定义 hook(注意 hook 必须以use开头,否则 eslint 会报 “called outside of a component”);
  • GitHub Actions 的run块内不要内嵌${{ }}(存在脚本注入风险),应通过env传入后再引用环境变量。

文档引用与延伸阅读

CLAUDE.md 末尾的“Full Documentation”给出了两个权威入口:

  • 编码风格全文:readme/dev/coding_style.md(本文已大量引用);
  • 贡献指南:CONTRIBUTING 文件,其内容指向 readme/dev/index.md 这一开发者指南总入口,该目录下还有构建(BUILD.md)、部署(DEPLOY.md)、技术规格(technical_spec.md)、本地化、构建排错 以及 readme/dev/spec/ 下数十篇子系统规格文档(同步、服务器、编辑器、插件等)。

适用前提小结

本文所述约定均以当前仓库实际内容为准:Node 引擎要求>=22.12、Yarn 版本锁定(engines 声明4.12.0packageManager声明yarn@4.16.0,见 package.json)。如果你要在 Joplin 仓库内提交代码,最短执行路径是:写完代码后先跑yarn tsc --noEmit做类型检查、yarn linter ./修风格问题、新增.ts文件后执行yarn updateIgnored、遇到拼写告警按 readme/dev/spellcheck.md 处理词表——pre-commit 钩子会在提交时兜底拦截不符合 CLAUDE.md 约定与 ESLint 规则的代码。

【免费下载链接】joplinJoplin - the privacy-focused note taking app with sync capabilities for Windows, macOS, Linux, Android and iOS.项目地址: https://gitcode.com/GitHub_Trending/jo/joplin

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 14:24:29

从TikTok成瘾性设计解析推荐算法与交互优化技术实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 14:23:54

MODBUS协议调试笔记:从报文结构到RS485实战排查

翻了翻手头的调试笔记&#xff0c;前面几篇写的是驱动、中断、总线调试&#xff0c;这次轮到MODBUS协议了。做嵌入式这些年&#xff0c;工业控制、仪器采集、能源监控这类项目里&#xff0c;MODBUS协议几乎是绕不开的选项&#xff1a;它简单、稳定、资料多&#xff0c;从单片机…

作者头像 李华
网站建设 2026/9/7 14:23:00

CUDA编程核心概念与实战:从并行计算到AI推理优化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 14:19:00

大数据背景做RAG:权限和日志才是小团队最难啃的骨头

聊《我用大数据经验做了次 AI 项目&#xff0c;最先失效的是旧方法》之前&#xff0c;先说一句实在的&#xff1a;别急着背概念&#xff0c;先看它在真实项目里到底解决什么问题。摘要去年我们组接到一个需求&#xff1a;把内部文档库接进大模型&#xff0c;做问答系统。前端同…

作者头像 李华
网站建设 2026/9/7 14:18:28

DDS在汽车以太网中的原理、QoS配置与量产落地

汽车以太网这个方向近几年被问得最多的协议&#xff0c;除了SOME/IP&#xff0c;就是DDS。很多人第一次接触DDS是因为ROS 2&#xff0c;后来发现AUTOSAR Adaptive、智能驾驶域控制器里也频繁出现它的身影。也有不少同行过来问我&#xff1a;DDS到底是干什么的&#xff0c;跟SOM…

作者头像 李华
网站建设 2026/9/7 14:17:46

Linux Platform驱动模型:设备树匹配机制与i.MX6ULL实战解析

写驱动这东西&#xff0c;我一开始也绕了不少弯路。尤其在 i.MX6ULL 这种 Cortex-A7 内核的板子上&#xff0c;网上资料虽然多&#xff0c;但大多是给你扔一个 GPIO 点灯例程&#xff0c;照着抄完能跑&#xff0c;却不知道自己到底在干什么。一旦换一颗芯片&#xff0c;或者换一…

作者头像 李华