浏览 AI 使用教程
操作教程

Claude Code重构教程:拆分HTML渲染与写入,并锁住JSON-LD输出

给出重构前后完整Node模块和回归测试,用字节对比及JSON-LD检查固定输出,再把渲染从文件写入中提取;不把测试通过扩大成全站验证。

Claude Code终端协作与项目工作流主视觉
本文目录 · 跳到当前步骤

这次重构目标很小:调用方能够直接拿到 HTML 字符串做检查,不必先写磁盘。输出内容、编码和已有写文件入口保持一致。它不包含改页面样式、改 SEO 字段或加依赖。

重构前:渲染与写入放在一起

在新建的 refactor-demo 目录使用 Node.js 22 或之后版本,保存 before.mjs:

javascript
import { writeFileSync } from 'node:fs';
const escape = text => String(text).replace(/[&<>"']/g,
  ch => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[ch]));
export function buildArticle(article, destination) {
  const schema = JSON.stringify({ '@context': 'https://schema.org',
    '@type': 'Article', headline: article.title }).replace(/</g, '\\u003c');
  const html = '<!doctype html><html lang="zh-CN"><head><meta charset="utf-8">'
    + '<title>' + escape(article.title) + '</title></head><body><h1>'
    + escape(article.title) + '</h1><script type="application/ld+json">'
    + schema + '</script></body></html>';
  writeFileSync(destination, html, 'utf8');
}

这是教学用微型渲染器,只有标题和 JSON-LD。真实项目已有的页脚、导航、canonical、错误处理等都应另外纳入基线,不能用本例替换整个生产渲染器。

把不变条件写进任务

text
请先读before.mjs。只把纯HTML生成提取为renderArticle(article),
新模块保存为after.mjs,保留buildArticle(article,destination)的调用方式。
before.mjs保留为本次对照,不修改。输出字节、HTML转义、JSON-LD应一致。
不新增依赖,不调整HTML格式,不修改其他文件。
完成后运行本文的refactor.test.mjs,报告实际结果。

参考的 after.mjs:

javascript
import { writeFileSync } from 'node:fs';
const escape = text => String(text).replace(/[&<>"']/g,
  ch => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[ch]));
export function renderArticle(article) {
  const schema = JSON.stringify({ '@context': 'https://schema.org',
    '@type': 'Article', headline: article.title }).replace(/</g, '\\u003c');
  return '<!doctype html><html lang="zh-CN"><head><meta charset="utf-8">'
    + '<title>' + escape(article.title) + '</title></head><body><h1>'
    + escape(article.title) + '</h1><script type="application/ld+json">'
    + schema + '</script></body></html>';
}
export function buildArticle(article, destination) {
  writeFileSync(destination, renderArticle(article), 'utf8');
}

用四种输入检查,而不是只看退出码

保存 refactor.test.mjs,与两个模块放同一目录:

javascript
import test from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync, readFileSync, existsSync, unlinkSync, rmdirSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { buildArticle as before } from './before.mjs';
import { buildArticle as after, renderArticle } from './after.mjs';
const titles = ['普通标题', 'A & B', '带"引号"的标题', '</script><p>边界</p>'];
for (const title of titles) {
  test('保留输出:' + title, () => {
    const dir = mkdtempSync(join(tmpdir(), 'render-demo-'));
    try {
      const oldFile = join(dir, 'old.html');
      const newFile = join(dir, 'new.html');
      before({ title }, oldFile);
      after({ title }, newFile);
      const oldHtml = readFileSync(oldFile, 'utf8');
      const newHtml = readFileSync(newFile, 'utf8');
      assert.equal(newHtml, oldHtml);
      assert.equal(renderArticle({ title }), oldHtml);
      const json = newHtml.match(/<script type="application\/ld\+json">([\s\S]*?)<\/script>/);
      assert.ok(json, '必须保留JSON-LD');
      assert.equal(JSON.parse(json[1]).headline, title);
      if (title === 'A & B') assert.ok(newHtml.includes('<h1>A &amp; B</h1>'));
      if (title.startsWith('</script>')) assert.ok(newHtml.includes('<h1>&lt;/script&gt;'));
    } finally {
      for (const name of ['old.html', 'new.html']) {
        const file = join(dir, name);
        if (existsSync(file)) unlinkSync(file);
      }
      rmdirSync(dir);
    }
  });
}

运行 node --test refactor.test.mjs。预期四项通过;临时 HTML 只在该测试创建的系统临时子目录写入,结束后清理该目录。

如果删掉 after 模块中的 JSON-LD 拼接,即使页面仍显示标题,字节对比和结构化数据检查也应该失败。反过来,若测试仍绿,先检查实际运行的测试路径和导入文件,不要宣布重构成功。

怎样判断这次重构有价值

现在可以直接调用 renderArticle({title}) 检查输出,不需要构造磁盘路径;写文件入口仍兼容。这是可观察的结构改善。四个输入通过只证明本工件覆盖的行为,没有检查浏览器布局、网站其他模板、生产发布或搜索引擎展示。

2026-10-11,在 Windows、Node.js 24.15.0 下实际运行了上述四项测试,全部通过;再用故意移除JSON-LD内容的独立故障副本运行同组测试,四项均失败,退出码为1。没有修改正常参考模块。本文未调用 Claude Code 执行重构,读者获得模型生成版本后仍需重新比较。

资料来源

下一步

继续阅读与你当前问题相关的教程: