webpack 面试问题
webpack
简述 WebPack 的核心原理 ?
Webpack 是一个现代 JavaScript 应用程序的静态模块打包工具。它的核心原理可以概括为以下几个关键点:
模块:在 Webpack 中,一切都被视为模块。Webpack 允许将这些不同类型的资源视作模块来管理。
入口(entry):Webpack 的打包过程从指定的入口文件开始。Webpack 将从入口文件全部依赖的模块开始,生成一个依赖图。
依赖图(dependency graph):Webpack 通过分析入口文件及其所有依赖,构建一个完整的依赖图。这个图包含了应用所有模块及其之间的关系。
加载器(loaders):Webpack 支持将非 JavaScript 文件视为模块,因此需要使用加载器来处理这些文件。例如,可以使用 Babel 加载器将 ES6+ 代码转译为 ES5,或使用 CSS 加载器处理 CSS 文件。加载器在模块被加载之前对其进行转换。
插件(plugins):Webpack 通过插件系统来扩展自身的功能。插件可以在打包的不同阶段执行特定的任务,如优化打包结果、生成 HTML 文件、压缩代码等。
输出(output):最后,Webpack 将打包并生成一个或多个输出文件。输出文件的配置可以在 Webpack 配置文件中指定。
热模块替换(HMR):Webpack 还支持热模块替换功能,允许在不刷新页面的情况下更新模块,提升开发体验。
webpack 构建流程、生命周期
它的运行流程是一个串行的过程。
1、初始化阶段:从配置文件和 shell 语句中读取并且合并参数,得到最终的参数。
2、编译阶段:初始化 Compiler 对象,触发 run 钩子,开始递归编译。调用所有的 loader 对模块进行编译,找出模块依赖的模块,再递归本步骤。
3、构建阶段:编译器创建新的编译实例, 生成 compilation,触发 make 钩子。
4、优化模块:触发 optimize 钩子。
5、output 输出阶段:触发 emit 钩子。根据入口以及它们依赖关系组装成一个个包含多个模块的 chunk,再把每个 chunk 转换成单独的文件加入到输出列表中。
7、完成阶段:触发 done 钩子。根据配置确定输出的路径和文件名,把文件内容写到文件中,
(前端模块化)JavaScript 模块化是否等同于异步模块化?
JavaScript 模块化是指将代码分割成独立的模块,每个模块可以进行单独的开发、测试和维护。模块化的目的是提高代码的可复用性、可维护性和可读性。
异步模块化: 异步模块化则是指在模块加载时采用异步的方法。这种方法的优点是可以在页面渲染的同时加载模块,避免因加载模块造成的阻塞。
常见的模块化规范有:
CommonJS:主要用于服务器端的模块化(比如 Node.js),采用同步加载模块的方式。
ES6 模块:是 ECMAScript 2015 引入的模块化标准,支持导入导出,可以在浏览器和 Node.js 中使用。
AMD:用于在浏览器和服务器中加载模块,通过异步方式提升页面性能和用户体验。
CMD:用于浏览器中异步加载模块,模块使用时才加载模块,整合了 CommonJS 和 es6
ES6 和 CommonJS 的区别
ES6
因为 ESM 是静态化的,所以在运行前的编译阶段就可以确定模块关系,从而进行一些模块之间的操作,比如 treeshaking 等等.
ES6 通过 export 接口,可以取到模块内部实时的值。js 引擎对脚本静态分析的时候,遇到模块加载命令 import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值
CommonJS
CommonJS 只能在运行时确定这些东西。CommonJS 加载的是一个对象(即 module.exports 属性),该对象只有在脚本运行完才会生成
CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值
webpack 有哪些常见的 loader? 你用过哪些 loader?
thread-loader: 多进程 / 多实例构建
image-loader:加载并压缩图片文件
json-loader:加载 json 文件
babel-loader:将 es6 装换成 es5
ts-loader:将 ts 转换成 js
sass-loader、less-loader:都是转换成 css
css-loader:加载 css
webpack 有哪些常见的 Plugin? 你用过哪些 Plugin?
uglifyjs-webpack-plugin:压缩 js
webpack-parallel-uglify-plugin :可以并行运行 UglifyJS 插件,从而大大减少构建时间
terser-webpack-plugin :它使用 Terser 库来执行压缩 js,
mini-css-extract-plugin:分离样式文件,css 提取为单独的文件,支持按需加载
说说 Loader 和 Plugin 的区别
loader 本质是一个函数,对接收到的内容进行转换,返回转换后的结果,webpack 只认识 js,所以 loader 就相当于翻译官。对其他类型资源进行转译和预处理。
plugin:是插件,它基于事件流框架 Tabable,可以扩展 webpack 功能,在 webpack 运行的生命周期中会广播很多事件。plugin 可以监听这些事件,在适合的时机通过 webpackapi 改变输出的结果。
如何优化 webpack 的构建速度?
使用高版本 webpack 和 node.js
多进程 / 多实例构建 thread-loader
压缩代码 、多进程并行压缩 webpack-parallel-uglify-plugin
mini-css-extract-plugin : 可以避免将 CSS 代码打包到 JavaScript 文件中,减少 JavaScript 的体积,同时也可以使得 CSS 文件可以被浏览器缓存,提高页面加载速度。
图片压缩 (image-webpack-plugin)
缩小打包作用域
通过 exclude / include 来确定 loader 规则范围。
设置 resolve.extensions.
提取页面公共的资源
基础包分离
使用 html-webpack-externals-plugin 基础包通过 cdn 引入,不打入 bundle 中。
充分利用缓存提升二次构建速度。
使用 babel-loader 开启缓存
terser-webpack-plugin 开启缓存
cache-loader
tree shaking (没有用过的模块代码进行标记,从最终 bundle 中去掉)
是否写过 loader?简单描写一下编写 loader 的思路?
loader 是支持链式调用的,所有在开发中需要严格遵守单一职责。每个 loader 负责自己所需要负责的事情。
loader 是运行在 nodejs 中的,可以调用 nodejs api 或者安装第三方模块调用。 传给 loader 的原内容是 utf-8
编码的字符串 尽可能异步化 loader ,如果计算量小的化同步也可以
loader 是无状态的,不应该在 loader 中保留状态
实现过 style-loader -> css-loader
是否写过 Plugin?简单描写一下编写 Plugin 的思路?
webpack 运行生命周期中会广播出很多事件,让 plugin 监听事件,特定阶段钩入想要添加的自定义插件内容
webpack 通过 tapable 事件流机制保证插件有序性,使系统的扩张性很好
compiler 暴露整个生命周期相关钩子
compilation 对象代表了一次资源版本构建, compilation 对象也提供了很多关键时机的回调,以供插件做自定义处理时选择使用
plugin 需要再原型上绑定 apply 方法,才能访问 compiler 实例。
// 一个 JavaScript 类class MyExampleWebpackPlugin { // 在插件函数的 prototype 上定义一个 apply 方法,以 compiler 为参数。 apply(compiler) { // 指定一个挂载到 webpack 自身的事件钩子。 // compiler.hooks.事件或周期名.tapAsync或tap compiler.hooks.emit.tapAsync( "MyExampleWebpackPlugin", (compilation, callback) => { console.log("这是一个示例插件!"); console.log( "这里表示了资源的单次构建的 compilation 对象:", compilation ); // 用 webpack 提供的插件 API 处理构建过程 compilation.addModule(/* ... */); callback(); } ); }}
Plugin 的不同类型
同步钩子、瀑布流钩子(插件的输入时上一个的输出)、异步钩子
说说 Babel 原理
在 webpack 中,Babel 主要用于将 ES6 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其它环境中。Babel 的转化过程如下:
Parse:使用将原始代码转换为 AST 树,进行了词法分析与语法分析的过程。。
Transform:通过 babel-traverse 对前面的抽象语法树进行遍历修改,并获得新的抽象语法树。
Generator:使用 babel-generator 将抽象语法树转换为代码。
转换前的源码 -> 词法分析(将代码中的单词拆分) -> 形成 tokens 数组 -> 语法分析(解析关键字)-> AST 抽象语法树
https://juejin.cn/post/7051507027499024415#heading-1
webpack 热更新原理
Webpack 的热更新(HMR)是一种在开发时提供实时更新的功能,它使得在修改代码后,不需要完全刷新页面就能立即看到更新的效果。HMR 的原理涉及以下几个主要步骤:
启动时建立 WebSocket 连接:在项目启动时,Webpack 会创建与开发服务器的 WebSocket 连接,用于在构建完成后接收更新的模块。
compiler 需要发出 "update",将之前的版本更新到新的版本。"update" 由两部分组成:
更新后的 manifest (JSON): manifest 包括新的 compilation hash 和所有的 updated chunk 列表。
一个或多个 updated chunk (JavaScript): 每个 chunk 都包含着全部更新模块的最新代码(或一个 flag 用于表明此模块需要被移除)。
compiler 会确保在这些构建之间的模块 ID 和 chunk ID 保持一致。通常将这些 ID 存储在内存中(例如,使用 webpack-dev-server 时),但是也可能会将它们存储在一个 JSON 文件中。
webpack 如果使用了 hash 命名,是否每次都会重新生成 hash? 简单说下 webpack 的几种 hash 策略?
所以如果使用 hash,并不是每次都会重新生成新的 hash,需要看具体使用的是哪种 hash 策略。
hash 是跟整个项目的构建相关,只要项目里有文件更改,整个项目构建的 hash 值都会更改,并且全部文件都共用相同的 hash 值。
chunkhash 是根据不同的入口进行依赖文件解析,构建对应的 chunk 生成相应的 hash 值。只有被修改过的 chunk 在重新构建之后才会生成新的 hash 值,不会影响其它的 chunk。
contenthash 是跟每个生成文件有关,每个文件都有一个唯一的 hash 值。当要构建的文件内容发生改变时,就会生成新的 hash 值,并且该文件的改变并不会影响和它同一模块下的其他文件。
补充
补充:高频追问点速记:
- loader vs plugin:loader 是"文件级转换器"(从右到左链式执行),plugin 是"生命周期钩子"(基于 Tapable 事件流机制)。
- HMR 关键 API:
module.hot.accept标记模块可热替换;module.hot.dispose在替换前清理副作用。- 三种 hash 策略:
hash(全项目共用)、chunkhash(每个 chunk 独立)、contenthash(按内容,推荐用于长期缓存)。- tree-shaking 生效条件:ES Module 语法 +
sideEffects: false+ 启用usedExports,否则常见 CommonJS 互操作会破坏摇树。- webpack 5 内置特性:持久缓存(
cache: { type: 'filesystem' })、资源模块(asset/resource替代 file-loader)、FederationPlugin。
来源整理自:我的有道云笔记



