
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 的主要升级点
- 持久化缓存:
cache: { type: 'filesystem' },二次构建提速 90%+; - 资源模块(Asset Modules):取代
file-loader/url-loader/raw-loader,默认内置; - Module Federation:原生支持微前端、跨应用共享模块;
- Tree Shaking 增强:能追踪到嵌套模块导出、支持
sideEffects: false; - Web Worker 支持:
new Worker(new URL('./worker.js', import.meta.url)); - Node Polyfills 自动移除:仅对前端需要的 polyfill 提供;
- 新生成器(experiments):
output.module: true输出 ESM; - 更好的 SourceMap:
devtool: 'eval-cheap-module-source-map'更精准; - Chunk 拆分算法优化:默认
splitChunks策略更智能。
E3:Vite 的原理
(与 V15 互补,更详细)
冷启动快的原因:
- 不走打包:开发模式不做全量 bundling;
- 预构建依赖:用 esbuild(Go 编写,比 JS 快 10-100 倍)预编译
node_modules为 ESM; - 按需编译:浏览器请求哪个模块才编译哪个,请求与编译并行;
- 原生 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 | 零配置 | 自动处理依赖,但生态较小 |
| esbuild | Go 写的极速编译器 | 仅做转换,不做打包 |
| swc | Rust 写的 Babel 替代 | 比 Babel 快 20 倍 |
| Turbopack | Webpack 作者新项目 | Rust 编写,Webpack 继任者(Next.js 团队) |
| Bun | 全能运行时 | 自带打包器,性能优异 |
| Rspack | Rust 版的 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': '' }
}
}
}
};工作原理:
- 开发服务器(webpack-dev-server / vite)启动 HTTP 服务;
- 浏览器请求
/api/users实际请求本地开发服务器(同源); - 开发服务器将请求转发到真实后端(代理突破浏览器同源限制);
- 后端响应返回给开发服务器,再返回给浏览器。
为什么能解决跨域:
- 浏览器只关心协议 + 域名 + 端口;
- 跨域是浏览器行为,服务器之间无跨域限制;
- Proxy 是服务器端代理,绕过了浏览器同源策略。
注意:仅开发环境有效,生产需用 Nginx / 后端 CORS 配置。
E7:Webpack 热更新原理
HMR(Hot Module Replacement):运行时替换、添加、删除模块,无需刷新页面。
工作流程:
- 监听文件变化:webpack 使用
webpack-dev-middleware调用chokidar监听; - 重新编译:文件变化后,webpack 增量编译(仅编译变更模块);
- 推送更新:webpack-dev-server 通过 WebSocket 向浏览器推送
{ type: 'hash', data: hash }; - 拉取更新:浏览器收到通知,通过
HotModuleReplacementPlugin运行时请求hash.hot-update.json和chunk.hash.hot-update.js; - 模块替换:在浏览器端按模块 ID 替换旧模块,触发
module.hot.accept回调。
核心代码:
// 业务模块
if (module.hot) {
module.hot.accept('./library.js', () => {
// 重新执行使用 library 的代码
doSomething();
});
}E8:Loader 和 Plugin 的区别?如何编写?
| 维度 | Loader | Plugin |
|---|---|---|
| 作用 | 转换模块源码 | 扩展 webpack 生命周期 |
| 配置 | module.rules | plugins 数组 |
| 本质 | 函数,接收源码返回新源码 | 类,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 到单独文件 |
| TerserPlugin | JS 压缩 |
| CssMinimizerPlugin | CSS 压缩 |
| DefinePlugin | 定义编译时全局常量 |
| CopyWebpackPlugin | 拷贝静态资源 |
| CleanWebpackPlugin | 清理输出目录 |
| BundleAnalyzerPlugin | 可视化分析包体积 |
| HotModuleReplacementPlugin | 启用 HMR |
| ProvidePlugin | 自动加载模块(如 $) |
| IgnorePlugin | 忽略指定模块 |
| CompressionPlugin | 预生成 gzip 文件 |
| ForkTsCheckerWebpackPlugin | TS 类型检查 |
| ModuleFederationPlugin | 微前端 / 模块联邦 |
E10:常见 Webpack Loader 及解决的问题
| Loader | 作用 |
|---|---|
| babel-loader | ES6+ → ES5 |
| ts-loader / esbuild-loader / swc-loader | TS / JSX 转译 |
| css-loader | 解析 CSS 文件中的 @import / url() |
| style-loader | 将 CSS 注入 <style> |
| sass-loader / less-loader / stylus-loader | CSS 预处理器 |
| postcss-loader | PostCSS 处理(autoprefixer 等) |
| file-loader(已废) / Asset Modules | 处理文件资源 |
| url-loader(已废) | 小图转 base64 |
| vue-loader | Vue 单文件组件 |
| eslint-loader / stylelint-loader | 代码检查 |
| raw-loader | 以字符串形式导入文件 |
| thread-loader | 多线程加速 |
E11:Webpack 的构建流程
六大阶段:
初始化(Initialization):
- 读取配置、加载 Plugin、注册 Compiler 钩子;
编译(Compilation):
- 从入口文件出发,调用 Loader 转换模块;
- 构建模块依赖图(Module Graph);
完成编译(Finish Make):
- 收集所有模块、依赖关系;
封装(Sealing):
- 生成 Chunk、合并 SplitChunks、优化产物;
- 触发 Plugin 钩子;
输出(Emit):
- 将 Chunk 写入磁盘;
- 触发
emit钩子;
完成(Done):
- 触发
done钩子。
- 触发
简化流程图:
entry → loader处理模块 → 构建依赖图 → splitChunks → 优化 → 写入distE12:对 Webpack 的理解?解决了什么问题?
Webpack 本质:静态模块打包器(Static Module Bundler)。
核心能力:
- 模块化:支持 ESM / CommonJS / AMD / CSS / 图片等一切资源作为模块;
- 依赖管理:自动解析模块依赖,构建依赖图;
- 代码分割:按需加载、提取公共代码;
- Loader / Plugin 生态:转换和扩展能力强;
- 开发体验: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 构建速度
- 升级到 Webpack 5 + 开启
cache: { type: 'filesystem' }; - 多线程:
thread-loader、terser-webpack-plugin(默认多核); 缩小构建范围:
module: { rules: [ { test: /\.js$/, include: /src/, exclude: /node_modules/ } ] }- DLLPlugin:预打包不常变动的依赖;
- 减少 Loader 处理:少用昂贵的 Loader(babel-loader → swc-loader / esbuild-loader);
优化 resolve 配置:
resolve: { modules: [path.resolve('node_modules')], extensions: ['.js', '.json'], alias: { '@': path.resolve('src') } }- HappyPack / thread-loader:多进程并行;
- 动态 polyfill(按需加载);
- 可视化分析:
speed-measure-webpack-plugin找瓶颈; - 升级 Node 版本(新 Node 启动更快)。
E15:webpack-dev-server 原理
核心职责:启动 HTTP 服务 + 内存编译 + 监听变化 + 推送 HMR。
工作流程:
- 启动:读取 webpack 配置,调用 webpack 编译;
- 内存编译:编译产物存入内存文件系统(memfs),不写磁盘;
- HTTP 服务:express / http-proxy-middleware 启动服务器;
- 路由转发:请求
bundle.js时从内存文件系统读取; - 监听文件:chokidar 监听源文件变化,重新编译;
- HMR 推送:WebSocket 向浏览器发送更新事件;
- 代理配置:通过
http-proxy-middleware转发 API 请求。
对比 Vite:
- webpack-dev-server 每次修改都需重新构建依赖图(即使改动很小);
- Vite 利用浏览器原生 ESM + 按需编译,几乎瞬时响应。
E16:Babel 的 stage 代表什么意思?
TC39 阶段:JavaScript 提案进入标准要经过 5 个阶段(Stage 0 ~ 4),Babel 通过 @babel/preset-env 的 stage 配置控制启用哪些提案。
| Stage | 名称 | 含义 |
|---|---|---|
| Stage 0 | Strawman | 任意想法,可能进入规范 |
| Stage 1 | Proposal | 正式提案,值得讨论 |
| Stage 2 | Draft | 草案,初步规范 |
| Stage 3 | Candidate | 候选,预计纳入规范 |
| Stage 4 | Finished | 已纳入规范,下个版本发布 |
注意:
- 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/CD | GitHub 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.js(
getStaticProps); - Nuxt 3(
nuxt generate); - Astro(默认 SSG);
- VitePress(文档站首选)。
E21:聊聊 Vite 和 Webpack 的区别
| 维度 | Webpack | Vite |
|---|---|---|
| 打包策略 | 全量打包后启动 | 按需编译 + 原生 ESM |
| 冷启动 | 慢(10s+) | 极快(<1s) |
| HMR | 较慢(重新编译 + 推送) | 极快(仅更新变更模块) |
| 生产构建 | Webpack 5 / Terser | Rollup |
| 生态 | 极其丰富(Loader / Plugin 1000+) | 较小(但增长快) |
| 配置复杂度 | 较复杂 | 极简(约定大于配置) |
| TypeScript | ts-loader / babel | 内置 esbuild |
| JSX | babel-loader | 内置 esbuild |
| CSS | css-loader + style-loader | 内置 |
| 大项目 | 经过考验 | 逐渐成熟 |
| 适用规模 | 任意规模 | 中小型项目更优,大型项目渐可 |
选择建议:
- 新项目:优先 Vite(开发体验好);
- 大型复杂项目:Webpack 5 + 优化(生态成熟);
- 存量项目:短期不动,长期评估迁移成本。
E22:Webpack Tree Shaking 原理
原理:基于 ES Module 静态分析,删除未被使用的导出代码。
工作条件:
- 使用 ESM 语法(
import/export),不能是 CommonJS; - 设置
mode: 'production'(默认开启); - 标记无副作用:在
package.json中添加"sideEffects": false或数组形式["*.css"]; - 避免副作用代码:模块顶层不能有立即执行的副作用(修改全局变量、polyfill)。
原理流程:
- 入口分析:从 entry 开始标记所有使用的导出;
- 依赖标记:递归遍历 import 语句,标记所有引用的导出;
- 未引用清除:未被标记的导出在压缩阶段删除;
- 作用域提升(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 视角):
- 收集 exports:从入口出发,分析所有 ES Module 的 export;
- 标记 usedExports:从 entry 开始递归,跟踪哪些 export 被实际使用;
- 生成 usedExports 标记:webpack 内部标记哪些 export 保留、哪些删除;
- Terser 压缩:根据标记删除未使用代码;
- 合并模块(Scope Hoisting):多个模块合并为一个函数,减少闭包与函数声明。
前提条件:
- ES Module 语法(
import/export); mode: 'production';package.json设置"sideEffects": false;- 代码无运行时副作用。
失效场景:
- 第三方库使用 CommonJS;
- 模块顶层有副作用(
window.xxx = ...); - 动态导入的命名空间(
import * as); - Babel 转译 CommonJS(需配置
modules: false)。