简历描述模板(STAR法则)
模板一:Vite 插件开发
主导开发 Vite 自动化插件生态,包括自动路由生成、组件自动导入、Markdown 文档编译等 5+ 插件,开发效率提升 40%,新人上手时间从 3 天缩短至半天。
- 开发自动路由插件,基于文件系统自动生成路由配置,减少 80% 路由代码编写
- 实现组件按需导入插件,打包体积减少 35%,首屏加载时间降低 28%
- 封装通用插件开发模板,团队成员可快速开发业务插件
模板二:Webpack 打包体积优化
负责主站 Webpack 打包优化,通过代码分割、Tree Shaking、依赖分析等手段,将生产环境资源体积从 8.5MB 压缩至 1.2MB(减少 86%),首屏加载时间从 4.2s 降至 1.1s。
- 使用 webpack-bundle-analyzer 深度分析,识别并优化 15+ 冗余依赖
- 实施动态 import + React.lazy,路由级代码分割覆盖率达 100%
- 配置 SplitChunks 策略,提取公共 vendor,缓存命中率提升 70%
模板三:Rspack 迁移
主导团队从 Webpack 5 迁移至 Rspack,构建速度从 120s 提升至 8s(提升 15 倍),HMR 热更新从 3s 降至 100ms,大幅提升开发体验。
- 制定 Webpack → Rspack 迁移方案,兼容性覆盖率达 98%
- 优化 Rspack 配置,利用 Rust 并行编译能力,构建速度提升 15 倍
- 建立性能监控体系,持续追踪构建性能指标
模板四:构建速度综合优化
系统性优化前端构建流程,通过缓存策略、并行编译、依赖优化等手段,将 CI 构建时间从 380s 优化至 35s(提升 10.8 倍),本地开发启动时间从 45s 降至 2.8s。
- 引入持久化缓存(hard-source-webpack-plugin),二次构建提速 85%
- 使用 esbuild-loader 替代 babel-loader,编译速度提升 20 倍
- 配置多核并行压缩(terser-webpack-plugin parallel),压缩时间减少 70%
面试话术模板
一、Vite 插件开发实战
场景一:面试官问"你开发过哪些 Vite 插件?"
【核心回答框架】
"我开发的插件都是为了解决实际业务痛点,不是为了技术而技术。"
背景(Situation): 我们团队使用 Vite + Vue3 技术栈,项目发展到 50+ 页面后,遇到三个核心问题:
- 路由配置繁琐:每新增一个页面需要手动配置路由,容易遗漏
- 组件导入重复:每个文件都要 import 常用组件,代码冗余
- 开发体验割裂:Markdown 文档无法集成到项目中预览
任务(Task): 我负责开发 Vite 插件生态,目标是提升开发效率、减少重复劳动。
行动(Action)
1️⃣ 自动路由生成插件
核心思路:基于文件系统约定式路由,类似 Nuxt.js
typescript
// vite-plugin-auto-routes.ts
import { Plugin } from 'vite';
import fg from 'fast-glob';
import path from 'path';
interface Options {
pagesDir?: string;
extensions?: string[];
}
export function AutoRoutesPlugin(options: Options = {}): Plugin {
const {
pagesDir = 'src/pages',
extensions = ['vue', 'tsx', 'jsx']
} = options;
let virtualModuleId = 'virtual:generated-routes';
let resolvedId = '\0' + virtualModuleId;
return {
name: 'vite-plugin-auto-routes',
// 解析虚拟模块
resolveId(id) {
if (id === virtualModuleId) {
return resolvedId;
}
},
// 加载虚拟模块内容
async load(id) {
if (id === resolvedId) {
const routes = await generateRoutes(pagesDir, extensions);
return `export default ${JSON.stringify(routes, null, 2)}`;
}
},
// 监听文件变化,热更新
handleHotUpdate({ file, server }) {
if (file.includes(pagesDir)) {
const module = server.moduleGraph.getModuleById(resolvedId);
if (module) {
server.moduleGraph.invalidateModule(module);
server.ws.send({
type: 'full-reload',
path: '*'
});
}
}
}
};
}
// 生成路由配置
async function generateRoutes(dir: string, exts: string[]) {
const files = await fg(`${dir}/**/*.{${exts.join(',')}}`, {
ignore: ['**/components/**', '**/_*']
});
return files.map(file => {
// src/pages/user/profile.vue -> /user/profile
const routePath = file
.replace(dir, '')
.replace(/\.(vue|tsx|jsx)$/, '')
.replace(/\/index$/, '');
// 动态路由: [id].vue -> :id
const path = routePath.replace(/\[(\w+)\]/g, ':$1');
return {
path: path || '/',
component: () => import(file),
name: routePath.replace(/\//g, '-').slice(1) || 'index'
};
});
}
使用方式:
typescript
// vite.config.ts
import { AutoRoutesPlugin } from './plugins/auto-routes';
export default defineConfig({
plugins: [
vue(),
AutoRoutesPlugin({
pagesDir: 'src/pages'
})
]
});
// main.ts
import routes from 'virtual:generated-routes';
const router = createRouter({
history: createWebHistory(),
routes
});
文件结构约定:
src/pages/
├── index.vue → /
├── about.vue → /about
├── user/
│ ├── index.vue → /user
│ ├── profile.vue → /user/profile
│ └── [id].vue → /user/:id
└── blog/
└── [slug].vue → /blog/:slug
2️⃣ 组件自动导入插件
核心思路:编译时分析 AST,自动注入 import 语句
typescript
// vite-plugin-auto-import.ts
import { Plugin } from 'vite';
import MagicString from 'magic-string';
import { parse } from '@vue/compiler-sfc';
interface Options {
components?: Record<string, string>;
dirs?: string[];
}
export function AutoImportPlugin(options: Options = {}): Plugin {
const { components = {}, dirs = [] } = options;
// 扫描目录下的组件
const componentMap = new Map<string, string>();
return {
name: 'vite-plugin-auto-import',
async buildStart() {
// 扫描组件目录
for (const dir of dirs) {
const files = await fg(`${dir}/**/*.vue`);
files.forEach(file => {
const name = path.basename(file, '.vue');
componentMap.set(name, file);
});
}
// 添加手动配置的组件
Object.entries(components).forEach(([name, path]) => {
componentMap.set(name, path);
});
},
transform(code, id) {
if (!id.endsWith('.vue')) return null;
// 解析 Vue SFC
const { descriptor } = parse(code);
const template = descriptor.template?.content || '';
// 查找使用的组件
const usedComponents = new Set<string>();
componentMap.forEach((_, name) => {
// 匹配 <ComponentName> 或 <component-name>
const kebabCase = name.replace(/([A-Z])/g, '-$1').toLowerCase().slice(1);
if (
template.includes(`<${name}`) ||
template.includes(`<${kebabCase}`)
) {
usedComponents.add(name);
}
});
if (usedComponents.size === 0) return null;
// 生成 import 语句
const imports = Array.from(usedComponents)
.map(name => {
const importPath = componentMap.get(name)!;
return `import ${name} from '${importPath}';`;
})
.join('\n');
// 注入到 <script setup> 中
const s = new MagicString(code);
const scriptSetup = descriptor.scriptSetup;
if (scriptSetup) {
s.appendLeft(scriptSetup.loc.start.offset, `\n${imports}\n`);
} else {
// 没有 script 则创建
s.append(`\n<script setup>\n${imports}\n</script>`);
}
return {
code: s.toString(),
map: s.generateMap({ hires: true })
};
}
};
}
配置使用:
typescript
// vite.config.ts
import { AutoImportPlugin } from './plugins/auto-import';
export default defineConfig({
plugins: [
AutoImportPlugin({
// 手动配置
components: {
Button: '@/components/Button.vue',
Input: '@/components/Input.vue'
},
// 自动扫描
dirs: ['src/components']
})
]
});
实际效果:
vue
<!-- 优化前 -->
<script setup>
import Button from '@/components/Button.vue';
import Input from '@/components/Input.vue';
import Modal from '@/components/Modal.vue';
</script>
<template>
<Button />
<Input />
<Modal />
</template>
<!-- 优化后:自动注入 import,无需手写 -->
<template>
<Button />
<Input />
<Modal />
</template>
\n([\s\S]+?)\n---\n/; const match = code.match(frontmatterRegex); let frontmatter = {}; let content = code;
if (match) {
frontmatter = parseFrontmatter(match[1]);
content = code.slice(match[0].length);
}
// 渲染 Markdown
const html = md.render(content);
// 生成 Vue 组件
const component = `
`.trim();
return {
code: component,
map: null
};
}
}; }
function parseFrontmatter(text: string) { const lines = text.split('\n'); const result: Record<string, any> = {};
lines.forEach(line => { const [key, ...values] = line.split(':'); if (key && values.length) { result[key.trim()] = values.join(':').trim(); } });
return result; }
**使用示例**:
markdown
```markdown
<!-- docs/guide.md -->
---
title: 快速开始
author: 张三
date: 2024-01-01
---
# 快速开始
这是一个示例文档
## 安装
\`\`\`bash
npm install
\`\`\`
vue
<!-- 在 Vue 组件中使用 -->
<script setup>
import Guide from './docs/guide.md';
</script>
<template>
<Guide />
</template>
结果(Result)
- 开发效率:路由配置时间从 10min/页面 降至 0,组件导入代码减少 80%
- 代码质量:避免人工配置错误,路由配置准确率 100%
- 团队协作:新人上手时间从 3 天降至半天
- 文档体验:Markdown 文档与项目无缝集成,文档更新率提升 200%
插件数据对比:
优化前:
- 新增页面耗时: 15min(配置路由 + 导入组件)
- 路由配置错误率: 15%
- 代码量: 每个文件平均 8 行 import
优化后:
- 新增页面耗时: 2min(只写页面代码)
- 路由配置错误率: 0%
- 代码量: 0 行 import(自动注入)
场景二:面试官追问"Vite 插件开发的核心原理是什么?"
【深度技术回答】
"Vite 插件本质是一个带钩子函数的对象,通过 Rollup 兼容的钩子系统介入构建流程。"
Vite 插件生命周期:
服务器启动
↓
configResolved (配置解析完成)
↓
buildStart (开始构建)
↓
resolveId (解析模块 ID)
↓
load (加载模块内容)
↓
transform (转换模块代码)
↓
buildEnd (构建结束)
核心钩子详解:
typescript
interface VitePlugin {
name: string; // 插件名称
// 1. 配置钩子
config?: (config, env) => UserConfig | null; // 修改 Vite 配置
configResolved?: (config) => void; // 配置解析完成
// 2. 服务器钩子
configureServer?: (server) => void; // 配置开发服务器
transformIndexHtml?: (html) => string; // 转换 index.html
// 3. 通用钩子(Rollup 兼容)
resolveId?: (id, importer) => string | null; // 解析模块路径
load?: (id) => string | null; // 加载模块内容
transform?: (code, id) => { code: string; map?: any }; // 转换代码
// 4. 热更新钩子
handleHotUpdate?: (ctx) => void; // 处理HMR
// 5. 构建钩子
buildStart?: () => void;
buildEnd?: () => void;
}
实战案例 - 环境变量注入插件:
typescript
export function EnvPlugin(): Plugin {
return {
name: 'vite-plugin-env',
// 在 transform 阶段注入环境变量
transform(code, id) {
if (!id.includes('node_modules')) {
// 替换 import.meta.env.CUSTOM_VAR
return code.replace(
/import\.meta\.env\.(\w+)/g,
(_, key) => JSON.stringify(process.env[key])
);
}
}
};
}
虚拟模块模式:
typescript
export function VirtualModulePlugin(): Plugin {
const virtualId = 'virtual:my-module';
const resolvedId = '\0' + virtualId;
return {
name: 'virtual-module',
resolveId(id) {
if (id === virtualId) {
return resolvedId; // 返回内部 ID
}
},
load(id) {
if (id === resolvedId) {
return `export const data = { foo: 'bar' };`; // 返回模块内容
}
}
};
}
// 使用
import { data } from 'virtual:my-module';
插件执行顺序控制:
typescript
export function MyPlugin(): Plugin {
return {
name: 'my-plugin',
enforce: 'pre', // 'pre' | 'post' | undefined
// pre: 在核心插件之前执行
// post: 在核心插件之后执行
// undefined: 默认顺序
};
}
二、Webpack 打包体积优化
场景一:面试官问"你是如何优化 Webpack 打包体积的?"
【核心回答框架】
"我的优化是系统性的,从分析 → 拆分 → 压缩 → 替换四个维度展开。"
背景(Situation): 项目上线后用户反馈首屏加载慢,通过 Lighthouse 检测发现:
- 打包后资源体积 8.5MB
- FCP (首次内容绘制) 4.2s
- LCP (最大内容绘制) 5.8s
任务(Task): 将资源体积压缩到 2MB 以下,FCP 降至 1.5s 以内。
行动(Action)
第一步:体积分析
javascript
// webpack.config.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static',
reportFilename: 'bundle-report.html',
openAnalyzer: false
})
]
};
分析结果发现:
- moment.js 占用 500KB(包含所有语言包)
- lodash 占用 300KB(全量引入)
- echarts 占用 800KB(全量引入)
- antd 占用 1.2MB(未按需加载)
- source-map 占用 2MB(生产环境未关闭)
第二步:依赖替换
javascript
// ❌ 优化前
import moment from 'moment';
import _ from 'lodash';
import * as echarts from 'echarts';
// ✅ 优化后
import dayjs from 'dayjs'; // moment → dayjs (500KB → 2KB)
import { debounce, throttle } from 'lodash-es'; // 按需引入
import * as echarts from 'echarts/core'; // 核心库
import { BarChart } from 'echarts/charts'; // 按需导入图表
import { CanvasRenderer } from 'echarts/renderers';
echarts.use([BarChart, CanvasRenderer]);
依赖替换清单:
| 原依赖 | 替换方案 | 体积对比 |
|---|---|---|
| moment.js | dayjs | 500KB → 7KB |
| lodash | lodash-es + tree-shaking | 300KB → 50KB |
| echarts | 按需引入 | 800KB → 200KB |
| antd | babel-plugin-import | 1.2MB → 300KB |
第三步:代码分割
javascript
// webpack.config.js
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
// 第三方库
vendor: {
test: /[\\/]node_modules[\\/]/,
name(module) {
// 将大型库单独拆分
const packageName = module.context.match(
/[\\/]node_modules[\\/](.*?)([\\/]|$)/
)[1];
// react 相关单独打包
if (['react', 'react-dom', 'react-router'].includes(packageName)) {
return 'vendor-react';
}
// antd 单独打包
if (packageName.startsWith('antd') || packageName.startsWith('@ant-design')) {
return 'vendor-antd';
}
return 'vendor-other';
},
priority: 10
},
// 公共模块
common: {
minChunks: 2,
priority: 5,
reuseExistingChunk: true,
name: 'common'
}
}
},
// 运行时代码单独提取
runtimeChunk: {
name: 'runtime'
}
}
};
路由懒加载:
javascript
// ❌ 优化前
import Dashboard from './pages/Dashboard';
import UserList from './pages/UserList';
import Settings from './pages/Settings';
const routes = [
{ path: '/dashboard', component: Dashboard },
{ path: '/users', component: UserList },
{ path: '/settings', component: Settings }
];
// ✅ 优化后
const routes = [
{
path: '/dashboard',
component: () => import(/* webpackChunkName: "dashboard" */ './pages/Dashboard')
},
{
path: '/users',
component: () => import(/* webpackChunkName: "users" */ './pages/UserList')
},
{
path: '/settings',
component: () => import(/* webpackChunkName: "settings" */ './pages/Settings')
}
];
第四步:Tree Shaking 优化
javascript
// webpack.config.js
module.exports = {
mode: 'production',
optimization: {
usedExports: true, // 标记未使用的导出
sideEffects: false // 允许删除无副作用的模块
}
};
// package.json
{
"sideEffects": [
"*.css",
"*.scss",
"*.less"
]
}
确保 ESM 格式:
javascript
// ❌ CommonJS 无法 Tree Shaking
const utils = require('./utils');
// ✅ ESM 可以 Tree Shaking
import { add, subtract } from './utils';
第五步:压缩优化
javascript
// webpack.config.js
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const CompressionPlugin = require('compression-webpack-plugin');
module.exports = {
optimization: {
minimize: true,
minimizer: [
// JS 压缩
new TerserPlugin({
parallel: true, // 多核并行
terserOptions: {
compress: {
drop_console: true, // 删除 console
drop_debugger: true,
pure_funcs: ['console.log'] // 删除特定函数
},
format: {
comments: false // 删除注释
}
},
extractComments: false
}),
// CSS 压缩
new CssMinimizerPlugin()
]
},
plugins: [
// Gzip 压缩
new CompressionPlugin({
algorithm: 'gzip',
test: /\.(js|css|html|svg)$/,
threshold: 10240, // 只压缩 >10KB 的文件
minRatio: 0.8
})
]
};
第六步:externals + CDN
javascript
// webpack.config.js
module.exports = {
externals: {
'react': 'React',
'react-dom': 'ReactDOM',
'vue': 'Vue',
'axios': 'axios'
}
};
// index.html
<script src="https://cdn.jsdelivr.net/npm/react@18/umd/react.production.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/react-dom@18/umd/react-dom.production.min.js"></script>
结果(Result)
| 指标 | 优化前 | 优化后 | 提升 |
|---|---|---|---|
| 总体积 | 8.5MB | 1.2MB | -86% |
| JS 体积 | 6.2MB | 800KB | -87% |
| CSS 体积 | 1.5MB | 200KB | -87% |
| 首屏资源 | 8.5MB | 500KB | -94% |
| FCP | 4.2s | 1.1s | -74% |
| LCP | 5.8s | 1.5s | -74% |
| Gzip 后 | 3.2MB | 350KB | -89% |
详细拆分:
优化前:
├── main.js: 4.2MB
├── vendor.js: 2.0MB
├── main.css: 1.5MB
└── assets: 0.8MB
优化后:
├── main.js: 200KB (路由懒加载)
├── vendor-react.js: 150KB (CDN)
├── vendor-antd.js: 250KB (按需)
├── chunk-dashboard.js: 180KB (按需)
├── chunk-users.js: 120KB (按需)
├── main.css: 150KB (PurgeCSS)
└── assets: 150KB (压缩)
三、Rspack 迁移方案
场景一:面试官问"为什么要迁移到 Rspack?迁移过程是怎样的?"
【核心回答框架】
"Rspack 是基于 Rust 的高性能打包工具,我们的迁移带来了 15 倍的构建提速。"
背景(Situation): Webpack 构建速度成为瓶颈:
- 开发环境冷启动: 45s
- HMR 热更新: 2-3s
- 生产环境构建: 120s
- CI 流水线耗时: 8分钟
任务(Task): 评估并迁移到 Rspack,保持现有功能的同时大幅提升构建速度。
行动(Action)
第一步:兼容性评估
Rspack 兼容性对比:
| 功能 | Webpack | Rspack | 兼容性 |
|---|---|---|---|
| Loader | ✅ | ⚠️ 部分支持 | 90% |
| Plugin | ✅ | ⚠️ 核心插件支持 | 70% |
| Code Splitting | ✅ | ✅ | 100% |
| Tree Shaking | ✅ | ✅ | 100% |
| HMR | ✅ | ✅ | 100% |
| Source Map | ✅ | ✅ | 100% |
不兼容的插件处理:
javascript
// ❌ Rspack 不支持
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
// ✅ Rspack 内置持久化缓存,无需插件
第二步:迁移配置对比
Webpack 配置:
javascript
// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
mode: 'development',
entry: './src/main.ts',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js'
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx'],
alias: {
'@': path.resolve(__dirname, 'src')
}
},
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader']
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './index.html'
}),
new MiniCssExtractPlugin()
]
};
Rspack 配置(几乎一致):
javascript
// rspack.config.js
const rspack = require('@rspack/core');
module.exports = {
mode: 'development',
entry: './src/main.ts',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js'
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx'],
alias: {
'@': path.resolve(__dirname, 'src')
}
},
module: {
rules: [
{
test: /\.tsx?$/,
use: 'builtin:swc-loader', // 内置 SWC loader
options: {
jsc: {
parser: {
syntax: 'typescript',
tsx: true
}
}
},
type: 'javascript/auto'
},
{
test: /\.css$/,
use: [rspack.CssExtractRspackPlugin.loader, 'css-loader']
}
]
},
plugins: [
new rspack.HtmlRspackPlugin({
template: './index.html'
}),
new rspack.CssExtractRspackPlugin()
],
// Rspack 特有配置
experiments: {
css: true // 内置 CSS 支持
}
};
第三步:渐进式迁移策略
方案一:双配置并行运行
json
// package.json
{
"scripts": {
"dev:webpack": "webpack serve --config webpack.config.js",
"dev:rspack": "rspack serve --config rspack.config.js",
"build:webpack": "webpack --config webpack.config.js",
"build:rspack": "rspack build --config rspack.config.js"
}
}
方案二:功能验证清单
markdown
## 迁移验证清单
- [x] 开发环境启动
- [x] HMR 热更新
- [x] TypeScript 编译
- [x] CSS 模块化
- [x] 图片/字体资源处理
- [x] 代码分割
- [x] 环境变量注入
- [x] Source Map 生成
- [x] 生产构建
- [x] Gzip 压缩
第四步:性能对比测试
测试脚本:
javascript
// benchmark.js
const { execSync } = require('child_process');
const fs = require('fs');
function measureBuildTime(command) {
const start = Date.now();
try {
execSync(command, { stdio: 'inherit' });
const end = Date.now();
return (end - start) / 1000; // 转为秒
} catch (error) {
console.error('构建失败:', error.message);
return null;
}
}
// 清除缓存
fs.rmSync('./dist', { recursive: true, force: true });
fs.rmSync('./.rspack-cache', { recursive: true, force: true });
fs.rmSync('./node_modules/.cache', { recursive: true, force: true });
console.log('🔨 开始 Webpack 构建...');
const webpackTime = measureBuildTime('npm run build:webpack');
console.log(`⏱️ Webpack 构建时间: ${webpackTime}s\n`);
// 清除缓存
fs.rmSync('./dist', { recursive: true, force: true });
console.log('🔨 开始 Rspack 构建...');
const rspackTime = measureBuildTime('npm run build:rspack');
console.log(`⏱️ Rspack 构建时间: ${rspackTime}s\n`);
if (webpackTime && rspackTime) {
const speedup = (webpackTime / rspackTime).toFixed(2);
console.log(`🚀 Rspack 构建速度提升: ${speedup}x`);
}
第五步:优化 Rspack 配置
javascript
// rspack.config.js (优化版)
module.exports = {
// 启用持久化缓存
cache: {
type: 'filesystem',
cacheDirectory: path.resolve(__dirname, '.rspack-cache')
},
// 优化解析
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx'],
alias: {
'@': path.resolve(__dirname, 'src')
},
// 减少查找范围
modules: ['node_modules']
},
// 使用内置 SWC
module: {
rules: [
{
test: /\.tsx?$/,
use: {
loader: 'builtin:swc-loader',
options: {
jsc: {
parser: {
syntax: 'typescript',
tsx: true,
decorators: true
},
transform: {
react: {
runtime: 'automatic'
}
},
target: 'es2015'
}
}
},
type: 'javascript/auto'
}
]
},
// 优化分割
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: 10
}
}
}
},
// 开启实验性功能
experiments: {
css: true,
asyncWebAssembly: true
}
};
结果(Result)
| 指标 | Webpack | Rspack | 提升 |
|---|---|---|---|
| 冷启动 | 45s | 2.8s | 16x ⚡ |
| HMR 更新 | 2-3s | 100ms | 20-30x ⚡ |
| 生产构建 | 120s | 8s | 15x ⚡ |
| 二次构建(缓存) | 25s | 1.5s | 16.6x ⚡ |
| CI 流水线 | 8min | 35s | 13.7x ⚡ |
| 内存占用 | 1.8GB | 600MB | -67% |
开发体验提升:
Webpack 开发流程:
修改代码 → 等待 3s → 页面更新 → 😰 打断思路
Rspack 开发流程:
修改代码 → 100ms → 页面更新 → 😊 流畅开发
场景二:面试官追问"遇到了哪些坑?怎么解决的?"
【问题解决回答】
坑1:部分 Loader 不兼容
问题:
javascript
// babel-loader 在 Rspack 中可能有问题
{
test: /\.jsx?$/,
use: 'babel-loader' // ❌ 可能不兼容
}
解决方案:
javascript
// 使用 Rspack 内置的 builtin:swc-loader
{
test: /\.jsx?$/,
use: {
loader: 'builtin:swc-loader',
options: {
jsc: {
parser: {
syntax: 'ecmascript',
jsx: true
}
}
}
}
}
// 或者配置 Babel 兼容层
{
test: /\.jsx?$/,
use: {
loader: 'babel-loader',
options: {
// 禁用缓存,让 Rspack 接管
cacheDirectory: false
}
},
type: 'javascript/auto'
}
坑2:CSS Module 配置差异
问题:
javascript
// Webpack 写法
{
test: /\.module\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: true
}
}
]
}
Rspack 写法:
javascript
{
test: /\.module\.css$/,
type: 'css/module', // 使用内置类型
parser: {
namedExports: true
}
}
坑3:环境变量注入方式
Webpack DefinePlugin:
javascript
new webpack.DefinePlugin({
'process.env.API_URL': JSON.stringify(process.env.API_URL)
})
Rspack DefinePlugin:
javascript
new rspack.DefinePlugin({
'process.env.API_URL': JSON.stringify(process.env.API_URL)
})
推荐统一方案(使用 dotenv):
javascript
// .env
API_URL=https://api.example.com
// rspack.config.js
require('dotenv').config();
new rspack.DefinePlugin({
'process.env': JSON.stringify(process.env)
})
四、构建速度提升 10 倍技巧
场景一:面试官问"构建速度优化的系统性方案是什么?"
【核心回答框架】
"我从缓存、并行、精简三个维度系统优化,最终实现 10 倍提速。"
背景(Situation): CI 构建时间 380s,严重影响迭代效率:
- 开发提交代码后等待 6 分钟才能看到结果
- 每天 50+ 次构建,累计浪费 5 小时
- 构建队列排队严重
任务(Task): 将 CI 构建时间降至 40s 以内。
行动(Action)
优化维度一:缓存策略(提速 4-5 倍)
1. 持久化缓存
Webpack 5 缓存配置:
javascript
module.exports = {
cache: {
type: 'filesystem',
cacheDirectory: path.resolve(__dirname, '.webpack-cache'),
buildDependencies: {
config: [__filename] // 配置变更时清除缓存
},
name: `${process.env.NODE_ENV}-cache`
}
};
效果:
首次构建: 180s
二次构建(缓存命中): 35s
提速: 5.1x
2. Loader 缓存
javascript
module.exports = {
module: {
rules: [
{
test: /\.jsx?$/,
use: [
{
loader: 'babel-loader',
options: {
cacheDirectory: true, // 开启缓存
cacheCompression: false // 不压缩缓存
}
}
]
},
{
test: /\.tsx?$/,
use: [
'cache-loader', // 缓存 loader 结果
'ts-loader'
]
}
]
}
};
3. CI 缓存配置
GitHub Actions 缓存:
yaml
name: Build
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
# 缓存 node_modules
- name: Cache node_modules
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
# 缓存 Webpack 构建
- name: Cache webpack build
uses: actions/cache@v3
with:
path: |
.webpack-cache
dist
key: ${{ runner.os }}-build-${{ hashFiles('src/**') }}
- run: npm ci
- run: npm run build
优化维度二:并行编译(提速 2-3 倍)
1. thread-loader 多线程编译
javascript
module.exports = {
module: {
rules: [
{
test: /\.tsx?$/,
use: [
{
loader: 'thread-loader',
options: {
workers: require('os').cpus().length - 1, // 留一个核心给主进程
poolTimeout: Infinity // 开发环境不超时
}
},
'ts-loader'
]
}
]
}
};
注意事项:
- 仅用于耗时的 loader(如 babel-loader、ts-loader)
- 小项目可能适得其反(线程创建开销)
2. TerserPlugin 并行压缩
javascript
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true, // 使用多核
terserOptions: {
compress: {
drop_console: true
}
}
})
]
}
};
效果:
串行压缩: 50s
并行压缩(8核): 12s
提速: 4.1x
3. HappyPack(Webpack 4 及以下)
javascript
const HappyPack = require('happypack');
const os = require('os');
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: 'happypack/loader?id=babel',
exclude: /node_modules/
}
]
},
plugins: [
new HappyPack({
id: 'babel',
loaders: ['babel-loader?cacheDirectory'],
threadPool: happyThreadPool
})
]
};
优化维度三:编译范围精简(提速 2-3 倍)
1. 精确匹配规则
javascript
module.exports = {
module: {
rules: [
{
test: /\.jsx?$/,
include: path.resolve(__dirname, 'src'), // 只编译 src
exclude: /node_modules/, // 排除 node_modules
use: 'babel-loader'
}
]
}
};
2. noParse 跳过解析
javascript
module.exports = {
module: {
noParse: /jquery|lodash/, // 跳过大型库的解析
rules: [...]
}
};
适用场景:
- 不依赖模块化的库(jQuery、Lodash)
- 已经打包好的库
3. DllPlugin 预编译
第一步:构建 DLL
javascript
// webpack.dll.config.js
const webpack = require('webpack');
const path = require('path');
module.exports = {
mode: 'production',
entry: {
vendor: ['react', 'react-dom', 'react-router-dom', 'axios']
},
output: {
path: path.resolve(__dirname, 'dll'),
filename: '[name].dll.js',
library: '[name]_library'
},
plugins: [
new webpack.DllPlugin({
path: path.resolve(__dirname, 'dll/[name]-manifest.json'),
name: '[name]_library'
})
]
};
第二步:引用 DLL
javascript
// webpack.config.js
const webpack = require('webpack');
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');
module.exports = {
plugins: [
new webpack.DllReferencePlugin({
manifest: require('./dll/vendor-manifest.json')
}),
new AddAssetHtmlPlugin({
filepath: path.resolve(__dirname, 'dll/vendor.dll.js')
})
]
};
效果:
首次构建: 180s
使用 DLL 后: 50s
提速: 3.6x
4. externals 外部化
javascript
module.exports = {
externals: {
'react': 'React',
'react-dom': 'ReactDOM',
'lodash': '_',
'moment': 'moment'
}
};
配合 CDN:
html
<script src="https://cdn.jsdelivr.net/npm/react@18/umd/react.production.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/react-dom@18/umd/react-dom.production.min.js"></script>
优化维度四:替换编译工具(提速 10-20 倍)
1. esbuild-loader 替代 babel-loader
javascript
module.exports = {
module: {
rules: [
{
test: /\.jsx?$/,
use: {
loader: 'esbuild-loader',
options: {
loader: 'jsx',
target: 'es2015'
}
}
},
{
test: /\.tsx?$/,
use: {
loader: 'esbuild-loader',
options: {
loader: 'tsx',
target: 'es2015'
}
}
}
]
}
};
性能对比:
babel-loader: 120s
esbuild-loader: 6s
提速: 20x
2. swc-loader 替代
javascript
module.exports = {
module: {
rules: [
{
test: /\.tsx?$/,
use: {
loader: 'swc-loader',
options: {
jsc: {
parser: {
syntax: 'typescript',
tsx: true
},
transform: {
react: {
runtime: 'automatic'
}
}
}
}
}
}
]
}
};
优化维度五:优化 resolve 配置
javascript
module.exports = {
resolve: {
// 减少后缀尝试
extensions: ['.js', '.jsx', '.ts', '.tsx'],
// 别名减少查找
alias: {
'@': path.resolve(__dirname, 'src'),
'components': path.resolve(__dirname, 'src/components')
},
// 指定查找目录
modules: [path.resolve(__dirname, 'node_modules')],
// 禁用软链接
symlinks: false,
// 指定 main 字段
mainFields: ['browser', 'module', 'main']
}
};
优化维度六:开发环境优化
javascript
module.exports = {
mode: 'development',
// 使用 eval-cheap-module-source-map
devtool: 'eval-cheap-module-source-map', // 最快的 source-map
// 优化 watch 配置
watchOptions: {
ignored: /node_modules/,
aggregateTimeout: 300,
poll: false
},
// 开发服务器优化
devServer: {
hot: true,
liveReload: false
},
optimization: {
removeAvailableModules: false,
removeEmptyChunks: false,
splitChunks: false, // 开发环境不分割
runtimeChunk: true
}
};
综合优化效果
完整优化对比
| 优化项 | 初始 | 优化后 | 提速 |
|---|---|---|---|
| 基础构建 | 380s | 380s | 1x |
| + 持久化缓存 | 380s | 75s | 5x |
| + 并行编译 | 75s | 35s | 10.8x |
| + 精简范围 | 35s | 20s | 19x |
| + esbuild | 20s | 8s | 47.5x |
| + Rspack | 8s | 3s | 126x |
分场景对比
开发环境冷启动:
优化前: 45s
优化后: 2.8s (16x)
开发环境 HMR:
优化前: 2-3s
优化后: 100ms (20-30x)
生产环境构建:
优化前: 380s
优化后: 35s (10.8x)
CI 构建(含缓存):
优化前: 380s
优化后: 15s (25x)
面试高频追问
Q1: 如何判断是否需要优化构建速度?
回答: 看三个指标:
- 开发体验:HMR > 1s 影响开发流畅度
- CI 效率:构建 > 5min 影响迭代速度
- 成本:构建机器资源占用过高
我们项目达到了这三个阈值,所以启动优化。
Q2: 缓存失效了怎么办?
回答: 建立缓存失效机制:
javascript
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename], // 配置变更清缓存
packages: ['package-lock.json'] // 依赖变更清缓存
},
version: process.env.CACHE_VERSION || '1.0' // 手动控制
}
Q3: 多核并行是否一定更快?
回答: 不一定,需要考虑:
- 项目规模:小项目(<100个文件)多核开销可能大于收益
- 机器配置:CI 机器核心数
- 编译任务:只对耗时任务(babel/ts)使用多核
我们通过 benchmark 测试选择最优方案。
Q4: 为什么不直接用 Vite?
回答: 技术选型考虑:
- 历史包袱:老项目迁移成本高
- 生态兼容:部分插件只支持 Webpack
- 渐进式优化:Rspack 可以平滑迁移
新项目我们优先选 Vite。
简历亮点总结
一句话总结:
系统性优化前端构建流程,通过缓存、并行、工具替换等手段,将 CI 构建从 380s 降至 35s(10.8倍),开发环境启动从 45s 降至 2.8s(16倍),大幅提升团队效率。
可量化指标:
- 构建速度提升 10.8 倍
- HMR 提速 20 倍
- CI 资源成本降低 70%
- 团队开发效率提升 40%
- 包体积减少 86%