# Vite 面试题(含详细解答) > 基于本项目的 Vue 3 + Vite + 自定义插件实战经验整理。 --- ## 1. Vite 和 Webpack 在项目开发中有哪些区别? 这是一道高频面试题,关键不是背概念,而是从**开发体验、构建机制、配置复杂度、生态兼容性**四个维度说清楚。 ### 1.1 核心差异:开发阶段的模块处理方式 **Webpack** 是一个 **Bundle-based** 的构建工具。开发时它会从入口文件出发,递归解析所有依赖,把所有模块打包成一个(或多个)bundle,然后启动 dev server。每次代码变更,它需要重新打包受影响的模块并刷新 bundle,这就是为什么大项目中 Webpack 的 HMR 有时会明显变慢。 **Vite** 在开发阶段不走打包流程,而是利用浏览器原生支持的 **ES Module (ESM)**。它直接把 `.vue`、`.ts`、`.jsx` 等源码文件按需编译后通过 ESM 发给浏览器。浏览器通过 ` ``` ### 24.3 SSR 的工程化挑战 | 挑战 | 说明 | 解决方案 | | ------------------ | ---------------------------------------------- | ------------------------------------- | | **服务端无 DOM** | `window`/`document` 在 Node.js 中不存在 | 用 `import.meta.client` / `process.client` 守卫 | | **数据同步** | 服务端获取的数据要传递给客户端,避免重复请求 | Nuxt 的 `useFetch` / Next 的 `getServerSideProps` | | **内存泄漏** | 服务端每个请求都创建 Vue 实例,容易泄漏 | 使用工厂函数创建实例,避免单例 | | **缓存策略** | 每次请求都渲染太慢 | 页面级缓存(Redis)、组件级缓存 | | **部署复杂度** | 需要 Node.js 服务器,而不是纯静态文件 | Serverless(Vercel/Netlify)、容器化 | ### 24.4 什么时候选哪种模式 ``` 需要 SEO + 实时数据? → SSR(电商、新闻、社区) 需要 SEO + 内容不常变?→ SSG(博客、文档、官网) 不需要 SEO? → CSR(后台管理系统、工具型应用) 混合场景? → ISR / 混合渲染(部分页面 SSR,部分 SSG) ``` --- ## 25. 如何做前端项目的依赖治理? ### 25.1 依赖治理的三个维度 ``` 依赖治理 ├── 安全维度 — 漏洞修复(npm audit) ├── 版本维度 — 升级策略 & 兼容性管理 └── 体积维度 — 依赖瘦身 & 重复检测 ``` ### 25.2 安全漏洞修复 ```bash # 审计当前项目依赖 npm audit # 查看漏洞列表 npm audit fix # 自动修复(仅 semver 兼容的补丁版本) npm audit fix --force # 强制修复(可能包含 breaking change) # pnpm pnpm audit ``` **生产环境的漏洞处理流程:** ``` 1. npm audit → 发现高危/严重漏洞 2. 查看是否影响运行时(devDependencies 的漏洞可能无需紧急处理) 3. 尝试 npm audit fix(不破坏项目的前提下自动升级) 4. 如果 fix 失败 → 手动升级单个包 → 跑测试 → 部署 5. 建立定期审计机制(CI 中跑 npm audit --audit-level=high) ``` ### 25.3 版本升级策略 | 策略 | 做法 | 风险 | 适用场景 | | ---------- | ---------------------------------------- | ---- | -------------- | | 激进升级 | 经常升级到最新版 | 高 | 个人项目 | | 保守升级 | 只在需要新功能/修复时升级 | 低 | 生产项目 | | 定期批处理 | 每月/每季度集中升级一次 | 中 | **推荐** | | 工具辅助 | Renovate / Dependabot 自动提 PR | 中 | 团队协作 | **Renovate 配置示例:** ```json // renovate.json { "extends": ["config:base"], "schedule": ["before 8am on Monday"], // 每周一早上自动提 PR "packageRules": [ { "matchUpdateTypes": ["patch"], "automerge": true // patch 自动合并 }, { "matchDepTypes": ["devDependencies"], "automerge": true // 开发依赖自动合并 } ] } ``` ### 25.4 依赖瘦身 **① 找出重复/冗余依赖** ```bash npx depcheck # 找出来安装但未使用的包 npx npm-dedupe # 去重 npm 依赖树 pnpm dedupe # pnpm 去重 ``` **② 分析依赖体积** ```bash # 可视化分析 npx vite-bundle-visualizer # Vite 项目 npx webpack-bundle-analyzer # Webpack 项目 # 命令行快速查看 npx cost-of-modules # 每个包的大小 ``` **③ 替代重型依赖** | 重包 | 轻量替代 | 体积差异 | | ----------- | ---------------- | -------------- | | `moment` | `dayjs` | 72KB → 2KB | | `lodash` | `lodash-es` + 按需引入 | 全量 70KB → 按需 2-3KB | | `axios` | `ky` / `ofetch` | 13KB → 3KB | | `antd` | 按需引入 + tree-shaking | 视情况定 | ### 25.5 CI 中的依赖质量门禁 ```yaml # GitHub Actions 示例 - name: Dependency Check run: | npm audit --audit-level=high # 高危漏洞阻断 CI npx depcheck --ignores="eslint,prettier" # 检查未使用依赖 npx bundlesize # 检查产物大小是否超标 ``` --- ## 26. dev server proxy 的原理是什么?跨域问题有哪些工程化解决方案? ### 26.1 为什么会有跨域问题 浏览器的**同源策略(Same-Origin Policy)**:协议、域名、端口三者必须完全相同才能自由通信。开发时,Vite dev server 跑在 `localhost:5173`,后端 API 跑在 `localhost:3000`——端口不同,跨域了。 ``` 开发阶段 生产阶段 localhost:5173 ──跨域──→ api.example.com 前端域名: app.example.com (前端) (后端) 后端域名: api.example.com 不同子域名也跨域! ``` ### 26.2 三种工程化方案 **方案一:dev server proxy(开发环境首选)** ```js // vite.config.js export default defineConfig({ server: { proxy: { '/api': { target: 'http://localhost:3000', changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, '') } } } }) ``` **原理**:浏览器请求 `localhost:5173/api/users` → Vite dev server 收到 → 转发给 `localhost:3000/users` → 拿到响应 → 返回浏览器。整个过程对浏览器透明,浏览器只和同源的 `localhost:5173` 通信,不存在跨域。 **方案二:CORS(服务端配置)** ```js // 后端 Express 示例 app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', 'https://my-app.com') res.header('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE') res.header('Access-Control-Allow-Headers', 'Content-Type,Authorization') res.header('Access-Control-Allow-Credentials', 'true') next() }) ``` - ✅ 标准方案,任何环境都可用 - ⚠️ 需要后端配合,不能配置 `*` 时还要处理携带 cookie 的场景 **方案三:Nginx 反向代理(生产环境推荐)** ```nginx server { listen 80; server_name app.example.com; # 前端静态文件 location / { root /var/www/dist; try_files $uri /index.html; } # API 代理到后端 location /api/ { proxy_pass http://api-server:3000/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } } ``` - ✅ 前端和后端在同一个域名下,完全不存在跨域 - ✅ 生产环境的最佳实践 ### 26.3 方案选择指南 | 阶段 | 推荐方案 | 原因 | | -------- | ----------------- | ------------------------------ | | 本地开发 | dev server proxy | 零配置、即时生效、真实模拟 | | 生产环境 | Nginx 反向代理 | 同域部署,无跨域,性能好 | | 跨域 API | CORS + 白名单 | 前后端分开部署时的标准方案 | | 临时调试 | 浏览器插件关掉 CORS | ⚠️ 仅限本地调试,绝不用于生产 | --- ## 27. 微前端有哪些实现方案?Module Federation 解决了什么问题? ### 27.1 什么是微前端 微前端(Micro Frontends)是把一个大型前端应用**拆分为多个独立的小型应用**,每个小型应用可以由不同团队独立开发、测试、部署。 ``` ┌─────────────────────────────────────────────┐ │ 基座应用(Shell) │ │ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ │ │ 子应用 A │ │ 子应用 B │ │ 子应用 C │ │ │ │ (React) │ │ (Vue) │ │ (Angular) │ │ │ │ 团队 A │ │ 团队 B │ │ 团队 C │ │ │ └──────────┘ └──────────┘ └──────────────┘ │ └─────────────────────────────────────────────┘ ``` ### 27.2 主流方案对比 | 方案 | 原理 | 技术栈限制 | 学习成本 | 适用场景 | | ---------------------- | ------------------------------------------ | ---------- | -------- | ---------------------- | | **qiankun** | 基于 single-spa,JS 沙箱 + CSS 隔离 | 无限制 | 中 | 遗留系统迁移、多技术栈 | | **micro-app** | Web Component + Iframe 思想 | 无限制 | 低 | 快速接入、简单场景 | | **wujie(无界)** | Web Component + Iframe + 代理 | 无限制 | 低 | 隔离性要求高的场景 | | **Module Federation** | Webpack 5 原生,运行时共享模块 | Webpack 5 | 中高 | 新技术栈、同技术栈 | | **Emp** | 基于 Module Federation 的微前端框架 | Webpack 5 | 中 | Module Federation 增强 | ### 27.3 Module Federation 的核心原理 Module Federation 是 Webpack 5 内置的微前端方案,核心思想是**运行时共享模块**: ```js // 子应用 A(remote) — webpack.config.js new ModuleFederationPlugin({ name: 'appA', filename: 'remoteEntry.js', exposes: { './Header': './src/components/Header.vue', // 暴露组件 './utils': './src/utils/index.ts' // 暴露工具函数 }, shared: ['vue', 'vue-router'] // 共享依赖,避免重复加载 }) // 基座应用(host) — webpack.config.js new ModuleFederationPlugin({ name: 'host', remotes: { appA: 'appA@http://localhost:3001/remoteEntry.js' // 声明远程模块 } }) // 基座中使用子应用的组件 import Header from 'appA/Header' // 就像本地模块一样 import! ``` **关键优势**: | 对比维度 | qiankun 类方案 | Module Federation | | ------------ | --------------------------- | ---------------------------- | | 依赖加载 | 每个子应用打包完整的依赖 | 共享依赖(如 Vue 只加载一次)| | 组件共享 | 需要额外封装或全局注册 | 原生 import,类型安全 | | 构建方式 | 子应用独立构建部署 | 可以独立构建,也可以联合构建 | | 运行时性能 | 子应用加载有一定开销 | 几乎零开销 | ### 27.4 微前端的工程化挑战 | 挑战 | 说明 | 应对 | | ---------------- | ------------------------------------------ | ------------------------------ | | **CSS 冲突** | 多个子应用的全局样式互相覆盖 | CSS Modules / Shadow DOM / qiankun 沙箱 | | **JS 全局污染** | 子应用修改了 `window.xxx`,影响其他子应用 | JS 沙箱(qiankun 的 Proxy 沙箱)| | **通信机制** | 子应用之间需要共享数据和事件 | 全局 EventBus / 基座下发的 props | | **路由管理** | 多个子应用的路由需要统一分配和调度 | 基座统一管理 / 各自的子路由 | | **版本管理** | 基座和子应用版本如何协调 | 基座保持向后兼容,子应用可独立升级 | --- ## 28. 前端如何搭建性能与错误监控体系? ### 28.1 监控体系全景 ``` 前端监控体系 ├── 性能监控 │ ├── Core Web Vitals(LCP / FID / CLS) │ ├── 自定义指标(首屏时间、API 响应时间) │ └── 资源加载(慢资源、资源失败) ├── 错误监控 │ ├── JS 运行时错误(window.onerror) │ ├── Promise 未捕获异常(unhandledrejection) │ ├── 框架错误边界(Vue errorHandler / React ErrorBoundary) │ └── 接口错误(API 超时、5xx、网络异常) └── 行为监控(辅助排查) ├── 用户操作路径(点击、路由跳转) └── 录制回放(Sentry Replay / rrweb) ``` ### 28.2 JS 错误捕获的三种方式 ```js // ① 全局运行时错误 window.addEventListener('error', (event) => { reportToSentry({ type: 'js-error', message: event.message, filename: event.filename, lineno: event.lineno, colno: event.colno, stack: event.error?.stack }) }) // ② Promise 未捕获异常 window.addEventListener('unhandledrejection', (event) => { reportToSentry({ type: 'promise-rejection', reason: event.reason }) }) // ③ 框架级错误边界 // Vue 3 app.config.errorHandler = (err, instance, info) => { reportToSentry({ type: 'vue-error', message: err.message, stack: err.stack, component: instance?.$options?.name, info }) } ``` ### 28.3 性能指标采集 ```js // Core Web Vitals 采集 import { onLCP, onFID, onCLS, onINP, onTTFB } from 'web-vitals' onLCP(metric => reportMetric('LCP', metric.value)) // Largest Contentful Paint onFID(metric => reportMetric('FID', metric.value)) // First Input Delay onCLS(metric => reportMetric('CLS', metric.value)) // Cumulative Layout Shift onINP(metric => reportMetric('INP', metric.value)) // Interaction to Next Paint // 自定义首屏时间(Vue 项目) onMounted(() => { nextTick(() => { const fmp = performance.now() reportMetric('FMP', fmp) }) }) // 资源加载监控 const observer = new PerformanceObserver((list) => { list.getEntries().forEach(entry => { if (entry.duration > 3000) { // 资源加载超过 3 秒 reportSlowResource({ name: entry.name, duration: entry.duration }) } }) }) observer.observe({ entryTypes: ['resource'] }) ``` ### 28.4 常见工具对比 | 工具 | 类型 | 优势 | 适用场景 | | -------------- | -------- | ---------------------------------- | ---------------------- | | **Sentry** | 自建/云 | 错误聚合 + 堆栈解析 + 源码映射 | **标准选择**,错误监控首选 | | **阿里 ARMS** | 云服务 | 性能 + 错误一体化,国内用户友好 | 国内业务、阿里云用户 | | **腾讯 TAM** | 云服务 | 免费额度大,与小程序集成好 | 微信生态、小程序 | | **LogRocket** | 云服务 | 录制回放,可复现用户操作 | 复杂 Bug 排查 | | **自建方案** | 自建 | 可定制、数据安全 | 隐私敏感场景 | ### 28.5 监控的最佳实践 ``` ① 错误分级 - fatal:页面白屏、无法交互 → 立即告警 - error:功能异常但页面可用 → 记录 + 定时汇总 - warning:非预期但可降级 → 日志留存 ② Source Map 管理 - 生产环境不发 .map 到 CDN - .map 只上传到 Sentry 等监控平台 - Sentry 会自动根据堆栈找到原始源码位置 ③ 采样策略 - 错误监控:100% 上报(错误不应该漏) - 性能监控:10-30% 采样(节省成本) - 用户行为:视隐私合规要求决定 ④ 告警规则 - 5 分钟内错误数 > 100 → 紧急通知 - 核心 API 成功率 < 99% → 警告 - LCP 超过 4s 的比例 > 20% → 性能预警 ``` ---