前端工程化与性能监控面经
前端工程化核心知识
1. Webpack 深度
核心概念
Entry -> Loader 处理 -> Plugin 阁 -> Bundle 输出
Loader:转换单个文件(babel-loader / css-loader / file-loader)
Plugin:串联整个构建生命周期(HtmlWebpackPlugin / MiniCssExtractPlugin)
Resolver:模块寻址解析
Tree Shaking 原理
- 基于 ESM 静态分析模块导入导出
- Webpack 标记未使用的导出为 unused exports
- Terser 在压缩时删除未使用代码
sideEffects: false可帮助 Webpack 更激进地删除
Code Splitting
// 1. 入口分割
entry: { main: './src/index.js', vendor: './src/vendor.js' }
// 2. 动态导入
const LazyComp = () => import('./HeavyComponent')
// 3. SplitChunksPlugin 公共依赖提取
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /node_modules/,
name: 'vendors',
chunks: 'all'
}
}
}
}
Webpack vs Vite 对比
| 对比项 | Webpack | Vite |
|---|---|---|
| 开发启动 | 打包所有模块 | ESM 按需加载 |
| HMR 速度 | 所有模块出发 | 只有变化的模块 |
| 生产构建 | Webpack | Rollup |
| 配置复杂度 | 高 | 低 |
| 适用场景 | 复杂大型 | 中小型,新项目 |
2. Vite 深度
开发模式原理
1. 浏览器请求模块
2. Vite Dev Server 收到请求
3. 对模块应用转换(如 TypeScript 编译)
4. 返回转换后的模块
-> 无需打包,启动速度极快
预构建依赖
- 第一次启动时用 esbuild 预构建 node_modules
- esbuild 是 Go 写的,比 Babel 快 10-100x
- 预构建结果缓存在 node_modules/.vite 中
自定义插件
// vite.config.ts
export default {
plugins: [
{
name: 'my-transform',
transform(code, id) {
if (id.endsWith('.svg')) {
return `export default ${JSON.stringify(code)}`
}
}
}
]
}
3. CI/CD 流水线
# GitHub Actions 示例
name: Deploy
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- name: Install
run: pnpm install --frozen-lockfile
- name: Lint
run: pnpm lint
- name: Test
run: pnpm test --coverage
- name: Build
run: pnpm build
env:
VITE_API_BASE: ${{ secrets.API_BASE }}
- name: Deploy to CDN
run: pnpm deploy
4. Web 性能监控
Core Web Vitals
| 指标 | 全称 | 良好阈值 | 优化策略 |
|---|---|---|---|
| LCP | 最大内容绘制 | < 2.5s | SSR、图片优先级、CDN |
| INP | 交互到下一帧 | < 200ms | 少 JS、Web Worker |
| CLS | 累积布局偏移 | < 0.1 | 图片设宽高、字体预加载 |
Performance API
// 页面性能采样
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
if (entry.entryType === 'largest-contentful-paint') {
console.log('LCP:', entry.startTime)
}
if (entry.entryType === 'layout-shift') {
console.log('CLS score:', entry.value)
}
})
})
observer.observe({ type: 'largest-contentful-paint', buffered: true })
observer.observe({ type: 'layout-shift', buffered: true })
// Navigation Timing
const timing = performance.timing
const pageLoad = timing.loadEventEnd - timing.navigationStart
const domReady = timing.domContentLoadedEventEnd - timing.navigationStart
const ttfb = timing.responseStart - timing.requestStart
前端监控体系
// 错误监控
window.addEventListener('error', (event) => {
sendToSentry({
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
error: event.error?.stack
})
})
window.addEventListener('unhandledrejection', (event) => {
sendToSentry({ message: event.reason?.message })
})
// 资源监控
const resourceObserver = new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
if (entry.duration > 1000) {
// 资源加载过慢
reportSlowResource(entry)
}
})
})
resourceObserver.observe({ type: 'resource', buffered: true })
5. 包体积分析
# Webpack Bundle Analyzer
npx webpack-bundle-analyzer stats.json
# Vite 分析
npx vite-bundle-visualizer
# 常见优化手段
1. 添加 lodash 用 lodash-es 替代
2. moment.js 用 dayjs 替代(体积约 80% 小)
3. echarts 按需导入组件,不要全量导入
4. 图片用 WebP + 懒加载
5. 富文本编辑器就答动态加载
6. 资深面试题
- Webpack 的 HMR 原理?
- Dev Server 与浏览器建立 WebSocket
- 文件变化时,重新编译对应模块
- 向浏览器推送更新后的模块 hash
- 浏览器下载新模块,通过
module.hot.accept回调操作
- Vite 为什么开发时很快但构建时还用 Rollup?
- 开发时利用浏览器原生 ESM,无需打包
- 生产构建需要 Tree Shaking、代码分割、压缩等 Rollup 的能力
- 未来 Rolldown(Rust 实现)将统一开发和生产
- 如何实现前端性能监控平台?
- Performance API 采频 Core Web Vitals
- 错误监控捕获未处理异常
- User Timing API 自定义性能埋点
- 上报到分析平台(自建 + 各种 SaaS)
- Webpack 构建慢如何优化?
cache: { type: 'filesystem' }文件系统缓存thread-loader并行处理 Loaderbabel-loader配置include: /src/缩小范围- 将稳定依赖定义为 外部扩展 externals + CDN