文章目录
- 1.你知道webpack的作用是什么吗?
- 2. 用过哪些Loader?都有什么作用?
- 3. 用过哪些Plugin?都有什么作用?
- 4. 说一说Loader和Plugin的区别?
- 5. 说一下webpack的整个打包流程?
- 6.说一下模块打包运行原理?
- 7. 你知道sourceMap是什么吗?
- 8. 是否写过Loader?简单描述一下编写loader的思路?
- 9. 是否写过Plugin?简单描述一下编写plugin的思路?
- 10. 说一下Webpack 文件监听
- 11. 文件指纹是什么?怎么用?
- 12. 说一下 Webpack 的热更新
- 13. 如何优化 Webpack 的构建速度?
- 14. 聊一聊Babel原理吧?
- 说说gulp和webpack的区别
- webpack打包用过什么插件
- vue打包内存过大,怎么使用webpack来进行优化
1.你知道webpack的作用是什么吗?
1.模块打包。可以将不同模块的文件打包整合在一起,并且保证它们之间的引用正确,执行有序。保证项目结构的清晰和可读性。
2.编译兼容。通过webpack
的Loader
机制,兼容ES6
,以编译转换诸如.less, .vue, .jsx
这类在浏览器无法识别的格式文件。
3.能力扩展。通过webpack
的Plugin
机制,可以实现诸如按需加载
,代码压缩
等一系列功能,提高自动化程度,工程效率以及打包输出的质量。
2. 用过哪些Loader?都有什么作用?
file-loader
:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件 (处理图片和字体)url-loader
:与file-loader
类似,区别是用户可以设置一个阈值,大于阈值会交给file-loader
处理,小于阈值时返回文件base64
形式编码 (处理图片和字体)source-map-loader
:加载额外的Source Map
文件,以方便断点调试image-loader
:加载并且压缩图片文件json-loader
加载 JSON 文件(默认包含)babel-loader
:把 ES6 转换成 ES5ts-loader
: 将 TypeScript 转换成 JavaScriptsass-loader
:将SCSS/SASS代码转换成CSSless-loader
:将LESS代码转换成CSScss-loader
:加载 CSS,支持模块化、压缩、文件导入等特性style-loader
:把 CSS 代码注入到 JavaScript 中,通过DOM
操作去加载CSS
postcss-loader
:扩展 CSS 语法,使用下一代 CSS,可以配合 autoprefixer 插件自动补齐 CSS3 前缀eslint-loader
:通过 ESLint 检查 JavaScript 代码tslint-loader
:通过 TSLint检查 TypeScript 代码vue-loader
:加载 Vue.js 单文件组件jsx-loader
:加载 jsx 单文件组件
3. 用过哪些Plugin?都有什么作用?
html-webpack-plugin
: 复制HTML 模板,压缩HTML代码clean-webpack-plugin
: 目录清理mini-css-extract-plugin
: 分离样式文件,CSS 提取为独立文件,支持按需加载css-minimizer-webpack-plugin
: 压缩CSS ,减少CSS 文件大小speed-measure-webpack-plugin
: 可以看到每个 Loader 和 Plugin 执行耗时webpack-bundle-analyzer
: 可视化 Webpack 输出文件的体积webpack-parallel-uglify-plugin
: 多进程执行代码压缩,提升构建速度serviceworker-webpack-plugin
:为网页应用增加离线缓存功能
4. 说一说Loader和Plugin的区别?
Loader
主要工作时编译兼容。本质就是一个函数,在该函数中对接收到的内容进行转换,返回转换后的结果。Plugin
主要是能力扩展,基于事件流框架Tapable
,插件可以扩展Webpack
的功能,在Webpack
运行的生命周期中会广播出许多事件,Plugin
可以监听这些事件,在合适的时机通过Webpack
提供的 API 改变输出结果。Loader
在module.rules
中配置,作为模块的解析规则,类型为数组。每一项都是一个 Object,内部包含了 test(类型文件)、loader、options (参数)等属性。Plugin
在plugins
中单独配置,类型为数组,每一项是一个 Plugin 的实例,参数都通过构造函数传入。Loader
运行在打包文件之前(loader为在模块加载时的预处理文件)Plugin
在整个编译周期都起作用。
5. 说一下webpack的整个打包流程?
首先我们应该简单了解一下webpack的整个打包流程:
初始化参数
:合并shell
传入和webpack.config.js
文件配置的参数,形成最后的配置结果;开始编译
:创建Compiler
对象并开始解析项目;监听webpack
构建生命周期的事件节点,执行对象的run
方法开始执行编译确定入口
:从入口文件(entry
)开始解析,并且找到其导入的依赖模块,递归遍历分析,形成依赖关系树;编译模块
:对不同文件类型的依赖模块文件使用对应的Loader
进行编译,最终转为Javascript
文件;- 整个过程中
webpack
会通过发布订阅
模式,向外抛出一些hooks
,而webpack
的Plugin
即可通过监听这些关键的事件节点,执行插件任务进而达到干预输出结果的目的。 完成模块编译并输出
:递归完事后,得到每个文件结果,包含每个模块以及他们之间的依赖关系,根据entry
或分包配置生成代码块chunk
;输出完成
:输出所有的chunk
到文件系统;
6.说一下模块打包运行原理?
文件的解析与构建是一个比较复杂的过程,在webpack
源码中主要依赖于compiler
和compilation
两个核心对象实现。
compiler
对象是一个全局单例,他负责把控整个webpack
打包的构建流程。
compilation
对象是每一次构建的上下文对象,它包含了当次构建所需要的所有信息,每次热更新和重新构建,compiler
都会重新生成一个新的compilation
对象,负责此次更新的构建过程。
而每个模块间的依赖关系,则依赖于AST
语法树。每个模块文件在通过Loader
解析完成之后,会通过acorn
库生成模块代码的AST
语法树,通过语法树就可以分析这个模块是否还有依赖的模块,进而继续循环执行下一个模块的编译解析。
最终Webpack
打包出来的bundle
文件是一个IIFE
的执行函数。
// webpack 5 打包的bundle文件内容
(() => {
// webpackBootstrap
var __webpack_modules__ = ({
'file-A-path': ((modules) => {
// ... })
'index-file-path': ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => {
// ... })
})
// The module cache
var __webpack_module_cache__ = {
};
// The require function
function __webpack_require__(moduleId) {
// Check if module is in cache
var cachedModule = __webpack_module_cache__[moduleId];
if (cachedModule !== undefined) {
return cachedModule.exports;
}
// Create a new module (and put it into the cache)
var module = __webpack_module_cache__[moduleId] = {
// no module.id needed
// no module.loaded needed
exports: {
}
};
// Execute the module function
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
// Return the exports of the module
return module.exports;
}
// startup
// Load entry module and return exports
// This entry module can't be inlined because the eval devtool is used.
var __webpack_exports__ = __webpack_require__("./src/index.js");
})
和webpack4
相比,webpack5
打包出来的bundle
做了相当的精简。在上面的打包demo中,整个立即执行函数里边只有三个变量和一个函数方法,__webpack_modules__
存放了编译后的各个文件模块的JS内容,__webpack_module_cache__
用来做模块缓存,__webpack_require__
是Webpack内部实现的一套依赖引入函数。最后一句则是代码运行的起点,从入口文件开始,启动整个项目。
其中值得一提的是__webpack_require__
模块引入函数,我们在模块化开发的时候,通常会使用ES Module
或者CommonJS
规范导出/引入依赖模块,webpack
打包编译的时候,会统一替换成自己的__webpack_require__
来实现模块的引入和导出,从而实现模块缓存机制,以及抹平不同模块规范之间的一些差异性。
7. 你知道sourceMap是什么吗?
sourceMap
是一项将编译、打包、压缩后的代码映射回源代码的技术。sourceMap
可以帮助我们快速定位到源代码的位置,提高我们的开发效率。
使用方法是可以通过浏览器的开发者工具中的Sources
追根溯源
8. 是否写过Loader?简单描述一下编写loader的思路?
loader
的开发需要遵循一些规范,比如返回值必须是标准的JS代码字符串
,以保证下一个loader
能够正常工作,同时在开发上需要严格遵循“单一职责
”,只关心loader
的输出以及对应的输出。loader
函数中的this
上下文由webpack
提供,可以通过this
对象提供的相关属性,获取当前loader
需要的各种信息数据。- 推荐通过
loader-utils
读取options
配置
根目录创建loaders文件夹,里面创建replaceLoader.js:
const loaderUtils = require('loader-utils');
module.exports = function (source) {
// 推荐通过`loader-utils`读取`options`配置
const options = loaderUtils.getOptions(this);
// 如果 loader 配置了 options 对象,那么this.query将指向 options
// const options = this.query;
const result = source.replace('world', options.name);
// return result;
/*
* this.callback 参数:
* error:Error | null,当 loader 出错时向外抛出一个 error
* content:String | Buffer,经过 loader 编译后需要导出的内容
* sourceMap:为方便调试生成的编译后内容的 source map
* ast:本次编译生成的 AST 静态语法树,之后执行的 loader 可以直接使用这个 AST,进而省去重复生成 AST 的过程
*/
this.callback(null, result);
}
在webpack中配置
module: {
rules: [{
test: /\.js$/,
use: {
loader: path.resolve(__dirname, './loaders/replaceLoader.js'),
options: {
name: 'echo'
}
}
}]
}
这个loader的作用是将js文件中的world转换成echo
9. 是否写过Plugin?简单描述一下编写plugin的思路?
compiler
和compilation
是Webpack
两个非常核心的对象,其中compiler
暴露了和 Webpack
整个生命周期相关的钩子(compiler-hooks
),而compilation
则暴露了与模块和依赖有关的粒度更小的事件钩子(Compilation Hooks
)。
Plugin
的开发和开发Loader
一样,需要遵循一些开发上的规范和原则:
- 插件必须是一个函数或者是一个包含
apply
方法的对象,这样才能访问compiler
实例; - 传给每个插件的
compiler
和compilation
对象都是同一个引用,若在一个插件中修改了它们身上的属性,会影响后面的插件; - 异步的事件需要在插件处理完任务时调用回调函数通知
Webpack
进入下一个流程,不然会卡住;
根目录创建loaders文件夹,里面创建MyPlugin.js:
class MyPlugin {
// constructor会接受plugin的配置参数
constructor(options) {
console.log('通过options接受参数',options)
}
apply (compiler) {
// 找到合适的事件钩子,实现自己的插件功能
// emit钩子,它执行的时刻是生成资源到 output 目录之前
compiler.hooks.emit.tap('MyPlugin', (compilation, cb) => {
compilation.assets['MyPlugin.js'] = {
source: function () {
return 'console.log(\'12345\');'
},
size: function () {
return 21
}
}
cb&&cb();
})
}
}
module.exports = MyPlugin;
在webpack中配置:
plugins:[ // 配置插件
new MyPlugin({
name: 'MyPlugins十分牛逼'
}), // 自定义插件
],
此plugin会生成一个MyPlugin.js文件
更详细的开发文档可以直接查看官网的 Plugin API。
10. 说一下Webpack 文件监听
Webpack 开启监听模式,有两种方式:
- 启动命令:
package.json
中添加watch:'webpack --watch'
- 在
webpack.config.js
中设置
module.exports = {
entry:{
...},
output:{
path:...,filename:...}
watch:true,//默认false,不开启
watchOptions:{
ignored:/node-modules/,//忽略,默认为空
aggregateTimeout:300,//默认300ms,监听文件发生变化等待300ms,再去执行
poll:1000//轮询1s默认1000次
}
}
原理:轮询监听文件的编辑时间,如果发生变化,将变化了的文件记录缓存起来,等待aggregateTimeout
时间之后,将记录下来的文件一起进行构建,构建到filename
指定的文件中
缺点:需要手动刷新浏览器
11. 文件指纹是什么?怎么用?
文件指纹是打包后输出的文件名的后缀。
Hash
:和整个项目的构建相关,只要项目文件有修改,整个项目构建的hash
值就会更改Chunkhash
:和Webpack
打包的chunk
有关,不同的entry
会生出不同的chunkhash
Contenthash
:根据文件内容