用纯前端实现一个代码压缩美化工具,我踩了这三个坑

最近在做一个内部工具集时,需要给团队提供一个在线代码格式化功能。需求很简单:输入 HTML/JS/CSS,一键压缩或美化,结果直接复制或下载。

本来想直接找个现成的 npm 包塞进项目里,但考虑到这只是个轻量工具,不想为了它引入整个构建链路,而且团队成员经常需要临时处理一段代码,打开浏览器就能用才是最优解。于是决定写一个单文件 HTML 工具,所有逻辑都跑在浏览器本地。

第一版:直接用 js-beautify,结果被 CDN 坑了

最开始的思路很直接------用 js-beautify 这个老牌库。它同时支持 HTML、JS、CSS 三种语言的格式化,API 也简单:

javascript 复制代码
const result = html_beautify(code, { indent_size: 2 });

但问题出在 CDN 上。我一开始用的是 jsdelivr,结果国内网络环境下经常加载失败,页面直接白屏。后来换成 staticfile 才稳定下来。

这里有个细节值得注意:CDN 加载失败不能静默处理。我在 script 标签上加了 onerror 标记,这样即使库加载失败,用户也能看到明确的错误提示,而不是不明不白地发现按钮没反应。

html 复制代码
<script src="https://cdn.staticfile.org/js-beautify/1.15.1/beautifier.min.js" onerror="window.__beautifierFailed=true"></script>

第二版:压缩功能比想象中复杂

美化功能交给 js-beautify 就够了,但压缩(minify)才是真正的坑。

HTML 压缩:不能无脑删空白

我最初的实现是直接 replace(/\s+/g, ' '),把多余空白全折叠掉。结果发现 <pre> 标签里的代码被压成了一行,<script> 里的字符串也被破坏了。

解决方案是先把需要保护的块提取出来,压缩完再放回去:

javascript 复制代码
const blocks = [];
const protected = code.replace(/<(script|style|pre)\b[\s\S]*?<\/\1>/gi, m => {
  blocks.push(m);
  return '\x00BLOCK' + (blocks.length - 1) + '\x00';
});
// 压缩处理后再把 blocks 还原

JS 压缩:ASI 的坑

JS 压缩是最容易出问题的。一开始我想的是把换行也去掉,全部压成一行。但很快发现这样会破坏 ASI(自动分号插入)规则。

比如这段代码:

javascript 复制代码
return
{ a: 1 }

如果压成一行变成 return { a: 1 },语义就完全变了------前者返回 undefined,后者返回对象。

还有 a\n--b 这种写法,压成一行会变成 a--b,变成了自减运算。

所以 JS 压缩我做了个保守方案:保留换行,只压缩行内的多余空格和注释:

javascript 复制代码
result = noComments
  .split('\n')
  .map(l => l.trim().replace(/[ \t]{2,}/g, ' '))
  .filter(l => l !== '')
  .join('\n');

这样虽然压缩率不如真正的 minifier,但至少不会破坏代码语义。

CSS 压缩:相对简单但也有细节

CSS 压缩相对简单,核心就是删注释、折叠空白、压缩运算符周围的空格:

javascript 复制代码
result = code
  .replace(/\/\*[\s\S]*?\*\//g, '')
  .replace(/\s+/g, ' ')
  .replace(/\s*([{}:;,])\s*/g, '$1')
  .trim();

注意 replace(/\s+/g, ' ') 要放在 replace(/\s*([{}:;,])\s*/g, '$1') 之前,否则第二部操作会把空格删掉后,第一步又加回来。

第三版:AI 辅助开发的实际体验

这个工具的核心逻辑是我用 AI 辅助写的。整个过程不是一次成型,而是来回调了好几轮。

第一轮 prompt :我描述了功能需求,AI 生成了完整的单文件 HTML。代码结构清晰,i18n 机制也写得不错,但压缩逻辑过于简单------直接全局折叠空白,根本没考虑 <pre> 标签和 ASI 的坑。

第二轮对话 :我指出问题后,AI 给出了保护块的方案,但它的正则写错了,匹配 <script> 标签时用了 <script\b[\s\S]*?<\/script>,这样会把 <script src="..."> 也匹配进去。我手动修正后才跑通。

第三轮调整:JS 压缩的换行处理是我自己改的。AI 一开始的方案是全部压成一行,我试了几个边界案例发现会破坏 ASI,于是改成保留换行的保守策略。

我的感受:AI 适合生成骨架和常规逻辑,但对边界情况的处理需要开发者自己把关。尤其是压缩这类对语义敏感的操作,不能完全信任 AI 生成的代码。

实现细节与设计决策

国际化

i18n 我做了个轻量方案------一个包含中英文的字典对象,通过 t('key') 函数取值。语言检测优先级:URL 参数 ?lang=en > 浏览器语言 > 默认中文。

javascript 复制代码
const I18N = {
  zh: { beautify: "美化", minify: "压缩" },
  en: { beautify: "Beautify", minify: "Minify" }
};
const t = (k) => I18N[lang][k] || k;

字节数统计

new Blob([code]).size 来计算字节数,比 code.length 更准确------后者统计的是字符数,中文等多字节字符会算错。

深色模式

通过 CSS 变量配合 prefers-color-scheme 实现,不用 JS 切换,浏览器自动适配。

适用场景

这个工具适合以下场景:

  • 从压缩后的线上代码恢复可读性
  • 提交代码前压缩减小体积
  • 临时处理一段代码,不想打开 IDE

不适合的场景:

  • 生产环境的正式构建(应该用 terser 等专业工具)
  • 需要 sourcemap 的场景

最后

如果你也有类似需求,可以直接在线使用这个工具:craftvo.app/zh/tool/cod...

代码逻辑都在浏览器本地跑,不会上传你的代码。压缩功能不依赖外部库,即使 CDN 挂了也能用。

相关推荐
用户2832096793713 分钟前
从进程线程到 async/await:JS Event-loop事件循环机制底层解析
前端·javascript
岁月宁静22 分钟前
一、《从零手撸 Agent》 我用 10 行代码跑通了第一次大模型调用(顺便踩了 4 个坑)
前端·python·agent
计算机魔术师41 分钟前
Fable 5.1 来了,智能体任务成本降 45%——Anthropic 这次为何降价这么狠
前端
Csvn42 分钟前
现代 CSS 新特性深度:container query、:has()、@scope 与 subgrid
前端
计算机魔术师1 小时前
Anthropic 自己承认:模型越来越难管住了
前端
两只羊ovo1 小时前
Agent 干活的底层地基:path 路径 + fs 文件系统,一次讲透
前端
一用书生1 小时前
我给 ChatGPT、DeepSeek、Kimi 都加了一个「保存为笔记」按钮
前端·ai编程·vibecoding
咖啡无伴侣1 小时前
1. 从零搭建企业级 Monorepo 工程化模板:初始化、应用创建与共享 TypeScript 配置
前端·前端框架
moonsims1 小时前
再议AiBrainBox-V的前左右三目布局-满足多目SLAM算法;对比单下视VIO(低空、地面纹理丰富、飞行速度适中无人机 )&多目VIO
前端·人工智能·量子计算