五年后 json-tool 与 3000 名活跃用户

发布: (2026年2月4日 GMT+8 03:54)
11 min read
原文: Dev.to

Source: Dev.to

请提供您希望翻译的正文内容,我将为您翻译成简体中文并保留原有的格式、Markdown 语法以及技术术语。谢谢!

3k 用户?

每周活跃用户数是从 Snapcraft 渠道分发中获取的。“3k” 就来源于此,不过网页版本也有贡献。

问题

在 2021 年,我的工作流经常需要进行 JSON 操作:调试 API 响应、集成第三方服务以及检查数据结构。和许多开发者一样,我习惯于在 Google 上搜索 “JSON prettier”,并使用搜索结果中出现的第一个网站。像 JSON FormatterJSON Pretty Print 之类的工具还能用,但它们伴随着以下问题:

  • 到处都是广告
  • 对数据处理缺乏透明度
  • 无法保证我的 JSON 字符串没有被记录到某处

对于个人项目这只是稍微有点烦人。对于涉及生产数据或敏感信息的工作来说,这种做法显得不负责任。ThoughtWorks Tech Radar 第 27 期随后强调了这一点,警告开发者注意那些不符合数据司法管辖要求的格式化工具。

我想要一种不同的工具:不收集任何数据不显示广告,并且通过开源代码保持 透明。不需要复杂的功能集——只要一个可靠的 JSON 格式化工具,尊重用户隐私。这就是 json‑tool 诞生的原因。

First Implementation

最初的实现相当直接:

  • Electron 应用使用 React 构建
  • 发布到 snapcraft.io
  • 使用 CypressReact Testing Library 进行 TDD(我曾写过一篇关于此实践的文章,以确保核心功能从一开始就可靠)

第一个版本在典型使用场景下运行良好。开发者可以:

  1. 从日志中粘贴 JSON
  2. 验证它
  3. 使用自定义间距进行格式化
  4. 将结果复制回剪贴板

隐私优先的做法意味着处理敏感数据的企业开发者可以在无需企业防火墙的情况下使用它,最重要的是,一旦通过 Snapcraft 安装后,它即可离线运行。

冻结

随后现实敲门了:用户开始粘贴超过 1 MB 的 JSON 字符串。

应用程序彻底卡死——UI 完全无响应,按钮失效,整体体验崩溃。

通过使用 console.time() / console.timeEnd() 进行基准测试,我将瓶颈追溯到在 主线程 上进行的 JSON 解析和格式化。JavaScript 的单线程特性,加上事件循环的执行模型,意味着任何阻塞操作都会冻结整个 UI。

引入 Web Workers

根据 Ido Green 的著作《Web Workers: Multithreaded Programs in JavaScript》,如果你的 Web 应用需要完成超过 150 ms 的任务,就应该考虑使用 Web Workers。Green 特别列出了 “对大字符串进行编码/解码” 作为主要使用场景——这正是 json‑tool 所在的工作。

架构转变

之前(同步)

const onJsonChange = useCallback(async (value: string) => {
  setError('');
  if (!spacing) return;

  try {
    if (value) {
      JSON.parse(value);
    }
  } catch (e: any) {
    setError('invalid json');
  }

  let format = new Formatter(value, 2);
  const parseSpacing = parseInt(spacing);
  if (!isNaN(parseSpacing)) {
    format = new Formatter(value, parseSpacing);
  }

  const result = await format.format();
  setOriginalResult(value);
  setResult(result);
}, [spacing]);

之后(事件驱动,卸载到 worker)

// UI thread
const onChange = (eventValue: string, eventSpacing: string) => {
  if (worker.current) {
    worker.current.postMessage({
      jsonAsString: eventValue,
      spacing: eventSpacing,
    });
  }
  setOriginalResult(eventValue);
  setInProgress(true);
};
// worker.js
importScripts('https://unpkg.com/format-to-json@2.1.2/fmt2json.min.js');

if (typeof importScripts === 'function') {
  addEventListener('message', async (event) => {
    const { jsonAsString: value, spacing } = event.data;

    if (value) {
      const format = await fmt2json(value, {
        expand: true,
        escape: false,
        indent: parseInt(spacing),
      });

      try {
        JSON.parse(value);
      } catch (e) {
        postMessage({
          error: true,
          originalJson: value,
          result: format.result,
        });
        return;
      }

      postMessage({
        error: false,
        originalJson: value,
        result: format.result,
      });
    }
  });
}

这是否提升了原始计算时间? 没有——实际处理时间保持不变。但用户体验被彻底改变。 UI 保持响应,用户可以在 worker 执行繁重任务时继续交互。

要点

  • Web Workers 对于卸载 CPU 密集型任务(例如解析/格式化大型 JSON 字符串)非常宝贵。
  • 即使算法运行时间没有改变,将工作移出主线程也能防止 UI 卡顿。
  • 以隐私优先、离线优先的方式可以吸引大量用户,尤其是在企业环境中。
  • 随着使用模式的演变,持续的性能监控(基准测试、分析)是必不可少的。

参考文献

  • Green, Ido. Web Workers: Multithreaded Programs in JavaScript.
  • ThoughtWorks Tech Radar, Volume 27.

事件驱动系统与专用工作线程

const onChange = (eventValue: string, eventSpacing: string) => {
  if (worker.current) {
    worker.current.postMessage({
      jsonAsString: eventValue,
      spacing: eventSpacing,
    });
  }
  setOriginalResult(eventValue);
  setInProgress(true);
};

工作线程处理解析与格式化

importScripts('https://unpkg.com/format-to-json@2.1.2/fmt2json.min.js');

if (typeof importScripts === 'function') {
  addEventListener('message', async (event) => {
    const { jsonAsString: value, spacing } = event.data;

    if (!value) return;

    const format = await fmt2json(value, {
      expand: true,
      escape: false,
      indent: parseInt(spacing, 10),
    });

    try {
      JSON.parse(value);
    } catch (e) {
      postMessage({
        error: true,
        originalJson: value,
        result: format.result,
      });
      return;
    }

    postMessage({
      error: false,
      originalJson: value,
      result: format.result,
    });
  });
}

这提升了性能吗?
没有。实际的计算时间保持不变,但用户体验得到了彻底的改变。

Web Workers 来拯救

探索架构变更和性能影响。

保持 TDD 纪律

  • 我从 outside‑in TDD 开始,并且拒绝妥协。
  • 引入 worker 增加了测试复杂度:Worker API 在浏览器中存在,但在 Jest 的 jsdom 环境中 未定义,导致测试套件崩溃。

解决方案:jsdom-worker

  1. 安装

    npm i jsdom-worker
  2. setupTests.ts 中导入

    import 'jsdom-worker';

该库为 jsdom 模拟 Worker API。它不创建真实线程,并且有一些限制(不支持共享 worker,必须使用 Blob 模式)。

示例测试(行为驱动)

it('should format json from uploaded file', async () => {
  const file = new File(['{"a":"b"}'], 'hello.json', {
    type: 'application/json',
  });

  const { getByTestId } = render();

  await act(async () => {
    await userEvent.upload(getByTestId('upload-json'), file);
  });

  await waitFor(() => {
    expect(getByTestId('raw-result')).toHaveValue(`{
  "a": "b"
}`);
  });
});
  • 测试描述的是 应该发生什么,而不是 如何实现
  • 它验证上传的 JSON 能够正确格式化,无论是否使用了 worker。
  • 教训:Web worker 属于实现细节;测试应关注面向用户的行为。

实时编码环节

  • 将同步代码重构为使用 worker,同时保持测试覆盖率。
  • 在 coding‑dojo 场景中演示 TDD。

Worker 实现的权衡

  • 优点: 消除了 UI 卡顿。
  • 缺点: 引入了事件驱动架构,需要理解:
    • Worker 生命周期管理
    • 消息传递
    • 主线程之外的执行

缓解措施: 将 worker 逻辑保持隔离;其余应用保持简洁(React 组件、直接的状态、最小抽象)。文档中包含必要的细节。

开源维护的现实

  • 依赖需要定期更新。
  • 必须修补安全漏洞。
  • 浏览器 API 在演进;测试库也在变化。

可持续性胜于快速开发

  • 技术栈:React、TypeScript、Tailwind、CodeMirror —— 全部是稳定、维护良好且拥有庞大社区的项目。
  • 避免使用可能被抛弃的前沿依赖。

更新节奏:只有在我积极使用该工具并发现需求时,才会进行批量更新。该项目由社区驱动,而非提供 SLA 保证的商业服务。

关于开源的经验教训

  1. 透明性建立信任 – 用户可以审计代码,验证没有跟踪、数据外泄或广告。
  2. 专注的工具更好地解决问题json‑tool 专注于一件事:在保证隐私的前提下格式化 JSON。
  3. 测试驱动开发收益丰厚 – 最初的 TDD 投入使得多年后对工作线程的安全重构成为可能。

未来路线图(持续改进)

  • 性能:优化对极大 JSON 文件(> 100 MB)的处理。
  • 可访问性:添加屏幕阅读器支持并改进键盘导航。
  • 编辑器同步:保持两个面板同步滚动。
  • 依赖项:升级到最新的 CodeMirror 版本,保持 React 和 TypeScript 更新。

没有计划盈利、添加分析或超出核心目的的扩展。欢迎通过 GitHub Sponsors 进行赞助。

结束思考

这段旅程中最有价值的部分并不是达到 3,000 位用户;而是构建了一个 解决真实问题 的工具,其核心是简洁、可靠和隐私。

在不妥协原则的前提下。没有追踪。没有广告。没有数据收集。
只有一个兑现承诺、尊重用户的工具。

json‑tool 的源代码仍然托管在其仓库中,工具本身可以从 Snapcraft 商店下载。已经五年了——愿未来的岁月里继续构建尊重隐私、服务开发者的软件。

Back to Blog

相关文章

阅读更多 »

众筹平台

GitHub Copilot CLI 挑战提交 这是针对 GitHub Copilot CLI 挑战的提交:https://dev.to/challenges/github-copilot。Repository bash git...