返回笔记首页

构建工具深度优化

主题配置

简历描述模板(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+ 页面后,遇到三个核心问题:

  1. 路由配置繁琐:每新增一个页面需要手动配置路由,容易遗漏
  2. 组件导入重复:每个文件都要 import 常用组件,代码冗余
  3. 开发体验割裂:Markdown 文档无法集成到项目中预览

任务(Task): 我负责开发 Vite 插件生态,目标是提升开发效率、减少重复劳动。

行动(Action)

1️⃣ 自动路由生成插件

核心思路:基于文件系统约定式路由,类似 Nuxt.js

typescript

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

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
});

文件结构约定

plain
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

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

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

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;

text
  if (match) {
    frontmatter = parseFrontmatter(match[1]);
    content = code.slice(match[0].length);
  }

  // 渲染 Markdown
  const html = md.render(content);

  // 生成 Vue 组件
  const component = `
text
  `.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; }

text

**使用示例**:

markdown

```markdown
<!-- docs/guide.md -->
---
title: 快速开始
author: 张三
date: 2024-01-01
---

# 快速开始

这是一个示例文档

## 安装

\`\`\`bash
npm install
\`\`\`

vue

vue
<!-- 在 Vue 组件中使用 -->
<script setup>
import Guide from './docs/guide.md';
</script>

<template>
  <Guide />
</template>

结果(Result)
  • 开发效率:路由配置时间从 10min/页面 降至 0,组件导入代码减少 80%
  • 代码质量:避免人工配置错误,路由配置准确率 100%
  • 团队协作:新人上手时间从 3 天降至半天
  • 文档体验:Markdown 文档与项目无缝集成,文档更新率提升 200%

插件数据对比

plain
优化前:
- 新增页面耗时: 15min(配置路由 + 导入组件)
- 路由配置错误率: 15%
- 代码量: 每个文件平均 8 行 import

优化后:
- 新增页面耗时: 2min(只写页面代码)
- 路由配置错误率: 0%
- 代码量: 0 行 import(自动注入)

场景二:面试官追问"Vite 插件开发的核心原理是什么?"

【深度技术回答】

"Vite 插件本质是一个带钩子函数的对象,通过 Rollup 兼容的钩子系统介入构建流程。"

Vite 插件生命周期

plain
服务器启动
  ↓
configResolved (配置解析完成)
  ↓
buildStart (开始构建)
  ↓
resolveId (解析模块 ID)
  ↓
load (加载模块内容)
  ↓
transform (转换模块代码)
  ↓
buildEnd (构建结束)

核心钩子详解

typescript

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

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

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

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

javascript
// webpack.config.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode: 'static',
      reportFilename: 'bundle-report.html',
      openAnalyzer: false
    })
  ]
};

分析结果发现

  1. moment.js 占用 500KB(包含所有语言包)
  2. lodash 占用 300KB(全量引入)
  3. echarts 占用 800KB(全量引入)
  4. antd 占用 1.2MB(未按需加载)
  5. source-map 占用 2MB(生产环境未关闭)

第二步:依赖替换

javascript

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

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

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

javascript
// webpack.config.js
module.exports = {
  mode: 'production',
  optimization: {
    usedExports: true, // 标记未使用的导出
    sideEffects: false // 允许删除无副作用的模块
  }
};

// package.json
{
  "sideEffects": [
    "*.css",
    "*.scss",
    "*.less"
  ]
}

确保 ESM 格式

javascript

javascript
// ❌ CommonJS 无法 Tree Shaking
const utils = require('./utils');

// ✅ ESM 可以 Tree Shaking
import { add, subtract } from './utils';

第五步:压缩优化

javascript

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

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%

详细拆分

plain
优化前:
├── 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

javascript
// ❌ Rspack 不支持
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');

// ✅ Rspack 内置持久化缓存,无需插件

第二步:迁移配置对比

Webpack 配置

javascript

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

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

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

markdown
## 迁移验证清单
- [x] 开发环境启动
- [x] HMR 热更新
- [x] TypeScript 编译
- [x] CSS 模块化
- [x] 图片/字体资源处理
- [x] 代码分割
- [x] 环境变量注入
- [x] Source Map 生成
- [x] 生产构建
- [x] Gzip 压缩

第四步:性能对比测试

测试脚本

javascript

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

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%

开发体验提升

plain
Webpack 开发流程:
修改代码 → 等待 3s → 页面更新 → 😰 打断思路

Rspack 开发流程:
修改代码 → 100ms → 页面更新 → 😊 流畅开发

场景二:面试官追问"遇到了哪些坑?怎么解决的?"

【问题解决回答】

坑1:部分 Loader 不兼容

问题

javascript

javascript
// babel-loader 在 Rspack 中可能有问题
{
  test: /\.jsx?$/,
  use: 'babel-loader' // ❌ 可能不兼容
}

解决方案

javascript

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

javascript
// Webpack 写法
{
  test: /\.module\.css$/,
  use: [
    'style-loader',
    {
      loader: 'css-loader',
      options: {
        modules: true
      }
    }
  ]
}

Rspack 写法

javascript

javascript
{
  test: /\.module\.css$/,
  type: 'css/module', // 使用内置类型
  parser: {
    namedExports: true
  }
}

坑3:环境变量注入方式

Webpack DefinePlugin

javascript

javascript
new webpack.DefinePlugin({
  'process.env.API_URL': JSON.stringify(process.env.API_URL)
})

Rspack DefinePlugin

javascript

javascript
new rspack.DefinePlugin({
  'process.env.API_URL': JSON.stringify(process.env.API_URL)
})

推荐统一方案(使用 dotenv)

javascript

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

javascript
module.exports = {
  cache: {
    type: 'filesystem',
    cacheDirectory: path.resolve(__dirname, '.webpack-cache'),
    buildDependencies: {
      config: [__filename] // 配置变更时清除缓存
    },
    name: `${process.env.NODE_ENV}-cache`
  }
};

效果

plain
首次构建: 180s
二次构建(缓存命中): 35s
提速: 5.1x

2. Loader 缓存

javascript

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

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

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

javascript
const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        parallel: true, // 使用多核
        terserOptions: {
          compress: {
            drop_console: true
          }
        }
      })
    ]
  }
};

效果

plain
串行压缩: 50s
并行压缩(8核): 12s
提速: 4.1x

3. HappyPack(Webpack 4 及以下)

javascript

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

javascript
module.exports = {
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        include: path.resolve(__dirname, 'src'), // 只编译 src
        exclude: /node_modules/, // 排除 node_modules
        use: 'babel-loader'
      }
    ]
  }
};

2. noParse 跳过解析

javascript

javascript
module.exports = {
  module: {
    noParse: /jquery|lodash/, // 跳过大型库的解析
    rules: [...]
  }
};

适用场景

  • 不依赖模块化的库(jQuery、Lodash)
  • 已经打包好的库

3. DllPlugin 预编译

第一步:构建 DLL

javascript

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

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')
    })
  ]
};

效果

plain
首次构建: 180s
使用 DLL 后: 50s
提速: 3.6x

4. externals 外部化

javascript

javascript
module.exports = {
  externals: {
    'react': 'React',
    'react-dom': 'ReactDOM',
    'lodash': '_',
    'moment': 'moment'
  }
};

配合 CDN:

html

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

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'
          }
        }
      }
    ]
  }
};

性能对比

plain
babel-loader: 120s
esbuild-loader: 6s
提速: 20x

2. swc-loader 替代

javascript

javascript
module.exports = {
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: {
          loader: 'swc-loader',
          options: {
            jsc: {
              parser: {
                syntax: 'typescript',
                tsx: true
              },
              transform: {
                react: {
                  runtime: 'automatic'
                }
              }
            }
          }
        }
      }
    ]
  }
};

优化维度五:优化 resolve 配置

javascript

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

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

分场景对比

plain
开发环境冷启动:
优化前: 45s
优化后: 2.8s (16x)

开发环境 HMR:
优化前: 2-3s
优化后: 100ms (20-30x)

生产环境构建:
优化前: 380s
优化后: 35s (10.8x)

CI 构建(含缓存):
优化前: 380s
优化后: 15s (25x)

面试高频追问

Q1: 如何判断是否需要优化构建速度?

回答: 看三个指标:

  1. 开发体验:HMR > 1s 影响开发流畅度
  2. CI 效率:构建 > 5min 影响迭代速度
  3. 成本:构建机器资源占用过高

我们项目达到了这三个阈值,所以启动优化。


Q2: 缓存失效了怎么办?

回答: 建立缓存失效机制:

javascript

javascript
cache: {
  type: 'filesystem',
  buildDependencies: {
    config: [__filename], // 配置变更清缓存
    packages: ['package-lock.json'] // 依赖变更清缓存
  },
  version: process.env.CACHE_VERSION || '1.0' // 手动控制
}

Q3: 多核并行是否一定更快?

回答: 不一定,需要考虑:

  1. 项目规模:小项目(<100个文件)多核开销可能大于收益
  2. 机器配置:CI 机器核心数
  3. 编译任务:只对耗时任务(babel/ts)使用多核

我们通过 benchmark 测试选择最优方案。


Q4: 为什么不直接用 Vite?

回答: 技术选型考虑:

  1. 历史包袱:老项目迁移成本高
  2. 生态兼容:部分插件只支持 Webpack
  3. 渐进式优化:Rspack 可以平滑迁移

新项目我们优先选 Vite。


简历亮点总结

一句话总结

系统性优化前端构建流程,通过缓存、并行、工具替换等手段,将 CI 构建从 380s 降至 35s(10.8倍),开发环境启动从 45s 降至 2.8s(16倍),大幅提升团队效率。

可量化指标

  • 构建速度提升 10.8 倍
  • HMR 提速 20 倍
  • CI 资源成本降低 70%
  • 团队开发效率提升 40%
  • 包体积减少 86%