Joplin Web Clipper 中 Readability 库的维护指南:内容脚本文件清单、版本标注规范与集成原理
【免费下载链接】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 Web Clipper 依靠 Mozilla 开源的 Readability 库,在浏览器内容脚本(content script)中实现“简化页面”(阅读模式)剪藏与“页面是否可读”检测。由于内容脚本的加载机制限制,该仓库采用手动复制整份库文件的维护方式,并强制要求在每个文件顶部标注上游 commit 与版本号。本文以 packages/app-clipper/content_scripts/README.md 为骨架,结合index.js、service_worker.mjs与manifest.json源码,完整说明需要维护的三个文件、各自的职责、更新流程及在剪藏管线中的实际调用关系,帮助维护者安全、可追溯地升级这套随仓库分发的 Readability 代码。
为什么 Readability 必须以“手动复制”方式维护
原文档开宗明义:“Because of the way content scripts are loaded, we need to manually copy the whole Readability files here. That should be fine since they rarely change.”
在 Joplin Web Clipper 的实现中,内容脚本的入口 packages/app-clipper/content_scripts/index.js 是一个立即执行的 IIFE,其中直接以全局函数的方式调用 Readability 相关 API,例如:
const readability = new Readability(documentForReadability()); const article = readability.parse();以及:
const ok = isProbablyReaderable(documentForReadability());也就是说,Readability、isProbablyReaderable这两个符号在index.js中被当作全局作用域中的函数直接使用,而不是通过import/require引入的模块。配合 packages/app-clipper/content_scripts/setUpEnvironment.js 中“TypeScript 编译产物使用 CommonJSexports,而浏览器环境没有exports,因此需要window.exports ??= {}兜底”的注释可以推断:内容脚本运行在浏览器页面上下文,缺乏 Node 风格的模块解析能力,因此最稳妥的做法是把上游 Readability 的完整源码(含其依赖的 JSDOMParser)直接平铺复制到仓库内,让它们在脚本执行时挂载到全局作用域。这也是 README 强调“手动复制整份文件”的根本原因。
需要维护的三个文件及其职责
原文档明确列出更新时需要同步的三个文件:
| 文件 | 用途 | 上游版本(见文件首行) |
|---|---|---|
| Readability.js | 核心文章提取器,提供Readability(doc, options)构造器与parse()方法 | v0.4.4(commit49d345a) |
| Readability-readerable.js | 提供isProbablyReaderable(doc)函数,用于预估页面是否值得走 Readability 解析 | v0.4.4(commit49d345a) |
| JSDOMParser.js | 轻量级 DOMParser 实现,供 Readability 在受限环境下解析 HTML | v0.4.1(commit28843b6) |
Readability.js:核心文章提取器
Readability.js是 Readability 库的主文件,源码共 2300 余行,基于 Arc90 的 readability.js 1.7.1 演进而来。它在仓库内的调用点是 index.js 的readabilityProcess():
function readabilityProcess() { if (isPagePdf()) throw new Error('Could not parse PDF document with Readability'); const readability = new Readability(documentForReadability()); const article = readability.parse(); if (!article) throw new Error('Could not parse HTML document with Readability'); return { title: article.title, body: article.content, }; }注意其中的关键细节:documentForReadability()会先对当前页面执行document.cloneNode(true),再交给 Readability 处理。这是因为Readability 会直接修改传入的 document,克隆是为了保护原始网页不被破坏:
function documentForReadability() { // Readability directly change the passed document so clone it so as // to preserve the original web page. return document.cloneNode(true); }Readability-readerable.js:可读性预检
Readability-readerable.js只导出一个函数isProbablyReaderable(doc, options),返回布尔值,用于预测Readability.parse()是否可能成功。它通过一组正则(unlikelyCandidates、okMaybeItsACandidate等)对页面元素做启发式打分。
在剪藏管线中它对应isProbablyReaderable命令:
} else if (command.name === 'isProbablyReaderable') { const ok = isProbablyReaderable(documentForReadability()); return { name: 'isProbablyReaderable', value: ok }; }值得注意的维护细节:该文件头部注释特别提醒——其中的两条正则表达式与Readability.js中重复定义,两处必须保持同步("These two regular expressions are duplicated in Readability.js. Please keep both copies in sync.")。因此升级时若上游修改了评分正则,需要同时检查两份拷贝。
JSDOMParser.js:轻量 DOM 解析器
JSDOMParser.js是 Readability 的依赖项,提供“可以在 Web Worker 中安全使用的相对轻量的 DOMParser”。从源码注释可以确认它的能力边界:
- 只支持格式良好的 HTML/XML:直接解析 XHR 拿到的字符串可能出错,官方建议在主线程用
XMLSerializer.serializeToString()序列化后再传入; - 不支持 Live NodeList:
getElementsByTagName()等方法返回普通数组,节点增删后需要手动维护列表。
在Readability.js内部,通过this._doc.firstChild.__JSDOMParser__检测文档是否由 JSDOMParser 创建,并据此切换某些 DOM 行为的实现路径(例如是否处理_isLiveNodeList),详见 Readability.js 中的相关分支。
更新流程:版本与 commit 标注规范
原文档对更新动作给出了两条硬性要求:
- 完整替换三个文件(整份复制,不做裁剪);
- 在每个文件顶部添加 commit 与版本号,例如:
// v0.4.4 - https://github.com/mozilla/readability/commit/49d345a455da1f4aa93f8b41e0f50422f9959c7c仓库中三个文件的首行注释正是这一规范的落地证据:Readability.js与Readability-readerable.js标注 v0.4.4(commit49d345a),JSDOMParser.js标注 v0.4.1(commit28843b6)。这条标注不是装饰,它承担三个作用:
- 可追溯:出现 bug 时能快速定位对应上游版本与源码;
- 可审计:Review 时一眼看出当前仓库与上游的版本差距,判断是否值得升级;
- 可对照:需要排查问题时可直接对照该 commit 的上游代码差异。
由于 README 明确说明“这些文件很少变化”,这种手动脉冲式升级(而非每次构建都从上游拉取)是刻意选择的低维护成本策略:只在必要时(如上游修复了重要解析 bug)才升级,且每次升级都留下版本痕迹。
在剪藏管线中的完整调用链
结合 packages/app-clipper/manifest.json 与 service_worker.mjs 可以还原 Readability 相关命令的完整链路:
- 用户在浏览器中触发快捷键命令(如
clipSimplifiedPage),service_worker.mjs的sendClipMessage()将其映射为simplifiedPageHtml消息:case 'clipSimplifiedPage': message.name = 'simplifiedPageHtml'; break;随后通过
browser_.tabs.sendMessage(tabId, message)发送给内容脚本。 - index.js 的
browser_.runtime.onMessage监听器收到命令,进入prepareCommandResponse():simplifiedPageHtml:调用readabilityProcess()提取article.title与article.content,再配合getImageSizes()、getAnchorNames()组装成clippedContent/sendContentToJoplin响应;- 异常降级:若 Readability 解析失败(PDF 页面、无文章内容等),捕获异常后回退到
completePageHtml全页模式,并附加warning = 'Could not retrieve simplified version of page - full page has been saved instead.',见 index.js。
- 响应通过
browser_.runtime.sendMessage(response)返回给扩展后台,最终由桌面端剪藏服务器处理。
另一个相关命令是isProbablyReaderable(对应快捷命令未在manifest.json中单独暴露,但由内容脚本直接支持),它返回布尔值,可用于剪藏界面预判当前页面是否适合“简化页面”模式。
与 Readability 无关但与内容脚本强相关的同级代码
升级 Readability 时建议顺带回归验证index.js中与解析管线相邻的处理逻辑,因为它们共同决定剪藏质量:
preProcessDocument():对不可见元素(display: none、visibility: hidden,以及script、select、button等)添加joplin-clipper-hidden类,并在克隆文档中由cleanUpElement()移除;cleanUpElement():处理img/svg的尺寸硬编码、input/textarea的值导出(data-joplin-clipper-value)、embed/object的绝对 URL 化;hardcodePreStyles()与addSvgClass():为代码块与 SVG 后续 Markdown 转换做准备。
这些函数位于 index.js 中,虽不属于 Readability 库本身,但与简化/完整页面剪藏共用同一条数据链路,属于升级后的人工回归重点。
维护清单与自检要点
综合原文档与仓库实现,一次完整的 Readability 升级建议按以下步骤执行:
- 从上游获取三个文件的新版本源码(
Readability.js、Readability-readerable.js、JSDOMParser.js),整份替换到 packages/app-clipper/content_scripts/ 目录; - 在每个文件顶部更新版本号与 commit 标注行(若上游版本未变则保持原标注);
- 检查
Readability-readerable.js与Readability.js中重复定义的正则(unlikelyCandidates、okMaybeItsACandidate)是否仍保持一致; - 回归验证以下命令路径:
simplifiedPageHtml(简化页面)、isProbablyReaderable(可读性预检)、completePageHtml(降级兜底路径); - 重点测试 PDF 页面(
isPagePdf()应直接拒绝走 Readability)与无法提取文章的页面(应正确降级并携带 warning)。
按此流程操作,即可在保持内容脚本加载机制不变的前提下,安全地跟踪上游 Readability 的演进,同时确保每一次升级都有明确的版本记录可供追溯。
【免费下载链接】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),仅供参考