构建工具
Webpack
Webpack 是什么?它的核心功能是什么?
- Webpack 是模块化打包工具,将分散的 JS、CSS、图片等资源视为模块,分析依赖关系并打包成静态文件。
- 核心功能:代码编译、依赖分析、代码分割、资源优化。
Webpack 的核心概念是什么?
- 入口(Entry):指示 Webpack 应该从哪个文件开始构建依赖图(可以指定一个或多个入口文件)。
- 输出(Output):指示 Webpack 在哪里输出打包文件。
- 加载器(Loader):允许 Webpack 处理非 JavaScript 文件(如 CSS、图片、字体等)。
- 插件(Plugin):用于扩展 Webpack 功能(如:优化资源、压缩代码、添加环境变量等)。
- 模式(Mode):用于指定 Webpack 的构建模式,可以是开发模式(development)、生产模式(production)或其他。
Loader 和 Plugin 的作用及区别是什么?
- Loader:处理非 JS 文件(如转换 SASS/LESS 为 CSS、转换 ES6/ES7 为 ES5 等),按顺序链式调用。
- Plugin:扩展 Webpack 功能(如生成 HTML 文件,代码压缩、资源优化、环境变量注入等),通过钩子影响打包全流程。
记忆点:Loader 处理非 JS 文件,Plugin 扩展 Webpack 功能。
Webpack 如何实现代码分割(Code Splitting)?
- 入口分割:配置多入口(
entry: { a: 'a.js', b: 'b.js' })。 - 动态导入:使用
import()语法,生成独立 chunk。 - 配置
optimization.splitChunks提取公共代码。
记忆点:多入口 + 动态导入 + splitChunks 优化。
- 入口分割:配置多入口(
Webpack 的 Tree Shaking 如何生效?需要满足哪些条件?
- 基于 ES Module 静态语法(
import/export),删除未使用代码。 - 条件:生产模式(
mode: 'production')、禁用 Babel 的 ESM 转换、package.json中标记sideEffects: false。
记忆点:ESM 语法 + 生产模式 + sideEffects 标记。
- 基于 ES Module 静态语法(
Webpack 构建性能优化有哪些常见手段?
- Tree Shaking: 使用 ES6 模块语法并启用 Tree Shaking 来消除未使用的代码,减小打包体积。
- 代码拆分 (Code Splitting): 将应用程序拆分为多个 bundle,并实现按需加载,以减少初始加载时需要下载的资源量,加快页面加载速度。
- 使用 CDN: 将第三方依赖库(如 React、Vue、jQuery 等)从打包中移除,并通过 CDN 引入,减小打包体积并加速页面加载。
- 压缩资源: 使用 Webpack 的压缩插件(如 uglifyjs-webpack-plugin、terser-webpack-plugin 等)来压缩 JavaScript、CSS 代码等,减小文件体积。
- 优化图片: 使用 image-webpack-loader 或者 url-loader 来优化图片资源,压缩图片并将小图片转换为 base64 格式,减少 HTTP 请求并加快加载速度。
- 提取公共代码: 使用 Webpack 的 SplitChunksPlugin 来提取公共模块,减少重复代码,并将其打包到单独的文件中,以便浏览器缓存和复用。
- 缓存优化: 使用 Webpack 的持久化缓存(persistent caching)功能,利用构建结果的 hash 值来标识构建结果的变化,从而提高构建效率。
- 多进程构建: 使用 webpack-parallel-uglify-plugin、thread-loader 等插件来启用多进程构建,利用多核 CPU 提高构建速度。
- 减少不必要的 Loader: 只加载必要的 Loader,避免加载过多的 Loader,以减少构建时间。
- 使用 DllPlugin: 使用 webpack.DllPlugin 来预先编译第三方库,以减少每次构建时的依赖分析时间。
- 减少文件搜索范围: 使用 resolve.modules、resolve.extensions、resolve.alias 等配置来减少文件搜索范围,提高构建速度。
Vite
Vite 是什么?它解决了什么问题?
- Vite 是新一代前端构建工具,解决传统打包工具(如 Webpack)开发环境冷启动慢、热更新效率低的问题。
- 核心思想:开发环境基于浏览器原生 ESM,生产环境用 Rollup 打包(最新版本使用 Rolldown 打包)。
Vite 为什么开发环境比 Webpack 快?
- 无打包:浏览器直接加载 ESM 模块。
- 按需编译:只编译在浏览器中实际使用的代码。
- 预构建:用 esbuild 将 CommonJS 依赖转换为 ESM。
- 并行构建:多个进程中同时编译代码。
- 缓存:依赖预构建结果缓存,避免重复编译。
记忆点:原生 ESM + esbuild 预构建 + 缓存机制。
Vite 的热更新(HMR)为什么高效?
- 基于浏览器原生 ESM,仅更新修改的模块,无需重新构建依赖图。
- 服务端通过
WebSocket推送更新信息,客户端精准替换模块。
记忆点:精准模块替换 + WebSocket 推送。
Vite 如何处理 TypeScript 和 CSS 预处理器?
- TypeScript:内置支持,直接编译(无需额外配置)。
- CSS 预处理器:安装对应工具(如 sass/less/stylus)即可直接使用。
记忆点:TS 开箱即用,CSS 预处理需安装工具。
Vite 生产环境打包为什么选择 Rollup?
- Rollup 的 Tree Shaking 更彻底,输出代码更小。
- 更适合生成符合 ESM 标准的库或应用。
记忆点:Rollup 更小的输出 + 更严格的 ESM 支持。
Rollup
Rollup 是什么?它的设计目标是什么?
- Rollup 是专注于 JavaScript 库打包的工具,设计目标是生成更小、更高效的代码,尤其适合库的发布。
核心优势:Tree Shaking 彻底、输出格式灵活(ESM/UMD 等)。
- Rollup 是专注于 JavaScript 库打包的工具,设计目标是生成更小、更高效的代码,尤其适合库的发布。
Rollup 和 Webpack 的主要区别是什么?
- Rollup:适合库,输出扁平化代码,Tree Shaking 更彻底。
- Webpack:适合应用,支持代码分割、动态加载等复杂功能。
Rollup 如何处理 CommonJS 模块?
- 需安装
@rollup/plugin-commonjs插件转换 CommonJS 为 ESM。 - 配合
@rollup/plugin-node-resolve解析node_modules依赖。
记忆点:CommonJS 需插件转换,Node 模块需解析插件。
- 需安装
为什么 Rollup 的 Tree Shaking 更高效?
- 基于 ESM 静态分析,删除所有未使用的代码。
- 输出结果无 Webpack 的模块包装代码,结构更扁平。
记忆点:ESM 静态分析 + 无冗余代码。
Rollup 如何实现代码分割(Code Splitting)?
- 配置
output.dir代替output.file,输出多文件。 - 使用动态导入(
import())自动分割代码。
记忆点:多文件输出 + 动态导入自动分割。
- 配置
Rolldown
Rolldown 是什么?它与 Rollup 和 Webpack 的主要区别是什么?
Rolldown 是一个用 Rust 编写的新型 JavaScript 模块打包器,它兼容 Rollup 的 API 和配置格式。
主要区别在于:Rolldown 通过 Rust 实现获得更高的构建性能;设计目标是成为 Vite 未来的打包基石,相比 Webpack 配置更简洁,比 Rollup 速度更快。
Rolldown 在性能上有哪些优势?这些优势是如何实现的?
Rolldown 的性能优势主要体现在更快的构建速度。这得益于其底层的 Rust 实现,以及高效的并行处理能力。Rolldown 在模块分析、树摇 (Tree-shaking) 和代码生成等环节进行了性能优化。
如何在现有项目中初步配置和使用 Rolldown?
安装 Rolldown 后,创建一个
rolldown.config.js文件。配置需要定义入口点 (input) 和输出选项 (output),其格式与 Rollup 配置文件相似。例如:javascript// rolldown.config.js export default { input: 'src/index.js', output: { file: 'dist/bundle.js', format: 'esm' } };Rolldown 的核心概念有哪些?
Rolldown 的核心概念继承自 Rollup,主要包括:
- Input (入口):指定打包的起始文件。
- Output (输出):定义打包后文件的位置和格式 (如 ES module, CommonJS)。
- Plugins (插件):用于在打包过程的不同阶段执行自定义任务,扩展功能。
- Tree-shaking:静态分析代码,移除未被使用的导出,以减小最终包体积。
谈谈 Rolldown 的插件系统。如何编写一个自定义插件?
Rolldown 的插件系统与 Rollup 兼容。一个插件就是一个对象,它通过实现特定的 钩子函数 (Hook) 来在打包生命周期 (如
buildStart,transform,generateBundle) 中注入自定义逻辑。例如,一个简单的插件可以在transform钩子中处理代码:javascriptconst myPlugin = () => { return { name: 'my-plugin', transform(code, id) { // 在这里转换代码 return code; } }; };
Babel
Babel 是什么?它的工作原理是什么?
Babel 是一个 JavaScript 编译器,主要用于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。
工作原理可以简单概括为三个步骤:解析(Parse)、转换(Transform)、生成(Generate)。解析步骤将代码转换成抽象语法树(AST),转换步骤对 AST 进行遍历并执行各种变换操作,生成步骤将变换后的 AST 再转换成代码字符串。
Babel 的工作原理是什么?请描述其编译流程
Babel 的编译分为三个核心阶段:
- 解析 (Parsing):将源代码通过词法分析和语法分析转换为抽象语法树 (AST)
- 转换 (Transforming):遍历 AST,应用插件对语法节点进行转换、增删改
- 生成 (Code Generation):将转换后的 AST 重新生成为目标代码
Babel 插件和预设 (Preset) 有什么区别?执行顺序是怎样的?
- 插件:单个语法转换功能,如
@babel/plugin-transform-arrow-functions - 预设:插件集合,如
@babel/preset-env、@babel/preset-react - 执行顺序:插件在预设前执行,插件从前往后,预设从后往前
- 插件:单个语法转换功能,如
@babel/preset-env 的作用是什么?如何配置浏览器兼容性?
- 作用:根据目标环境自动确定需要的转换和 polyfill
- 配置方式:
javascript{ "presets": [ ["@babel/preset-env", { "targets": { "browsers": ["last 2 versions", "ie >= 11"] }, "useBuiltIns": "usage", // 按需引入 polyfill "corejs": 3 }] ] }Babel 的 polyfill 与 runtime 有什么区别?如何选择?
- @babel/polyfill:全局引入,会污染全局环境,包含所有 ES6+特性
- @babel/runtime:按需引入,局部变量,适合库开发
- 选择:应用开发可用 polyfill,库开发推荐 runtime
AST
什么是 AST?它在编程语言处理中起什么作用?
AST(抽象语法树)是源代码的结构化表示,它抽象掉代码的具体语法细节(如分号、括号),保留程序的结构和语义。主要作用:
- 代码分析:ESLint、类型检查
- 代码转换:Babel、代码压缩
- 代码生成:编译器后端、代码格式化
描述从源代码到 AST 的完整解析过程
解析过程分为两个关键阶段:
javascript// 源代码:const sum = (a, b) => a + b // 1. 词法分析 (Lexical Analysis) // 输出 tokens: [const, sum, =, (, a, ,, b, ), =>, a, +, b] // 2. 语法分析 (Syntax Analysis) // 构建 AST 结构: // VariableDeclaration // - Identifier: sum // - ArrowFunctionExpression // - Parameters: [a, b] // - Body: BinaryExpression (a + b)在 JavaScript 生态中,AST 有哪些具体应用场景?
主要应用场景:
- Babel:ES6+ 代码转换,JSX 编译
- ESLint/Prettier:代码规范检查和格式化
- Webpack:模块依赖分析,Tree Shaking
- TypeScript:类型检查,代码转换
- 代码压缩工具:UglifyJS、Terser
如何通过工具操作和遍历 AST?请举例说明
常用工具和模式:
javascript// 使用 @babel/parser 解析 const parser = require('@babel/parser'); const ast = parser.parse('const x = 1;'); // 使用 @babel/traverse 遍历和修改 const traverse = require('@babel/traverse').default; traverse(ast, { Identifier(path) { if (path.node.name === 'x') { path.node.name = 'y'; // 重命名变量 } } });编写 Babel 插件时,AST 节点的 path 对象包含哪些重要信息?
path 对象是关键 API,包含:
- node:当前 AST 节点
- parent:父节点引用
- parentPath:父节点的 path 对象
- scope:作用域信息(变量绑定)
- context:转换上下文
- 操作方法:replaceWith、remove、insertBefore 等
Gulp
Gulp 是什么?它适用于什么场景?
- Gulp 是基于流的自动化任务运行器,适用于文件转换、压缩、打包等重复性任务。
- 典型场景:静态资源处理(如压缩图片、编译 SASS)。
Gulp 的流(Stream)处理机制是什么?
- 通过
gulp.src()读取文件生成 Vinyl 虚拟文件流。 - 使用
pipe()连接插件处理文件(如gulp-sass编译 CSS)。 - 最终通过
gulp.dest()输出到磁盘。
记忆点:src → pipe 处理链 → dest 输出。
- 通过
如何用 Gulp 监听文件变化并自动执行任务?
- 使用
gulp.watch()监听文件变化。 - 示例:
gulp.watch('src/*.scss', gulp.series('compile-sass'))。
- 使用
Gulp 如何处理任务间的依赖关系?
- 使用
gulp.series()按顺序执行任务(如先清理再构建)。 - 使用
gulp.parallel()并行执行任务(如同时压缩 JS 和 CSS)。
记忆点:series 串行,parallel 并行。
- 使用
Tree Shaking
Tree Shaking 是什么?它的设计目标是什么?
- Tree Shaking 是消除 JavaScript 中未使用代码的优化技术,通过静态分析模块依赖关系,删除未被引用的代码(“死代码”)。
- 目标:减少代码体积,提升执行效率。
记忆点:静态分析依赖,删除无用代码。
Tree Shaking 的实现原理是什么?
- 依赖 ES Module:利用
import/export的静态语法特性(编译时分析依赖)。 - 标记未引用代码:构建工具(如 Webpack/Rollup)标记未被使用的导出。
- 删除阶段:在压缩阶段(如 Terser)移除标记的代码。
记忆点:ESM 静态分析 → 标记 → 压缩删除。
- 依赖 ES Module:利用
如何确保 Tree Shaking 生效?需要满足哪些条件?
- 代码层面:使用 ES Module(
import/export),避免 CommonJS。 - 工具配置:
- Webpack:
mode: 'production',optimization.usedExports: true。 - Rollup:默认开启,无需额外配置。
- Webpack:
- 库标记:在
package.json中设置sideEffects: false(或指定副作用文件)。
记忆点:ESM 语法 + 生产模式 + sideEffects 标记。
- 代码层面:使用 ES Module(
