技术文章
我在成都小县城远程办公的第18个月
去年这个时候,我还在为下一份简历发愁。虽然每天在青白江的小出租屋里写代码、调接口、修 Bug,看起来岁月静好,但内心其实慌得一批——毕竟“小镇做题家”这个标签不是白叫的:高考刷题上来的,大学靠 LeetCode 找工作,进了大厂又靠面试题挑战续命。如今远程办公久了,技术栈没怎么更新,连 GitHub 提交记录都快长草了。
直到上周五晚上,被产品经理@了一条消息:“能不能让这个页面首屏加载再快点?用户反馈卡成PPT了。” 我盯着屏幕,心里默默翻了个白眼——这已经是本月第三次提性能优化需求了,而我们的 JavaScript 包体积已经逼近 3MB。那一刻我意识到:不能再靠 Ctrl+C/V 混日子了,是时候深入搞点技术探索与实践了。
面试题挑战逼我重学模块拆分
说来惭愧,前阵子准备跳槽面了几家,被问到“你们项目怎么做的按需加载?”我支支吾吾说了句“用了 dynamic import”,结果面试官反问:“那你们的 chunk 切分策略是什么?有没有考虑过路由级和组件级的粒度平衡?” 我当场大脑宕机。
回来后痛定思痛,决定拿手头这个电商后台项目开刀。我们用的是 Vue 3 + Vite,早期为了赶双11上线,所有页面一股脑全塞进主 bundle,导致首屏加载时间高达 4.2s(本地开发环境测的,线上可能更惨)。
于是,我开始动手重构。核心思路就一条:把 JavaScript 拆得越细越好,但也不能太碎。这里有个微妙的平衡点——chunk 太少,加载慢;chunk 太多,HTTP 请求爆炸,反而更慢(尤其对移动端和弱网用户)。
我参考了 Webpack 的 splitChunks 策略,结合 Vite 的 rollupOptions 做了如下配置:
// vite.config.js
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks(id) {
// 把第三方库单独打包
if (id.includes('node_modules')) {
if (id.includes('element-plus')) return 'element-plus';
if (id.includes('echarts')) return 'echarts';
if (id.includes('lodash')) return 'lodash';
return 'vendor'; // 其他第三方归到 vendor
}
// 按页面路由拆分
if (id.includes('/src/views/')) {
const match = id.match(/src\/views\/([^/]+)/);
if (match) return `page-${match[1]}`;
}
// 公共组件单独打包
if (id.includes('/src/components/shared/')) {
return 'shared-components';
}
}
}
}
}
});
改完之后,主 bundle 从 2.8MB 降到了 600KB,首屏加载时间压到 1.3s(本地)。线上灰度发布后,Lighthouse 性能分从 48 提升到 76,PM 终于没再半夜@我了。
简历不能只写“熟悉 Vue”,得有工具链沉淀
很多像我一样的小镇做题家,简历上清一色写着“熟练掌握 JavaScript、Vue、React”,但真要问细节,比如“你们怎么监控前端错误?”、“怎么保证代码质量?”,就答不上来了。
其实,技术深度不体现在你会多少框架,而体现在你构建了多少自动化工具。
我们团队人少(远程+兼职一共5个人),没法像大厂那样配专门的 DevOps 工程师,所以我就自己撸了个轻量级工具链,主要解决三个问题:
- 代码规范:用 ESLint + Prettier + Husky,在 commit 前自动格式化并检查
- 错误监控:接入 Sentry,自定义上报逻辑,过滤掉测试环境噪音
- 性能基线:用 Lighthouse CI 在每次 PR 合并前跑性能测试,低于阈值就阻断合并
下面是我们 .husky/pre-commit 脚本的一部分:
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npm run lint-staged
npm run type-check
而 lighthouserc.json 配置则确保性能不退步:
{
"ci": {
"collect": {
"url": ["http://localhost:3000/dashboard"],
"startServerCommand": "npm run preview"
},
"assert": {
"assertions": {
"categories:performance": ["error", { "minScore": 0.7 }],
"first-contentful-paint": ["warn", { "maxNumericValue": 1500 }]
}
}
}
}
这些看似“小打小闹”的工具,其实极大提升了交付质量。更重要的是——它们都能写进简历!我现在简历里不再是“参与XX项目开发”,而是“主导前端工程化体系建设,实现自动化性能守门机制,降低线上 JS 错误率 62%”。HR 看不懂没关系,技术面试官一眼就知道你干过实事。
技术探索不是闭门造车,得去“蹭会”
别看我在县城,但我可没闲着。成都本地的技术沙龙我基本场场不落(虽然要坐1小时地铁进城)。上个月在天府软件园听了一场关于“微前端与模块联邦”的分享,主讲人提到他们用 Module Federation 动态加载不同团队的组件,我当时就心动了。
回来立马在测试环境试水。我们后台有商品、订单、用户三个大模块,原本是单体应用,现在想逐步解耦。用 Module Federation 的好处是:不用重构整个架构,就能实现跨应用组件共享。
宿主应用(Host)配置:
// vite.config.js (Host)
plugins: [
federation({
name: 'host_app',
remotes: {
productModule: 'productApp@http://localhost:3001/assets/remoteEntry.js'
},
shared: ['vue', 'pinia']
})
]
远程模块(Remote):
// product-app/vite.config.js
plugins: [
federation({
name: 'productApp',
filename: 'remoteEntry.js',
exposes: {
'./ProductList': './src/components/ProductList.vue'
},
shared: ['vue', 'pinia']
})
]
然后在 Host 里直接动态引入:
<script setup>
import ProductList from 'productModule/ProductList';
</script>
效果立竿见影:商品模块独立部署,更新时不影响主站。而且因为共享了 Vue 和 Pinia 实例,内存占用也没翻倍。
当然,踩坑也不少。比如一开始没配好 shared 版本,导致两个 Vue 实例打架,页面直接白屏。还有跨域问题、缓存失效……但这些问题恰恰是最好的学习素材。后来我把整个过程整理成一篇内部 Wiki,还录了个 15 分钟的分享视频,团队群里反响不错。
最佳实践总结:别为了技术而技术
经过这几个月折腾,我最大的感悟是:技术探索必须服务于业务痛点,否则就是自嗨。
- 如果你的用户都在强网环境,拼命压 JS 体积可能收益不大;
- 如果团队只有 3 个人,搞一套复杂的微前端反而增加维护成本;
- 如果产品迭代快如闪电,花两周搭 Lighthouse CI 可能不如先搞定需求。
但在合适的时机做合适的事,技术就能变成护城河。比如我们现在新招人,面试必问:“你怎么看待前端工程化?” 然后让人现场看我们的工具链设计——这比背八股文有用多了。
最后附上一个小对比表,看看重构前后的关键指标变化:
| 指标 | 重构前 | 重构后 | 提升幅度 |
|---|---|---|---|
| 主 JS Bundle | 2.8 MB | 600 KB | ↓78% |
| 首屏加载时间 | 4.2s | 1.3s | ↓69% |
| JS 错误率(日均) | 1,200+ | 450 | ↓62% |
| PR 合并阻断次数(性能) | 0 | 平均每周 2 次 | ✅建立守门机制 |
写这篇文章的时候,窗外正下着成都典型的毛毛雨。远程办公虽然自由,但也容易陷入舒适区。还好有这些技术挑战推着我往前走——不管是为了面试题不再卡壳,还是为了让简历有点干货,又或者单纯不想被时代甩下。
如果你也和我一样,在小城市远程搬砖,别躺平。参加个线上 Meetup,读篇 RFC,甚至只是把一个重复的手动操作写成脚本,都是进步。
毕竟,小镇做题家的终极武器,从来不是天赋,而是持续折腾的劲头。
共勉。

评论 0