参考文献: https://webpack.docschina.org/concepts/
简述一下
WebPack 是一个模块打包工具,可以使用 WebPack 管理模块。在 webpack 看来,项目里所有资源皆模块,分析模块间的依赖关系,最终编绎输出模块为 HTML、JavaScript、CSS 以及各种静态文件(图片、字体等),让开发过程更加高效。
是什么?
webpack
是一个用于现代JavaScript
应用程序的静态模块打包工具
静态模块
这里的静态模块指的是开发阶段,可以被 webpack
直接引用的资源(可以直接被获取打包进bundle.js
的资源)
当 webpack
处理应用程序时,它会在内部构建一个依赖图,此依赖图对应映射到项目所需的每个模块(不再局限js
文件),并生成一个或多个 bundle
webpack
的能力
编译代码能力,提高效率,解决浏览器兼容问题
模块整合能力,提高性能,可维护性,解决浏览器频繁请求文件的问题
万物皆可模块能力,项目维护性增强,支持不同种类的前端模块类型,统一的模块化方案,所有资源文件的加载都可以通过代码控制
原理
1、核心概念
(1)entry:一个可执行模块或者库的入口。
(2)chunk:多个文件组成一个代码块。可以将可执行的模块和他所依赖的模块组合成一个chunk,这是打包。
(3)loader:文件转换器。例如把es6转为es5,scss转为css等
(4)plugin:扩展webpack功能的插件。在webpack构建的生命周期节点上加入扩展hook,添加功能。
2、webpack构建流程(原理)
1.Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:1.初始化参数: 从配置文件和 Shell 语句中读取与合并参数,得出最终的参数
2.开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译确定入口: 根据配置中的 entry 找出所有的入口文件3 .
4编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
5。完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每人模块被翻译后的最终内容以及它们之间的依赖关系
6.输出资源: 根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会
7 .输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。
webpack 中常见的 Loader?
以下就是常见的 loader:
- image-loader:加载并且压缩图片文件
- css-loader:帮助 webpack 打包处理 css 文件,使用 css-loader 必须要配合使用 style-loader
- style-loader:用于将 css 编译完成的样式,挂载到页面的 style 标签上。但是要注意 loader 执行顺序,style-loader 要放在第一位,loader 都是从后往前执行
- babel-loader:把 ES6 转换成 ES5
- sass-loader: css 预处理器,能更好的编写 css
- postcss-loader: 用于补充 css 样式在各种浏览器的前缀,很方便,不需要手动写了
- eslint-loader: 用于检查代码是否符合规范,是否存在语法错误
- url-loader: 处理图片类型资源,可以根据图片的大小进行不同的操作,如果图片大小大于指定大小,则将图片进行资源打包,否则将图片转换为 base64 格式字符串,再把这个字符串打包到 JS 文件里
webpack 中常见的 Plugin?
Plugin
(Plug-in)是一种计算机应用程序,它和主应用程序互相交互,以提供特定的功能
是一种遵循一定规范的应用程序接口编写出来的程序,只能运行在程序规定的系统下,因为其需要调用原纯净系统提供的函数库或者数据
webpack
中的plugin
也是如此,plugin
赋予其各种灵活的功能,例如打包优化、资源管理、环境变量注入等,它们会运行在 webpack
的不同阶段(钩子 / 生命周期),贯穿了webpack
整个编译周期
配置方式
这里讲述文件的配置方式,一般情况,通过配置文件导出对象中plugins
属性传入new
实例对象。如下所示:
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 通过 npm 安装
const webpack = require('webpack'); // 访问内置的插件
module.exports = {
...
plugins: [
new webpack.ProgressPlugin(),
new HtmlWebpackPlugin({ template: './src/index.html' }),
],
};
二、特性
其本质是一个具有apply
方法javascript
对象
apply
方法会被 webpack compiler
调用,并且在整个编译生命周期都可以访问 compiler
对象
const pluginName = 'ConsoleLogOnBuildWebpackPlugin'
class ConsoleLogOnBuildWebpackPlugin {
apply(compiler) {
compiler.hooks.run.tap(pluginName, (compilation) => {
console.log('webpack 构建过程开始!')
})
}
}
module.exports = ConsoleLogOnBuildWebpackPlugin
compiler hook
的 tap
方法的第一个参数,应是驼峰式命名的插件名称
关于整个编译生命周期钩子,有如下:
- entry-option :初始化 option
- run
- compile: 真正开始的编译,在创建 compilation 对象之前
- compilation :生成好了 compilation 对象
- make 从 entry 开始递归分析依赖,准备对每个模块进行 build
- after-compile: 编译 build 过程结束
- emit :在将内存中 assets 内容写到磁盘文件夹之前
- after-emit :在将内存中 assets 内容写到磁盘文件夹之后
- done: 完成所有的编译过程
- failed: 编译失败的时候
webpack 热更新的实现原理?
Hot Module Replacement,简称 HMR,在不需要刷新整个页面的同时更新模块,能够提升开发的效率和体验。热更新时只会局部刷新页面上发生了变化的模块,同时可以保留当前页面的状态,比如复选框的选中状态等。
热更新的实现
webpack-dev-server 启动的时候会做三件事情
- 启动 webpack,生成 compiler 实例,compiler 实例的功能很多,比如用来启动 webpack 的编译工作,监听文件变化等。
- 使用 Express 启动一个本地服务,使得浏览器可以访问本地服务
- 启动 websocket 服务,用于浏览器和本地 node 服务进行通讯。
监听文件变化
webpack 监听文件变化主要是通过 webpack-dev-middleware 这个库来完成,它负责本地文件的编译、输出和监听 webpack-dev-middleware 中执行了 compiler.watch 方法,它主要做了两件事情
- 对本地文件编译打包
- 编译结束之后,开启监听,文件发生变化时重新编译,并持续进行监听
监听 webpack 编译结束
setupHooks 方法用来注册监听事件,当监听到 webpack 编译结束时,通过 websocket 给浏览器发通知,浏览器拿到 hash 只之后就可以做检查更新逻辑。
首先要知道server端和client端都做了处理工作:
1.第一步,在 webpack 的watch 模式下,文件系统中某一个文件发生修改,webpack 监听到文件变化,根据配置文
件对模块重新编译打包,并将打包后的代码通过简单的 JavaScript 对象保存在内存中。
2.第二步是 webpack-dev-server和 webpack 之间的接口交互,而在这一步,主要是 dev-server 的中间件webpack- dev-middleware 和 webpack 之间的交五,webpack-dev-middleware 调用 webpack 暴露的API对代码变化进行监 控,并且告诉 webpack,将代码打包到内存中。
3.第三步是 webpack-dev-server 对文件变化的一个监控,这一步不同于第一步,并不是监控代码变化重新3打包。当我们在配置文件中配置了devServer.watchContentBase 为 true 的时候,Server 会监听这些配置文件夹中静态文件的变化,变化后会通知浏览器诺对应用进行 live reload。注意,这儿是浏览器刷新,和HMR 是两个概念。
4.第四步也是 webpack-dev-server 代码的工作,该步骤主要是通过 sockis (webpack-dev-server 的依赖)4在浏览器端和服务端之间建立一个 websocket 长连接,将 webpack 编译打包的各个阶段的状态信息告知浏览器端,同时也包括第=步中 Server 监听静态文件变化的信息。浏览器端根据这些 socket 消息进行不同的操作。当然服务端传递的最主要信息还是新模块的 hash 值,后面的步骤根据这一 hash 值来进行模块热替换
5.webpack-dev-server/client 端并不能够请求更新的代码,也不会执行热更模块操作,而把这些工作又交回5给了webpack,webpack/hot/dev-server 的工作就是根据 webpack-dev-server/client 传给它的信息以及dev-server 的配置决定是刷新浏览器呢还是进行模块热更新。当然如果仅仅是刷新浏览器,也就没有后面那些步骤了。
6.HotModuleReplacement.runtime 是客户端 HMR 的中枢,它接收到上一步传递给他的新模块的 hash 值,它通过JsonpMainTemplate.runtime 向 server 端发送 Ajax 请求,服务端返回一个json,该json 包含了所有要更新的模块的 hash 值,获取到更新列表后,该模块再次通过jsonp 请求,获取到最新的模块代码。这就是上图中 7、8、9 步骤。
7.而第 10 步是决定 HMR 成功与否的关键步骤,在该步骤中,HotModulePlugin 将会对新旧模块进行对比,决定是否更新模块,在决定更新模块后,检查模块之间的依赖关系,更新模块的同时更新模块间的依赖引用。
8.最后一步,当 HMR 失败后,回退到 live reload 操作,也就是进行浏览器刷新来获取最新打包代码.
如何借助 webpack 来优化前端性能?
用webpack优化前端性能是指优化webpack的输出结果,让打包的最终结果在浏览器运行快速高效。
压缩代码: 删除多余的代码、注释、简化代码的写法等等方式。可以利用webpack的 UglifvJsPlugin 和ParallelUglifvPlugin 来压缩JS文件,利用 cssnano (css-loader?minimize) 来压缩css
利用CDN加速:在构建过程中,将引用的静态资源路径修改为CDN上对应的路径。可以利用webpack对于output 参数和冬loader的 publicPath 参数来修改资源路径
Tree Shaking:将代码中永远不会走到的片段删除掉。可以通过在启动webpack时追加参数 --optimizeminimize 来实现
Code Splitting: 将代码按路由维度或者组件分块(chunk),这样做到按需加载,同时可以充分利用浏览器缓存
提取公共第三方库: SplitChunksplugin插件来进行公共模块抽取,利用浏览器缓存可以长期缓存这些无需频繁变动的公共代码