聊起前端工程化,很多人的第一反应大概就是配置 Webpack、Vite,或者在项目里加一堆 ESLint 和 Prettier 规则。说实话,这些只是及格线,算不上真正的工具链优化。
干了十年业务和基础建设,我发现业务线同学最常抱怨的从来不是“我们为什么不用最新语法”,而是这几个很具体的问题:本地启动项目要喝一杯咖啡、改一行代码 HMR 等 5 秒、组件库发版全靠人肉打 tag、CI 流程慢得像便秘。
今天就结合我自己折腾组件库和内部基建的经历,聊聊我们是怎么把这套工具链一点点掰顺的。
1. 本地开发体验:解决代理与 Mock 的“最后一公里”
大部分中大型项目在本地跑不爽,除了打包器本身(现在换 Rspack 或 Vite 已经能解决 80% 的构建速度问题),剩下的痛点全在联调和网络层。
以前我们前端本地开发,各种切换测试环境、配置 Host、抓包改 Header,动不动就用 Node.js 起一个本地代理服务。但是当项目依赖上百个微服务接口、每天有几十个人同时联调时,基于 Node.js 的代理经常在处理大流量文件流或者频繁 WebSocket 握手时卡死。
后来我用 Go 写了一个轻量级的本地代理 CLI,二进制分发,不依赖本地 Node 版本。它核心就做三件事:智能请求转发、静态规则 Mock、接口时延模拟。
比如一个极简的本地路由映射配置:
{
"routes": [
{
"prefix": "/api/v1/user",
"target": "https://qa-env-3.internal.net",
"changeOrigin": true
},
{
"prefix": "/api/v1/order/mock",
"mockFile": "./mocks/order.json",
"delay": 200
}
]
}
Go 编译出来的单个二进制文件只有不到 10MB,丢进 PATH 就能跑,内存占用稳定在 20MB 以内,再也没人找我抱怨“本地代理又崩了导致页面空白”的问题。
2. 组件库工程化:Design Token 到发版的闭环
维护通用组件库或者业务物料库,最怕的就是人工介入。人一旦介入,版本号就会乱,文档就会过期,设计规范就会走形。
我们目前的方案是把 Design Token 直接作为单一事实源(Single Source of Truth)。设计团队在 Figma 维护好颜色、间距、圆角等变量,通过插件直接导出 JSON,工具链自动将它们编译成各个端能消费的代码。
写个简单的构建脚本处理 Token 映射:
// scripts/build-tokens.ts
import fs from 'fs-extra';
import path from 'path';
interface DesignTokens {
color: Record<string, string>;
spacing: Record<string, string>;
}
function generateCSSVariables(tokens: DesignTokens): string {
let css = ':root {\n';
for (const [key, value] of Object.entries(tokens.color)) {
css += ` --ui-color-${key}: ${value};\n`;
}
for (const [key, value] of Object.entries(tokens.spacing)) {
css += ` --ui-spacing-${key}: ${value};\n`;
}
css += '}\n';
return css;
}
// 编译输出 CSS / SCSS / TS 常量
const rawTokens: DesignTokens = fs.readJsonSync(path.resolve(__dirname, '../tokens.json'));
fs.writeFileSync(path.resolve(__dirname, '../src/styles/tokens.css'), generateCSSVariables(rawTokens));
在版本发布上,果断丢掉手动 npm publish,全面拥抱 Changesets。每个 PR 必须附带一个 changeset 描述文件,CI 跑完直接合并到发版分支,由 GitHub Actions 或内部 GitLab Runner 自动发版、生成 CHANGELOG,顺便把文档站点打包部署。
3. CI/CD 瓶颈优化:别让构建机做重复劳动
很多团队的 CI 慢,本质上是在反复做无用功。我们在优化 CI 时主要抓了三点:
- 依赖缓存(Node Modules Cache):使用 pnpm 的话,直接按 lockfile 的 hash 做缓存 key。只有 lockfile 变动时才拉完整依赖,平时直接命中本地 store。
- 构建缓存与增量构建:接入 Turborepo 或 Nx,把 lint、test、build 的产物缓存起来。一个 Monorepo 里即使有十几个 package,改了其中一个组件,其他组件的测试和构建全走 cache,CI 时间直接从 8 分钟压到 1.5 分钟。
- 资源处理工具重构:以前我们在 CI 阶段会用 Node 脚本跑大量的 SVG 压缩、雪碧图合成和国际化文案 Key 提取。当图标和文案体量变大后,Node.js 单线程的瓶颈很明显。后来这块处理逻辑我也换成了 Go 的并发处理,直接跑满构建机的多核 CPU,几千个 SVG 压缩在 2 秒内就能完成。
// 极简并发处理文件的小例子
package main
import (
"fmt"
"sync"
)
func processAsset(file string, wg *sync.WaitGroup) {
defer wg.Done()
// 处理 svg 压缩或资源上传逻辑
fmt.Printf("Processed: %s\n", file)
}
func main() {
assets := []string{"icon-a.svg", "icon-b.svg", "icon-c.svg"}
var wg sync.WaitGroup
for _, asset := range assets {
wg.Add(1)
go processAsset(asset, &wg)
}
wg.Wait()
fmt.Println("All assets processed.")
}
总结
搞前端基建这么多年,我最大的体会就是:不要为了技术指标去造轮子,一切以团队的开发吞吐率为准。
如果一个现成的开源工具加点配置就能解决 90% 的问题,就坚决不要自己写一套;但如果业务流程里存在明显的卡点(比如跨多端的联调成本、超大项目的构建耗时、混乱的发布规范),那就值得花时间,甚至跳出前端技术栈,用更适合的工具(比如 Rust 或 Go)去把卡脖子的地方彻底推平。
工具链的终极目标,是让业务开发的同学完全感觉不到工具链的存在。
License: CC BY-NC 4.0
Updated 2 hours ago
Was this article helpful? Give it a like.
0 comments


