
基于 snapdom-plugin-template 开发 SnapDOM v3 插件从脚手架到发布完整指南【免费下载链接】snapdomHigh-performance engine for capturing, modifying, and converting DOM elements into any format.项目地址: https://gitcode.com/GitHub_Trending/sn/snapdom本指南以仓库中的 插件模板说明 为主体结合 插件规范、插件核心实现 与官方插件包 packages/plugins 中的源码级证据系统讲解 SnapDOM v3 插件从脚手架搭建、生命周期钩子开发、自定义导出到 npm 发布的全流程。读完本文你将能独立完成一个工厂函数 生命周期钩子 自定义导出的标准 SnapDOM 插件并正确声明其运行阶段needs与纯度pure在捕获、变换、导出、集成四类场景中落地。模板是什么一个零依赖的插件起步包packages/plugin-template是 SnapDOM v3 官方的插件脚手架其 package.json 表明它是一个独立的 npm 包type: module、main: index.js、入口为./index.js。关键设计是模板对核心没有相对依赖不依赖 monorepo 的目录结构即可独立工作因此你可以把它复制到任何位置开发插件。模板的核心文件是 index.js它导出一个工厂函数myPlugin(options)export function myPlugin(options {}) { const { example default, } options; return { name: my-plugin, // Pick the hook(s) you need. Delete the rest. // Full lifecycle: beforeSnap → beforeClone → resolveNode → afterClone → beforeRender → afterRender // → defineExports → [beforeExport → exporter → afterExport] → afterSnap afterClone(ctx) { // Runs after cloning style inlining. ctx.clone is the cloned DOM tree. // This is the most common hook — modify the clone here. }, // ...其余钩子以注释形式给出 }; } export default myPlugin;从模板源码可以提炼出插件的三条基本规范工厂函数模式接受options对象并给出默认值返回插件实例。模板中默认导出myPlugin同时保留具名导出与默认导出便于不同导入方式。唯一namename用于去重与局部覆盖全局的优先级判定见下文源码分析模板使用 kebab-case 的my-plugin。按需挑选钩子模板把所有钩子以注释形式列出开发者只取消注释自己需要的部分其余删除。安装与脚手架两条等价路径路径一直接安装核心作为开发依赖npm install --save-dev zumer/snapdomlatest路径二用 degit 从模板脚手架化推荐npx degit zumerlab/snapdom/packages/plugin-template snapdom-plugin-yourname cd snapdom-plugin-yourname npm install --save-dev zumer/snapdomlatestdegit会将模板目录克隆为snapdom-plugin-yourname注意 npm 社区插件命名约定为snapdom-plugin-[name]。模板没有相对核心依赖因此脱离 monorepo 布局也能独立安装运行。发布前请务必替换包名、描述与示例 option并将peerDependencies的版本范围模板中为zumer/snapdom: ^3.0.0与你实际测试过的核心版本对齐——官方 插件规范 与安装说明始终跟踪已发布的核心版本。使用方式在捕获中挂载插件模板 README 给出的最小使用示例import { snapdom } from zumer/snapdom; import { myPlugin } from ./index.js; const result await snapdom(element, { plugins: [myPlugin({ example: value })] }); const image await result.toPng();plugins选项支持两种注册方式per-capture每次捕获与全局注册。全局注册方式为snapdom.plugins(myPlugin())适用于所有捕获per-capture 方式在单次捕获中生效。两者叠加时从 插件核心 的mergePlugins实现可以看出per-capture 插件优先于全局插件同名插件会被去重后者覆盖前者。该模块还允许三种插件定义形式纯对象、[factory, options]数组、{ plugin, options }对象见normalizePlugin模板使用的工厂函数形式是官方推荐的标准形态。Options 设计参数表与实现对应模板 README 给出了示例参数表OptionTypeDefaultDescriptionexamplestringdefaultDescribe this option对应 index.js 中的解构实现export function myPlugin(options {}) { const { example default } options; return { name: my-plugin, afterClone(ctx) { /* ... */ } }; }这是工厂模式的标配所有选项必须有默认值解构赋默认值这样myPlugin()不带参数调用也不会崩溃。官方插件 context-export 是更完整的范例——它解构了format、maxTextLength、maxNodes、geometry四个选项并各自给出默认值还通过options.needs ?? render暴露了运行阶段选项。发布前请把example替换为你的真实选项并在 README 中用同样的三列格式Option/Type/Default/Description记录每个参数。生命周期钩子模板钩子背后的完整规范模板 README 明确说明afterClone修改克隆后的 DOM 树模板中的钩子体为空直到你添加自己的实现插件还可以通过defineExports增加 HTML 或结构化上下文等输出。要真正理解这些钩子需要结合 PLUGIN_SPEC.md 与 src/core/plugins.js 的实现。完整钩子时序beforeSnap → beforeClone → resolveNode (per node) → afterClone → beforeRender → afterRender → defineExports → [beforeExport → exporter → afterExport] → afterSnap其中resolveNode对捕获子树中的每个源节点执行方括号内的导出段对每一次导出调用执行afterSnap在首次成功导出后执行一次。各钩子的运行时机与典型用途Hook运行时机常见用途beforeSnap捕获工作开始前校验选项、设置默认值beforeCloneDOM 被克隆前预处理源 DOM须在 afterClone 中撤销resolveNode克隆构建期间逐节点替换/跳过单个节点脱敏、自定义组件afterCloneDOM 与样式克隆完成后变换克隆叠加层、样式、替换beforeRender选定的渲染器运行前调整克隆或生成的 CSSafterRender渲染产物生成后读取ctx.dataURL/ctx.metaSVG 路径下还有ctx.svgStringdefineExports捕获后的结果构建期新增导出格式toPdf、toAsciibeforeExport每次导出调用前调整导出选项质量、尺寸afterExport每次导出调用后观察导出结果日志、上传、测量afterSnap首次成功导出后一次清理捕获范围内的资源钩子上下文 ctx捕获类钩子beforeSnap到afterRender以及afterSnap共享同一个上下文对象ctx其中既有归一化后的捕获选项scale、dpr、width、height、backgroundColor、quality、useProxy、cache、embedFonts、filter、exclude、engine等也有各阶段产生的中间值clone克隆 DOM 树、nodeMap、classCSS/fontsCSS/baseCSS、svgString序列化 SVG 源、dataURL、meta冻结的渲染几何信息。注意clone、nodeMap、styleCache、svgString在afterRender运行后即被释放避免每个结果对象常驻整棵克隆树。导出钩子收到的是带export块的每导出拷贝其中export.url默认是 SVG data URL原生 html-in-canvas 捕获成功后则为 PNGbeforeExport/afterExport的第二个参数形如beforeExport(ctx, { format, options }) // format: png | blob | download | 你的自定义 key afterExport (ctx, { format, options, result }) // result: 导出器实际返回的结果钩子规则钩子可以是同步或异步的SnapDOM 会await所有钩子见 runHook返回非undefined时会替换累积的 payload。捕获选项应在beforeSnap中设置如ctx.scale、ctx.width、ctx.clip、ctx.outerTransforms。例外是plugins、needs、invalidate、cache这四个在钩子运行前就已解析的选项——在钩子中修改它们无效。改导出选项用beforeExport观察结果用afterExport提供全新导出器用defineExports。beforeClone中对真实 DOM 的修改必须撤销典型做法是在afterClone中还原不能影响线上页面。afterExport与 v2 相比不再把返回值链式传递给下一个钩子自定义输出应当放到defineExports中。深入理解 needs插件声明运行到哪一步模板的注释与规范都强调needs声明。捕获流水线只有两个阶段element ──▶ [clone] ──▶ [render] ──▶ exports{ name: my-plugin, needs: clone }跑到afterClone为止不渲染像素{ name: my-plugin }默认render完整跑完克隆与渲染。从 stages.js 的resolveStage实现可见两条硬性规则捕获会跑到所有插件声明的最大深度默认render从未产生的产物绝不会被伪造——如果捕获停在clone调用url、toPng()、toCanvas()等图像导出会抛出异常并指名是哪个插件降低了阶段且绝不会按需重新捕获那将是另一个时间点的图像。result.needs会报告实际运行的阶段。两个重要限制只有 per-capture 插件可以降低阶段。全局插件若声明needs: clone会在注册时被拒绝registerPlugins 直接throw因为那会让应用里每一次捕获都停在像素之前。旧版本曾存在的dom阶段停在克隆之前已移除合法值只有clone与render。插件可用assertNeeds(my-plugin, options.needs, [clone, render])从zumer/snapdom/plugins导出在构造时校验调用方传入的阶段值。同时支持两个阶段的插件应接受一个needs选项export function myPlugin(options {}) { return { name: my-plugin, needs: options.needs ?? render, /* hooks */ } }官方范例contextExport({ needs: clone })仅要结构化文本跳过渲染与agentMap({ image: false, needs: clone })只要元素坐标地图不要图像。注意 context-export 源码 在beforeClone阶段就把整棵语义树冻结进ctx.__contextSnapshottoContext()只负责格式化这份冻结快照——这正是克隆即冻结原则的体现延迟读取会得到另一个时刻的页面。引擎快路径与 pure 声明v3 新增SnapDOM 会复用未变化的捕获memoization或重建变化的子树差分重捕获。默认情况下包含resolveNode、beforeSnap、beforeClone、afterClone、beforeRender或afterRender的插件会禁用这些快路径确保它们的钩子不会被跳过。判断逻辑在 hasImpureRenderPlugins捕获插件列表中任一未声明pure: true且含上述渲染钩子的插件都会让 auto-burst 与 diff 路径采取保守策略。如果你的捕获钩子是确定性且幂等的相同输入必然产生相同输出不依赖时间戳、计数器等外部状态可以声明{ name: my-plugin, pure: true, afterClone(ctx) { /* … */ } }pure: true会让插件重新加入未变化重复捕获的 memo 快路径。注意事项捕获钩子若只限beforeRender/afterRender还可使用差分重捕获但参与克隆构建的钩子beforeSnap、beforeClone、resolveNode、afterClone在内容变化后仍强制完整重捕获拼接路径无法跳过它们。停在needs: clone的捕获永远不做 memo没有渲染产物可供复用。声明 pure 却读取变化的外部状态会提供过期结果——这是 v3 中需要特别小心的正确性边界。用 defineExports 添加自定义导出模板的defineExports注释展示了核心模式返回一个自定义导出方法对象调用后成为result.toMyFormat()。规范中 PDF 导出的完整示例export function pdfExport(options {}) { return { name: pdf-export, defineExports(ctx) { return { pdf: async (ctx, opts) { const captureUrl ctx.export.url; // SVG by default; PNG after successful native html-in-canvas // convert to PDF... return pdfBlob; } }; } }; } // 注册后 const result await snapdom(element, { plugins: [pdfExport()] }); const blob await result.toPdf({ width: 800 });实现层面核心用runAll收集所有插件defineExports的返回值runAll非undefined结果按插件顺序收集因此每个插件返回自己的一份导出方法映射。导出 key 冲突时的优先级是per-capture 插件 全局插件 核心内置——也就是说你的局部插件可以覆盖toPng、toJpg、toCanvas等内置导出器。构建自定义导出时可使用ctx.export.url或在defineExports内访问ctx.exports包含不含钩子的核心导出器png、canvas、blob等便于组合而不重复进入导出流水线。官方包中 ascii-export 是纯导出类插件的范例toAscii()context-export 则演示了beforeClone 冻结快照 defineExports 格式化输出的组合模式二者都值得对照阅读。从模板到发布包配置与目录提交package.json 关键字段模板的 package.json 已经给出了完整骨架发布时替换名称与描述即可{ name: snapdom-plugin-yourname, version: 1.0.0, description: A SnapDOM plugin that does X, type: module, main: index.js, exports: { .: ./index.js }, files: [index.js, README.md], keywords: [snapdom, snapdom-plugin, dom-capture], peerDependencies: { zumer/snapdom: ^3 }, license: MIT }要点核心必须放在peerDependencies而非 dependenciesexports限定模块入口files只打包发布需要的文件。官方 packages/plugins/package.json 展示了多入口插件的exports写法每个插件一个子路径便于 tree-shaking 单独导入。命名约定npm 包名snapdom-plugin-[name]插件name字段小写 kebab-case如watermark、redact、pdf-export主导出camelCase 工厂函数如myPlugin发布流程npm publish发布后可通过提交 PR在 docs/community-plugins.md 的表格中追加一行格式为| name | description | category | npm | github | author |将插件列入插件目录也可以开 Issue 提供插件名、npm 链接、分类与一句话描述。更详细的提交流程见 CONTRIBUTING_PLUGINS.md。实战模板从 example 选项到可用插件把模板改造成一个可运行的插件只需三步替换 name、替换选项、填充钩子。以规范中的水印插件为例它演示了 afterClone 变换克隆的完整写法export function watermark(options {}) { const { text © SnapDOM, fontSize 14, color rgba(0,0,0,0.15), position bottom-right, rotate -30, } options; return { name: watermark, afterClone(ctx) { const overlay document.createElement(div); const posStyles { top-left: top:8px;left:8px, top-right: top:8px;right:8px, bottom-left: bottom:8px;left:8px, bottom-right: bottom:8px;right:8px, center: top:50%;left:50%;transform:translate(-50%,-50%) }; overlay.style.cssText position:absolute; ${posStyles[position] || posStyles[bottom-right]}; font-size:${fontSize}px; color:${color}; pointer-events:none; z-index:999999; white-space:nowrap; ${position ! center rotate ? transform:rotate(${rotate}deg) : } ; overlay.textContent text; ctx.clone.style.position relative; ctx.clone.appendChild(overlay); } }; }水印插件的要点与模板注释一致afterClone拿到的是ctx.clone克隆并内联样式后的 DOM 树对它做任何修改都不会影响线上页面——这正是在克隆上工作这一插件核心安全模型。同类参考还包括官方 filter对克隆施加 CSS 滤镜、replace-text替换克隆中的文本、redact-inputs掩码敏感输入并支持整块排除含beforeRender额外处理等。插件开发最佳实践综合模板、规范与仓库测试如 plugins.stages.test.js 中对defineExports返回自定义导出方法的验证插件开发应遵循只在需要的钩子内做插件工作。还原 DOMbeforeClone中做了修改就在afterClone中撤销。坚持工厂模式始终接受 options、始终提供默认值。name 唯一先查插件目录避免冲突。尽量从错误中恢复否则抛出有用的错误而不是返回不完整的输出。文档化你的选项类型、默认值、描述对应模板 README 的参数表格式。依赖最小化理想情况下为零依赖。用scale: 2测试高 DPI 最容易暴露像素计算问题。仅当捕获钩子确定且幂等时标记pure: true避免返回陈旧结果。将核心声明为peerDependencies并在 README 中提供安装、用法、选项与示例。插件按能力可分为五类Capture修改 DOM 捕获方式如自定义组件解析器、Transform改变克隆输出如水印、滤镜、脱敏、Export新增输出格式如 PDF、ASCII、Integration对接外部服务如上传 S3、Utility调试覆盖层、性能计时器等开发工具。以本模板为起点你可以轻松落地其中任何一类。结语packages/plugin-template为 SnapDOM v3 插件开发提供了零依赖、可独立工作的起点工厂函数 生命周期钩子 defineExports是插件的三大支柱needs与pure则是 v3 中决定捕获深度与快路径参与的关键声明。模板本身是骨架真正的能力来自 PLUGIN_SPEC.md 中定义的完整钩子契约、src/core/plugins.js 中的插件调度实现以及 packages/plugins 中 13 个官方插件可对照研读的实战范例。照着模板改一个afterClone变换或defineExports导出再配合同步的peerDependencies发布你的第一个 SnapDOM 社区插件即可上线。【免费下载链接】snapdomHigh-performance engine for capturing, modifying, and converting DOM elements into any format.项目地址: https://gitcode.com/GitHub_Trending/sn/snapdom创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考