Harper WordPress 插件:将离线隐私优先的语法检查器嵌入 WordPress 块编辑器
【免费下载链接】harperOffline, privacy-first grammar checker. Fast, open-source, Rust-powered项目地址: https://gitcode.com/GitHub_Trending/har/harper
Harper 是一个离线运行、隐私优先、基于 Rust 的语法检查器,而本仓库中的packages/wordpress-plugin将其能力带入了 WordPress 的块编辑器:在写作时实时检查文档内每个富文本块的语法问题,并在编辑器侧边栏中列出可忽略的问题与建议。读完本文,你将理解该插件从 PHP 入口、block.json块注册,到通过 WASM 引擎在浏览器中执行lint调用的完整链路,以及如何在本机构建并打包出可分发的harper.zip。
插件定位与模块构成
根据 packages/wordpress-plugin/README.md,该目录包含 Harper 的 WordPress 插件,官方明确标注其为work-in-progress(进行中),尚属于早期原型阶段。从源码结构看,模块由三层构成:
| 层次 | 文件/目录 | 职责 |
|---|---|---|
| PHP 入口 | harper.php | 向 WordPress 注册块类型 |
| 构建配置 | package.json | 基于wp-scripts的构建脚本与依赖声明 |
| 块前端(React) | src/harper/ | 侧边栏插件、高亮渲染、WASM 检查引擎封装 |
核心能力——即真正的语法检查逻辑——并不在这个插件里,而是来自同仓库的 harper.js 包(以"harper.js": "workspace:*"的方式作为 pnpm workspace 依赖引入)。插件本身负责的是“在 WordPress 编辑界面里采集文本、展示结果、管理用户偏好”这一交互层。
PHP 入口:harper.php 如何注册块
harper.php 是整个插件的 PHP 侧全部实现,逻辑非常克制:
/** * Plugin Name: Harper * Version: 0.0.1 * Requires at least: 6.7 * Requires PHP: 7.4 * License: GPL-2.0-or-later * Text Domain: harper */ declare( strict_types = 1 ); if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } function create_harper_block_init() { register_block_type( __DIR__ . '/build/harper' ); } add_action( 'init', 'create_harper_block_init' );几个值得注意的细节:
- 头部元数据声明了最低环境要求:WordPress6.7、PHP7.4,许可证为 GPL-2.0-or-later——这是 WordPress 插件目录的分发前提。
ABSPATH防护:直接访问该文件时立即退出,遵循 WordPress 插件的标准安全惯例。register_block_type( __DIR__ . '/build/harper' ):注册的不是 PHP 渲染逻辑,而是一个指向构建产物目录build/harper的路径。WordPress 会读取其中的 block.json 元数据,并自动处理资源入队(enqueue)。这意味着该插件是典型的元数据块(metadata-only block):所有交互逻辑都在前端 JS 中。
block.json:块的元数据契约
src/harper/block.json 是构建时build/harper目录的核心文件,完整内容为:
{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "harper/harper", "version": "0.0.1", "title": "Harper", "category": "text", "icon": "smiley", "description": "Harper is the grammar checker that respects your privacy", "example": {}, "supports": { "html": false }, "textdomain": "harper", "editorScript": "file:./index.js", "editorStyle": "file:./index.css" }apiVersion: 3使用最新的块编辑器 API;supports.html: false表示该块不支持切换原始 HTML——它不产生独立的内容结构,而更像挂载在文档上的“检查器”;editorScript/editorStyle仅声明编辑器资源,即该插件只作用于编辑器环境,不向文章前台注入任何脚本,这与“隐私优先、离线检查”的定位一致。
构建系统与 npm 脚本
package.json 定义了插件的构建与打包方式:
| 脚本 | 命令 | 作用 |
|---|---|---|
build | wp-scripts build --webpack-copy-php | 生产构建,并将harper.php复制进build/ |
start | wp-scripts start --webpack-copy-php | 开发模式,带热重载监听 |
plugin-zip | zip harper.zip build harper.php screenshot.png -r | 按 WordPress 插件格式打包可上传的 zip |
packages-update | wp-scripts packages-update | 同步@wordpress/*依赖到仓库内 vendor 目录 |
依赖侧的关键信息:
- devDependencies中的
@wordpress/scripts(^30.9.0)提供wp-scripts工具链;@wp-now/wp-now(^0.1.74)用于在本地快速拉起一个 WordPress 实例做开发验证(从依赖结构看,仓库将本地 WordPress 环境纳入了开发流程)。 - dependencies中除 React 18 与若干
@wordpress/*包外,最关键的是"harper.js": "workspace:*"——检查引擎来自本 monorepo 内部,构建时会一并打包进产物。
块编辑器插件注册:index.tsx
src/harper/index.tsx 展示了插件与块编辑器(block editor)的挂载方式——它并没有注册新的块,而是注册了一个编辑器侧边栏插件(PluginSidebar):
function Sidebar() { return ( <> <PluginSidebarMoreMenuItem target="harper-sidebar" icon={Logo()}> Harper </PluginSidebarMoreMenuItem> <PluginSidebar name="harper-sidebar" title="Harper" icon={Logo}> <LinterProvider> <SidebarControl /> </LinterProvider> </PluginSidebar> </> ); } if (!window.__harperSidebarRegistered) { registerPlugin('harper-sidebar', { render: Sidebar }); window.__harperSidebarRegistered = true; }这里通过@wordpress/edit-post的PluginSidebar与@wordpress/plugins的registerPlugin,把 Harper 挂到编辑器“更多(More)”菜单下的独立侧边栏中。window.__harperSidebarRegistered是一个防重复注册的保护标志(开发模式下模块可能被重复求值)。block.json里的块名harper/harper则提供了块在文档中的占位能力,而侧边栏承载了主要的检查交互。
引擎层:WorkerLinter 与内联二进制
src/harper/LinterProvider.tsx 是整个插件与 Harper 引擎的衔接点:
import { type Linter, WorkerLinter } from 'harper.js'; import { binaryInlined } from 'harper.js/binaryInlined'; const linterContext = createContext<Linter>(new WorkerLinter({ binary: binaryInlined })); export default function LinterProvider({ children }: { children: ReactNode | ReactNode[] }) { const linter = useRef(new WorkerLinter({ binary: binaryInlined })); return <linterContext.Provider value={linter.current}>{children}</linterContext.Provider>; }可以从源码结构中确认:
WorkerLinter是harper.js提供的 Linter 实现,检查工作在 Web Worker 中异步进行,避免阻塞编辑器主线程;binaryInlined(来自harper.js/binaryInlined子入口)提供了内联的 Rust 引擎二进制(从命名与harper-wasm子项目的存在看,即打包进 JS 的 WASM 产物),因此检查完全在浏览器本地完成,不上传任何文本——这正是 README 中 “respects your privacy” 的技术落点;- Provider 用
useRef保证整个侧边栏生命周期内只有一个 Linter 实例,通过 React Context 向下分发;配套的useLintDescriptions()会在挂载后拉取各 lint 规则的描述文本,供侧边栏展示。
检查流水线:SidebarControl 与 useLintBoxes
交互层的核心是两个文件,它们共同回答了“WordPress 文档是怎么被实时检查的”。
文本采集与 DOM 观察
src/harper/SidebarControl.tsx 的流程:
- 通过
DataBlock.getContainer()拿到文档内容容器(由 DataBlock.ts 基于块编辑器数据模型实现); DataBlock.getTerminalDataBlocks()取出所有“终端”数据块,并用MutationObserver监听容器子树变化,块结构变动时重新采集;- 从每个块中
getAllRichText()提取富文本字段,交给useLintBoxes执行检查; - 检查结果通过
createPortal渲染成 Highlighter 组件,把高亮框直接传送(portal)回文档 DOM,在正文对应位置显示问题标记; - 侧边栏本体由
SidebarTabContainer渲染,汇总展示所有lintBoxes及加载状态。
配置同步与 lint 执行
src/harper/useLintBoxes.ts 是插件与Linter接口对话最密集的地方,其updateLints回调完整展示了引擎的调用序列:
// 1. 校验“已忽略”状态,不一致则清空后重导 if ((await linter.exportIgnoredLints()) !== ignoreState) { await linter.clearIgnoredLints(); } // 2. 设置方言(如 en-US / en-GB) await linter.setDialect(dialect); // 3. 导入个人词典 if (personalDictionary) { await linter.importWords(personalDictionary); } // 4. 同步 lint 规则配置 if (JSON.stringify(await linter.getLintConfig()) !== JSON.stringify(config)) { await linter.setLintConfig(config); } // 5. 恢复被用户忽略的具体 lint 项 if (ignoreState) { await linter.importIgnoredLints(ignoreState); } // 6. 并行 lint 所有富文本字段 const newLints = await Promise.all( richTexts.map((richText) => linter.lint(richText.getTextContent())) );这段代码揭示了引擎侧的完整能力面:setDialect(方言,对应 useDialect.ts,可推断基于@wordpress/preferences-persistence持久化)、importWords(个人词典,usePersonalDictionary.ts)、getLintConfig/setLintConfig(按规则粒度的开关配置,useLintConfig.ts)、importIgnoredLints/exportIgnoredLints/clearIgnoredLints(“忽略此问题”状态,useIgnoredLintState.ts)。每个 hook 各自管理一块偏好状态,插件采取**“比对—按需推送”**的策略:仅当本地状态与引擎内状态不一致时才写入引擎,避免无谓的重复导入。
执行层面还有两个工程细节:
- 对每个富文本元素建立独立
MutationObserver(监听childList/characterData/subtree),打字即触发updateLints重新检查——实时性由此而来; - 高亮框的位置通过
requestAnimationFrame每帧重算(源码注释坦承 “Probably overkill”,并留有 TODO 说明后续会改为更惰性的布局回调),因为块编辑器中滚动、折叠、拖拽都会改变目标元素的位置。
本地开发、构建与打包
结合 package.json 的脚本,完整工作流如下(命令在该目录下执行):
# 开发模式:构建 + 热重载 npm run start # 生产构建(产物输出到 build/) npm run build # 打包 WordPress 插件 zip(build 目录 + harper.php + 截图) npm run plugin-zip--webpack-copy-php参数保证harper.php会被复制进build/,最终 zip 的结构即 WordPress 标准插件包形态:harper.zip内含build/(块资源)、harper.php(入口)与screenshot.png。开发时配合@wp-now/wp-now依赖,可快速在本地得到一个 WordPress 环境来加载并调试该插件。README 中提到的贡献说明详见 Harper 官方在线文档的 WordPress 贡献指南部分。
现状与演进方向
需要再次强调 README 的定调:插件是 work-in-progress,"Here be dragons!"。版本号在 PHP 头、block.json、package.json三处统一为0.0.1,表明它仍处于原型迭代期。已成型的核心架构——PHP 元数据注册 + React 侧边栏 + WASM 引擎 Worker 化执行 + 偏好状态“比对推送”同步——已经比较完整;而 useLintBoxes.ts 中每帧重算高亮位置等 TODO,则指明了性能优化是当前明确的改进方向。对于想阅读 Harper 引擎实现细节的读者,可以继续深入 harper.js 的 TypeScript 封装与 harper-core 的 Rust 规则集。
【免费下载链接】harperOffline, privacy-first grammar checker. Fast, open-source, Rust-powered项目地址: https://gitcode.com/GitHub_Trending/har/harper
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考