说实话,折腾前端工程化这么多年,我见过太多团队掉进同一个坑里:项目一卡,立刻去搜现成的 Webpack / Vite 插件,搜不到就硬堆 Shell 脚本,最后本地开发环境跑起来像拖拉机,维护这些构建脚本的时间比写业务组件还长。
其实很多时候,与其到处找现成轮子去迁就别人的逻辑,真不如自己动手写个轻量工具。今天就聊聊我自己做前端工具开发的一点经验,顺便拿个具体例子盘一盘。
痛点往往藏在琐碎事情里
工具开发不是为了 KPI 硬造轮子,它通常来自业务里那些让人烦躁的重复操作。
举个我之前在维护公司基础组件库时遇到的真实案例:图标库(Icon Component)膨胀。
设计团队隔三差五扔过来几十个 SVG,前端需要把它们转成 React / Vue 组件,同时还要确保:
- 没有同名或相似的废弃图标。
- 属性(比如
fill、stroke)必须被干掉,替换为currentColor。 - 压缩 SVG 路径数据,剔除无用的 Sketch / Illustrator 元数据。
- 业务项目打包时,能精准分析出哪些图标根本没被引用过。
如果全靠人工或者每次构建时走复杂的 Rollup 插件去转,构建速度会被生生拖垮。这种事情就非常适合独立抽成一个 CLI 工具,在 Git Hooks 或者 CI 阶段一次性解决。
选型:Node.js 还是 Golang?
做前端工具,Node.js 肯定是第一直觉,毕竟 AST 工具链成熟,@babel/parser、postcss 用起来顺手。但如果你的工具涉及到大量的本地文件 I/O、图片/静态资源处理、或者高并发分析,我强烈建议你试试 Golang。
我现在的习惯是:
- 涉及 JS/TS 代码语义、AST 深度转换:老老实实用 Node.js / TS 写,生态优势摆在那里。
- 文件扫描、静态资源压缩、代码规范正则筛查、CI 提速工具:直接上 Golang。单个二进制文件扔进 Docker 镜像里就能跑,没有
node_modules那堆依赖地狱,启动速度基本都是毫秒级。
实战:写一个快速清理 SVG 的小工具
就拿刚才提到的 SVG 处理来举例,如果用 Node.js 做原型,很多人喜欢直接调 svgo。但如果我们要做一个兼顾“提取元信息 + 批量规范化”的独立工具,思路其实很简单。
下面是一段用 Node.js 实现的简化版处理核心:
import fs from 'node:fs/promises';
import path from 'node:path';
import { optimize } from 'svgo';
async function processSvg(filePath) {
const rawSvg = await fs.readFile(filePath, 'utf-8');
// 务实一点,别搞复杂的配置,只做组件库最需要的转换
const result = optimize(rawSvg, {
path: filePath,
plugins: [
'removeDoctype',
'removeXMLProcInst',
'removeComments',
'removeMetadata',
{
name: 'convertColors',
params: {
currentColor: true, // 强制转 currentColor
},
},
{
name: 'removeAttrs',
params: {
attrs: '(width|height)', // 去掉固定宽高,交给 CSS 控制
},
},
],
});
return result.data;
}
但当图标数量上千个时,Node.js 单线程读写的性能瓶颈就出来了。后来我用 Go 重写了这个处理流程,利用 Goroutine 做并发读写,速度从十几秒直接降到 300ms 左右:
package main
import (
"fmt"
"os"
"path/filepath"
"regexp"
"sync"
)
var (
fillReg = regexp.MustCompile(`fill="[^"]+"`)
strokeReg = regexp.MustCompile(`stroke="[^"]+"`)
)
func cleanSvg(content string) string {
// 简单的属性重写,对于规整的 SVG 来说正则足矣且性能极高
out := fillReg.ReplaceAllString(content, `fill="currentColor"`)
out = strokeReg.ReplaceAllString(out, `stroke="currentColor"`)
return out
}
func processFile(path string, wg *sync.WaitGroup) {
defer wg.Done()
data, err := os.ReadFile(path)
if err != nil {
return
}
cleaned := cleanSvg(string(data))
_ = os.WriteFile(path, []byte(cleaned), 0644)
}
func main() {
var wg sync.WaitGroup
files, _ := filepath.Glob("./icons/*.svg")
for _, file := range files {
wg.Add(1)
go processFile(file, &wg)
}
wg.Wait()
fmt.Printf("成功处理 %d 个图标\n", len(files))
}
代码一点都不高大上,甚至有点糙,但放在 CI 流水线里,它就是比拉取几百兆的 Node 环境跑 npm install 要稳得多。
别把工程化工具写成“玩具”
很多同学自己写工具,最后死在推进落地这一步。大家都不愿意用,为什么?
- 报错信息太反人类:挂掉了只扔个
Error: ENOENT或者一个空指针,别人排查半天发现是某个路径配错了。友好的输出格式(哪一行、哪个文件、建议怎么改)比你核心逻辑多牛逼更重要。 - 缺乏逃生通道:写规则检查工具时,总有人有特殊业务场景。如果你不给一个类似
// tool-ignore-next-line这样的行内忽略注释支持,业务同学被阻塞了两次之后,第一反应就是找领导申请把这个工具卸载掉。 - 侵入性过强:最好的工具应该像空气一样。比如做性能优化,优先做成无感知的(比如 Vite 插件、CLI 预处理),而不是让业务方去改几十处业务代码来适配你的工具。
总结
前端做久了就会发现,市面上的框架和库层出不穷,但底层的问题永远是那几个:加载速度、构建耗时、协作规范、包体积。
别把工具开发看得很神秘,也不用动不动就重写整个编译器。从一个几行代码的脚本开始,解决团队里一个具体且令人恶心的小问题,把它打磨得稳定、快、好用,这就是前端工程化最实在的价值。
License: CC BY-NC 4.0
Updated 13 hours ago
Was this article helpful? Give it a like.
0 comments


