前端开发面试题:工程化(23 题)

技术 · 2025-06-26
前端开发面试题:工程化(23 题)

2026-06-26T15:45:03.png

E1:package.json 中 devDependencies 和 dependencies 区别?

字段说明打包是否包含
dependencies运行时依赖,项目运行必需
devDependencies开发时依赖,仅本地开发、测试、构建需要
peerDependencies同伴依赖,期望使用者提供由宿主安装
optionalDependencies可选依赖,安装失败不报错由宿主选择

示例

{
  "dependencies": {
    "vue": "^3.4.0",         // 运行必需
    "axios": "^1.6.0"
  },
  "devDependencies": {
    "vite": "^5.0.0",        // 构建工具
    "typescript": "^5.3.0",
    "@types/node": "^20.0.0"
  }
}

最佳实践

  • 发布 npm 包时,peerDependencies 声明框架版本,避免重复安装;
  • 发布组件库时,业务依赖放 dependencies,开发工具放 devDependencies
  • 使用 npm i --save-dev xxx / npm i xxx 区分。

E2:Webpack 5 的主要升级点

  1. 持久化缓存cache: { type: 'filesystem' },二次构建提速 90%+;
  2. 资源模块(Asset Modules):取代 file-loader / url-loader / raw-loader,默认内置;
  3. Module Federation:原生支持微前端、跨应用共享模块;
  4. Tree Shaking 增强:能追踪到嵌套模块导出、支持 sideEffects: false
  5. Web Worker 支持new Worker(new URL('./worker.js', import.meta.url))
  6. Node Polyfills 自动移除:仅对前端需要的 polyfill 提供;
  7. 新生成器(experiments)output.module: true 输出 ESM;
  8. 更好的 SourceMapdevtool: 'eval-cheap-module-source-map' 更精准;
  9. Chunk 拆分算法优化:默认 splitChunks 策略更智能。

E3:Vite 的原理

(与 V15 互补,更详细)

冷启动快的原因

  1. 不走打包:开发模式不做全量 bundling;
  2. 预构建依赖:用 esbuild(Go 编写,比 JS 快 10-100 倍)预编译 node_modules 为 ESM;
  3. 按需编译:浏览器请求哪个模块才编译哪个,请求与编译并行;
  4. 原生 ESM:浏览器原生加载 ESM,无需 IIFE / AMD 包装。

热更新 HMR

// vite 内部
const watcher = chokidar.watch(root, { ignored: ['**/.git/**', '**/node_modules/**'] });
watcher.on('change', async file => {
  await invalidateModule(file);
  ws.send({ type: 'update', path: file, timestamp: Date.now() });
});
  • WebSocket 推送更新;
  • 浏览器仅请求变更的模块;
  • 框架插件(vue / react-refresh)接收更新,热替换组件。

生产构建:使用 Rollup(生态丰富、产物优化好)。


E4:与 Webpack 类似的工具及区别

工具定位特点
Vite新一代构建工具开发用 esbuild + 原生 ESM,生产用 Rollup
Rollup库打包首选输出更干净,Tree Shaking 优秀
Parcel零配置自动处理依赖,但生态较小
esbuildGo 写的极速编译器仅做转换,不做打包
swcRust 写的 Babel 替代比 Babel 快 20 倍
TurbopackWebpack 作者新项目Rust 编写,Webpack 继任者(Next.js 团队)
Bun全能运行时自带打包器,性能优异
RspackRust 版的 Webpack兼容 Webpack 生态,速度提升 5-10 倍

选型建议

  • 应用开发:Vite(首选)、Rspack(大型项目);
  • 组件库开发:Rollup;
  • 极致构建速度:esbuild / swc;
  • 存量 Webpack 项目:短期不动,长期考虑迁移。

E5:如何借助 Webpack 优化前端性能

(与 P-OPT21 互补,补充更多细节)

1. 体积优化

// webpack.config.js
module.exports = {
  mode: 'production', // 启用 production 内置优化
  optimization: {
    minimize: true,
    minimizer: [new TerserPlugin({ parallel: true })],
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          priority: 10,
          name: 'vendor'
        }
      }
    }
  },
  performance: {
    hints: 'warning',
    maxAssetSize: 250000,
    maxEntrypointSize: 250000
  }
};

2. 加载优化

// 动态导入
const LazyComp = () => import('./LazyComp.vue');

// Prefetch / Preload
import(/* webpackPrefetch: true */ './LoginModal');

3. 持久化缓存

cache: {
  type: 'filesystem',
  buildDependencies: { config: [__filename] }
}

E6:Webpack Proxy 工作原理与跨域解决

跨域原因:浏览器同源策略,协议 / 域名 / 端口不同即为跨域。

Proxy 解决方案

// vue.config.js / webpack.config.js
module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'http://backend.com',
        changeOrigin: true,
        pathRewrite: { '^/api': '' }
      }
    }
  }
};

工作原理

  1. 开发服务器(webpack-dev-server / vite)启动 HTTP 服务;
  2. 浏览器请求 /api/users 实际请求本地开发服务器(同源);
  3. 开发服务器将请求转发到真实后端(代理突破浏览器同源限制);
  4. 后端响应返回给开发服务器,再返回给浏览器。

为什么能解决跨域

  • 浏览器只关心协议 + 域名 + 端口
  • 跨域是浏览器行为,服务器之间无跨域限制;
  • Proxy 是服务器端代理,绕过了浏览器同源策略。

注意:仅开发环境有效,生产需用 Nginx / 后端 CORS 配置。


E7:Webpack 热更新原理

HMR(Hot Module Replacement):运行时替换、添加、删除模块,无需刷新页面

工作流程

  1. 监听文件变化:webpack 使用 webpack-dev-middleware 调用 chokidar 监听;
  2. 重新编译:文件变化后,webpack 增量编译(仅编译变更模块);
  3. 推送更新:webpack-dev-server 通过 WebSocket 向浏览器推送 { type: 'hash', data: hash }
  4. 拉取更新:浏览器收到通知,通过 HotModuleReplacementPlugin 运行时请求 hash.hot-update.jsonchunk.hash.hot-update.js
  5. 模块替换:在浏览器端按模块 ID 替换旧模块,触发 module.hot.accept 回调。

核心代码

// 业务模块
if (module.hot) {
  module.hot.accept('./library.js', () => {
    // 重新执行使用 library 的代码
    doSomething();
  });
}

E8:Loader 和 Plugin 的区别?如何编写?

维度LoaderPlugin
作用转换模块源码扩展 webpack 生命周期
配置module.rulesplugins 数组
本质函数,接收源码返回新源码类,apply 方法挂载钩子
数量多对一处理全局干预

编写 Loader(示例:自定义 markdown-loader)

module.exports = function(source: string) {
  // this 是 webpack 提供的上下文
  this.cacheable && this.cacheable();
  const html = marked(source);
  return `module.exports = ${JSON.stringify(html)}`;
};
// module.exports.raw = true; // 如果要接收二进制

编写 Plugin(示例:自定义 WriteFilePlugin)

class WriteFilePlugin {
  apply(compiler: any) {
    compiler.hooks.emit.tapAsync('WriteFilePlugin', (compilation: any, cb: Function) => {
      for (const [name, asset] of Object.entries(compilation.assets)) {
        require('fs').writeFileSync(`./dist/${name}`, asset.source());
      }
      cb();
    });
  }
}

E9:常见 Webpack Plugin 及解决的问题

Plugin作用
HtmlWebpackPlugin自动生成 HTML 并注入 bundle
MiniCssExtractPlugin提取 CSS 到单独文件
TerserPluginJS 压缩
CssMinimizerPluginCSS 压缩
DefinePlugin定义编译时全局常量
CopyWebpackPlugin拷贝静态资源
CleanWebpackPlugin清理输出目录
BundleAnalyzerPlugin可视化分析包体积
HotModuleReplacementPlugin启用 HMR
ProvidePlugin自动加载模块(如 $)
IgnorePlugin忽略指定模块
CompressionPlugin预生成 gzip 文件
ForkTsCheckerWebpackPluginTS 类型检查
ModuleFederationPlugin微前端 / 模块联邦

E10:常见 Webpack Loader 及解决的问题

Loader作用
babel-loaderES6+ → ES5
ts-loader / esbuild-loader / swc-loaderTS / JSX 转译
css-loader解析 CSS 文件中的 @import / url()
style-loader将 CSS 注入 <style>
sass-loader / less-loader / stylus-loaderCSS 预处理器
postcss-loaderPostCSS 处理(autoprefixer 等)
file-loader(已废) / Asset Modules处理文件资源
url-loader(已废)小图转 base64
vue-loaderVue 单文件组件
eslint-loader / stylelint-loader代码检查
raw-loader以字符串形式导入文件
thread-loader多线程加速

E11:Webpack 的构建流程

六大阶段

  1. 初始化(Initialization)

    • 读取配置、加载 Plugin、注册 Compiler 钩子;
  2. 编译(Compilation)

    • 从入口文件出发,调用 Loader 转换模块;
    • 构建模块依赖图(Module Graph);
  3. 完成编译(Finish Make)

    • 收集所有模块、依赖关系;
  4. 封装(Sealing)

    • 生成 Chunk、合并 SplitChunks、优化产物;
    • 触发 Plugin 钩子;
  5. 输出(Emit)

    • 将 Chunk 写入磁盘;
    • 触发 emit 钩子;
  6. 完成(Done)

    • 触发 done 钩子。

简化流程图

entry → loader处理模块 → 构建依赖图 → splitChunks → 优化 → 写入dist

E12:对 Webpack 的理解?解决了什么问题?

Webpack 本质:静态模块打包器(Static Module Bundler)。

核心能力

  1. 模块化:支持 ESM / CommonJS / AMD / CSS / 图片等一切资源作为模块;
  2. 依赖管理:自动解析模块依赖,构建依赖图;
  3. 代码分割:按需加载、提取公共代码;
  4. Loader / Plugin 生态:转换和扩展能力强;
  5. 开发体验:HMR、Source Map、devServer。

解决的问题

  • 浏览器不识别 import / require,需打包成单文件;
  • 多文件加载导致 HTTP 请求过多;
  • 高级语法(TS / JSX / Sass)浏览器不识别;
  • 代码组织混乱,无法模块化开发。

生态地位:曾经的事实标准(Vue 2 / React 主流选择),现在被 Vite 挑战。


E13:Loader 和 Plugin 的实现原理

Loader 实现原理

  • 本质是一个函数,接收模块源码,返回转换后源码;
  • webpack 调用 Loader 时通过 this 注入上下文(query / cacheable / addDependency 等);
  • Loader 默认接收字符串,可通过 module.exports.raw = true 接收 Buffer;
  • 支持链式调用(从右到左、从下到上)。

Plugin 实现原理

  • 本质是一个带 apply 方法的类;
  • apply 接收 compiler 对象,通过 compiler.hooks.<name>.tap(...) 注册钩子回调;
  • webpack 在不同生命周期触发钩子,Plugin 在特定时机介入。

常用钩子

compiler.hooks.beforeRun.tap('Plugin', () => {});
compiler.hooks.compile.tap('Plugin', () => {});
compilation.hooks.optimize.tap('Plugin', () => {});
compilation.hooks.emit.tapAsync('Plugin', (compilation, cb) => cb());

E14:如何提高 Webpack 构建速度

  1. 升级到 Webpack 5 + 开启 cache: { type: 'filesystem' }
  2. 多线程thread-loaderterser-webpack-plugin(默认多核);
  3. 缩小构建范围

    module: {
      rules: [
        { test: /\.js$/, include: /src/, exclude: /node_modules/ }
      ]
    }
  4. DLLPlugin:预打包不常变动的依赖;
  5. 减少 Loader 处理:少用昂贵的 Loader(babel-loader → swc-loader / esbuild-loader);
  6. 优化 resolve 配置

    resolve: {
      modules: [path.resolve('node_modules')],
      extensions: ['.js', '.json'],
      alias: { '@': path.resolve('src') }
    }
  7. HappyPack / thread-loader:多进程并行;
  8. 动态 polyfill(按需加载);
  9. 可视化分析speed-measure-webpack-plugin 找瓶颈;
  10. 升级 Node 版本(新 Node 启动更快)。

E15:webpack-dev-server 原理

核心职责:启动 HTTP 服务 + 内存编译 + 监听变化 + 推送 HMR。

工作流程

  1. 启动:读取 webpack 配置,调用 webpack 编译;
  2. 内存编译:编译产物存入内存文件系统(memfs),不写磁盘;
  3. HTTP 服务:express / http-proxy-middleware 启动服务器;
  4. 路由转发:请求 bundle.js 时从内存文件系统读取;
  5. 监听文件:chokidar 监听源文件变化,重新编译;
  6. HMR 推送:WebSocket 向浏览器发送更新事件;
  7. 代理配置:通过 http-proxy-middleware 转发 API 请求。

对比 Vite

  • webpack-dev-server 每次修改都需重新构建依赖图(即使改动很小);
  • Vite 利用浏览器原生 ESM + 按需编译,几乎瞬时响应。

E16:Babel 的 stage 代表什么意思?

TC39 阶段:JavaScript 提案进入标准要经过 5 个阶段(Stage 0 ~ 4),Babel 通过 @babel/preset-envstage 配置控制启用哪些提案。

Stage名称含义
Stage 0Strawman任意想法,可能进入规范
Stage 1Proposal正式提案,值得讨论
Stage 2Draft草案,初步规范
Stage 3Candidate候选,预计纳入规范
Stage 4Finished已纳入规范,下个版本发布

注意

  • Babel 7 之后不再推荐按 stage 配置,建议用 @babel/preset-env + 浏览器目标自动注入 polyfill;
  • Stage 4 已纳入 ES 标准,浏览器原生支持,无需 Babel 转换。

E17:Webpack 的 module、bundle、chunk 区别?

概念定义
module源码模块(JS / CSS / 图片),webpack 处理的基本单位
chunk打包过程中的代码块(一个或多个 module 组成),由 webpack 内部管理
bundle输出的最终文件(通常是 chunk 的产物),运行在浏览器
chunk group一组 chunk 的集合

关系示例

src/index.js  →  module
src/utils.js  →  module
src/style.css →  module

webpack 打包后:
  entry chunk (main.js)    → 由 entry 引用的 module 组成
  vendor chunk (vendors.js) → 由 node_modules 中的 module 组成
  async chunk (login.js)   → 由动态 import 的 module 组成

这些 chunk 输出到 dist/ 后称为 bundle。

E18:什么是 CI/CD?

CI(Continuous Integration,持续集成)

  • 频繁地将代码集成到主干(每天多次);
  • 每次集成通过自动化构建、测试、代码检查快速验证;
  • 目标:尽早发现集成错误。

CD(Continuous Deployment / Delivery,持续部署 / 交付)

  • 持续交付:代码通过 CI 后自动打包成可发布版本,需人工确认发布;
  • 持续部署:代码通过 CI 后自动发布到生产环境,无需人工。

典型流程

push → CI触发
  → 代码拉取 → 安装依赖 → 单元测试 → 代码检查
  → 构建 → 集成测试 → 打包 → 上传产物
→ CD触发
  → 灰度发布 → 全量发布 → 监控告警

常用工具

  • CI:Jenkins、GitLab CI、GitHub Actions、CircleCI、Travis CI;
  • CD:Argo CD、Spinnaker、Jenkins X;
  • 代码检查:ESLint、SonarQube。

E19:前端工程化的理解?

定义:使用一系列工具、规范、流程将前端开发过程标准化、自动化,提升开发效率与代码质量。

核心组成

维度工具 / 规范
开发规范ESLint、Prettier、EditorConfig、commitlint
模块化ESM、CommonJS、UMD
组件化React/Vue 组件库、Storybook
构建工具Webpack、Vite、Rollup
包管理npm、yarn、pnpm(monorepo)
测试Jest、Vitest、Cypress、Playwright
版本控制Git、GitFlow
CI/CDGitHub Actions、Jenkins
部署Docker、Nginx、Vercel
监控Sentry、Lighthouse、Web Vitals

目标

  • 提升团队协作效率;
  • 降低 bug 率;
  • 缩短发布周期;
  • 提升代码可维护性。

E20:对 SSG 的理解?

SSG(Static Site Generation,静态站点生成):构建时生成完整 HTML 静态文件,部署到 CDN 直接返回。

与 SSR / CSR 对比

模式渲染时机适用
CSR(客户端渲染)浏览器加载 JS 后渲染后台应用、SPA
SSR(服务端渲染)每次请求服务端渲染内容频繁更新
SSG(静态生成)构建时预渲染博客、文档、营销页

优点

  • 首屏极快(直接返回 HTML);
  • SEO 友好;
  • CDN 缓存成本低;
  • 安全性高(无运行时服务端逻辑)。

缺点

  • 构建时间长(页面多时);
  • 不适合实时数据;
  • 数据更新需重新构建。

框架支持

  • Next.jsgetStaticProps);
  • Nuxt 3nuxt generate);
  • Astro(默认 SSG);
  • VitePress(文档站首选)。

E21:聊聊 Vite 和 Webpack 的区别

维度WebpackVite
打包策略全量打包后启动按需编译 + 原生 ESM
冷启动慢(10s+)极快(<1s)
HMR较慢(重新编译 + 推送)极快(仅更新变更模块)
生产构建Webpack 5 / TerserRollup
生态极其丰富(Loader / Plugin 1000+)较小(但增长快)
配置复杂度较复杂极简(约定大于配置)
TypeScriptts-loader / babel内置 esbuild
JSXbabel-loader内置 esbuild
CSScss-loader + style-loader内置
大项目经过考验逐渐成熟
适用规模任意规模中小型项目更优,大型项目渐可

选择建议

  • 新项目:优先 Vite(开发体验好);
  • 大型复杂项目:Webpack 5 + 优化(生态成熟);
  • 存量项目:短期不动,长期评估迁移成本。

E22:Webpack Tree Shaking 原理

原理:基于 ES Module 静态分析,删除未被使用的导出代码。

工作条件

  1. 使用 ESM 语法import / export),不能是 CommonJS;
  2. 设置 mode: 'production'(默认开启);
  3. 标记无副作用:在 package.json 中添加 "sideEffects": false 或数组形式 ["*.css"]
  4. 避免副作用代码:模块顶层不能有立即执行的副作用(修改全局变量、polyfill)。

原理流程

  1. 入口分析:从 entry 开始标记所有使用的导出;
  2. 依赖标记:递归遍历 import 语句,标记所有引用的导出;
  3. 未引用清除:未被标记的导出在压缩阶段删除;
  4. 作用域提升(Scope Hoisting):合并模块,减少函数声明。
// package.json
{
  "sideEffects": false  // 或 ["*.css"]
}

// a.js
export const used = 1;
export const unused = 2; // 被 Tree Shaking 删除

// index.js
import { used } from './a';
console.log(used);

E23:介绍下 Tree Shaking 及其工作原理

概念回顾:Tree Shaking 是移除 JavaScript 上下文中未被引用代码(dead-code elimination)的优化手段,源自 ES Module 静态结构特性。

工作流程(webpack 视角):

  1. 收集 exports:从入口出发,分析所有 ES Module 的 export;
  2. 标记 usedExports:从 entry 开始递归,跟踪哪些 export 被实际使用;
  3. 生成 usedExports 标记:webpack 内部标记哪些 export 保留、哪些删除;
  4. Terser 压缩:根据标记删除未使用代码;
  5. 合并模块(Scope Hoisting):多个模块合并为一个函数,减少闭包与函数声明。

前提条件

  • ES Module 语法(import / export);
  • mode: 'production'
  • package.json 设置 "sideEffects": false
  • 代码无运行时副作用。

失效场景

  • 第三方库使用 CommonJS;
  • 模块顶层有副作用(window.xxx = ...);
  • 动态导入的命名空间(import * as);
  • Babel 转译 CommonJS(需配置 modules: false)。

本文作者:小码哥

本文链接:https://wesee.club/archives/1192/

版权声明:自由转载-非商用-非衍生-保持署名(cc 创意共享 3.0 许可证

Theme Jasmine by Kent Liao

粤ICP备2023052298号-1