先上效果 优化前:12S 优化后:1s(理想情况下) 我们在讲解热更新优化之前先来简单了解一下webpack热更新原理: ● 通过 webpack-dev-server 创建两个服务器:提供静态资源的服务(express)和 Socket 服务 ● express server 负责直接提供静态资源的服务(打包后的资源直接被浏览器请求和解析) ● socket server 是一个 websocket 的长连接,双方可以通信 ● 当 socket server 监听到对应的模块发生变化时,会生成两个文件.json(manifest 文件)和.js 文件(update chunk) ● 通过长连接,socket server 可以直接将这两个文件主动发送给客户端(浏览器) ● 浏览器拿到两个新的文件后,通过 HMR runtime 机制,加载这两个文件,并且针对修改的模块进行更新 热更新速度分析:speed-measure-webpack-plugin 它可以看到每个loader和插件的执行耗时,这样就让你知道优化你的webpack构建速度你的注意力该集中在哪里。 安装: 使用: 效果: 从上图中我们发现CompressionPlugin耗费了7.2S的时间,但是我们只是在生产环境需要代码压缩,因此我们可以在开发环境将其屏蔽。 改进路径 1.开发环境屏蔽CompressionPlugin插件 plugins: [ 如果你的项目中有只在生产环境使用的插件,可以使用该方法过滤掉,来加快热更新的速度。 效果如下: 除了删除多余的插件,我们也可以借助sourcemap的修改来提升热更新速度。 2.Sourcemap与热更新 SourceMap 生成过程中,由于项目过大导致需要计算处理的映射节点(SourceNode)特别多,这也导致 SourceMap 生成过程中内存飙升频繁 GC,构建十分缓慢甚至 OOM。 因此合适的sourcemap有助于提升我们的热更新速度。 可用的SourceMap 非常多,此处我们只列出一些具有代表性的: ? source-map:外部。可以查看错误代码准确信息和源代码的错误位置。 ? eval:内联。每个模块会封装到 eval 里包裹起来执行,只能定位到报错模块的位置。 ? inline-source-map:内联。只生成一个内联 Source Map,可以查看错误代码准确信息和源代码的错误位置 ? hidden-source-map:外部。可以查看错误代码准确信息,但不能追踪源代码错误,只能提示到构建后代码的错误位置。 ? eval-source-map:内联。每一个文件都生成对应的 Source Map,都在 eval 中,可以查看错误代码准确信息 和 源代码的错误位置。 ? nosources-source-map:外部。可以查看错误代码错误原因,但不能查看错误代码准确信息,并且没有任何源代码信息。 ? cheap-source-map:外部。生成一个没有列信息的 SourceMaps 文件,不包含 loader 的 sourcemap(譬如 babel 的 sourcemap) ? cheap-module-source-map:外部。生成一个没有列信息的 SourceMaps 文件,同时 loader 的 sourcemap 也被简化为只包含对应行的。与cheap-source-map的区别为,如果加了 module 那就是 loader 处理前的源码,例如报错时会报const错误而不是loader编译后的var。 ? eval-cheap-module-source-map:内联。与cheap-module-source-map的区别为,它是内联的不生成多余文件,构建速度更快 内联和外部的区别: ①.外部生成了文件(.map),内联没有。 ②.内联构建速度更快。 下面更直观的列出了各种source-map的特点对比 速度说明 ? +越多速度越快,0介于+ -之间,-越多速度越慢 品质说明 ? 原始源代码 你写的代码,未经过任何处理 ? 转换过的代码 经过各种loader转译后的代码 ? 生成后的代码 转译后并经过webpack处理,注入webpack运行时代码后的代码 ? 打包后的代码 合成一个chunk后的、压缩过的代码 修改sourcemap 这里一般推荐使用vuecli5默认的eval-cheap-module-source-map,这个配置提供到源代码的的完整映射,方便定位问题,并且不需要通常不必要的行映射,提高了构建速度,并且由于使用了eval,重构建速度更高。如果你想以报错的准确度来换取更快的构建速度,也可以使用其他sourcemap例如eval。 3.runtimeChunk runtimeChunk的作用: 设置runtimeChunk是将包含chunks 映射关系的 list单独从 app.js里提取出来,因为每一个 chunk 的 id 基本都是基于内容 hash 出来的,所以每次改动都会影响它,如果不将它提取出来的话,等于app.js每次都会改变。缓存就失效了。设置runtimeChunk之后,webpack就会生成一个个runtime~xxx.js的文件。 然后每次更改所谓的运行时代码文件时,打包构建时app.js的hash值是不会改变的。如果每次项目更新都会更改app.js的hash值,那么用户端浏览器每次都需要重新加载变化的app.js,如果项目大切优化分包没做好的话会导致第一次加载很耗时,导致用户体验变差。现在设置了runtimeChunk,就解决了这样的问题。所以这样做的目的是避免文件的频繁变更导致浏览器缓存失效,所以其是更好的利用缓存。提升用户体验。 runtimeChunk为何设置为single 当runtimeChunk设置为"single"时,Webpack会把所有的运行时代码都打包到一个独立的文件中。这个文件会被缓存,并且只有在运行时版本发生变化时才会重新加载。这样可以减少初始加载的文件大小,并且提高缓存命中率。 而当runtimeChunk设置为true时,Webpack会为每个入口点生成一个运行时文件,如果你的入口过多,会导致生成的runtime文件很多,与single相比热更新效率就会下降。 因此,将runtimeChunk设置为"single"会将所有运行时代码打包到一个文件中,这样更加有利于热更新。 在vuecli5中runtimeChunk默认为single,如果你使用的vuecli版本默认值并不是single,可以使用如下方式更改 module.exports = { 4.多页面导致热更新速度慢 当我们的项目中页面过多也会导致热更新速度慢,此时我们可以使用两种方式来解决该问题(此两种方式可以同时使用): ①. babel-plugin-dynamic-import-node 在开发环境下进行热更新处理动态导入时,每次修改导入模块都会导致整个模块重新解析和加载,影响了热更新的速度。 babel-plugin-dynamic-import-node的作用就是通过静态分析代码,将动态导入import语句转化为静态导入require,从而加快热更新的速度。使得模块在每次热更新时只需重新加载修改过的模块,而不需要重新解析整个模块。这种方式在开发环境中可以大大提高热更新的速度。 注意该方法仅对使用import 懒加载路由过多导致的热更新慢有效。 安装: npm install babel-plugin-dynamic-import-node --save-dev 使用配置: 如果你使用的是:babel.config.js 修改文件 module.exports = { ②. 屏蔽多余的路由 我们在日常的开发中,每次开发涉及的页面可能只有几个,如果我们只根据我们在当前开发时需要的路由来注册开发环境的路由,可以大大增加热更新速度,下面给出样例。 首先我们创建环境变量 NODE_ENV=development VUE_APP_MY_MODEL=myMoudle 按需配置路由 const routes = process.env.VUE_APP_MY_MODEL ==="myMoudle"? 总结 通过以上一系列优化,可以将热更新速度降到1S内,这样修改的效果就能立刻体验啦,从而加快开发者的开发迭代速度,减少调试和错误修复时间。 来源:微信公众号:360质量效能 出处:https://mp.weixin.qq.com/s/CNlEFrsKh-3Yy6u6Lh9Anwnpm install --save-dev speed-measure-webpack-plugin
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
module.exports = {
publicPath: "./",
configureWebpack: (config) => {
return smp.serve({
...
});
},
};
(isProduction
? new CompressionWebpackPlugin({
filename: "[path].gz[query]",
algorithm: "gzip",
test: /\.(js|css)(\?.*)?$/i,
threshold: 2048, // 对超过10k的数据进行压缩
minRatio: 0.8, // 只有压缩率小于这个值的资源才会被处理
deleteOriginalAssets: false // 删除原文件
})
: undefined)
].filter(Boolean)configureWebpack: (config) => {
config.devtool = "eval";//eval为构建速度最快的sourcemap,但报错信息只会显示eval函数的位置
}
configureWebpack: {
optimization: {
runtimeChunk: "single"
}
}
}
presets: [
'@vue/app'
],
env: {
development: {//仅在开发环境生效
plugins: ['dynamic-import-node']
}
}
}
[{//本次开发需要的路由
path:"/mypath",
name:"myPathName",
component:myCom
}]
:[...]//完整的路由