参考文章:「吐血整理」再来一打Webpack面试题
1. 用过哪些 loader
- file-loader:处理文件和字体,把文件打包到一个文件夹,而代码中可以通过相对地址去引用文件。
- url-loader: file-loader 类似,区别是用户可以设置一个阈值,大于阈值会交给 file-loader 处理,小于阈值时返回文件 base64 形式编码 (处理图片和字体)
- image-loader: 加载并压缩图片
- json-loader: 加载 JSON 文件
- babel-loader: 把 ES6 转成 ES5
- ts-loader: 把 TS 转成 JS
- awesome-typescript-loader:将 TypeScript 转换成 JavaScript,性能优于 ts-loader
- css-loader:加载 CSS,支持模块化、压缩、文件导入等特性
- eslint-loader:通过 ESLint 检查 JavaScript 代码
- tslint-loader:通过 TSLint检查 TypeScript 代码
2. 用过哪些 plugin
- mini-css-extract-plugin: 分离样式文件,CSS 提取为独立文件,支持按需加载 (替代extract-text-webpack-plugin)
- html-webpack-plugin: 为应用程序生成一个 HTML 文件,并自动注入所有生成的 bundle
- webpack-parallel-uglify-plugin: 多进程执行代码压缩,提升构建速度
- clean-webpack-plugin: 目录清理
- commons-chunk-plugin: 提取块之间共享的公共模块
- hot-module-replacement-plugin: 启用热模块更换(HMR)
- webpack-bundle-analyzer: 可视化 Webpack 输出文件的体积 (业务组件、依赖第三方模块)
- ignore-plugin:忽略部分文件
3. loader 和 plugin 的区别
loader 本质上就是一个函数
webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。loader 让 webpack 能够去处理其他类型的文件。
在 webpack 的配置文件中的 module.rules 配置 loader,loader 有两个属性:
- test 属性,识别出哪些文件会被转换。
- use 属性,定义出在进行转换时,应该使用哪个 loader。
例:
//webpack.config.js
const path = require('path');
module.exports = {
output: {
filename: '...'
},
module: {
rules: [
{ test: /\.txt$/, use: 'raw-loader' }
]
}
};
plugin 是插件,插件可以扩展 webpack 功能。
webpack 运行的生命周期中会触发很多事件,plugin 可以监听这些事件。在合适的时机,通过 webpack 提供的 API 改变输出结果。
在 webpack 的配置文件的 plugins 中单独配置,每一项是一个 Plugin 的实例,参数都通过构造函数传入。
例:
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 通过 npm 安装
const webpack = require('webpack'); // 用于访问内置插件
module.exports = {
module: {
rules: [
{ test: /\.txt$/, use: 'raw-loader' }
]
},
plugins: [
new HtmlWebpackPlugin({template: './src/index.html'})
]
};
3. source map是什么?
Source Map,顾名思义,是保存源代码映射关系的文件。
在项目开发完进行打包后,在打包的文件夹里通常除了js,css,图片字体等资源文件外,大家一定还见过xxx.js.map的文件。这种带map后缀的文件就是Source Map文件——它保存了源代码和转换之后代码(通常经过压缩混淆和其他转换)的关系。 最好的例子就是 jquery 的开发版和线上发布时候的 min 版本了。
这样虽然对带宽很友好,但是调试起来就不是那么轻松了。
我们都希望报错时,像第二种,能在控制台中提示我们具体的报错位置。
map文件只要不打开开发者工具,浏览器是不会加载的。
生产环境的话可以,通过 nginx 设
置 .map 文件对白名单开发。
4. 文件监听原理呢?
Webpack开启监听模式,有两种方式:
- 启动 webpack 命令时,带上 --watch 参数
- 在配置 webpack.config.js 中设置 watch:true
缺点:每次需要手动刷新浏览器
原理:轮询判断文件的最后编辑时间是否变化,如果某个文件发生了变化,并不会立刻告诉监听者,而是先缓存起来,等 aggregateTimeout 后再执行。
module.export = {
// 默认false,也就是不开启
watch: true,
// 只有开启监听模式时,watchOptions才有意义
watchOptions: {
// 默认为空,不监听的文件或者文件夹,支持正则匹配
ignored: /node_modules/,
// 监听到变化发生后会等300ms再去执行,默认300ms
aggregateTimeout:300,
// 判断文件是否发生变化是通过不停询问系统指定文件有没有变化实现的,默认每秒问1000次
poll:1000
}
}
5. 热更新原理
Webpack 的热更新又称热替换(Hot Module Replacement),缩写为 HMR。 这个机制可以做到不用刷新浏览器而将新变更的模块替换掉旧的模块。
HMR的核心就是客户端从服务端拉去更新后的文件,准确的说是 chunk diff (chunk 需要更新的部分)
webpack-dev-server (下面简称WDS)与浏览器之间维护了一个 Websocket,当本地资源发生变化时,WDS 会向浏览器推送更新,并带上构建时的 hash,让客户端与上一次资源进行对比。客户端对比出差异后会向 WDS 发起请求来获取更改内容(文件列表、hash),这样客户端就可以再借助这些信息继续向 WDS 发起请求获取该chunk的增量更新。
6. 文件指纹是什么?怎么用?
文件指纹是打包后输出的文件名的后缀。
- Hash:和整个项目的构建相关,只要项目文件有修改,整个项目构建的 hash 值就会更改。图片的文件指纹设置就是使用 hash。
- Chunkhash:和 Webpack 打包的 chunk 有关,不同的 entry 会生出不同的 chunkhash。JS的文件指纹设置就是使用 chunkhash。
- Contenthash:根据文件内容来定义 hash,文件内容不变,则 contenthash 不变。CSS的文件指纹设置就是使用Contenthash。
7. 代码分割的本质和意义
使得文件大小更合理。
两种极端情况:
- 源代码直接上线。过程可控,但是http请求多,性能开销大。
- 打包成唯一脚本main.bundle.js。服务器压力小,但是页面空白期长,用户体验不好,生产环境下报错追踪困难。
代码分割的本质其实就是在这两种极端方案之间的一种更适合实际场景的中间状态。
8. 编写 loader 的思路
- Loader 运行在 Node.js 中,我们可以调用任意 Node.js 自带的 API 或者安装第三方模块进行调用
- Loader 支持链式调用,所以开发上需要严格遵循“单一职责”,每个 Loader 只负责自己需要负责的事情。
- Loader 是无状态的,我们不应该在 Loader 中保留状态。每次运行都应该独立于其他编译模块以及相同模块之前的编译结果。
9. Babel原理
Babel大概分为三大部分:
- 解析:将代码转换成 AST
- 词法分析:将代码(字符串)分割为token流,即语法单元成的数组
- 语法分析:分析token流(上面生成的数组)并生成 AST
- 转换:访问 AST 的节点进行变换操作生产新的 AST
- 生成:以新的 AST 为基础生成代码