webpack 的长处
webpack 从配置的入口登程,能够打包所有前端资源,同时能够配置多种 loader 来解决不同类型文件的转换,并且能够配置 plugin 来扩大模块打包流程,满足更多构建中非凡的需要,开发过程中还能够应用 HMR 晋升本地开发效率和体验,生产环境中能够利用代码压缩和代码宰割来晋升前端加载性能,总之就是既能够晋升开发效率,又能够晋升利用性能。
webpack 中 loader 和 plugin 的区别
webpack 中 loader 次要用于解决非 JavaScript 模块的转换,在固定的阶段中应用,自身只是一个函数,返回转换后的后果,因为 webpack 只能解决 js;而 plugin 是用于解决非凡的构建需要,比方将 css 代码独自输入为一个文件、定义环境中的变量等,利用了 webpack 的 hooks 染指构建过程中不同的阶段,能够定制我的项目的构建流程。
开始编译,读取 webpack 配置,创立 NormalModuleFactory,创立 NormalModule(应用 resolveLoader 解析 loader 门路),开始编译模块(loader-runner)
如何利用 webpack 晋升利用性能
webpack 优化性能大略有三种形式:
代码压缩、代码宰割、去除无用代码,次要的方向是缩小申请体积,代码宰割有的状况下还能够利用浏览器缓存,缩小申请次数
- 代码压缩(次要就缩小申请体积)
咱们能够对 optimization 下的 minimize 和 minimizer 配置项进行设置,配置 TerserPlugin 对 js 代码进行压缩解决,还能够应用插件如 HTMLWebpackPlugin 对 html 文件进行压缩。 - 代码宰割(既缩小一次的申请体积,使申请返回更快,有的状况还能够利用浏览器缓存,缩小申请次数)
我理解的大略几种形式,第一种是设置 optimization 下的 splitChunks 配置项、第二种 css 代码利用 MiniCssExtractPlugin 插件独自生成文件、第三是利用 webpack 的 dllPlugin 插件将第三方库打包成独立的文件。
splitChunks 能够将模块抽离为独自的文件,chunks 能够设置为 all/async/inital 三个值的其中一个,all 就是所有的模块都打包到一个 chunk,async 是把异步加载的模块打包到 chunk 中,如通过动静 import 加载的模块,initial 是把同步加载的模块打包到 chunk 中。能够配合按需加载应用,按需加载应用 ES 动静加载语法 import 来加载模块,webpack 会主动解决应用这种语法编写的模块,把模块独自拆散成文件,能够缩小大型利用初始化时须要加载的前端资源,晋升用户体验。
css 代码利用 plugin 插件生成独自的文件,能够在后续申请中,利用浏览器缓存,如果是多页面利用,长处更显著,不必屡次加载 css。
dllPlugin 也是将局部模块抽离成独自的文件,然而它和 splitChunks 不同,在代码不变的状况下不必反复打包,能够独自写一个配置文件,运行打包,在后续我的项目的构建流程间接应用 DLLReferencePlugin 援用文件就能够,通常能够用来解决不太变动的第三方库,能够看进去这样不仅能够利用浏览器缓存,还能进步开发效率。 - 去除无用代码(缩小申请体积)
能够利用 tree-shaking、还有 sideEffects,将未应用的代码移除。
要利用到 tree-shaking 就要应用别离导入、而不是整体导入,未引入和未应用的模块办法就不会被打包,sideEffects 对没有副作用的模块也能够将模块中未应用到的代码不进行打包。
还有 webpack 的 IgnorePlugin 插件,能够在打包中疏忽掉某些依赖包中体积大但又不太须要的文件,如 react 脚手架生成的我的项目中就默认将 moment 的 locale 文件夹整个疏忽掉,这个语言包会十分大,然而通常理论中不须要这些多语言的配置,独自引入须要的语言就能够。
webpack 的热更新原理
热更新 HMR 就是不必刷新页面而将新变更的模块替换掉旧的模块,防止了频繁手动刷新页面、利用状态失落,也缩小了页面刷新时的期待。它的外围是客户端去服务端拉取更新后的文件。在 DevServer 开启 hot 后,webpack 会往利用代码中增加 websocket 相干的代码,用于和服务器放弃连贯,期待更新动作,本地代码变更时告诉浏览器做相应的解决;webpack 还会往利用代码中增加 HMR 运行时的代码,用于定义利用更新时的 API。当有更新时,webpack-dev-server 发送更新信号给 HMR 运行时,而后 HMR 再申请所须要的更新数据,服务端返回一个 json 蕴含所有要更新的模块的 hash 值,对应模块再次申请获取到最新的模块代码,没有问题的话就进行利用更新。
利用更新的 API 常见的有 module.hot.accept、module.hot.decline、module.hot.dispose 等,accept 是在利用特定代码模块更新时执行相应的 callback;decline 是对于指定的代码模块,回绝进行更新;dispose 用于增加一个处理函数,在模块代码被替换时运行(可用于移除之前增加的长久化资源或者相干状态)。
如何优化 webpack 的构建速度
总的来说方向就是缩小 webpack 的工作量。
- 首先在开发环境下应用配置 mode 为 develpment,webpack 自身就默认不配置一些压缩优化的插件,这样能够缩小在优化操作上的工夫耗费;
- 其次提前解决一些文件资源,如应用 imagemin 或者其余工具提前压缩好图片,能够缩小在图片解决上的耗时,一些不频繁更新的第三方库应用 dllPlugin 打包,在我的项目中间接援用,不打包到利用代码中,也能够大大减少构建所用工夫;
- 还能够通过配置 resolve,设置适宜的 extensions、modules、mainFields、mainFiles 的值,缩小模块解析时门路的查问范畴,配置 module 的 rules 应用 loader 解决不同的文件时,通过 include 和 exclude 限度解决范畴,缩小耗时;
- 另外还能够应用 thread-loader 利用多过程减速 loader 执行,利用 tree-shaking 缩小 webpack 解决打包的代码量,利用缓存晋升二次构建速度,像 babel-loader、terser-webpack-plugin 都能够开启缓存。
- 生产环境还能够配置 devtool 不输入 sourcemap。
- 如果我的项目十分大,波及代码模块过多,在适合的状况下也能够依据肯定的粒度,把不同的业务代码拆分到不同的代码库去保护和治理,缩小 webpack 解决的代码量。
如何开发一个 webpack 的 plugin
plugin 的实现能够是一个类或者函数,应用时传入相干配置来创立一个实例,plugin 实例最重要的办法就是 apply,在 webpack compiler 装置插件时会被调用,接管 webpack 的 compiler 对象实例的援用作为参数,在 compiler 对象实例上咱们能够注册各种事件钩子函数 hooks,在 compiler 的有些 hooks 中,还能够获取到 compilation 对象实例,在 compilation 对象实例上注册各种 hooks,咱们能够通过注册各种 hooks 来影响 webpack 的所有构建流程,以便实现更多其余构建工作。
hooks 能够简略分为同步和异步两种,同步类型的 hooks 只能应用 tap 来注册事件,异步的还能够应用 tapPromise 和 tapAsync 来注册。
本地开发 plugin,能够使自定义 plugin 对外裸露一个类,而后在 webpack 配置文件中引入,运行 webpack 构建查看后果就能够,能够应用 node 命令进行调试。
compiler 一些 hooks:entryOption、beforeRun、emit、compilation、thisCompilation、make(compilation 实现编译后执行)、shouldEmit(管制是否输入对应的构建后果)、assetEmitted(在构建后果输入之后执行,能够获取输入内容的相干信息)、done、failed(构建失败时执行)
compilation 一些 hooks:buildModule、finishModules、chunkAsset(chunk 对应的一个输入资源增加到 compilation 时执行)、processAssets
如何开发一个 webpack 的 loader
webpack loader 实质就是一个实现转换性能的函数,接管 content、map、meta 三个参数。content 就是要进行转化的资源内容,能够是字符串或者 buffer,如图片、字体等文件,map 是 sourcemap 对象。
通常 webpack loader 都是基于一个实现外围性能的类库来开发的,如果间接 return 一个值,这个值就是转换后的内容,如果要返回 sourcemap 对象或者其余数据,或是抛出一个异样,须要应用 this.callback(err, content, map, meta) 来传递这些数据;有些 loader 在执行过程中可能依赖内部 I / O 的后果,就须要应用异步的形式来解决,在 loader 执行时调用 this.async() 来标识该 loader 是异步解决的,this.async() 会返回一个函数,而后咱们能够调用这个函数用于返回 loader 的处理结果。
应用本地开发的 loader 有两种形式:一种是配置 loader 时应用本地的门路;另一种是在 loader 门路解析中退出本地开发 loader 的目录,具体是在 loader 所在目录下增加 package.json 文件并配置 name 字段,而后配置 resolveLoader 的 modules 将 loader 所在目录的门路增加进去,并在配置 loader 时应用 package.json 中 name 的值,这种比拟适宜多个 loader 的状况。
官网提供的一个工具库 loader-utils 能够帮忙咱们获取给 loader 传递的 options,咱们还能够应用官网提供的 schema-utils 对传入的 options 进行校验。