diff --git a/.reasonix/desktop-topic-created-at.json b/.reasonix/desktop-topic-created-at.json index baa3934..77066b9 100644 --- a/.reasonix/desktop-topic-created-at.json +++ b/.reasonix/desktop-topic-created-at.json @@ -1,3 +1,4 @@ { - "topic_20260626-084849_ae13b749f70ea556": 1782463729575 + "topic_20260626-084849_ae13b749f70ea556": 1782463729575, + "topic_20260626-155701_2112a87ef28bf822": 1782489421463 } \ No newline at end of file diff --git a/.reasonix/desktop-topic-title-sources.json b/.reasonix/desktop-topic-title-sources.json index 0d49143..5aa67ab 100644 --- a/.reasonix/desktop-topic-title-sources.json +++ b/.reasonix/desktop-topic-title-sources.json @@ -1,5 +1,7 @@ { "topic_20260626-084849_ae13b749f70ea556": "auto", "topic_20260626-085627_e3928116af40710e": "auto", - "topic_20260626-093001_b040128cfc0f086c": "auto" + "topic_20260626-093001_b040128cfc0f086c": "auto", + "topic_20260626-155701_2112a87ef28bf822": "auto", + "topic_20260626-160318_753694613866863b": "auto" } \ No newline at end of file diff --git a/.reasonix/desktop-topic-titles.json b/.reasonix/desktop-topic-titles.json index 82b8ed8..27da4fb 100644 --- a/.reasonix/desktop-topic-titles.json +++ b/.reasonix/desktop-topic-titles.json @@ -1,5 +1,7 @@ { "topic_20260626-084849_ae13b749f70ea556": "这个文件中的plugin调用的函数h…", "topic_20260626-085627_e3928116af40710e": "我写这个项目主要是用做面试用的,所以…", - "topic_20260626-093001_b040128cfc0f086c": "帮我在plugins/plugin…" + "topic_20260626-093001_b040128cfc0f086c": "帮我在plugins/plugin…", + "topic_20260626-155701_2112a87ef28bf822": "第五个问题关于钩子生命周期的回答写的…", + "topic_20260626-160318_753694613866863b": "帮我添加10个前端工程化中常问的题目…" } \ No newline at end of file diff --git a/docs/vite.md b/docs/vite.md index 1fff1a2..a99370e 100644 --- a/docs/vite.md +++ b/docs/vite.md @@ -1,617 +1,1617 @@ # Vite 面试题(含详细解答) -> 基于本项目的 Vue 3 + Vite + 自定义插件实战经验整理。 +> 基于本项目的 Vue 3 + Vite + 自定义插件实战经验整理。 ---- +--- ## 1. Vite 和 Webpack 在项目开发中有哪些区别? -这是一道高频面试题,关键不是背概念,而是从**开发体验、构建机制、配置复杂度、生态兼容性**四个维度说清楚。 +这是一道高频面试题,关键不是背概念,而是从**开发体验、构建机制、配置复杂度、生态兼容性**四个维度说清楚。 ### 1.1 核心差异:开发阶段的模块处理方式 -**Webpack** 是一个 **Bundle-based** 的构建工具。开发时它会从入口文件出发,递归解析所有依赖,把所有模块打包成一个(或多个)bundle,然后启动 dev server。每次代码变更,它需要重新打包受影响的模块并刷新 bundle,这就是为什么大项目中 Webpack 的 HMR 有时会明显变慢。 +**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/, '') + } } } }) ``` -函数语法更适合实际项目:你可以按 `node_modules` 来源拆分 vendor,按业务域拆分 page chunks,或者按体积阈值动态决定是否拆分。 +**原理**:浏览器请求 `localhost:5173/api/users` → Vite dev server 收到 → 转发给 `localhost:3000/users` → 拿到响应 → 返回浏览器。整个过程对浏览器透明,浏览器只和同源的 `localhost:5173` 通信,不存在跨域。 ---- +**方案二:CORS(服务端配置)** -## 9. Vite 的 CSS 处理有什么特点? - -1. **原生 CSS 支持**:直接 `import './style.css'`,Vite 自动注入到页面。 -2. **CSS Modules**:文件名以 `.module.css` 结尾,自动启用 CSS Modules。 -3. **PostCSS**:项目根目录放置 `postcss.config.js` 即可,Vite 会自动应用。 -4. **CSS 代码分割**:生产构建时,每个异步 chunk 的 CSS 会被提取为独立文件,按需加载。 -5. **`@import` 内联与 rebase**:`@import` 和 `url()` 路径会自动重写,避免开发与生产路径不一致。 -6. **预处理器**:安装 `sass` 或 `less` 即可直接使用 `.scss`、`.less` 文件,无需额外配置 loader。 - ---- - -## 10. Vite 的静态资源处理策略是怎样的? - -- **小资源(< 4KB)**:自动转为 base64 内联,减少 HTTP 请求。 -- **大资源**:复制到 `dist/assets/`,文件名包含 hash 用于缓存。 -- **`public` 目录**:此目录下的资源**不会被处理**,直接复制到 `dist/` 根目录,适合 `robots.txt`、`favicon.ico` 等。 -- **`new URL`**:支持通过 `new URL('./img.png', import.meta.url)` 动态引用资源。 - ---- - -## 11. 如何调试 Vite 的自定义插件? - -1. **添加 `name`**:每个插件必须有唯一的 `name`,方便定位问题。 -2. **在项目中引入 vite-plugin-inspect** -3. **使用 console.log**:在插件的各个钩子中添加日志,观察执行时机和参数。 -**启用 debug 模式**: - ```bash - DEBUG=vite:* vite - ``` -5. **使用 Vite 的 `apply` 选项**:可以限制插件只在开发或生产环境生效: - ```js - { - name: 'dev-only-plugin', - apply: 'serve' // 或 'build',分别限制开发/生产 - } - ``` - ---- - -## 12. Vite 项目从开发到部署的完整流程是怎样的? - -```bash -# 开发 -npm run dev # vite — 启动 dev server,ESM 按需编译 + HMR - -# 构建 -npm run build # vite build — Rollup 打包,输出到 dist/ - -# 本地预览生产构建 -npm run preview # vite preview — 在本地启动静态服务器预览 dist/ +```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() +}) ``` -典型部署:将 `dist/` 目录部署到 CDN 或静态服务器(Nginx、Vercel、Netlify 等)。 +- ✅ 标准方案,任何环境都可用 +- ⚠️ 需要后端配合,不能配置 `*` 时还要处理携带 cookie 的场景 -对于 SPA 应用,需要配置 fallback 到 `index.html`: +**方案三:Nginx 反向代理(生产环境推荐)** ```nginx -location / { - try_files $uri $uri/ /index.html; -} -``` +server { + listen 80; + server_name app.example.com; ---- + # 前端静态文件 + location / { + root /var/www/dist; + try_files $uri /index.html; + } -## 13. 有没有了解微内核设计?Vite 的插件化设计思想是怎样完成个性化打包构建需求的? - -### 13.1 什么是微内核架构 - -微内核(Microkernel)是一种架构模式,核心思想是:**内核只提供最精简的基础能力,所有扩展功能通过插件机制加载**。操作系统领域最经典的例子是 Minix 和 macOS 的 XNU 内核。 - -在前端构建工具领域,这个思想同样适用: - -``` -┌──────────────────────────────────────────┐ -│ 插件层(Plugin Layer) │ -│ Vue SFC │ JSX │ TS │ CSS │ ... │ -├──────────────────────────────────────────┤ -│ 微内核(Minimal Core) │ -│ dev server │ HMR │ module graph │ -│ esbuild预构建 │ 中间件机制 │ -└──────────────────────────────────────────┘ -``` - -### 13.2 Vite 的微内核设计 - -Vite 的核心(内核)只做几件事: - -| 内核能力 | 说明 | -|---|---| -| Dev Server | 基于 connect 的 HTTP 服务器,拦截浏览器请求 | -| 模块图谱 | 追踪所有模块的依赖关系,驱动 HMR | -| esbuild 预构建 | CJS → ESM 转换 + 细碎模块合并 | -| 中间件/钩子系统 | 提供插件接入点,但不做具体编译 | - -**Vue SFC 编译、JSX 转换、TypeScript 类型检查、CSS 预处理 —— 这些全部由插件完成,不在内核里。** 这就是微内核思想:内核保持精简稳定,功能由插件按需扩展。 - -### 13.3 插件机制如何实现个性化构建 - -Vite 的插件体系基于 **Rollup 插件接口** 并做了增强: - -**① 通用钩子(Rollup 兼容层)** - -```js -{ - name: 'my-plugin', - // 构建开始 - buildStart() {}, - // 模块解析(可拦截 import 路径,实现虚拟模块) - resolveId(id) {}, - // 模块加载(可返回自定义内容) - load(id) {}, - // 代码转换(最常用) - transform(src, id) {} -} -``` - -**② Vite 独有钩子** - -```js -{ - name: 'my-vite-plugin', - // 配置解析阶段(修改用户配置) - config(config) {}, - // dev server 启动后(注入自定义中间件) - configureServer(server) {}, - // 处理 HMR 更新 - handleHotUpdate(ctx) {} -} -``` - -### 13.4 一个完整的个性化构建案例 - -回到本项目的 `helloPlugin`,它实现了一个个性化需求——「生产环境自动移除 console.log」: - -```js -// vite.config.js -function helloPlugin() { - return { - name: 'helloPlugin', - // 接入 transform 钩子,拿到每个模块的源码 - transform(src, id) { - if (!id.includes('/src/')) return null // 只处理业务代码 - if (process.env.NODE_ENV === 'production') { - const result = src.replace(/console\.log\([^)]*\);?/g, '') - return { code: result, map: null } - } - return null // 不处理则透传 - } + # API 代理到后端 + location /api/ { + proxy_pass http://api-server:3000/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; } } ``` -这个插件展示了插件化的核心价值:**不需要修改 Vite 源码,不需要 fork 项目,只需写一个函数接入钩子,就能定制打包行为。** 这种「内核不动、插件扩展」的模式,正是微内核架构在前端工程化中的最佳实践。 +- ✅ 前端和后端在同一个域名下,完全不存在跨域 +- ✅ 生产环境的最佳实践 -### 13.5 总结:Vite 的插件化设计思想怎样完成个性化打包构建需求? +### 26.3 方案选择指南 -一个结构化的回答应包含以下几点: - -1. **架构层面**:Vite 采用了**微内核设计**。它的核心非常轻量,仅包含一个 ESM 开发服务器和一套插件加载机制。所有高层功能,如框架支持、CSS 预处理、特定文件加载等,都由独立的插件实现。 -2. **接口层面**:Vite 提供了一套**兼容 Rollup 的、统一的插件接口**。这套接口通过暴露一系列生命周期钩子 (**Hooks**),允许开发者在构建过程的各个关键节点(如配置解析、模块加载、代码转换、HTML 生成等)注入自定义逻辑。 -3. **实践层面**:开发者可以编写一个插件,通过选择合适的钩子来完成特定任务。例如,使用 `transform` 钩子可以支持一种新的语言或文件格式;使用 `configureServer` 钩子可以在开发时添加自定义的服务器中间件;使用 `transformIndexHtml` 则可以动态修改主页面的内容。 +| 阶段 | 推荐方案 | 原因 | +| -------- | ----------------- | ------------------------------ | +| 本地开发 | dev server proxy | 零配置、即时生效、真实模拟 | +| 生产环境 | Nginx 反向代理 | 同域部署,无跨域,性能好 | +| 跨域 API | CORS + 白名单 | 前后端分开部署时的标准方案 | +| 临时调试 | 浏览器插件关掉 CORS | ⚠️ 仅限本地调试,绝不用于生产 | --- -## 14. 站在前端架构角度说说 Bundleless 原理 +## 27. 微前端有哪些实现方案?Module Federation 解决了什么问题? -### 14.1 什么是 Bundleless +### 27.1 什么是微前端 -传统的 Bundle 模式(Webpack 为代表)是「先打包再启动」: +微前端(Micro Frontends)是把一个大型前端应用**拆分为多个独立的小型应用**,每个小型应用可以由不同团队独立开发、测试、部署。 ``` -源码 → 打包器(Bundle)→ 一个/多个 bundle 文件 → 浏览器加载 +┌─────────────────────────────────────────────┐ +│ 基座应用(Shell) │ +│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ +│ │ 子应用 A │ │ 子应用 B │ │ 子应用 C │ │ +│ │ (React) │ │ (Vue) │ │ (Angular) │ │ +│ │ 团队 A │ │ 团队 B │ │ 团队 C │ │ +│ └──────────┘ └──────────┘ └──────────────┘ │ +└─────────────────────────────────────────────┘ ``` -Bundleless 模式(Vite 为代表)跳过了打包这一步: +### 27.2 主流方案对比 -``` -源码 → 开发服务器按需编译 → 浏览器通过 ESM 直接加载 +| 方案 | 原理 | 技术栈限制 | 学习成本 | 适用场景 | +| ---------------------- | ------------------------------------------ | ---------- | -------- | ---------------------- | +| **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! ``` -### 14.2 为什么 Bundleless 现在才成为主流 +**关键优势**: -Bundleless 不是新概念——ESM 规范早在 2015 年就发布了。之前无法普及的原因: +| 对比维度 | qiankun 类方案 | Module Federation | +| ------------ | --------------------------- | ---------------------------- | +| 依赖加载 | 每个子应用打包完整的依赖 | 共享依赖(如 Vue 只加载一次)| +| 组件共享 | 需要额外封装或全局注册 | 原生 import,类型安全 | +| 构建方式 | 子应用独立构建部署 | 可以独立构建,也可以联合构建 | +| 运行时性能 | 子应用加载有一定开销 | 几乎零开销 | -| 时间 | 瓶颈 | 现状 | -|---|---|---| -| 2015-2018 | 浏览器 ESM 支持率低(IE 占主流) | IE 已淘汰,现代浏览器全覆盖 | -| 2015-2019 | npm 包几乎全是 CJS 格式 | 新包逐渐提供 ESM 入口,且有 esbuild 做转换 | -| 2015-2019 | HTTP/1.1 并发连接有限(6个/域名) | HTTP/2 多路复用,数百个请求不再是瓶颈 | +### 27.4 微前端的工程化挑战 -**三个条件同时满足——浏览器 ESM 支持、npm 生态转换能力、HTTP/2 普及——Bundleless 才真正可行。** - -### 14.3 Bundleless 的架构优势 - -**① 按需编译,冷启动 O(1)** - -传统打包:启动时间 ∝ 项目模块数。Bundleless:只编译浏览器当前请求的文件,启动几乎恒定。 - -**② HMR 粒度天然匹配 ESM 边界** - -每个文件就是一个 ESM 模块,热更新不需要重新打包 bundle,只需要让浏览器重新 import 变更的那个文件。 - -**③ 开发与生产的明确分工** - -``` -开发阶段(Bundleless) 生产阶段(Bundled) -───────────────────── ───────────────────── - 不打包,按需编译 Rollup 全量打包 - esbuild 做预构建 Tree-shaking - ESM 原生加载 代码分割 + 压缩 - 追求开发体验 追求加载性能 -``` - -这两个阶段的目标本就不同:开发要快,生产要小。Bundleless 把这个分工理清了。 - -### 14.4 Bundleless 的工程代价 - -从架构角度,不是所有场景都适合 Bundleless: - -| 场景 | Bundleless 是否适用 | 原因 | -|---|---|---| -| 现代浏览器开发 | ✅ 完美 | ESM + HTTP/2 | -| 需要兼容 IE | ❌ 不行 | IE 不支持 ESM | -| 巨型 node_modules | ⚠️ 需预构建 | 否则请求数爆炸 | -| 非 JS 资源(CSS/图片) | ⚠️ 需额外处理 | ESM 只管 JS | - -这也是为什么 Vite 不是纯 Bundleless——它用预构建和 Rollup 生产打包弥补了 Bundleless 的短板。**务实的设计比纯粹的理念更重要。** +| 挑战 | 说明 | 应对 | +| ---------------- | ------------------------------------------ | ------------------------------ | +| **CSS 冲突** | 多个子应用的全局样式互相覆盖 | CSS Modules / Shadow DOM / qiankun 沙箱 | +| **JS 全局污染** | 子应用修改了 `window.xxx`,影响其他子应用 | JS 沙箱(qiankun 的 Proxy 沙箱)| +| **通信机制** | 子应用之间需要共享数据和事件 | 全局 EventBus / 基座下发的 props | +| **路由管理** | 多个子应用的路由需要统一分配和调度 | 基座统一管理 / 各自的子路由 | +| **版本管理** | 基座和子应用版本如何协调 | 基座保持向后兼容,子应用可独立升级 | --- -## 扩展思考:如果你来设计一个类似的工具,会怎么做? +## 28. 前端如何搭建性能与错误监控体系? -这通常是大厂面试的进阶追问。可以从以下角度组织回答: +### 28.1 监控体系全景 -1. **利用浏览器原生能力**:现代浏览器已经支持 ESM、ES2020+ 语法,开发阶段不需要向下兼容。 -2. **语言选择**:高频编译用 Go/Rust 实现的工具(如 esbuild、SWC),生产打包用成熟的 JS 生态工具。 -3. **按需编译**:改变「先打包再启动」的思路,变成「请求时编译」。 -4. **插件兼容**:设计插件接口时尽量对齐已有标准(如 Rollup 插件格式),降低迁移成本。 +``` +前端监控体系 +├── 性能监控 +│ ├── 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% → 性能预警 +``` + +--- \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 51aa035..275b10a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68,7 +68,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" @@ -81,7 +80,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "tslib": "^2.4.0" } @@ -891,7 +889,6 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -1001,7 +998,6 @@ "integrity": "sha512-BuJcQK/56NQTWDGn4ABea3q4SSBdNPWwNZKTkkUpcMPnLoquSYH8llRtSUIgoL1KSCpHt5eghLShn50mH36y7Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", @@ -1079,7 +1075,6 @@ "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.39.tgz", "integrity": "sha512-xmZCYabFGcirU8r0fTuvl/LICc1OU620rnqepaJDL/a141ZigkG7AyaxQLdqJ02ZRYzWe6YPaDHeQx7MfknQfA==", "license": "MIT", - "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.39", "@vue/compiler-sfc": "3.5.39", diff --git a/qodana.yaml b/qodana.yaml new file mode 100644 index 0000000..b7c88e8 --- /dev/null +++ b/qodana.yaml @@ -0,0 +1,46 @@ +#-------------------------------------------------------------------------------# +# Qodana analysis is configured by qodana.yaml file # +# https://www.jetbrains.com/help/qodana/qodana-yaml.html # +#-------------------------------------------------------------------------------# + +################################################################################# +# WARNING: Do not store sensitive information in this file, # +# as its contents will be included in the Qodana report. # +################################################################################# +version: "1.0" + +#Specify inspection profile for code analysis +profile: + name: qodana.starter + +#Enable inspections +#include: +# - name: + +#Disable inspections +#exclude: +# - name: +# paths: +# - + +#Execute shell command before Qodana execution (Applied in CI/CD pipeline) +#bootstrap: sh ./prepare-qodana.sh + +#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline) +#plugins: +# - id: #(plugin id can be found at https://plugins.jetbrains.com) + +# Quality gate. Will fail the CI/CD pipeline if any condition is not met +# severityThresholds - configures maximum thresholds for different problem severities +# testCoverageThresholds - configures minimum code coverage on a whole project and newly added code +# Code Coverage is available in Ultimate and Ultimate Plus plans +#failureConditions: +# severityThresholds: +# any: 15 +# critical: 5 +# testCoverageThresholds: +# fresh: 70 +# total: 50 + +#Specify Qodana linter for analysis (Applied in CI/CD pipeline) +linter: jetbrains/qodana-js:2026.1 diff --git a/src/main.js b/src/main.js index 2070404..8eb9e79 100644 --- a/src/main.js +++ b/src/main.js @@ -2,6 +2,7 @@ import { createApp } from 'vue' import './style.css' import App from './App.vue' +console.log(123456) console.log(123456) createApp(App).mount('#app')