由于知识是不断更新的,所以该文档打算采取动长期的态更新方式
路由懒加载和热更新速度
路由懒加载一般在后台管理系统用的最多,尤其是页面50个起步的那种。只有在使用这个component的时候才会加载这个相应的组件,这样写大大减少了初始页面js的大小并且能更好的利用浏览器缓存。如下代码就是路由懒加载的方法:
// Vue懒加载方式
const UserList = resolve => require(['./UserList.vue'], resolve)
//或者
const UserList = () => import('./UserList')
// React懒加载路由方式
// React提供了lazy方法用来使用懒加载路由
const UserList = lazy(() => import('./UserList'))
// Angular懒加载方式
const routes: Routers = [
{
path: 'user',
loadChildren: () => import('./user.module').then(m => m.UserModule)
}
]
生产环境的懒加载路由虽好,但是开发环境的懒加载路由一旦很多的时候,就是一件痛苦的事情了,随随便便改一行代码热更新都要好几秒钟。原因其实是webpack的cache对懒加载的路由不生效了,所以每次的重新编译(rebuild)才会很慢。
方法1
export const loadView = (view) => {
if (process.env.NODE_ENV === 'development') {
return (resolve) => require([`@/views/${view}`], resolve)
} else {
// 使用 import 实现生产环境的路由懒加载
return () => import(`@/views/${view}`)
}
}
通过环境变量来判断是使用require还是import。但是webpack在生产环境会带来一些负优化,比如一个页面的组件会打包出多个js文件,而且多出来的js文件也许永远都不可能会用到(相关issue)。主要的原因在webpack官方文档中的 import()中的表达式 也有写道。
我们也可以看看ChatGPT的回答:
当使用动态导入(Dynamic Import)的方式加载组件时,Webpack 在编译生产环境时可能会生成一些额外的组件。这是由 Webpack 的工作原理决定的。
在开发环境中,Webpack 会根据动态导入的语法将每个组件单独打包成独立的文件,这样在开发过程中可以实现按需加载和热重载等功能,提高开发效率。
但是,在生产环境中,Webpack 会尝试对模块进行优化,以提高应用的加载性能。为了实现这个优化,Webpack 会对所有的动态导入语句进行静态分析,并将它们打包成一个或多个代码块(chunk),以减少网络请求的数量。
这意味着,即使你在代码中只使用了部分组件,Webpack 在编译过程中仍然会将所有可能被动态导入的组件打包进代码块。这些多余的组件可能永远不会被加载,但它们仍然存在于最终生成的代码中。
这种行为是为了保证编译后的代码的完整性和一致性。虽然会增加一些冗余代码,但它可以确保在应用运行时,所有可能被动态导入的组件都是可用的。
如果你希望进一步减少生产环境中的冗余组件,可以考虑使用代码分割(Code Splitting)和按需加载的策略,以减少生成的代码大小并提高加载性能。Webpack 提供了一些配置选项和插件,例如使用 import() 方法的动态导入,或使用 splitChunks 进行代码块的优化等。这些工具和策略可以根据你的具体需求进行配置和调整。
方法2
很显然上述方法早已过时。现在可以使用 babel-plugin-dynamic-import-node 插件,这个babel的功能就只干一件事,就是将所有的 import转化为require(),而且不会有上述方法与webpack带来的负优化,只需要在开发环境的可以直接放心使用。
module.exports = {
'env': {
'development': { // 只在开发环境使用此 babel
'plugins': ['dynamic-import-node']
}
}
}
Gzip压缩优化
考虑一种场景,我有4个文本文件平均256MB大小,要传给另一台电脑,怎样效率更快?
Gzip 是一种用于文件压缩与解压缩的文件格式。Web 服务器与现代浏览器普遍地支持 Gzip,这意味着服务器可以在发送文件之前自动使用 Gzip 压缩文件,而浏览器可以在接收文件时自行解压缩文件。大概的流程图为下方所示:
如何使用压缩
- 前端构建编译生产环境的时候使用插件将部分文件进行Gzip压缩处理(建议只压缩文本类型的文件例如:html、css、js、svg、json这种)
- 在服务端启动Gzip功能
前端配置
不同的脚手架可能要使用到不同的压缩插件
- Vue
如果是使用Vue+Webpack的可以使用 compression-webpack-plugin 插件
vue.config.js文件配置如下:
module.exports = {
...
configureWebpack: {
plugins: [
...
new CompressionPlugin({
cache: false, // 不启用文件缓存
test: /\.(js|css|html)?$/i, // 压缩文件格式
filename: '[path].gz[query]', // 压缩后的文件名
algorithm: 'gzip', // 使用gzip压缩
minRatio: 0.8 // 压缩率小于1才会压缩
})
...
]
}
...
}
如果你是用的是Vue3+Vite的话可以使用 vite-plugin-compression 插件
vite.config.js
import viteCompression from 'vite-plugin-compression'
export default () => {
return {
plugins: [
viteCompression()
]
}
}
- React 或 Angular
React 或 Angular直接使用 gzipper 插件,package.json文件配置如下:
{
...
"scripts": {
"build" : "yarn run ng-high-memory build && gzipper c --include js,ts,css,html ./dist"
}
...
}
编译和压缩完成后的dist文件里大概长这样👇👇👇👇👇👇
建议不要压缩图片类型(jpg、png、webp等),因为压缩后的文件大小跟源文件差不多。
服务端配置
- Nginx配置
Nginx需要使用 ngx_http_gzip_module 和 ngx_http_gzip_static_module 这两个模块的配置
http {
server {
...
# gzip压缩
gzip on; # 是否启用Gzip压缩
gzip_min_length 1k; # 设置被压缩的最小请求,少于这个值大小的请求不会被压缩
gzip_buffers 16 64K; # 指定缓存压缩应答的缓冲区数量和大小
gzip_http_version 1.1; # 压缩版本(默认1.1,前端如果是squid2.5请使用1.0)
gzip_comp_level 5; # 压缩级别,1-10,数字越大压缩的越好,时间也越长
# 进行压缩的文件类型
gzip_types text/plain application/x-javascript text/css application/xml application/javascript;
gzip_vary on; # 跟Squid等缓存服务有关,on的话会在Header里增加"Vary: Accept-Encoding"
gzip_disable "MSIE [1-6]\."; # IE6对Gzip不怎么友好,不给它Gzip了
# 同时建议开启解压缩静态文件(开启后会使用已压缩好的文件,如果没有Nginx会自动压缩 不开启Nginx会将允许压缩的文件类型自动压缩,)
gzip_static on;
...
}
}
- NodeJs使用
npm install compression
npm install @types/compression --save-dev
const compression = require('compression')
const app = express()
app.use(compression({level: 5}))
优点
- 传输速度快
- 主流浏览器都支持
缺点
- 文件越小,且文件越多,效率上的收益可能会很低也有可能会形成负收益
- 不支持IE6
- 可能会遭受到 BREACH 攻击
webpack-bundle-analyzer
webpack-bundle-analyzer,这个插件可以生成模块分析报告,可以很直观的展示打包后的模块和文件有哪些,以及大小,占比情况,各文件的Gzip压缩之后的大小等。通过这个插件后可以分析各个模块和文件,达到合理的优化和分配文件,从而提升网站性能。
上面这张图片就是 webpack-bundle-analyzer 生成出来的。
通过上面图可以给出以下优化:
- chunk-vendors.js都是第三方库的依赖,打包后的体积有2.28MB的大小,在这种属于是稍微大的情况下可以使用 代码分割(Code Splitting)的方法将 elementUI 和 echarts分割出去。
- 使用CDN引入第三方库,比如将 elementUI 使用CDN的方式引入,这样打包后的体积就小了也不会占用服务器带宽了。
- echarts可能只用过饼图、折线图和柱状图这几种,那么我们完全可以直接使用 按需引入 的方式减少echarts打包后的大小。
一级标题
如果你的项目使用的是 webpack4 ,那么webpack4就会自动开启 Code Splitting(代码分割)。
如上图所示,在没有配置任何东西的情况下,webpack4就职能的帮你做了代码分包。入口文件以来的文件都被打包进了app.js,那些大于30kb的第三方包,比如 codemirro、echarts、codemirror、showdown等都被单独打包成了独立bundle。
内置的代码分割策略:
- 新的chunk是否为共享模块或是node_modules的模块
- 新的chunk体积在压缩之前是否大于30KB
- 按需夹杂chunk的并发请求数量小于等于5个
- 页面初始加载时的并发请求小于等于3个
但有一些小的组件,就像下面面这张图片上的 vue-count-to 在未压缩的情况下只有5kb的大小,虽然他被多个页面使用了,但是 webpack4默认的情况下还是会将它和那些懒加载的页面代码打包到一起,并不会单独将他拆成一个独立的bundle。(虽然被共用了,但是体积大小少于30KB)
你可能会觉得 webpack 默认策略是不是有问题,我一个组件被多个页面,你每个页面都将这个组件打包进去了,岂不是会重复打包很多次这个组件?就拿vue-count-to来举例,你可以把共用两次以上的组件或者代码单独抽出来打包成一个 bundle,但你不要忘了vue-count-to未压缩的情况下就只有 5kb,gizp 压缩完可能只有 1.5kb 左右,你为了共用这 1.5kb 的代码,却要额外花费一次 http 请求的时间损耗,得不偿失。我个人认为 webpack 目前默认的打包规则是一个比较合理的策略了。
但有些场景下这些规则可能就显得不怎么合理了。比如我有一个管理后台,它大部分的页面都是表单和 Table,我使用了一个第三方 table 组件,几乎后台每个页面都需要它,但它的体积也就 15kb,不具备单独拆包的标准,它就这样被打包到每个页面的 bundle 中了,这就很浪费资源了。这种情况下建议把大部分页面能共用的组件单独抽出来,合并成一个component-vendor.js的包(后面会介绍)。
优化没有银弹,不同的业务,优化的侧重点是不同的。个人认为 webpack 4 默认拆包已经做得不错了,对于大部分简单的应用来说已经够用了。但作为一个通用打包工具,它是不可能满足所有的业务形态和场景的,所以接下来就需要我们自己稍微做一些优化了。
为什么要做分包优化
Webpack的分包优化是为了提高应用程序的性能和加载速度。以下是一些原因:
- 减小初始加载大小:当应用程序的代码包较大时,初始加载时间会变长。通过将代码分割成多个块,并按需加载,可以减小初始加载的文件大小,使应用程序更快地展现给用户。
- 提高页面加载速度:将代码分割成多个块并使用按需加载的方式,可以减少不必要的资源请求和下载。这将导致页面加载速度更快,用户能够更快地访问和使用应用程序。
- 优化缓存策略:当应用程序的代码分割得当时,可以更好地利用浏览器的缓存机制。如果某个模块发生变化,只需重新加载该模块对应的块,而不需要重新加载整个应用程序的代码。这可以减少网络传输和服务器负载。
- 实现按需加载:在大型应用程序中,不是所有的模块都需要在初始加载时加载。通过按需加载,只有在需要使用某个模块时才加载它,可以提高应用程序的运行效率和资源利用率。
- 并行加载:当应用程序的代码被分割成多个块时,这些块可以并行加载,利用浏览器的并行下载能力。这可以加快整体的加载速度,提高用户体验。
综上所述,Webpack的分包优化可以显著改善应用程序的性能,减小初始加载大小,加快页面加载速度,并优化缓存策略。这些优化措施对于提升用户体验、降低服务器负载以及应对大型复杂应用程序的需求非常重要。
Vue的组件按组分块
Vue提供了按组分块的组件分割策略,异步组件(Async Components)或懒加载组件(Lazy Loading Components)。这种策略可以帮助减小初始加载的文件大小,提高应用程序的性能。相关文档
使用webpack
有时候我们想把某个路由下的所有组件都打包在同个异步块 (chunk) 中。只需要使用命名 chunk,一个特殊的注释语法来提供 chunk name (需要 Webpack > 2.4):
const UserDetails = () =>
import(/* webpackChunkName: "group-user" */ './UserDetails.vue')
const UserDashboard = () =>
import(/* webpackChunkName: "group-user" */ './UserDashboard.vue')
const UserProfileEdit = () =>
import(/* webpackChunkName: "group-user" */ './UserProfileEdit.vue')
使用Vite
在Vite中,你可以在rollupOptions下定义分割:
// vite.config.js
export default defineConfig({
build: {
rollupOptions: {
// https://rollupjs.org/guide/en/#outputmanualchunks
output: {
manualChunks: {
'group-user': [
'./src/UserDetails',
'./src/UserDashboard',
'./src/UserProfileEdit',
],
},
},
},
},
})
但是不能随随便便进行路由分组不然就会出现公共资源也被单独打包进来(WSPlayer、MapConfig、api),如下图: