npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@json-visual-diff/dom-renderer

v0.0.1

Published

DOM 渲染器,将差异结果渲染为可交互的浏览器 DOM 元素,支持主题切换、无障碍访问和键盘导航

Readme

@json-visual-diff/dom-renderer

用于在浏览器中可视化 JSON 差异的 DOM 渲染器。该包提供了一个可插拔的渲染器实现,将标准化的 diff 结果转换为交互式 HTML 元素。

English Documentation

特性

  • 🎨 视觉区分: 为添加、删除、修改和未改变的值提供清晰的颜色编码显示
  • 🌓 主题支持: 内置浅色和深色主题,支持自定义颜色配置
  • 🔄 交互式 UI: 可折叠/展开的嵌套结构,便于导航
  • 可访问性: 语义化 HTML 和 ARIA 属性,支持屏幕阅读器
  • ⌨️ 键盘导航: 完整的键盘支持交互元素
  • 🎯 可自定义: 灵活配置颜色、缩进和显示选项
  • 💪 TypeScript: 包含完整的类型定义

安装

npm install @json-visual-diff/dom-renderer @json-visual-diff/core
# 或
pnpm add @json-visual-diff/dom-renderer @json-visual-diff/core
# 或
yarn add @json-visual-diff/dom-renderer @json-visual-diff/core

快速开始

import { diff } from '@json-visual-diff/core';
import { DOMRenderer } from '@json-visual-diff/dom-renderer';

const oldObj = {
  name: 'John',
  age: 25,
  hobbies: ['reading', 'gaming']
};

const newObj = {
  name: 'John',
  age: 26,
  hobbies: ['reading', 'coding', 'gaming'],
  email: '[email protected]'
};

// 计算差异
const result = diff(oldObj, newObj);

// 创建渲染器并渲染到 DOM
const renderer = new DOMRenderer();
const element = renderer.render(result);

// 添加到页面
document.body.appendChild(element);

API 文档

DOMRenderer

实现了 @json-visual-diff/coreRenderer 接口的主渲染器类。

构造函数

new DOMRenderer(config?: RendererConfig)

参数:

  • config?: RendererConfig - 可选的渲染器配置

示例:

import { DOMRenderer } from '@json-visual-diff/dom-renderer';

const renderer = new DOMRenderer({
  theme: 'dark',
  indent: 4,
  expandDepth: 2,
  showUnchanged: true,
  colors: {
    added: '#00ff00',
    deleted: '#ff0000'
  }
});

方法

render(diffResult, config?)

将完整的 diff 结果渲染为 HTML 元素。

参数:

  • diffResult: DiffResult - 来自 @json-visual-diff/core 的 diff 结果
  • config?: RendererConfig - 可选配置,覆盖构造函数配置

返回值: HTMLElement - 渲染的 DOM 元素

示例:

const element = renderer.render(result, {
  theme: 'light',
  showUnchanged: false
});
renderNode(node, config?)

将单个 diff 节点渲染为 HTML 元素。

参数:

  • node: DiffNode - 单个 diff 节点
  • config?: RendererConfig - 可选配置

返回值: HTMLElement - 渲染的 DOM 元素

示例:

const nodeElement = renderer.renderNode(result.root);
renderAdded(node, config?)

渲染被添加的节点。

参数:

  • node: DiffNode - 添加的节点
  • config?: RendererConfig - 可选配置

返回值: HTMLElement

renderDeleted(node, config?)

渲染被删除的节点。

参数:

  • node: DiffNode - 删除的节点
  • config?: RendererConfig - 可选配置

返回值: HTMLElement

renderModified(node, config?)

渲染被修改的节点。

参数:

  • node: DiffNode - 修改的节点
  • config?: RendererConfig - 可选配置

返回值: HTMLElement

renderUnchanged(node, config?)

渲染未改变的节点。

参数:

  • node: DiffNode - 未改变的节点
  • config?: RendererConfig - 可选配置

返回值: HTMLElement

toggleExpand(path)

切换给定路径节点的展开/折叠状态。

参数:

  • path: string[] - 节点的路径

示例:

renderer.toggleExpand(['user', 'address']);
isExpanded(path)

检查给定路径的节点是否已展开。

参数:

  • path: string[] - 节点的路径

返回值: boolean

配置

RendererConfig

interface RendererConfig {
  theme?: 'light' | 'dark' | 'custom';
  colors?: {
    added?: string;
    deleted?: string;
    modified?: string;
    unchanged?: string;
  };
  indent?: number;          // 缩进空格数(默认: 2)
  expandDepth?: number;     // 默认展开深度(默认: 3)
  showUnchanged?: boolean;  // 显示未改变的节点(默认: true)
}

主题配置

浅色主题(默认)

const renderer = new DOMRenderer({ theme: 'light' });

默认颜色:

  • 添加: #22863a (绿色)
  • 删除: #cb2431 (红色)
  • 修改: #e36209 (橙色)
  • 未改变: #6a737d (灰色)

深色主题

const renderer = new DOMRenderer({ theme: 'dark' });

默认颜色:

  • 添加: #28a745 (绿色)
  • 删除: #d73a49 (红色)
  • 修改: #f97583 (粉色)
  • 未改变: #959da5 (灰色)

自定义颜色

const renderer = new DOMRenderer({
  theme: 'custom',
  colors: {
    added: '#00ff00',
    deleted: '#ff0000',
    modified: '#ffaa00',
    unchanged: '#888888'
  }
});

高级用法

控制展开/折叠

import { diff } from '@json-visual-diff/core';
import { DOMRenderer } from '@json-visual-diff/dom-renderer';

const result = diff(oldObj, newObj);
const renderer = new DOMRenderer({
  expandDepth: 2  // 默认只展开前 2 层
});

const element = renderer.render(result);
document.body.appendChild(element);

// 程序化切换展开状态
renderer.toggleExpand(['user', 'address']);

// 检查是否已展开
if (renderer.isExpanded(['user', 'address'])) {
  console.log('地址已展开');
}

隐藏未改变的值

const renderer = new DOMRenderer({
  showUnchanged: false  // 隐藏未改变的值
});

const element = renderer.render(result);

自定义缩进

const renderer = new DOMRenderer({
  indent: 4  // 使用 4 个空格缩进
});

const element = renderer.render(result);

动态主题切换

const renderer = new DOMRenderer({ theme: 'light' });

// 使用浅色主题渲染
let element = renderer.render(result);
document.body.appendChild(element);

// 切换到深色主题
element = renderer.render(result, { theme: 'dark' });
document.body.replaceChild(element, document.body.firstChild);

渲染单个节点

import { diff } from '@json-visual-diff/core';
import { DOMRenderer } from '@json-visual-diff/dom-renderer';

const result = diff(oldObj, newObj);
const renderer = new DOMRenderer();

// 只渲染特定节点
if (result.root.children) {
  for (const child of result.root.children) {
    if (child.type === 'modified') {
      const nodeElement = renderer.renderNode(child);
      document.getElementById('modified-container')?.appendChild(nodeElement);
    }
  }
}

HTML 结构

渲染器生成以下 HTML 结构:

<div class="json-diff-container" role="region" aria-label="JSON difference visualization">
  <!-- 统计信息 -->
  <div class="json-diff-stats" role="status" aria-live="polite">
    <span class="stat-added" aria-label="1 items added">+1</span>
    <span class="stat-deleted" aria-label="1 items deleted">-1</span>
    <span class="stat-modified" aria-label="2 items modified">~2</span>
  </div>

  <!-- 内容 -->
  <div class="json-diff-content" role="tree">
    <div class="diff-node diff-modified" role="treeitem" aria-label="Modified: root">
      <div class="diff-line">
        <button class="toggle-button" aria-expanded="true">▼</button>
        <span class="key">name:</span>
        <span class="value">"John"</span>
      </div>
      <div class="children-container" role="group">
        <!-- 子节点 -->
      </div>
    </div>
  </div>
</div>

CSS 类

渲染器应用以下 CSS 类,您可以自定义样式:

  • .json-diff-container - 主容器
  • .json-diff-stats - 统计信息区域
  • .stat-added - 添加计数
  • .stat-deleted - 删除计数
  • .stat-modified - 修改计数
  • .json-diff-content - 内容区域
  • .diff-node - 单个 diff 节点
  • .diff-added - 添加的节点
  • .diff-deleted - 删除的节点
  • .diff-modified - 修改的节点
  • .diff-unchanged - 未改变的节点
  • .diff-line - diff 中的单行
  • .toggle-button - 展开/折叠按钮
  • .key - 属性键
  • .value - 属性值
  • .old-value - 旧值(用于修改)
  • .new-value - 新值(用于修改)
  • .arrow - 旧值和新值之间的箭头
  • .children-container - 子节点容器

自定义样式示例

.json-diff-container {
  font-family: 'Monaco', 'Courier New', monospace;
  font-size: 14px;
  padding: 16px;
  background: #f6f8fa;
  border-radius: 8px;
}

.json-diff-stats {
  margin-bottom: 12px;
  padding: 8px;
  background: white;
  border-radius: 4px;
}

.stat-added,
.stat-deleted,
.stat-modified {
  margin-right: 12px;
  padding: 4px 8px;
  border-radius: 3px;
  font-weight: bold;
}

.toggle-button:hover {
  background: rgba(0, 0, 0, 0.05);
  border-radius: 3px;
}

.diff-line {
  padding: 2px 0;
  line-height: 1.5;
}

.key {
  font-weight: 600;
}

可访问性特性

渲染器包含全面的可访问性支持:

  • 语义化 HTML: 使用适当的 HTML 元素(<button><div> 等)
  • ARIA 属性: 包含 rolearia-labelaria-expandedaria-live
  • 键盘导航: 完整的键盘支持交互元素
    • EnterSpace 切换展开/折叠
    • Tab 在交互元素之间导航
  • 屏幕阅读器支持: 所有交互元素都有描述性标签

浏览器支持

  • Chrome/Edge: 最新 2 个版本
  • Firefox: 最新 2 个版本
  • Safari: 最新 2 个版本

性能考虑

  • 大型 Diff: 对于非常大的 diff 结果,考虑使用 showUnchanged: false 来减少 DOM 节点
  • 深层嵌套: 使用 expandDepth 限制初始渲染深度
  • 内存: 渲染器在内存中维护展开/折叠状态;对于非常大的 diff,考虑定期重新创建渲染器

集成示例

React 集成

import { useEffect, useRef } from 'react';
import { diff } from '@json-visual-diff/core';
import { DOMRenderer } from '@json-visual-diff/dom-renderer';

function DiffViewer({ oldValue, newValue }) {
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (containerRef.current) {
      const result = diff(oldValue, newValue);
      const renderer = new DOMRenderer({ theme: 'light' });
      const element = renderer.render(result);

      containerRef.current.innerHTML = '';
      containerRef.current.appendChild(element);
    }
  }, [oldValue, newValue]);

  return <div ref={containerRef} />;
}

Vue 集成

<template>
  <div ref="container"></div>
</template>

<script setup>
import { ref, watch, onMounted } from 'vue';
import { diff } from '@json-visual-diff/core';
import { DOMRenderer } from '@json-visual-diff/dom-renderer';

const props = defineProps(['oldValue', 'newValue']);
const container = ref(null);

const renderDiff = () => {
  if (container.value) {
    const result = diff(props.oldValue, props.newValue);
    const renderer = new DOMRenderer({ theme: 'dark' });
    const element = renderer.render(result);

    container.value.innerHTML = '';
    container.value.appendChild(element);
  }
};

watch(() => [props.oldValue, props.newValue], renderDiff);
onMounted(renderDiff);
</script>

许可证

MIT

相关包

贡献

欢迎贡献!请随时提交 Pull Request。

支持

如果您遇到任何问题或有疑问,请在我们的 GitHub 仓库 上提交 issue。