🧩序言
在前端的面试中, webpack
也是面试官考察的重点之一。如果说候选人不会 webpack
,从某种层面上来说不会特别影响面试官最终的面试结果。但从某种意义上来说,候选人给面试官的印象分就会稍微折扣了一点点。所以呢,以不变应万变,还是抓紧时间复盘学习,应变面试官有可能涉及到的各种问题。
那么在接下来的这篇文章当中呢,将梳理 webpack
的基础知识,以及将对常见面试题进行汇总和解答。一起来学习吧~😝
🎨一、基础知识学习
我们先用一张思维导图来对 webpack
的一些基础知识进行总结归纳。详情见下图👇
对于以上内容,周一有整理过 5
篇文章。戳webpack基础知识传送门
大家可以到专栏进行学习查看,同时,如果想对 webpack
有一个系统一点的学习的话,推荐给大家可以直接看 《Webpack实战:入门、进阶与调优》这本书。
这本书相对来说对入门选手会友好一点,也是周一刚学习时看的一本书。其他书我也还没看过……等以后再来补充。
基础知识学会了,那么我们继续来看下比较常见的一些面试题汇总和解答~
🎲二、常见面试题汇总
同样地,我们先用一张思维导图来了解 webpack
中常见的一些面试题。详情见下图👇
现在我们来对这些问题进行一一解答。
🎯三、构建和打包
1、前端代码如何进行构建和打包?
Webpack
的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:
- 初始化参数: 从配置文件和
Shell
语句中q去读取与合并参数,得出最终的参数; - 开始编译: 用上一步得到的参数初始化
Compiler
对象,加载所有配置的插件,执行对象的run
方法开始执行编译; - 确定入口: 根据配置中的
entry
找出所有的入口文件; - 编译模块: 从入口文件出发,调用所有配置的
Loader
对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理; - 完成模块编译: 在经过第
4
步使用Loader
翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系; - 输出资源: 根据入口和模块之间的依赖关系,组装成一个个包含多个模块的
Chunk
,再把每个Chunk
转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会; - 输出完成: 在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统;
在以上过程中,Webpack
会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack
提供的 API
来改变 Webpack
的运行结果。
简单来说:
- 初始化参数: 启动构建,读取与合并配置参数,加载
Plugin
,实例化Compiler
; - 编译模块: 从
Entry
出发,针对每个Module
串行调用对应的Loader
去翻译文件的内容,再找到该Module
依赖的Module
,递归地进行编译处理; - 完成输出: 将编译后的
Module
组合成Chunk
,将Chunk
转换成文件,输出到文件系统中。
2、前端为何要进行打包和构建?
从代码层面来说:
- 进行构建和打包后的项目体体积更小(
Tree-Shaking
、压缩代码、合并代码,加载更快); - 可以对高级语言或语法进行编译(
TS
,ES6+
,模块化、scss
); - 对代码的兼容性和错误进行检查(
polyfill
、postcss
、eslint
)。
从研发层面来说:
- 使得研发有着更统一、高效的开发环境;
- 让项目组有着统一的构建流程和产出标准;
- 在提测和上线等流程中慢慢集成公司的构建规范。
3、webpack原理
- 首先需要先解析入口文件
entry
,使用@babel/parser
,将其转为AST(抽象语法树)
; - 使用
@babel/traverse
插件,去找出入口文件中的所有依赖模块
; - 然后使用
@babel/core+
和@babel/preset-env
插件,将入口文件的AST
转为Code
; - 将
2
中找到的入口文件的依赖模块,进行遍历递归
,重复执行1,2,3
; - 重写
require
函数,并与4
中生成的递归关系图
一起,输出到bundle
中。
🎰四、模块相关
1、module chunk bundle 分别是什么意思,有何区别?
- module - 代表各个源码文件,
webpack
中一切皆模块; - chunk - 表示多模块合并成的,如
entry
、import()
、splitChunk
; - bundle - 表示最终的输出文件。
2、source map是什么?开发环境和生产环境如何使用?
source map
是将编译、打包、压缩后的代码映射回源代码的过程。打包压缩后的代码不具备良好的可读性,想要调试源码就需要 soucre map
。map
文件只要不打开开发者工具,浏览器是不会加载的。下面给出在开发环境和生产环境下最适用的方式。
开发环境:cheap-module-eval-source-map
生产环境:cheap-module-source-map
注意点: 避免在生产中使用 inline-
和 eval-
,因为它们会增加 bundle
体积大小,并降低整体性能。
3、如何引用一个自己编写的库lib(第三方模块)
在
output
中配置filename
、library
以及libraryTarget
等项,来引用自己编写的第三方库;解决库引用冲突:
externals
。
🧶五、loader和plugin
1、webpack中常见的loader有哪些
- file-loader:常用于处理图片和字体,旨在把文件输出到一个文件夹中,之后在代码中通过相对的
url
去引用输出的文件。 - url-loader:常用于处理图片和字体,与
file-loader
类似,两个的区别在于,对于url-loader
来说,用户可以设置一个阈值,当大于阈值时会交给file-loader
处理,小于阈值时返回文件base64
形式编码。 - css-loader:用于加载
CSS
文件,支持模块化、压缩、文件导入等特性。 - style-loader:用于把
CSS
代码注入到JavaScript
中,通过DOM
操作去加载CSS
。 - sass-loader:用于将
SCSS/SASS
代码转换成CSS
代码。 - postcss-loader:解决兼容性问题,扩展
CSS
语法,使用下一代CSS
,可以配合autoprefixer
插件自动补齐CSS3
的厂商前缀。
2、webpaack中常见的plugin有哪些
html-webpack-plugin:自动创建一个新的
html
文件,并把打包生成的js
文件自动引入到这个html
文件中。clean-webpack-plugin:清空指定目录或者文件夹。
ignore-plugin:忽略部分文件。
splitChunkPlugin:对
js
文件进行代码分割。mini-css-extract-plugin:对
css
文件进行代码分割,对于分割后的代码来说,支持按需加载。optimize-css-assets-webpack-plugin:用于压缩
css
文件,减少css
文件的大小。
3、loader和plugin的区别?
loader
,可以说是一个模块转换器,用于处理我们引用的模块。比如说,我们想要去引入一个js
文件,或者是css
格式的文件,都需要loader
来帮助我们处理。plugin
,可以说是一个扩展插件,它用于在打包过程中的某些时刻里生效。常见的有htmlWebpackPlugin
、cleanWebpackPlugin
等等。
4、是否写过Loader?简单描述一下编写loader的思路?
其本质为函数,函数中的
this
将会被作为上下文来提供给webpack
填充,因此我们不能将loader
设为一个箭头函数;函数接受一个参数,这个参数为
webpack
传递给loader
的文件源内容;函数中
this
是由webpack
提供的对象,能够获取当前loader
所需要的各种信息;函数中有异步操作或同步操作,异步操作通过
this.callback
返回,返回值要求为string
或者Buffer
。
代码如下所示:
// 导出一个函数,source为webpack传递给loader的文件源内容
module.exports = function(source) {
const content = doSomeThing2JsString(source);
// 如果 loader 配置了 options 对象,那么this.query将指向 options
const options = this.query;
// 可以用作解析其他模块路径的上下文
console.log('this.context');
/*
* this.callback 参数:
* error:Error | null,当 loader 出错时向外抛出一个 error
* content:String | Buffer,经过 loader 编译后需要导出的内容
* sourceMap:为方便调试生成的编译后内容的 source map
* ast:本次编译生成的 AST 静态语法树,之后执行的 loader 可以直接使用这个 AST,进而省去重复生成 AST 的过程
*/
this.callback(null, content); // 异步
return content; // 同步
}
5、是否写过Plugin?简单描述一下编写plugin的思路?
(1) 由于webpack
基于发布订阅模式,在运行的生命周期中会广播出许多事件,插件通过监听这些事件,就可以在特定的阶段来执行自己的插件任务。
(2) webpack
编译会创建两个核心对象:
compiler:包含了
webpack
环境种的所有配置信息,包括options
,loader
和plugin
,和webpack
整个生命周期相关的钩子。compilation:作为
plugin
内置事件回调函数的参数,包含了当前的模块资源、编译生成资源、变化的文件以及被跟踪依赖的状态信息。当检测到一个文件变化,一次新的Compilation
将被创建。
(3) 如果自己要实现 plugin
,也需要遵循一定的规范:
插件必须是一个函数或者是一个包含
apply
方法的对象,这样才能访问compiler
实例。传给每个插件的
compiler
和compilation
对象都是同一个引用,因此不建议修改。异步的事件需要在插件处理完任务时去调用回调函数,之后通知
Webpack
进入下一个流程,否则会卡住。
(4) 实现plugin
的模板如下:
class MyPlugin {
// Webpack 会调用 MyPlugin 实例的 apply 方法给插件实例传入 compiler 对象
apply (compiler) {
// 找到合适的事件钩子,实现自己的插件功能
compiler.hooks.emit.tap('MyPlugin', compilation => {
// compilation: 当前打包构建流程的上下文
console.log(compilation);
// do something...
})
}
}
🥁六、babel相关
1、babel和webpack的区别
babel
,编译JS
新语法的一个工具,它不关心模块化;- 而
webpack
,是一个打包构建工具,是多个loader
和plugin
的集合,它关心模块化。
2、babel-polyfill和babel-runtime的区别
babel-polyfill
,旨在解决低版本浏览器无法兼容ES6
的部分新的语法变量的问题,但它有一个缺点就是会污染全局,即@babel/preset-env
会将Promise
翻译成全局变量var _Promise
。- 而
babel-runtime
则不会污染全局,babel-runtime
提供了单独的包,用以提供编译模块的工具函数。启用插件babel-plugin-transform-runtime
后,Babel
就会使用babel-runtime
下的工具函数。 - 同时,值得注意的是,当引用自己编写的第三方库
lib
时,要用babel-runtime
。
3、为何Proxy不能被Polyfill?
Proxy
没有办法被polyfill
,也就是说低版本浏览器无法兼容一些新的语法变量,所以这也是我们常说的为啥vue3
无法兼容低版本浏览器。像
class
可以用function
模拟,Promise
可以用callback
来模拟,但Proxy
的功能用Object.defineProperty
就无法模拟了,所以vue3
暂时无法减容低版本浏览器。
🥊七、性能优化相关
1、webpack如何实现懒加载?
import()
语法。使用
preloading
和prefetching
来文件进行异步加载。preload
与prefetch
的区别在于:preload
是跟主文件同时进行加载,而不是在主文件加载完才加载的。一般来说,我们都用prefetch
, 只有等主文件把活干完了,再来加载剩余的我们想要的文件,这样的逻辑和对页面的优化才是比较完美的。
2、webpack常见性能优化(如何优化webpack的构建速度?)
(1)从开发环境的角度
生产环境优化:
- 优化
babel-loader
; - 使用
IgnorePlugin
,排除掉一些不使用的模块,且用来缩小打包作用域; - noParse;
- happyPack(不维护了);
- ParallelUglifyPlugin。
开发环境优化:
- 自动刷新:使用
webpackDevServer
来实现自动刷新编译结果。 - 热模块更新HMR:热更新又称热替换, 这个机制可以做到不用刷新浏览器而将新变更的模块替换掉旧的模块。
- DllPlugin:使用
DllPlugin
进行分包,使用DllReferencePlugin
(索引链接) 对manifest.json
引用,让一些基本不会改动的代码先打包成静态资源,避免反复编译浪费时间。
(2)从产出代码的角度
- 懒加载;
- 提取公共代码;
- 使用
cdn
加速; IgnorePlugin
;- 使用
production
; - 使用
url-loader
对小图片进行base64
编码; - 对生成的
bundle
文件加hash
值; - Scope Hosting:
- 构建后的代码会存在大量闭包,造成体积增大,运行代码时创建的函数作用域变多,内存开销变大。
Scope hoisting
将所有模块的代码按照引用顺序放在一个函数作用域里,然后适当的重命名一些变量以防止变量名冲突.- 同时,必须是
ES6
的语法,因为有很多第三方库仍采用CommonJS
语法。 - 因此,为了充分发挥
Scope hoisting
的作用,需要配置mainFields
对第三方模块优先采用jsnext:main
中指向的ES6
模块化语法。
🎬八、结束语
在上面的文章中,我们从构建和打包、模块相关、 loader
和 plugin
、 babel
、性能优化相关这五个方面,对 webpack
的一些常见面试题进行了归纳总结。相信通过上文的学习,大家对这一块的内容又有了一定的了解。关于 webpack
的常见面试题讲到这里就结束啦!希望对大家有帮助~
如文章有误或有想要补充的内容,欢迎留言或联系 vx:MondayLaboratory
。
最后就是祝各位看到这篇文章的小伙伴们,都能够斩获到自己心仪的 offer
呀~🥂🥂🥂
🐣彩蛋 One More Thing
(:参考资料
👉腾讯面试官:兄弟,你说你会Webpack,那说说他的原理?
👉面试官:说说Webpack中Loader和Plugin的区别?编写Loader,Plugin的思路?
(:pdf获取
👉 微信搜索 星期一研究室
并关注,回复关键词 webpack面试pdf
获取相关pdf内容~
👉回复 面试大全pdf
可获取整专栏内容🗂️
(:更新地址
(:番外篇
- 关注公众号星期一研究室,第一时间关注优质文章,更多精选专栏待你解锁~
- 如果这篇文章对你有用,记得留个脚印jio再走哦~
- 以上就是本文的全部内容!我们下期见!👋👋👋