「每周前端面试题专栏」- 滴水之功,开拓大厂之路(第四周)

微信扫一扫,分享到朋友圈

「每周前端面试题专栏」- 滴水之功,开拓大厂之路(第四周)

写在前面

炎炎夏日,来道知识点大餐提提神?

本周是 webpack 专题啦,希望大家希望~

值新的一岁之际,希望自己可以: 勤思考,多动手,善总结,能坚持

建了一个公众号, 每周周一 至 周五,每天发布若干道面试题,并奉上个人觉得还行的解答,周六或周天发一遍汇总~

希望大家有所得,希望自己有所得。

下面是本周的汇总(2020.07.27 – 2020.07.31)。

本篇题目主要来自 童大哥的文章-「吐血整理」再来一打 Webpack 面试题
[1]
,我这里只做了小小的改动与梳理 = =

目录

  1. 谈谈你对 webpack 的看法

  2. webpack 与 grunt、gulp、rollup 的不同

  3. 在什么情况下选择 webpack?在什么情况下选择 rollup?

  4. webpack 有什么优劣势

  5. webpack 有哪些常见的 Loader?你用过哪些 Loader?

  6. webpack 有哪些常见的 Plugin?你用过哪些 Plugin?

  7. 那你再说一说 Loader 和 Plugin 的区别?

  8. webpack 构建流程简单说一下

  9. webpack 如何解析代码路径的

  10. 使用 webpack 开发时,你用过哪些可以提高效率的插件?

  11. source map 是什么?生产环境怎么用?

  12. 模块打包原理知道吗?

  13. 文件监听原理呢?

  14. 说一下 webpack 的热更新原理吧

  15. 如何对 bundle 体积进行监控和分析?

  16. 文件指纹是什么?怎么用?

  17. 在实际工程中,配置文件上百行乃是常事,如何保证各个 loader 按照预想方式工作?

  18. 代码分割的本质是什么?有什么意义?你是如何拆分的?

详情

一、谈谈你对 webpack 的看法

webpack 是一个 模块打包工具,可以使用 webpack 管理模块依赖,并编译输出模块们所需的静态文件。它能 很好地管理、打包 Web 开发中所用到的 HTML、JS、CSS 以及各种静态文件(图片、字体等),让开发过程更加高效。对于不同类型的资源, webpack 有对应的模块加载器。 webpack 模块打包器会分析模块间的依赖关系,最后生成了优化且合并后的静态资源。

特点:

  1. 对 CommonJS、AMD、ES6 的语法做了兼容

  2. 对 js、css、图片等资源文件都支持打包

  3. 串联模块加载器以及插件机制,让其具有更好的灵活性和扩展性,例如对 ES6 的支持

  4. 可以将代码切割成不同的 chunk,实现按需加载,降低了初始化时间

  5. 支持 sourcemap,易于调试

  6. 具有强大的 plugin 接口,大多是内部插件,使用起来比较灵活

二、webpack 与 grunt、gulp、rollup 的不同

优点:

  1. 专注于模块化项目

  2. plugins 能做很多事情

  3. 社区活跃

缺点:

  1. 上手难度较高

  2. plugins 繁多,需要不断学习才能灵活掌握,还经常出现老文章里推荐的 plugin 已经停止维护

  3. 对于初学者,调试很难定位问题

  4. 构建速度较慢,这也是后起之秀主要针对的点

三、在什么情况下选择 webpack?在什么情况下选择 rollup?

  • webpack 适用于大型复杂的前端站点构建

  • rollup 适用于基础库的打包,如:vue、react

另外一个角度:
如果你需要进行代码分割,或者你有很多的静态资源,再或者你做的东西深度依赖 CommonJS,选择 Webpack。

如果你的代码基于 ES2015 模块编写,并且做的东西是准备给他人使用的,可以考虑 rollup。

四、webpack 有什么优劣势

grunt 和 gulp 是基于任务和流(Task、Stream)的,找到一个(或一类)文件,对齐做一些列链式操作,更新流上的数据,整条链式操作构成了一个任务,多个任务就构成了整个 web 的构建流程

rollup 与 webpack 类似,但专注于 ES6 的模块打包。它最大的亮点是利用 ES6 模块设计,生成更简洁、更简单的代码。

webpack 是模块化管理工具和打包工具,它是基于入口的。 webpack 会自动递归解析入口所需要加载的所有资源文件,然后用不同 Loader 来处理不同的文件,用 Plugin 来扩展 webpack 功能。

虽然现在 webpack 相对比较主流,但一些轻量化的任务还是会用 gulp 来处理,比如单独打包 CSS 文件;另外一些类库的代码使用 rollup 打包。

五、webpack 有哪些常见的 Loader?你用过哪些 Loader?

我经常使用的有:

文件:

raw-loader
: 加载文件原始内容

file-loader
:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件(处理图片和字体)

url-loader
:与 file-loader 类似,区别是用户可以设置一个阈值,大于阈值会交给 file-loader 处理,小于时返回文件的 base64 形式编码(处理图片和字体)

转换编译:

babel-loader
: 把 ES6 转换成 ES5

ts-loader
: 将 ts 转换成 js

awesome-typescript-loader
: 将 ts 转换成 js ,性能比 ts-loader 好点

样式:

css-loader
: 加载 CSS, 支持模块化、压缩、文件导入等特性

less-loader:将 less 代码转换成 css

postcss-loader
:扩展 CSS 语法,使用下一代 CSS,可以配合 autoprefixer 插件自动补齐 CSS3 前缀

style-loader
:把 CSS 代码注入到 JS 中,通过 DOM 操作去加载 CSS

其他:

image-loader
:加载并压缩图片文件

eslint-loader
: 通过 ESLint 检查 js 代码

tslint-loader
: 通过 TSLint 检查 ts 代码

cache-loader
:可以在一些性能开销较大的 Loader 前添加,目的是将结果缓存到磁盘中

thread-loader
:开启多线程打包

六、webpack 有哪些常见的 Plugin?你用过哪些 Plugin?

ignore-plugin
: 从 bundle 中排出某些模块

html-webpack-plugin
:简单创建 HTML 文件

terser-webpack-plugin
:js 代码压缩,支持压缩 ES6

extract-text-webpack-plugin
:分离样式文件,从 bundle 中提取 css 到单独的文件

clean-webpack-plugin
: 目录清理

webpack-bundle-analyzer
: 可视化 Webpack 输出文件的体积 (业务组件、依赖第三方模块)

compression-webpack-plugin
:开启 gzip 压缩

hard-source-webpack-plugin
:开发阶段使用,为模块提供中间缓存步骤,加快二次编译速度

progress-bar-webpack-plugin
: 项目启动或者打包进度条插件

friendly-errors-webpack-plugin
:开发友好的错误提示插件

七、那你再说一说 Loader 和 Plugin 的区别?

从功能角度区分:

Loader
用于加载待打包的资源, Plugin
用于扩展 webpack 功能。

Loader
本质就是一个函数,在该函数中对接收到的内容进行转换,返回转换后的结果,主要用于加载某些资源文件。因为 webpack 只认识 js,这就需要对应的 loader 将资源转化,加载进来。

Plugin
用于扩展 webpack 的功能(loader 也是扩展功能,但只专注于转化文件这一领域),在 webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 webpack 提供的 API 改变输出结果。

从运行时机角度区分:

Loader
运行在打包文件之前(loader 为在模块加载时的预处理文件)

Plugin
在整个编译周期都起作用。

从使用角度区分:

Loader
在 rules 中配置,类型为数组。每一项都是一个 Object , 内部包含了 test(类型文件)、loader、options(参数)等属性。

Plugin
在 plugins 中单独配置,类型为数组,每一项是一个 Plugin 的实例,参数都通过构造函数传入。

八、webpack 构建流程简单说一下

Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:

  • 初始化参数
    :从配置文件和 Shell 语句中读取与合并参数,得出最终的参数

  • 开始编译
    :用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译

  • 确定入口
    :根据配置中的 entry 找出所有的入口文件

  • 编译模块
    :从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出改模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理

  • 完成模块编译
    :在经过第 4 步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系

  • 输出资源
    :根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk, 再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会

  • 输出完成
    :在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统

在以上过程中,Webpack 会在特定的事件点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果。

简单地说:

  • 初始化
    :启动构建,读取与合并配置参数,加载 Plugin,实例化 Compiler

  • 编译
    :从 Entry 出发,针对每个 Module 串行调用对应的 Loader 去翻译文件的内容,再找到该 Module 依赖的 Module,递归地进行编译处理。

  • 输出
    :将编译后的 Module 组合成 Chunk,将 Chunk 转换成文件,输出到文件系统中

九、webpack 如何解析代码路径的

webpack 依赖 enhanced-resolve 来解析代码模块的路径,这个模块像 Node.js 那一套模块路径解析的增强版本,有很多可以自定义的解析配置。

模块解析规则分三种:

  • 解析相对路径

    1. 查找相对当前模块的路径下是否有对应文件或文件夹,是 文件
      则直接加载
    2. 如果是文件夹则找到对应文件夹下是否有 package.json
      文件
    3. 有的话就按照文件中的 main
      字段的文件名来查找文件
    4. 没有 package.json 或 main,则查找 index.js
      文件
  • 解析绝对路径
    直接查找对应路径的文件,不建议使用,因为不同的机器用绝对路径会找不到

  • 解析模块名
    查找当前文件目录,父级直至根目录下的 node_modules 文件夹,看是否有对应名称的模块

另外:通过设置 resolve.alias 、 resolve.extensions 、 resolve.modules 、 resolve.mainFields 、 resolve.resolveLoader 等选项来优化路径查找速度。

十、使用 webpack 开发时,你用过哪些可以提高效率的插件?

  • webpack-merge
    :提取公共配置,减少重复配置代码

  • HotModuleReplacementPlugin
    :模块热替换

  • thread-loader
    : 并行打包,加快启动速度

  • babel-plugin-transform-remove-console
    : 自动删除 console.log

十一、source map 是什么?生产环境怎么用?

source map
是将编译、打包、压缩后的代码映射回源码的过程。打包压缩后的代码不具备良好的可读性,想要调试源码就需要 source map,出错的时候,浏览器控制台将直接显示原始代码出错的位置。

map 文件只要不打开开发者工具,浏览器是不会加载的。

线上环境一般有三种处理方案:

  • source-map
    :map 文件包含完整的原始代码,但是打包会很慢。打包后的 js 最后一行是 map 文件地址的注释。通过 nginx 设置将 .map 文件只对白名单开放(公司内网)

  • hideen-source-map
    :与 sourceMap 相同,也生成 map 文件,但是打包后的 js 最后没有 map 文件地址的引用。浏览器不会主动去请求 map 文件,一般用于网站错误分析,需要让错误分析工具按名称匹配到 map 文件。 或者借助第三方错误监控平台 Sentry 使用

  • nosources-source-map
    :只会显示具体行数以及查看源码的错误栈。安全性比 sourcemap 高

注意:避免在生产中使用 inline-
eval-
,因为它们会增加 bundle 体积大小,并降低整体性能

十二、模块打包原理知道吗?

webpack 根据 webpack.config.js 中的入口文件,在入口文件里识别模块依赖,不管这里的模块依赖是用 CommonJS 写的,还是 ES6 Module 规范写的,webpack 会自动进行分析,并通过转换、编译代码,打包成最终的文件。 最终文件中的模块实现是基于 webpack 自己实现的 webpack_require(es5 代码)
,所以打包后的文件可以跑在浏览器上。

同时以上意味着在 webapck 环境下,你可以只使用 ES6 模块语法书写代码(通常我们都是这么做的),也可以使用 CommonJS 模块语法,甚至可以两者混合使用。因为从 webpack2 开始,内置了对 ES6、CommonJS、AMD 模块化语句的支持, webpack 会对各种模块进行语法分析,并做转换编译

另外,针对异步模块: webpack 实现模块的异步加载有点像 jsonp 的流程

遇到异步模块时,使用 __webpack_require__.e
函数去把异步代码加载进来。该函数会在 html 的 head 中动态增加 script 标签,src 指向指定的异步模块存放的文件。

加载的异步模块文件会执行 webpackJsonpCallback
函数,把异步模块加载到主文件中。

所以后续可以像同步模块一样,直接使用 __webpack_require__("./src/async.js")
加载异步模块。

十三、文件监听原理呢?

在发现源码发生变化时,自动重新构建出新的输出文件。

缺点:每次需要手动刷新浏览器

原理:轮询判断文件的最后编辑时间是否变化,初次构建时把文件的修改时间储存起来,下次有修改时会和上次修改时间比对,发现不一致的时候,并不会立刻告诉监听者,而是先缓存起来,等 aggregateTimeout
后,把变化列表一起构建,并生成到 bundle 文件夹。

module.export = {
  // 默认 false,也就是不开启
  watch: true,
  watchOptions: {
    // 默认为空,不监听的文件夹或者文件,支持正则匹配
    ignore: /node_modules/,
    // 监听到变化发生后会等 300ms 再去执行,默认 300ms
    aggregateTimeout: 300,
    // 判断文件是否发生变化是通过不停询问系统指定文件有没有变化实现的,默认每秒询问 1000 次
    poll: 1000,
  },
};
复制代码

十四、说一下 webpack 的热更新原理吧

Webpack
的热更新又称为热替换( Hot Module Replacement
),缩写为 HMR
。这个机制可以做到不用刷新浏览器而将变更的模块替换掉旧的模块。

相对于手动刷新页面,HMR 的优点在于可以保存应用的状态,提高开发效率。

  1. webpack 构建的项目,分为 server 端和 client 端(也就是浏览器),项目启动时,双方会保持一个 socket 连接,用于通话。

  2. 当本地资源发生变化时,server 向浏览器发送新资源的 hash 值,浏览器调用 reloadApp 方法,检查是否有变化,有差异是会向 server 发起 Ajax
    获取更改内容(文件列表、hash),这样浏览器继续借助这些信息向 server 端发起请求,通过 jsonp
    的方式获取 chunk 的增量更新。

后续的部分(拿到增量更新之后如何处理?哪些状态该保留?哪些又需要更新?)由 HotModulePlugin
来完成,提供了相关 API 以供开发者针对自身场景进行处理,像 react-hot-loader
vue-loader
都是借助这些 API 实现 HMR。

十五、如何对 bundle 体积进行监控和分析?

  1. VSCode
    中有一个插件 Import Cost
    可以帮助我们对引入模块的大小进行实时监测

  2. webpack-bundle-analyzer
    生成 bundle
    的模块组成图,显示所占体积

  3. bundlesize
    工具包可以进行自动化资源体积监控,集成到 CI 中,就可以在应用过大的时候收到提醒。

十六、文件指纹是什么?怎么用?

文件指纹是打包后输出的文件名的后缀。

  • Hash
    :和整个项目的构建相关,只要项目文件有修改,整个项目构建的 hash 值就会更改

  • Chunkhash
    : 和 Webpack 打包的 chunk 有关,不同的 entry 会生成不同的 chunkhash

  • Contenthash
    :根据文件内容来定义 hash,文件内容不变,则 contenthash 不变。

js 的文件指纹设置:设置 output 的 filename,用 chunkhash

module.exports = {
  entry: {
    app: './src/app.js',
    search: './src/search.js',
  },
  output: {
    filename: '[name][chunkhash:8].js',
    path: __dirname + '/dist',
  },
};
复制代码

css 的文件指纹设置:

  • 设置 MiniCssExtractPlugin 的 filename,使用 contenthash

  • 设置 ExtractTextPlugin 的 filename

module.exports = {
  // ...
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name][contenthash:8].css',
    })
    new ExtractTextPlugin('[name][contenthash].css'),
  ]
}
复制代码

图片的文件指纹设置
设置 file-loader 或 url-loader 的 name, 使用 hash。

十七、在实际工程中,配置文件上百行乃是常事,如何保证各个 loader 按照预想方式工作?

webpack 配置中,通过 module.rules 中的 enforce 字段,将 loader 分为 preLoader
postLoader
loader
三种,执行顺序为 pre -> loader -> inline -> post

pre
代表在所有正常 loader 之前执行, post
是所有 loader 之后执行。(inline 官方不推荐使用)

十八、代码分割的本质是什么?有什么意义?你是如何拆分的?

代码分割的本质其实是在 源代码直接上线
打包成唯一脚本 main.bundle.js
这两种极端方案之间的一种更适合实际场景的中间状态。

用可接受的服务器性能压力增加来换取更好的用户体验。

源代码直接上线:虽然过程可控,但是 http 请求多,性能开销大。

打包成唯一脚本:

  • 服务器压力小,但是页面空白期长,用户体验不好

  • 大体积文件会增加编译时间,影响开发效率

  • 多页应用,独立访问单个页面时,需要下载大量不相干的资源

代码分割(splitChunk)的意义:

  • 复用的代码抽离到公共模块中,解决代码冗余

  • 公共模块再按照使用的页面多少(或其他思虑)进一步拆分,用来减小文件体积,顺便优化首屏加载速度

拆分原则:

如何拆分因项目而异,普遍适应的拆分原则:

  1. 业务代码和第三方库分离打包,实现代码分割

  2. 业务代码中的公共业务模块提取打包到一个模块

  3. 首屏相关模块单独打包

写在最后

前端的世界纷繁复杂,远非笔者所能勾画,部分面试题不是靠背诵记忆就能掌握的,希望我摘录的、总结的简单答案可以引起读者的兴趣,闲暇时可以自己深入总结,如果读者有更好的答案,或有想了解的题目,欢迎留言。

笔者新建了一个 github 仓库,对公众号内发布的面试题做进一步分类,同时也会在周六同步本周题目、公布下周问题,欢迎大家关注~

想要实时关注笔者最新的文章和最新的文档更新请关注公众号 前端地基
,后续的文章会优先在公众号更新.

github: 关注笔者最新的仓库
[2]

本文参考

「吐血整理」再来一打 Webpack 面试题

官方文档-loader
[3]

官方文档-plugin
[4]

前端面试题:wepack 中 loader 和 plugin 的区别
[5]

webpack 的 loader 和 plugin
[6]
webpack 编译流程
[7]

[webpack]–webpack 如何解析代码模块路径
[8]

【webpack】你所不知道的 sourceMap
[9]

Webpack 模块打包原理
[10]

webpack–文件监听的原理
[11]

为了前端的深度-webpack 热更新
[12]

【译】使用 webpack 进行 web 性能优化:监控和分析应用
[13]

webpack 文件指纹配置
[14]

【webpack 进阶】你真的掌握了 loader 么?- loader 十问
[15]

Webpack Loader 种类以及执行顺序
[16]

浅谈 webpack 优化之代码分割与公共代码提取详解
[17]

被捧上天的流量巨星GPT-3,突然就不香了?

上一篇

亲属卡被当成诈骗工具 微信详解:这三点要注意

下一篇

你也可能喜欢

「每周前端面试题专栏」- 滴水之功,开拓大厂之路(第四周)

长按储存图像,分享给朋友