前端工程化
第一部分:构建工具核心(Webpack/Vite 必问)
一、Webpack 底层原理与配置
Q1:Webpack 的核心概念?构建流程完整拆解?
标准答案:
- 核心概念(5 大核心,缺一不可):
入口(entry):指定 Webpack 打包的起始文件(单入口/多入口);
出口(output):配置打包产物的输出路径、文件名(多入口需用占位符);
加载器(loader):处理非 JS/JSON 文件(如 CSS、图片),将其转为可打包模块;
插件(plugin):扩展 Webpack 功能(如压缩、拆分、热更新),作用于构建全流程;
模式(mode):区分环境(development/production/none),自动启用对应优化。
完整构建流程(从启动到输出):
初始化:读取配置文件(webpack.config.js)、合并 CLI 参数,创建 Compiler 实例;
入口解析:从 entry 出发,递归解析依赖,生成依赖图谱(AST 语法分析);
模块编译:对非 JS 模块调用对应 loader,将所有模块转为 JS 模块;
chunk 打包:根据依赖图谱,将模块合并为 chunk(代码块),处理代码分割;
产物生成:通过 plugin 优化 chunk,输出最终产物(bundle.js)到 output 指定路径;
收尾:执行插件的 done 钩子,完成构建(开发环境启动 devServer)。
坑点:loader 仅处理模块转换,不参与 chunk 打包;plugin 可干预全流程,需注意执行顺序。
追问:Compiler 和 Compilation 的区别?
补充:Compiler 是全局唯一实例,管理构建全生命周期;Compilation 是每次构建(如热更新)的实例,负责单次构建的模块解析、打包。
Q2:loader 与 plugin 的核心区别?常用 loader/plugin 及场景?
标准答案:
- 核心区别:
| 维度 | loader | plugin |
|---|---|---|
| 作用对象 | 非 JS/JSON 模块(如 CSS、图片) | 整个构建流程(入口、编译、输出等) |
| 作用时机 | 模块解析阶段(构建中期) | 全生命周期(初始化、编译、输出等) |
| 用法 | 配置在 module.rules 中,指定 test 和 use | 配置在 plugins 数组中,实例化调用 |
- 常用 loader 及场景:
样式处理:css-loader(解析 CSS 依赖)、style-loader(将 CSS 插入 DOM)、less-loader/sass-loader(编译预处理器)、postcss-loader(自动前缀、CSS 变量兼容);
资源处理:file-loader(输出文件到指定路径)、url-loader(小资源转 base64,减少请求)、asset-module(Webpack5 内置,替代 file/url-loader);
JS 处理:babel-loader(ES6+转 ES5)、eslint-loader(构建时校验 JS);
其他:vue-loader(处理 Vue 单文件组件)、ts-loader(处理 TypeScript)。
- 常用 plugin 及场景:
打包优化:HtmlWebpackPlugin(生成 HTML,自动引入 bundle)、MiniCssExtractPlugin(提取 CSS 为单独文件,替代 style-loader)、TerserPlugin(压缩 JS)、CssMinimizerPlugin(压缩 CSS);
工程化工具:CleanWebpackPlugin(清空输出目录)、DefinePlugin(注入环境变量)、CopyWebpackPlugin(复制静态资源);
热更新/调试:HotModuleReplacementPlugin(热模块替换,HMR)、SourceMapDevToolPlugin(配置 source-map)。
坑点:loader 执行顺序是“从右到左、从下到上”(如 use: ['style-loader', 'css-loader'],先执行 css-loader,再执行 style-loader);plugin 执行顺序由配置顺序和钩子决定。
Q3:Webpack 优化方案(打包体积、构建速度)?实战配置示例?
标准答案:
一、构建速度优化
缩小打包范围:
entry 精准配置,避免冗余入口;
用 exclude/include 过滤无需处理的文件(如 node_modules);
配置 resolve.alias(别名),缩短模块查找路径(如@指向 src);
限制 resolve.extensions(文件后缀)的查找范围,优先配置常用后缀。
缓存优化(复用编译结果):
开启 loader 缓存(如 babel-loader 配置 cacheDirectory);
开启 Webpack 缓存(配置 cache: { type: 'filesystem' },Webpack5 默认支持);
开发环境启用 HMR,避免全量重新打包。
并行构建:
用 thread-loader 开启多线程(处理 JS/CSS 编译,替代 uglifyjs-webpack-plugin);
Webpack5 内置并行压缩,TerserPlugin 默认开启多线程。
其他:开发环境禁用压缩、提取 CSS 等耗时操作;避免使用体积过大的 loader/plugin。
二、打包体积优化
代码分割(Code Splitting):
多入口分割:output 配置[name]占位符,每个入口生成单独 bundle;
公共模块分割:splitChunks 配置提取多入口公共模块、第三方依赖(如 react/vue);
动态导入分割:用 import()语法(懒加载),拆分路由、组件模块。
Tree Shaking(摇树优化):
开启条件:使用 ESM(import/export)、mode 为 production(默认开启);
配置 package.json 的 sideEffects(标记无副作用的文件,避免误删)。
资源压缩与优化:
JS 压缩:TerserPlugin(移除注释、死代码,压缩变量名);
CSS 压缩:CssMinimizerPlugin,配合 MiniCssExtractPlugin 提取单独 CSS;
图片优化:url-loader 将小图片转 base64,大图片用 image-webpack-loader 压缩;
字体优化:提取字体文件,开启 woff2 格式,减少体积。
移除冗余代码:
用 eslint-plugin-import 检查未使用的导入;
禁用 production 环境的 console、debugger(TerserPlugin 配置)。
三、实战优化配置(Webpack5)
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
mode: process.env.NODE_ENV === "production" ? "production" : "development",
entry: "./src/index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "[name].[contenthash].js", // 内容哈希,缓存优化
clean: true, // 替代CleanWebpackPlugin,清空dist
},
cache: { type: "filesystem" }, // 开启文件缓存
resolve: {
alias: { "@": path.resolve(__dirname, "src") }, // 别名
extensions: [".js", ".jsx", ".json"], // 缩短后缀查找
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/, // 排除第三方依赖
use: [
"thread-loader", // 多线程
{ loader: "babel-loader", options: { cacheDirectory: true } }, // babel缓存
],
},
{
test: /\.css$/,
use: [
process.env.NODE_ENV === "production"
? MiniCssExtractPlugin.loader
: "style-loader",
"css-loader",
"postcss-loader",
],
},
{
test: /\.(png|jpg|jpeg)$/,
type: "asset", // Webpack5内置,自动判断转base64
parser: { dataUrlCondition: { maxSize: 10 * 1024 } }, // 10kb以下转base64
generator: { filename: "assets/images/[name].[hash][ext]" },
},
],
},
optimization: {
splitChunks: {
// 代码分割
chunks: "all", // 分割所有chunk(同步+异步)
cacheGroups: {
vendor: {
// 提取第三方依赖
test: /node_modules/,
name: "vendors",
chunks: "all",
},
common: {
// 提取公共模块
minSize: 20000, // 最小体积
minChunks: 2, // 最少被引用2次
reuseExistingChunk: true, // 复用已有的chunk
},
},
},
minimizer: [
// 压缩优化
new TerserPlugin({
parallel: true,
terserOptions: { compress: { drop_console: true } },
}),
new CssMinimizerPlugin(),
],
runtimeChunk: "single", // 提取runtime代码,避免缓存失效
},
plugins: [
new HtmlWebpackPlugin({
template: "./public/index.html",
minify: process.env.NODE_ENV === "production",
}),
...(process.env.NODE_ENV === "production"
? [new MiniCssExtractPlugin({ filename: "css/[name].[contenthash].css" })]
: []),
],
devServer: { port: 3000, hot: true, open: true }, // 开发环境热更新
};追问:Webpack5 相比 Webpack4 有哪些核心优化?
补充:内置 asset-module,替代 file/url-loader;默认开启 tree-shaking 和缓存;优化 chunk 分割策略;内置并行压缩;支持模块联邦(Module Federation)。
Q4:Webpack 热更新(HMR)原理?为什么生产环境不用 HMR?
标准答案:
HMR 核心原理(基于 devServer,仅开发环境生效):
启动阶段:devServer 启动 HTTP 服务器和 WebSocket 服务器,与客户端(浏览器)建立长连接;
文件监听:Webpack 监听源码变化,当文件修改时,重新编译对应模块,生成更新后的 chunk;
更新通知:WebSocket 将更新信息(模块 ID、hash)发送给客户端;
模块更新:客户端接收通知后,请求更新的 chunk,替换旧模块(不刷新页面);
状态维护:插件(如 vue-loader、react-refresh-webpack-plugin)处理模块替换后的状态保留(避免组件重置)。
生产环境不用 HMR 的原因:
性能开销:HMR 需要额外的 WebSocket 连接、文件监听、模块替换逻辑,增加生产环境体积和性能负担;
安全风险:长连接可能存在安全隐患,且生产环境无需实时更新;
无实际需求:生产环境代码已压缩优化,更新需重新部署,而非实时热替换。
坑点:HMR 不支持入口文件、公共模块的热更新,修改这类文件会触发全页面刷新;需配合对应框架的 loader/plugin 才能保留组件状态。
二、Vite 核心原理与对比
Q5:Vite 与 Webpack 的核心区别?底层原理差异?
标准答案:
一、核心区别(高频考点)
| 维度 | Webpack | Vite |
|---|---|---|
| 构建模式 | 先打包,再启动服务(全量编译) | 按需编译,启动服务后请求哪个模块编译哪个 |
| 模块解析 | 支持 ESM/CommonJS,需编译为 CommonJS | 开发环境原生支持 ESM(浏览器直接解析),生产环境用 Rollup 打包 |
| 启动速度 | 慢(全量编译,项目越大越慢) | 快(按需编译,忽略 node_modules) |
| 热更新速度 | 慢(需重新打包对应 chunk) | 快(仅编译修改的模块,无打包开销) |
| 生产构建 | 内置优化,适配复杂场景 | 基于 Rollup,打包体积更小,优化更极致 |
| 兼容性 | 强,支持所有前端场景(老项目、复杂工程) | 现代浏览器(需兼容旧浏览器需配置 polyfill) |
二、底层原理差异
Webpack 原理:基于“打包”,递归解析所有依赖,生成完整依赖图谱,将所有模块打包为 bundle,浏览器加载 bundle 后执行;
Vite 原理(分环境):
开发环境:启动 HTTP 服务器,拦截浏览器对 ESM 模块的请求,对非 JS 模块(CSS、Vue)进行实时编译(用 ESBuild),返回编译后的模块;node_modules 依赖提前预构建(转为 ESM),避免浏览器重复请求;
生产环境:用 Rollup 打包(Rollup 更适合 ESM,打包体积比 Webpack 小),执行 tree-shaking、代码分割等优化,生成最终产物。
关键:Vite 快的核心是“避免全量打包”和“ESBuild 极速编译”(ESBuild 用 Go 编写,比 JS 编写的 Webpack loader 快 10-100 倍)。
追问:Vite 开发环境为什么能直接支持 ESM?
补充:Vite 给 HTML 中插入的 script 标签添加了 type="module",浏览器会原生解析 ESM 语法,Vite 仅需拦截模块请求,实时编译非 ESM 模块即可。
Q6:Vite 的核心配置?如何解决 Vite 兼容旧浏览器的问题?
标准答案:
- Vite 核心配置(vite.config.js):
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import path from "path";
import legacy from "@vitejs/plugin-legacy";
export default defineConfig({
plugins: [
vue(), // Vue插件
legacy({
// 兼容旧浏览器
targets: ["defaults", "not IE 11"],
additionalLegacyPolyfills: ["regenerator-runtime/runtime"],
}),
],
resolve: {
alias: { "@": path.resolve(__dirname, "src") }, // 别名
extensions: [".mjs", ".js", ".ts", ".jsx", ".tsx", ".json", ".vue"],
},
server: {
port: 3000,
hot: true, // 热更新(Vite默认开启)
open: true,
proxy: {
// 跨域代理
"/api": {
target: "http://localhost:8080",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ""),
},
},
},
build: {
outDir: "dist", // 输出目录
assetsDir: "assets", // 静态资源目录
minify: "terser", // 压缩方式
sourcemap: process.env.NODE_ENV === "development", // source-map
rollupOptions: {
// Rollup配置(代码分割)
output: {
chunkFileNames: "js/[name].[hash].js",
entryFileNames: "js/[name].[hash].js",
assetFileNames: "[ext]/[name].[hash].[ext]",
manualChunks: (id) => {
// 自定义代码分割
if (id.includes("node_modules")) return "vendor";
},
},
},
},
css: {
// CSS配置
preprocessorOptions: {
less: { additionalData: '@import "@/styles/variables.less";' }, // 全局注入less变量
},
devSourcemap: true, // 开发环境CSS source-map
},
});- 旧浏览器兼容方案(主要解决 ESM 不支持问题):
使用@vitejs/plugin-legacy 插件,生成 legacy 版本的代码(ES5),自动注入 polyfill;
配置 targets 指定兼容的浏览器版本(如支持 IE11);
对于不支持的 API(如 Promise、fetch),用 core-js 注入 polyfill;
生产环境打包时,Vite 会生成两个版本的代码,浏览器自动选择适配的版本。
坑点:Vite 开发环境不支持 CommonJS 模块,若依赖是 CommonJS,需用@vitejs/plugin-commonjs 插件转换;legacy 插件会增加打包体积,需按需配置。
第二部分:模块化与包管理(工程化基础)
一、前端模块化规范
Q1:ESM(ES6 模块)与 CommonJS 的核心区别?加载机制差异?
标准答案:
一、核心区别
| 维度 | CommonJS | ESM |
|---|---|---|
| 适用环境 | Node.js 原生支持,浏览器需打包转换 | 现代浏览器原生支持,Node.js 14+ 支持 |
| 导出方式 | module.exports(默认导出)、exports(命名导出) | export default(默认导出)、export(命名导出) |
| 导入方式 | require()(动态导入,可在任意位置) | import(静态导入,需在顶层)、import()(动态导入) |
| 加载机制 | 运行时加载,加载的是值的拷贝 | 编译时加载(静态分析),加载的是值的引用 |
| 可变性 | 导入的模块可修改(修改的是拷贝,不影响原模块) | 导入的模块不可修改(只读引用) |
| 循环依赖 | 支持,但可能导致模块未完全初始化 | 支持,编译时处理,避免未初始化问题 |
| Tree Shaking | 不支持(动态加载,无法静态分析) | 支持(静态分析,可移除未使用代码) |
二、加载机制差异(重点)
CommonJS 加载机制(运行时):
执行 require()时,同步加载模块,执行模块代码,生成 module.exports 对象;
缓存模块:第一次加载后,将 module.exports 缓存,后续 require 直接读取缓存;
加载的是值的拷贝:若原模块修改了导出的值,导入方不会感知(除非导出的是引用类型)。
ESM 加载机制(编译时):
编译时解析 import/export,确定依赖关系,生成依赖图谱(不执行代码);
运行时按需加载模块,执行模块代码,建立模块间的引用关系;
加载的是值的引用:原模块修改导出的值,导入方会同步感知(只读,不能修改);
静态导入必须在模块顶层,不能在条件语句、函数内(动态导入 import()除外)。
坑点:Node.js 中使用 ESM,需在 package.json 中设置"type": "module",否则会被解析为 CommonJS;此时 require()不可用,需用 import/import()。
追问:如何解决 ESM 与 CommonJS 模块的互操作问题?
补充:ESM 中可导入 CommonJS 模块(默认导入获取 module.exports 对象);CommonJS 中不能直接导入 ESM 模块,需用动态 import()(返回 Promise)。
Q2:UMD 模块规范的原理?适用场景?
标准答案:
原理:UMD(Universal Module Definition)是通用模块规范,兼容 CommonJS、AMD、全局变量三种方式,实现“一处编写,多环境运行”。
核心逻辑(判断环境,选择对应导出方式):
(function (root, factory) {
// 1. 若支持CommonJS(Node.js),用module.exports导出
if (typeof module === "object" && typeof module.exports === "object") {
module.exports = factory();
}
// 2. 若支持AMD(如RequireJS),用define导出
else if (typeof define === "function" && define.amd) {
define([], factory);
}
// 3. 否则暴露为全局变量(浏览器)
else {
root.MyModule = factory();
}
})(this, function () {
// 模块核心逻辑
return { foo: "bar" };
});- 适用场景:
开发第三方库(如 lodash、jquery),需兼容浏览器、Node.js、AMD 等多环境;
旧项目迁移,需同时支持多种模块化规范。
缺点:代码冗余(包含环境判断逻辑),不支持 Tree Shaking,现代第三方库更倾向于用 ESM+CommonJS 双版本发布。
二、包管理工具(npm/yarn/pnpm)
Q1:npm 与 pnpm 的核心区别?pnpm 的优势?
标准答案:
一、核心区别
| 维度 | npm | pnpm |
|---|---|---|
| 依赖安装机制 | 扁平化依赖(hoisting),所有依赖平铺到 node_modules 根目录 | 非扁平化,用符号链接(symlink)关联依赖,依赖存储在全局仓库 |
| 磁盘空间占用 | 大,多个项目重复安装相同依赖 | 小,相同依赖全局仅存储一份,项目间共享 |
| 安装速度 | 慢,依赖解析和安装效率低 | 快,并行安装,复用全局缓存 |
| 依赖冲突 | 易出现(扁平化导致版本覆盖) | 不易出现,每个依赖的依赖独立存储 |
| monorepo 支持 | 需额外配置,支持较差 | 原生支持,通过 workspace 配置,高效管理多包项目 |
| 安全性 | 低,允许访问未声明的依赖(扁平化导致) | 高,仅允许访问声明的依赖,避免依赖逃逸 |
二、pnpm 核心优势
高效缓存:相同版本的依赖全局仅存储一份,项目间共享,节省磁盘空间,加速安装;
符号链接机制:避免依赖扁平化带来的冲突,每个依赖的依赖树独立,更规范;
原生 monorepo 支持:通过 pnpm-workspace.yaml 配置,管理多个子包,支持子包间依赖关联、批量执行脚本;
更高安全性:禁止访问未在 package.json 中声明的依赖,避免恶意依赖攻击;
兼容 npm/yarn:可直接使用 package.json 和 lock 文件,无需修改配置。
坑点:pnpm 的非扁平化依赖可能导致部分旧项目不兼容(如依赖未声明的全局依赖),需手动在 package.json 中添加 peerDependencies 或 devDependencies。
追问:pnpm 的 workspace 配置如何写?适用场景?
补充:pnpm-workspace.yaml 配置示例(monorepo 场景):
# pnpm-workspace.yaml
packages:
- "packages/*" # 所有子包放在packages目录下
- "apps/*" # 应用放在apps目录下
- "!**/node_modules" # 排除node_modules
- "!**/dist" # 排除dist目录Q2:package.json 中 dependencies、devDependencies、peerDependencies 的区别?
标准答案:
dependencies(生产依赖):
定义:项目运行时必须依赖的包(如 vue、react、axios);
安装:npm install 自动安装,打包时会被包含在产物中;
示例:npm install axios --save(--save 可省略,npm5+默认)。
devDependencies(开发依赖):
定义:仅开发和构建时需要的包(如 Webpack、ESLint、Prettier、babel);
安装:npm install --save-dev 安装,生产环境(npm install --production)不会安装;
示例:npm install webpack --save-dev。
peerDependencies(同伴依赖):
定义:项目需要依赖的“宿主”包,且要求宿主包有指定版本(如 vue 插件依赖 vue);
作用:避免重复安装宿主包,明确依赖的兼容性(如 vue-router@4 依赖 vue@3);
安装:不会自动安装,需用户手动安装指定版本的宿主包,否则会报警告;
示例:vue-router 的 peerDependencies 要求 vue 版本 ≥3.0.0。
补充:peerDependenciesMeta(npm7+支持):用于标记 peer 依赖是否可选,避免强制用户安装。
坑点:npm7+ 会自动安装 peerDependencies,可能导致版本冲突;pnpm 会严格校验 peerDependencies 版本,不匹配会报错。
Q3:lock 文件(package-lock.json/pnpm-lock.yaml)的作用?为什么不能提交到.gitignore?
标准答案:
- 核心作用:
锁定依赖版本:记录每个依赖的精确版本(包括子依赖),避免“语义化版本”导致的安装差异(如 package.json 中^1.0.0 可能安装 1.0.1、1.1.0 等);
记录依赖树:存储依赖的层级关系、下载地址、哈希值,确保所有开发者、部署环境安装的依赖完全一致;
加速安装:lock 文件包含依赖的下载地址和缓存信息,安装时无需重新解析依赖,直接下载。
- 必须提交到版本库的原因:
保证团队开发环境一致:避免不同开发者安装不同版本的依赖,导致代码运行差异、bug 难以复现;
保证部署环境一致:生产部署时,根据 lock 文件安装依赖,避免线上版本与测试版本不一致;
避免依赖漏洞:锁定已知安全的依赖版本,防止自动升级到有漏洞的版本。
坑点:不同包管理工具的 lock 文件不兼容(如 npm 的 lock 文件不能用于 pnpm),需在项目中统一包管理工具(如用.npmrc、.pnpmrc 指定)。
Q4:monorepo 是什么?pnpm 如何实现 monorepo 管理?优势?
标准答案:
monorepo 定义:单仓库多包管理模式,将多个相关的子包(如组件库、工具库、应用)放在同一个代码仓库中管理。
pnpm 实现 monorepo 的核心配置(三步):
创建 pnpm-workspace.yaml,指定子包目录: `packages:
- 'packages/*' # 子包目录
- 'apps/*' # 应用目录
- '!**/node_modules'
- '!**/dist'`
每个子包创建独立的 package.json,声明名称、版本、依赖(子包间依赖可直接引用包名);
使用 pnpm 命令管理子包:
安装所有子包依赖:pnpm install;
给指定子包安装依赖:pnpm add axios --filter @xxx/utils;
批量执行脚本:pnpm run build --filter *;
子包间关联:pnpm add @xxx/utils --filter @xxx/app(将 utils 包作为 app 的依赖)。
monorepo 核心优势:
统一版本管理:所有子包共享依赖,避免版本冲突,便于统一升级;
子包联动高效:子包修改后,其他依赖该子包的项目可实时感知,无需发布 npm 包;
简化协作:团队共享一个仓库,无需维护多个仓库,便于代码复用、评审;
统一构建部署:可批量执行构建、测试、部署脚本,提升效率。
对比:多仓库(multirepo)的缺点是子包关联复杂、版本同步困难、代码复用成本高,适合独立的、不相关的项目。
追问:monorepo 如何处理子包版本管理和发布?
补充:用 changesets、lerna 等工具,自动检测子包变更,生成变更日志,批量升级版本、发布子包。
第三部分:代码质量与规范(工程化落地)
一、ESLint 与 Prettier
Q1:ESLint 与 Prettier 的核心区别?如何解决两者的冲突?
标准答案:
一、核心区别
| 维度 | ESLint | Prettier |
|---|---|---|
| 核心作用 | 检查 JS/TS 代码质量(语法错误、逻辑问题、代码规范) | 格式化代码(排版、缩进、引号、换行等格式问题) |
| 检查范围 | JS/TS/JSX/TSX,可通过插件支持 Vue、React 等 | 多语言(JS/TS/CSS/HTML/JSON 等) |
| 配置方式 | .eslintrc.js/.eslintrc.json,支持规则自定义 | .prettierrc.js/.prettierignore,支持格式自定义 |
| 冲突点 | 包含部分格式规则(如缩进、引号),可能与 Prettier 冲突 | 仅管格式,不关心代码质量 |
二、冲突解决方案(核心:让 ESLint 放弃格式检查,交给 Prettier)
- 安装依赖: `# 核心依赖 pnpm add eslint prettier -D
解决冲突的插件
pnpm add eslint-config-prettier eslint-plugin-prettier -D`
配置 ESLint(.eslintrc.js):
module.exports = { env: { browser: true, es2021: true, node: true }, extends: [ 'eslint:recommended', 'plugin:react/recommended', 'plugin:prettier/recommended' // 关键:集成Prettier ], parser: '@babel/eslint-parser', parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, plugins: ['react'], rules: { // 关闭ESLint中的格式规则,交给Prettier 'indent': 'off', 'quotes': 'off', 'semi': 'off', // 开启Prettier插件的规则(格式错误报ESLint错误) 'prettier/prettier': 'error' } };创建.prettierignore,忽略无需格式化的文件:
node_modules dist build *.config.js添加脚本(package.json):
"scripts": { "lint": "eslint .", "lint:fix": "eslint . --fix", // 自动修复ESLint和Prettier问题 "format": "prettier --write ." // 仅格式化 }
核心逻辑:eslint-config-prettier 关闭 ESLint 中与 Prettier 冲突的格式规则;eslint-plugin-prettier 将 Prettier 的格式规则转为 ESLint 规则,统一用 ESLint 命令管理。
Q2:ESLint 的工作原理?如何自定义 ESLint 规则?
标准答案:
核心工作原理:
解析代码:用 ESLint 内置的 Espree 解析器(基于 Acorn),将 JS 代码转为 AST(抽象语法树);
遍历 AST:按照配置的规则,遍历 AST 节点,检查节点是否符合规则;
报告错误:对不符合规则的节点,根据规则级别(off/warn/error)生成警告或错误;
自动修复:对支持修复的规则,根据规则逻辑修改 AST,生成修复后的代码。
规则级别:
0(off):关闭该规则;
1(warn):违反规则报警告,不影响构建;
2(error):违反规则报错,中断构建(需修复才能继续)。
(注:文档部分内容可能由 AI 生成)