一、什么是 Webpack
定义:Webpack 是一个打包模块化 javascript 的工具
作用:在 Webpack 里一切文件皆模块,通过 loader 转换文件,通过 plugin 注入钩子,最后输出由多个模块组合成的文件,Webpack 专注构 建模块化项目,Webpack 可以看做是模块打包机:它做的事情是,分析你的项目结构,找到 JavaScript 模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript 等),并将 其打包为合适的格式以供浏览器使用
二、Webpack 的优点是什么?
- 专注于处理模块化的项目,能做到开箱即用,一步到位
- 通过 plugin 扩展,完整好用又不失灵活
- 使用场景不局限于 web 开发
- 社区庞大活跃,经常引入紧跟时代发展的新特性,能为大多数场景找到已有的开源 扩展
- 提供了更好的开发体验
三、Webpack 的构建流程是什么?
Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:
- 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数
- 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对 象的 run 方法开始执行编译
- 确定入口:根据配置中的 entry 找出所有的入口文件
- 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块 依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
- 完成模块编译:在经过第 4 步使用 Loader 翻译完所有模块后,得到了每个模块被翻译 后的最终内容以及它们之间的依赖关系
- 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再 把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会
- 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入 到文件系统,在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴 趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果
四 、Webpack 的热更新原理
定义:Webpack 的热更新又称热替换(Hot Module Replacement),缩写为 HMR。这个机制可以做到不用刷新浏览器而将新变更的模块替换掉旧的模块。
核心原理:
- HMR 的核心就是客户端从服务端拉去更新后的文件,准确的说是 chunk diff (chunk 需要更新的部分),实际上 WDS 与浏览器之间维护了一个 websocket,当本地资源发生 变化时,WDS 会向浏览器推送更新,并带上构建时的 hash,让客户端与上一次资源进行对比
- 客户端对比出差异后会向 WDS 发起 Ajax 请求来获取更改内容(文件列表、hash), 这样客户端就可以再借助这些信息继续向 WDS 发起 jsonp 请求获取该 chunk 的增量更新 2,3)后续的部分(拿到增量更新之后如何处理?哪些状态该保留?哪些又需要更新?) 由 HotModulePlugin 来完成,提供了相关 API 以供开发者针对自身场景进行处理,像 reacthot-loader 和 vue-loader 都是借助这些 API 实现 HMR
1.HMR工作原理
- webpack --watch启动监听模式之后,webpack第一次编译项目,并将结果存储在内存文件系统,相比较磁盘文件读写方式内存文件管理速度更快,内存webpack服务器通知浏览器加载资源,浏览器获取的静态资源除了JS code内容之外,还有一部分通过webpack-dev-server注入的的 HMR runtime代码,作为浏览器和webpack服务器通信的客户端(webpack-hot-middleware 提供类似的功能)。
- 文件系统中一个文件(或者模块)发生变化,webpack监听到文件变化对文件重新编译打包,每次编译生成唯一的hash值,根据变化的内容生成两个补丁文件:说明变化内容的manifest(文件格式是hash.hot-update.json,包含了hash和chundId用来说明变化的内容)和chunk js(hash.hot-update.js)模块。
- hrm-server通过websocket将manifest推送给浏览器浏览器接受到最新的hotCurrentHash,触发 hotDownloadManifest函数,获取manifest json 文件。
- 浏览器端hmr runtime根据manifest的hash和chunkId使用ajax拉取最新的更新模块chunk
- 触发render流程实现局部热重载 HMR runtime 调用window"webpackHotUpdate" 方法,调用hotAddUpdateChunk
var parentHotUpdateCallback = window["webpackHotUpdate"];
window["webpackHotUpdate"] = function webpackHotUpdateCallback(chunkId, moreModules) {
hotAddUpdateChunk(chunkId, moreModules);
if (parentHotUpdateCallback) parentHotUpdateCallback(chunkId, moreModules);
};
hotAddUpdateChunk动态的更新代码模块,并调用hotUpdateDownloaded函数
function hotAddUpdateChunk(chunkId, moreModules) {
hotRequestedFilesMap[chunkId] = false;
for (var moduleId in moreModules) {
if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
hotUpdate[moduleId] = moreModules[moduleId]; }
}
if (--hotWaitingFiles === 0 && hotChunksLoading === 0) {
hotUpdateDownloaded();
}
}
hotUpdateDownloaded执行hotApply执行热重载
function hotUpdateDownloaded() {
hotSetStatus("ready");
Promise.resolve()
.then(function() {
return hotApply(hotApplyOnUpdate);
})
}
五、有哪些常见的 Loader?他们是解决什么问题的?
- file-loader:分发文件到output目录并返回相对路径
- url-loader:和 file-loader 类似,但是能在文件很小的情况下以 base64 的方式返回一个Data Url并把文件内容注入到代码中去
- source-map-loader:加载额外的 Source Map 文件,以方便断点调试
- image-loader:加载并且压缩图片文件
- babel-loader:用bable来把 ES6 转换成 ES5
- css-loader:加载 CSS,支持模块化、压缩、文件导入等特性
- style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS
- eslint-loader:通过 ESLint 检查 JavaScript 代码
1.loader特性
那既然写到这了那顺带提一下loader的特性:
- loader 从右到左地取值(evaluate)/执行(execute)
- loader 支持链式传递,链中的每个 loader 会将转换应用在已处理过的资源上
- loader 也可以内联显示指定
- loader 可以是同步的,也可以是异步的
- loader 运行在 Node.js 中,并且能够执行任何 Node.js 能做到的操作
- loader 可以通过 options 对象配置
- 除了常见的通过 package.json 的 main 来将一个 npm 模块导出为 loader,还可以在 module.rules 中使用 loader 字段直接引用一个模块
- loader 能够产生额外的任意文件
六.Loader 和 Plugin 的不同?
1、不同的作用
1,1)Loader 直译为"加载器"。Webpack 将一切文件视为模块,但是 Webpack 原生是只 能解析 js 文件,如果想将其他文件也打包的话,就会用到 loader。 所以 Loader 的作用是让 Webpack 拥有了加载和解析非 JavaScript 文件的能力。
1,2)Plugin 直译为"插件",Plugin 可以扩展 Webpack 的功能,让 Webpack 具有更多 的灵活性。 在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在 合适的时机通过 Webpack 提供的 API 改变输出结果
2、不同的用法
2,1)Loader 在 module.rules 中配置,也就是说他作为模块的解析规则而存在。 类型 为数组,每一项都是一个 Object,里面描述了对于什么类型的文件(test),使用什么加载(loader) 和使用的参数(options)
2,2)Plugin 在 plugins 中单独配置。 类型为数组,每一项是一个 plugin 的实例,参 数都通过构造函数传入
七.如何利用 Webpack 来优化前端性能?
- 压缩代码。uglifyJsPlugin 压缩 js 代码, mini-css-extract-plugin 压缩css代码
- 利用 CDN 加速,将引用的静态资源修改为 CDN 上对应的路径,可以利用 Webpack对于output 参数和loader 的 publicpath 参数来修改资源路径
- 删除死代码(tree shaking),css 需要使用 Purify-CSS
- 提取公共代码。Webpack4移除了CommonsChunkPlugin (提取公共代码) ,用optimization.splitChunks 和 optimization.runtimeChunk 来代替
八.写loader的思路
基本定义: Loader 像一个"翻译官"把读到的源文件内容转义成新的文件内容,并且每个 Loader 通 过链式操作,将源文件一步步翻译成想要的样子。
1. 编写思路
- 编写 Loader 时要遵循单一原则,每个 Loader 只做一种"转义"工作, 每个 Loader 的拿到的是源文件内容(source),可以通过返回值的方式将处理后的内容输出,也可以调 用 this.callback()方法,将内容返回给 Webpack,还可以通过 this.async()生成一个 callback 函数,再用这个 callback 将处理后的内容输出出去,此外 Webpack 还为开发者准 备了开发 loader 的工具函数集——loader-utils
- 相对于 Loader 而言,Plugin 的编写就灵活了许多, Webpack 在运行的生命周期 中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果
2. 编写注意事项
- Loader 支持链式调用,所以开发上需要严格遵循“单一职责”,每个 Loader 只负责 自己需要负责的事情
- Loader 运行在 node.js 中,我们可以调用任意 node.js 自带的 API 或者安装第三 方模块进行调用
- Webpack 传给 Loader 的原内容都是 UTF-8 格式编码的字符串,当某些场景下 Loader 处理二进制文件时,需要通过 exports.raw = true 告诉 Webpack 该 Loader 是 否需要二进制数据
- 尽可能的异步化 Loader,如果计算量很小,同步也可以
- Loader 是无状态的,我们不应该在 Loader 中保留状态
- 使用 loader-utils 和 schema-utils 为我们提供的实用工具
- 加载本地 Loader 方法
九.使用 Webpack 开发时可以提高效率的插件
- Webpack-dashboard:可以更友好的展示相关打包信息。
- Webpack-merge:提取公共配置,减少重复配置代码
- speed-measure-Webpack-plugin:简称 SMP,分析出 Webpack 打包过程中 Loader 和 Plugin 的耗时,有助于找到构建过程中的性能瓶颈
- size-plugin:监控资源体积变化,尽早发现问题
- HotModuleReplacementPlugin:模块热替换
十.如何做到长缓存优化?
- 什么是长缓存浏览器在用户访问页面的时候,为了加快加载速度,会对用户访问的静态资源进行存 储,但是每一次代码升级或者更新,都需要浏览器去下载新的代码,最方便和最简单的更新方式 就是引入新的文件名称
- 具体实现 在 Webpack 中,可以在 output 给出输出的文件制定 chunkhash,并且分离经常更新的 代码和框架代码,通过 NameModulesPlugin 或者 HashedModulesPlugin 使再次打包文件名不变
十一.如何提高 Webpack 的构建速度?
在多入口情况下,使用 CommonsChunkPlugin 来提取公共代码
- 通过 externals 配置来提取常用库
- 利用 DllPlugin 和 DllReferencePlugin 预编译资源模块 通过 DllPlugin 来对那些我们 引用但是绝对不会修改的 npm 包来进行预编译,再通过 DllReferencePlugin 将预编译的模块加 载进来。
- 使用 Happypack 实现多线程加速编译
- 使用 Webpack-uglify-parallel 来提升 uglifyPlugin 的压缩速度。 原理上 Webpackuglify-parallel 采用了多核并行压缩来提升压缩速度
- 使用 Tree-shaking 和 Scope Hoisting 来剔除多余代码
十二.怎么实现 Webpack 的按需加载?什么是神奇注释?
- 按需加载 在 Webpack 中,import 不仅仅是 ES6 module 的模块导入方式,还是一个类似 require 的函数,我们可以通过 import('module')的方式引入一个模块,import()返回的是一个 Promise 对象;使用 import()方式就可以实现 Webpack 的按需加载
- 神奇注释 在 import()里可以添加一些注释,如定义该 chunk 的名称,要过滤的文件,指定引 入的文件等等,这类带有特殊功能的注释被称为神器注释
总结
根据这些年的工作经历总结出一些零零碎碎的webpack知识点,内容没有很深入,大多数都是一些概念(详解的话这篇小文估摸着得花点心思跟时间,白天还要应付领导😶🌫️,等什么时候有空了一个点一个详解🐼🐼🐼🐼) 注:不知名小Bug