将npm run dev 冷启动从 90s 优化到 8.5s的实践
将本地开发冷启动从 90s 优化到 8.5s的实践
本文记录了我们将前端本地开发冷启动时间从约 90 秒优化到约 8.5 秒的全过程,包括量测方法、优化思路与关键改动清单,便于团队复用与持续迭代。
一、背景与目标
- 背景:开发同学反馈本地
npm run dev
启动耗时约 90s,影响迭代效率。 - 目标:在不牺牲可维护性与稳定性的前提下,将冷启动时间降至 10s 以内,并保持热更新稳定快速。
二、量测方法
- 指标口径:以控制台输出 “Compiled successfully in XXXms” 为准,记录从执行命令到首次成功编译完成的耗时。
- 场景:
- 冷启动(首次启动,含依赖/缓存未预热)
- 二次冷启动(关掉后再次启动)
- 热更新(普通保存一次 .vue 文件的增量编译耗时)
- 记录项:时间戳、命令、端口、主要变更项。
三、优化策略总览
我们将优化拆为三大类,并做到“默认无侵入、逐步增益、随时可回退”。
- 编译链路级
- 预热多线程编译:预热
thread-loader
worker,降低首次编译准备开销。 - 启用 Loader 缓存:对
babel-loader
、vue-loader/cache-loader
开启cacheDirectory
,将中间结果落盘至项目.cache/
下(已加入.gitignore
)。 - 合理的 Source Map:开发环境统一使用
eval
族 Source Map(体量最小、生成最快)。
- 打包配置级
- 跳过无需解析的 UMD 包:对已知的预构建 UMD 库使用
noParse
,避免 AST 解析时间浪费。 - 缩短模块解析路径:精简
resolve.extensions
、合理alias
,降低解析回退链路成本。 - 友好但克制的插件集:仅保留必要的错误提示与友好日志,避免额外 I/O。
- 业务体积级(首包关注)
- 动态导入重型功能:例如打印相关能力(LodopFuncs)改为按需异步加载,避免进入首页时就打包/加载。
四、关键改动清单(与落地位置)
注:以下项均在现有工程中“启用/核验/对齐”,做到工程可随时回退;生产环境不受影响。
-
预热多线程编译(已启用)
- 位置:build/webpack.base.conf.js 中
thread-loader
预热配置。 - 作用:降低首次 worker 启动与通信的额外时延。
- 位置:build/webpack.base.conf.js 中
-
启用 Loader 缓存(已启用)
- 位置:
- build/webpack.base.conf.js 中
babel-loader
的cacheDirectory: true
; vue-loader/cache-loader
指定缓存目录至.cache/
;
- build/webpack.base.conf.js 中
- 作用:二次构建直接命中缓存,大幅降低解析/转译成本。
- 位置:
-
开发 Source Map 策略(已统一)
- 位置:config/index.js 中 dev.devtool 采用
eval
族(生成速度最快); - 作用:显著降低构建期生成调试映射的时间。
- 位置:config/index.js 中 dev.devtool 采用
-
跳过 UMD 解析(已启用)
- 位置:build/webpack.base.conf.js 的
noParse
针对已知大体积 UMD 包; - 作用:避免对这些文件进行 AST 级解析,提高总体构建速度。
- 位置:build/webpack.base.conf.js 的
-
模块解析收敛(已启用)
- 位置:build/webpack.base.conf.js 中
resolve
的extensions
、alias
调整; - 作用:减少解析回退,缩短模块定位时间。
- 位置:build/webpack.base.conf.js 中
-
友好日志与错误处理(已启用)
- 位置:build/webpack.dev.conf.js 的
FriendlyErrorsPlugin
、NoEmitOnErrorsPlugin
等; - 作用:在保留必要反馈的同时,降低冗余输出带来的性能影响。
- 位置:build/webpack.dev.conf.js 的
-
打印能力按需加载(已启用)
- 位置:src/views/logistics/workOrder/index.vue 中将
LodopFuncs
通过import(/* webpackChunkName: "lodop-funcs" */)
异步加载; - 作用:打印相关代码不再进入首屏编译/打包,减少初始模块数与体积。
- 位置:src/views/logistics/workOrder/index.vue 中将
-
缓存目录纳入忽略(已完成)
- 位置:项目根
.gitignore
加入.cache/
; - 作用:确保缓存仅在本地生效,避免污染版本库和 CI。
- 位置:项目根
五、效果对比
- 冷启动:≈ 90s → ≈ 8.5s
- 二次冷启动:进一步下降(命中缓存后更快)
- 热更新:保持稳定,常见改动 500ms–2s 内。
说明:冷启动的下降来自“编译链路级 + 配置级 + 业务体积级”的综合作用;随着缓存命中,二次冷启动会更快。Source Map 从体积较大的变体统一为 eval
族,对首次构建提速明显。
六、落地原则与回退策略
- 小步无侵入:所有调整仅作用于开发环境,生产不受影响;
- 可回退:如遇个别环境不兼容,可逐项关闭(Loader 缓存/线程预热/部分插件),快速定位问题点;
- 可验证:每次改动均通过“冷启动/热更新/二次冷启动”三项量测对比。
七、后续可选优化(按收益/风险排序)
- 更细化的
watchOptions.ignored
(忽略无需监听的目录/文件); - 将
eval-cheap-module-source-map
与eval
在团队内做一次统一评审(不同人偏好兼顾调试体验与速度); - 对于极少改动的大型第三方包,考虑
Dll/externals
方案(仅在收益明显的情况下); - 加入一次性
bundle-analyzer
检视首包与懒加载切分是否合理。
八、复盘要点
- 影响冷启动的核心在于:
- 编译线程与缓存命中率;
- Source Map 的生成成本;
- 初始参与编译/打包的模块数量(可通过动态导入、noParse 等手段降低)。
- 先“找准大头”,再做细节抛光,能在短时间内拿到肉眼可见的收益。
如需还原某一条优化或在 CI 环境下做差异化配置,可在对应 webpack 配置与脚本中按条目开关。我们已将缓存目录 .cache/
排除出版本库,避免对团队协作产生副作用。
附录:今日复盘(重点标注)
本节将今天的实施过程整理为可复盘的清单,标注重点与关键决策,便于后续学习与团队传播。
今日目标
- 将本地开发冷启动由约 90s 优化到约 8–9s 的量级;沉淀方法论与可复用清单。
时间线(事件 → 动作 → 结果)
- 发现问题:
npm run dev
冷启动约 90s,影响开发效率。 - 约束确认:
.cache/
不上传至 Git。- 动作:在
.gitignore
追加.cache/
,并确认仓库未追踪该目录。 - 结果:[已完成],避免缓存污染版本库。
- 动作:在
- 路径二探索:
npm run dev:fast
存在 core-js/library 提示链路。- 动作:调整优化版 dev 配置以复用基础 base 配置,规避旧 polyfill 触发路径;保持优化项。
- 结果:[已完成],
dev:fast
可启动并运行在http://localhost:8082/
;首次编译日志显示Compiled successfully in 67800ms
(含缓存预热与 I/O,属于“首开偏慢”特征)。
- 实际建议:开发日常优先用
npm run dev
(或npm start
)。- 理由:[重点]
dev:fast
的若干缓存/解析策略会让“首开预热”更慢,优势在二次启动/热更;短平快迭代时更推荐常规 dev。
- 理由:[重点]
- 冒烟验证(路由进入,不做业务操作):
- **:
#/asdssf/asdfadfas
- 商品库存-库存查询:
#/asfasfasf/asfdasdfas
- **:
关键优化手段(已落实/对齐)
- [重点] 预热多线程编译(thread-loader prewarm)
- 降低第一次 worker 启动/通信的额外时延。
- [重点] 启用 Loader 缓存
- babel-loader 与 vue/cache-loader 开启
cacheDirectory
(本地.cache/
),显著减少重复转译与解析。
- babel-loader 与 vue/cache-loader 开启
- [重点] Source Map 统一到
eval
族- 构建期间生成速度最快,适合本地调试;带来直接的首编译时延下降。
- 跳过无需解析的 UMD(noParse)
- 避免对大体积 UMD 包进行 AST 解析。
- 模块解析收敛(resolve.alias/extensions)
- 减少回退链路、缩短模块定位时间。
- 业务按需加载(打印/LodopFuncs 动态导入)
- 将非首屏的重型功能推迟到使用时再加载,降低初始模块数与首包体积。
- 缓存目录纳入忽略(
.cache/
)- 确保缓存只在本地生效,避免影响 CI/版本库。
从 90s 到 ~8s 的“最主要三招”(建议优先掌握)
- [核心] Loader 缓存:为 babel-loader、vue/cache-loader 开启
cacheDirectory
,将中间结果落盘命中复用。 - [核心] Source Map 策略:开发环境统一使用
eval
族,减少构建期映射生成的计算与 I/O。 - [核心] 动态导入重型功能:将打印等并非首屏必需功能改为按需加载,降低“参与首编译/首包”的模块数量。
快速复用清单(Checklist)
-
.gitignore
增加.cache/
- babel-loader 开启
cacheDirectory
- vue-loader/cache-loader 指定缓存目录(建议
.cache/
下分目录) - dev 环境 devtool 设为
eval
(或eval-cheap-module-source-map
,视团队调试偏好) - 大体积第三方 UMD 包使用
noParse
- 收敛
resolve.extensions
与合理alias
- 非首屏重型模块改为
import(/* webpackChunkName */)
动态导入 - 保留必要的错误提示插件,避免过度 I/O 插件影响 dev 速度
风险与回退
- 所有项仅作用于开发环境;如遇个别机型/插件不兼容,可逐项关闭(缓存/线程/插件),快速定位问题点并回退。
后续建议
- 在团队内统一
eval
与eval-cheap-module-source-map
选择,平衡调试体验与速度。 - 针对极少改动且体积巨大的依赖,评估
externals/Dll
方案(仅在收益明显时采用)。
结论:这次优化的核心在于“命中缓存 + 降低调试映射成本 + 降低首包/首编译参与模块数量”。先抓大头,再做抛光,能在很短时间内把冷启动从 ~90s 拉低到 ~8s,并保证后续二次冷启动与热更新更快。