一、构建工具与模块化
- Vite 相比 Webpack 的核心优势是什么?为什么大型项目也开始用 Vite?
-
优势:
-
开发环境基于浏览器原生 ES 模块(ESM),无需打包,启动速度极快(毫秒级)。
-
热更新(HMR)基于模块依赖图,只更新修改的模块,而非整个包。
-
生产环境使用 Rollup 打包,输出更精简的代码(Tree-shaking 更彻底)。
-
大型项目适配:Vite 2.x+ 支持多页面应用、依赖预构建(node_modules 预打包为 ESM)、缓存策略优化,解决了早期大型项目的性能瓶颈。
-
- Webpack 的 Tree-shaking 原理是什么?为什么需要 package.json 配置 sideEffects ?
-
原理:基于 ES 模块的静态分析(import/export),标记未被引用的代码,打包时剔除。
-
sideEffects 作用:告知 Webpack 哪些文件有副作用(如全局样式、polyfill),避免被误删。例如:
{ "sideEffects": ["*.css", "src/utils/polyfill.js"] }
- ES 模块(ESM)与 CommonJS(CJS)的区别?如何在项目中混用?
-
区别:
- ESM 是静态的(编译时确定依赖),支持 import() 动态导入、export 命名导出;
- CJS 是动态的(运行时确定依赖),使用 require()/module.exports,同步加载。
-
混用方案:
-
Webpack/Vite 会自动处理模块转换(ESM 转 CJS 或反之)。
-
注意:CJS 中 require() ESM 模块会得到 { default: ... } 包裹的对象,需显式取 default。
-
- pnpm 相比 npm/yarn 的优势?为什么能解决「幽灵依赖」问题?
-
优势:
-
采用硬链接 + 符号链接的存储方式,依赖只存一份,节省磁盘空间。
-
安装速度比 npm 快 2-3 倍(依赖复用率高)。
-
严格的依赖隔离,避免「幽灵依赖」(未在 package.json 声明却能被引用的依赖)。
-
-
解决幽灵依赖:pnpm 会创建严格的依赖目录结构,只有 package.json 声明的依赖才会被链接到 node_modules,避免意外引用子依赖。
- 如何配置 Vite 实现多页面应用(MPA)?
- 在 vite.config.js 中配置 build.rollupOptions.input:
// vite.config.js
export default {
build: {
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html'),
admin: resolve(__dirname, 'admin/index.html')
}
}
}
};
-
- 每个页面对应独立的 HTML 文件,共享 node_modules 和配置。
- 模块化设计中,如何避免循环依赖?
-
预防措施:
-
拆分公共逻辑到独立模块(如将 A 和 B 都依赖的逻辑抽为 C)。
-
采用「依赖注入」模式,通过参数传递依赖,而非模块内直接导入。
-
延迟导入(在函数内部 import() 动态加载,避免模块初始化时的依赖)。
-
二、代码规范与质量
- ESLint、Prettier、EditorConfig 的作用及区别?如何协同工作?
-
ESLint:检测代码语法错误和风格问题(如未使用变量、不符合规范的命名)。
-
Prettier:专注代码格式化(如缩进、换行、引号),不关心语法逻辑。
-
EditorConfig:统一不同编辑器的基础格式(如缩进空格数、换行符)。
-
协同方案:
-
ESLint 禁用格式化相关规则(用 eslint-config-prettier),避免与 Prettier 冲突。
-
用 prettier-eslint 将 Prettier 格式化后的代码再交给 ESLint 修复。
-
- husky + lint-staged 如何保证提交代码质量?
-
husky:管理 Git 钩子(如 pre-commit、pre-push),在提交 / 推送前执行脚本。
-
lint-staged:只对暂存区(staged)的文件执行检查(避免全量检查耗时)。
-
配置示例:
// package.json
{
"husky": { "hooks": { "pre-commit": "lint-staged" } },
"lint-staged": { "*.{js,vue}": ["eslint --fix", "prettier --write"] }
}
- 如何在团队中统一 Node 版本?
- 使用 .nvmrc(配合 nvm)或 .node-version(配合 fnm)指定版本:
# .nvmrc
v18.18.0
- 结合 engines 字段在 package.json 中声明,并通过 npm install 时的 engine-strict 配置强制校验:
{ "engines": { "node": ">=18.0.0" } }
- TypeScript 如何配置才能兼顾类型严格性和开发效率?
- 核心配置 tsconfig.json:
{
"compilerOptions": {
"strict": true, // 开启严格模式(推荐)
"noImplicitAny": true, // 禁止隐式 any 类型
"strictNullChecks": true, // 严格 null 检查(避免 null 错误)
"skipLibCheck": true, // 跳过第三方库类型检查(提升速度)
"esModuleInterop": true // 兼容 CJS 和 ESM
}
}
- 对复杂类型暂时用 any 时,添加 // @ts-ignore 并注明原因,后续优化。
- 如何检测项目中的未使用依赖?
- 使用 depcheck 工具自动分析:
npx depcheck
- 结合 CI 流程,在提交时触发检查,避免冗余依赖增大包体积。
三、性能优化与构建优化
- 前端打包体积过大?如何分析并优化?
-
分析工具:
-
Vite:vite build --report 生成打包分析报告。
-
Webpack:webpack-bundle-analyzer 可视化包结构。
-
-
优化方案:
-
代码分割(splitChunks 提取公共依赖)。
-
动态导入(import() 懒加载非首屏组件)。
-
替换大体积库(如用 lodash-es 替代 lodash,支持 Tree-shaking)。
-
图片压缩(vite-plugin-imagemin)、字体子集化。
-
- 如何实现代码分割(Code Splitting)?有哪些场景?
-
实现方式:
-
路由级分割(Vue Router 配合 () => import('./Page.vue'))。
-
组件级分割(const HeavyComponent = () => import('./Heavy.vue'))。
-
Webpack/Vite 自动分割(splitChunks 配置提取公共库)。
-
-
场景:首屏优化(只加载必要代码)、大型组件(如富文本编辑器)、按需加载的功能模块(如图表)。
- 生产环境如何优化静态资源加载?
-
CDN 加速:将 JS/CSS/ 图片部署到 CDN,利用边缘节点缓存。
-
缓存策略:
-
文件名加哈希(app.[hash].js),配合 Cache-Control: max-age=31536000 长期缓存。
-
HTML 不缓存(Cache-Control: no-cache),确保每次请求最新版本。
-
-
压缩与合并:Gzip/Brotli 压缩(服务器或构建工具配置)、合并小文件减少请求数。
- 如何优化 Vite 的构建速度?
-
启用依赖预构建缓存(node_modules/.vite,默认开启)。
-
缩小 include/exclude 范围,避免不必要的文件处理。
-
生产环境禁用 sourcemap(build.sourcemap: false)。
-
使用 esbuild 作为 JS 压缩工具(build.minify: 'esbuild')。
- 图片资源在工程化中的最佳实践?
-
格式选择:小图标用 SVG;照片用 WebP/AVIF(兼容性不足时降级为 JPEG);透明图用 PNG-8。
-
按需加载:
-
用 loading="lazy" 延迟加载非首屏图片。
-
响应式图片(srcset 配合不同分辨率):
-
<img src="small.jpg" srcset="medium.jpg 800w, large.jpg 1200w" alt="示例">
- 构建处理:用 vite-plugin-svg-icons 整合 SVG 图标,vite-plugin-imagemin 自动压缩。
- 如何避免第三方库对项目打包体积的影响?
- CDN 引入:通过 script 标签加载(如 React、Vue),排除在打包范围外:
// vite.config.js
export default {
externals: { vue: 'Vue', 'vue-router': 'VueRouter' }
};
-
按需导入:使用 babel-plugin-import 自动导入组件库的按需模块(如 Element Plus、Ant Design)。
-
替代轻量库:用 date-fns 替代 moment.js,lodash-es 替代全量 lodash。
四、CI/CD 与自动化部署
- 什么是 CI/CD?如何用 GitHub Actions 实现前端自动化部署?
-
CI(持续集成) :代码提交后自动执行构建、测试,确保代码质量。
-
CD(持续部署) :通过 CI 后自动部署到测试 / 生产环境。
-
GitHub Actions 配置示例(部署到 GitHub Pages):
# .github/workflows/deploy.yml
name: Deploy
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm install && npm run build
- uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./dist
- 前端项目如何实现灰度发布?
-
方案 1:CDN 灰度:配置 CDN 按比例(如 10% 用户)指向新版本资源,监控无问题后全量切换。
-
方案 2:路由分发:后端根据用户 ID/IP 分段,将部分用户路由到新版本前端服务。
-
方案 3:特性开关(Feature Flag) :在代码中通过配置控制功能是否启用,动态切换版本。
- Docker 部署前端项目的优势?如何编写 Dockerfile?
-
优势:环境一致性(避免「本地能跑,线上崩」)、隔离性强、部署流程标准化。
-
Dockerfile 示例:
# 构建阶段
FROM node:18-alpine as builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# 生产阶段(nginx 部署)
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
- 如何实现前端项目的回滚机制?
-
版本化部署:每次部署生成唯一版本号(如 Git commit hash),保留历史版本文件。
-
快速切换:通过修改 CDN 配置或 Nginx 路由,指向历史版本的静态资源目录。
-
自动化回滚:在 CI/CD 中配置监控(如错误率超标),自动触发回滚到上一稳定版本。
- 如何在 CI 流程中集成前端测试?
- 在 GitHub Actions/GitLab CI 中添加测试步骤:
# .github/workflows/test.yml
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm install
- run: npm run lint # ESLint 检查
- run: npm run test # 单元测试(Jest/Vitest)
- run: npm run test:e2e # E2E 测试(Cypress)
配置测试不通过则阻断后续部署流程。
五、测试与质量保障
- 单元测试、集成测试、E2E 测试的区别及适用场景?
-
单元测试:测试独立函数 / 组件(如工具函数、UI 组件),用 Jest/Vitest,快且隔离。
-
集成测试:测试模块间交互(如组件组合、API 调用),用 @vue/test-utils,验证协作逻辑。
-
E2E 测试:模拟用户操作测试完整流程(如登录→下单),用 Cypress/Playwright,覆盖真实场景。
- 如何用 Vitest 测试 Vue3 组件?
-
安装依赖:npm install vitest @vue/test-utils --save-dev
-
测试示例(按钮点击事件):
// Button.test.js
import { describe, it, expect, mount } from 'vitest';
import Button from './Button.vue';
describe('Button', () => {
it('emits "click" event when clicked', async () => {
const wrapper = mount(Button);
await wrapper.trigger('click');
expect(wrapper.emitted('click')).toBeTruthy();
});
});
- 前端自动化测试的覆盖率目标如何设定?
-
核心模块:业务逻辑(如支付、权限)覆盖率 ≥ 80%,避免关键路径 bug。
-
工具函数:覆盖率 ≥ 90%,确保基础功能稳定。
-
UI 组件:聚焦交互逻辑(如点击、输入),覆盖率 ≥ 70%,样式相关可降低要求。
-
避免盲目追求 100% 覆盖率(如简单的 getter/setter 无需过度测试)。
- 如何模拟 API 请求进行测试?
- 使用 vitest-mock-extended 或 msw(Mock Service Worker)模拟接口:
// 用 msw 模拟 GET /api/user
import { rest } from 'msw';
import { setupServer } from 'msw/node';
const server = setupServer(
rest.get('/api/user', (req, res, ctx) => {
return res(ctx.json({ name: 'test' }));
})
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
六、架构与工程化进阶
- 微前端的核心价值是什么?基于 Module Federation 如何实现?
-
核心价值:按业务拆分独立应用(如电商的商品、订单、支付),团队独立开发 / 部署,共享基础库。
-
Module Federation 实现(Webpack/Vite 支持):
- 主应用配置 exposes 暴露组件,子应用配置 remotes 引入主应用模块:
// 主应用 vite.config.js
export default {
plugins: [
federation({
name: 'host',
remotes: { app1: 'app1@http://localhost:5001/assets/remoteEntry.js' }
})
]
};
- Monorepo 适合什么场景?如何用 pnpm workspace 配置?
-
适合场景:多包项目(如组件库、工具库)、团队共享代码(如多个应用共用 UI 组件)。
-
pnpm workspace 配置:
# pnpm-workspace.yaml
packages:
- 'packages/*' # 子包目录
- 'apps/*' # 应用目录
- 优势:统一依赖管理、跨包引用无需发布、原子提交(一次提交更新多个包)。
- 如何设计前端工程化的目录结构?
- 推荐结构(按功能 / 业务拆分):
src/
├── assets/ # 静态资源(图片、字体)
├── components/ # 公共组件(按功能分组:form、table)
├── composables/ # 组合函数(useFetch、useAuth)
├── hooks/ # 自定义钩子
├── router/ # 路由配置
├── store/ # 状态管理(Pinia)
├── services/ # API 服务
├── utils/ # 工具函数
├── views/ # 页面组件(按路由划分)
└── App.vue
- 前端工程化如何应对多环境(开发、测试、生产)配置?
-
方案 1:环境变量(Vite 示例):
- 创建 .env.development、.env.production 等文件,用 VITE_ 前缀定义变量:
# .env.production
VITE_API_URL=https://api.example.com
-
代码中访问:import.meta.env.VITE_API_URL。
-
方案 2:配置中心:复杂项目用远程配置中心(如 Apollo),动态拉取环境配置,无需重新打包。