1、路由懒加载
SPA 项目,一个路由对应一个页面,如果不做处理,项目打包后,会把所有页面打包成一个文件,当用户打开首页时,会一次性加载所有的资源,造成首页加载很慢,降低用户体验
列一个实际项目的打包详情:
app.js 初始体积:1175kb
app.css 初始体积:274kb
将路由全部改成懒加载
// 通过webpackChunkName设置分割后代码块的名字
const Home = () => import(/* webpackChunkName: "home" */ "@/views/home/index.vue");
const MetricGroup = () => import(/* webpackChunkName: "metricGroup" */ "@/views/metricGroup/index.vue");
const routes = [
{
path: "/",
name: "home",
component: Home
},
{
path: "/metricGroup",
name: "metricGroup",
component: MetricGroup
},
]
重新打包后,首页资源拆分为 app.js 和 home.js,以及对应的 css 文件
- app.js:
244 KB
、 home.js:35KB
- app.css:
67 KB
、home.css:15KB
通过路由懒加载,该项目的首页资源压缩约 52%
懒加载前提的实现:ES6的动态地加载模块——
import()
webpackChunkName 作用是 webpack 在打包的时候,对异步引入的库代码(lodash)进行代码分割时,设置代码块的名字。webpack 会将任何一个异步模块与相同的块名称组合到相同的异步块中
vite也有类似的功能
2、组件懒加载
home 页面 和 about 页面,都引入了 dialogInfo 弹框组件,该弹框不是一进入页面就加载,而是需要用户手动触发后才展示出来
home 页面示例:
<template>
<div class="homeView">
<p>home 页面</p>
<el-button @click="dialogVisible = !dialogVisible">打开弹框</el-button>
<dialogInfo v-if="dialogVisible" />
</div>
</template>
<script>
import dialogInfo from '@/components/dialogInfo';
export default {
name: 'homeView',
components: {
dialogInfo
}
}
</script>
项目打包后,发现 home.js 和 about.js 均包括了该弹框组件的代码(在 dist 文件中搜索dialogInfo弹框组件)
当用户打开 home 页时,会一次性加载该页面所有的资源,我们期望的是用户触发按钮后,再加载该弹框组件的资源
这种场景下,就很适合用懒加载的方式引入
弹框组件懒加载:
<script>
const dialogInfo = () => import(/* webpackChunkName: "dialogInfo" */ '@/components/dialogInfo');
export default {
name: 'homeView',
components: {
dialogInfo
}
}
</script>
重新打包后,home.js 和 about.js 中没有了弹框组件的代码,该组件被独立打包成 dialogInfo.js,当用户点击按钮时,才会去加载 dialogInfo.js 和 dialogInfo.css
最终,使用组件路由懒后,该项目的首页资源进一步减少约 11%
组件懒加载的使用场景
有时资源拆分的过细也不好,可能会造成浏览器 http 请求的增多
总结出三种适合组件懒加载的场景:
1)该页面的 JS 文件体积大,导致页面打开慢,可以通过组件懒加载进行资源拆分,利用浏览器并行下载资源,提升下载速度(比如首页)
2)该组件不是一进入页面就展示,需要一定条件下才触发(比如弹框组件)
3)该组件复用性高,很多页面都有引入,利用组件懒加载抽离出该组件,一方面可以很好利用缓存,同时也可以减少页面的 JS 文件大小(比如表格组件、图形组件等)
骨架屏优化白屏时长
用骨架屏,可以缩短白屏时间,提升用户体验。国内大多数的主流网站都使用了骨架屏,特别是手机端的项目
SPA 单页应用,无论 vue 还是 react,最初的 html 都是空白的,需要通过加载 JS 将内容挂载到根节点上,这套机制的副作用:会造成长时间的白屏
常见的骨架屏插件就是基于这种原理,在项目打包时将骨架屏的内容直接放到 html 文件的根节点中
使用骨架屏插件,打包后的 html 文件(根节点内部为骨架屏):
同一项目,对比使用骨架屏前后的 FP 白屏时间: 1063ms
有骨架屏:白屏时间 144ms
骨架屏插件
vue-skeleton-webpack-plugin
// 骨架屏
const SkeletonWebpackPlugin = require("vue-skeleton-webpack-plugin");
module.exports = {
configureWebpack: {
plugins: [
new SkeletonWebpackPlugin({
// 实例化插件对象
webpackConfig: {
entry: {
app: path.join(__dirname, './src/skeleton.js') // 引入骨架屏入口文件
}
},
minimize: true, // SPA 下是否需要压缩注入 HTML 的 JS 代码
quiet: true, // 在服务端渲染时是否需要输出信息到控制台
router: {
mode: 'hash', // 路由模式
routes: [
// 不同页面可以配置不同骨架屏
// 对应路径所需要的骨架屏组件id,id的定义在入口文件内
{ path: /^\/home(?:\/)?/i, skeletonId: 'homeSkeleton' },
{ path: /^\/detail(?:\/)?/i, skeletonId: 'detailSkeleton' }
]
}
})
]
}
}
JS 的6种加载方式
1)正常模式
<script src="index.js"></script>
2)async 模式
<script async src="index.js"></script>
async 模式下,它的加载是异步的,JS 不会阻塞 DOM 的渲染,async 加载是无顺序的,当它加载结束,JS 会立即执行
使用场景:若该 JS 资源与 DOM 元素没有依赖关系,也不会产生其他资源所需要的数据时,可以使用async 模式,比如埋点统计
3)defer 模式
<script defer src="index.js"></script>
defer 模式下,JS 的加载也是异步的,defer 资源会在 DOMContentLoaded
执行之前,并且 defer 是有顺序的加载
如果有多个设置了 defer 的 script 标签存在,则会按照引入的前后顺序执行,即便是后面的 script 资源先返回
所以 defer 可以用来控制 JS 文件的执行顺序,比如 element-ui.js 和 vue.js,因为 element-ui.js 依赖于 vue,所以必须先引入 vue.js,再引入 element-ui.js
<script defer src="vue.js"></script>
<script defer src="element-ui.js"></script>
defer 使用场景:一般情况下都可以使用 defer,特别是需要控制资源加载顺序时
4)module 模式
<script type="module">import { a } from './a.js'</script>
在主流的现代浏览器中,script 标签的属性可以加上 <font style="color:rgb(152, 195, 121);background-color:rgb(40, 44, 52);">type</font><font style="color:rgb(171, 178, 191);background-color:rgb(40, 44, 52);">=</font><font style="color:rgb(152, 195, 121);background-color:rgb(40, 44, 52);">"module"</font>
,浏览器会对其内部的 import 引用发起 HTTP 请求,获取模块内容。这时 script 的行为会像是 defer 一样,在后台下载,并且等待 DOM 解析
Vite 就是利用浏览器支持原生的 <font style="color:rgb(171, 178, 191);background-color:rgb(40, 44, 52);">es</font><font style="color:rgb(171, 178, 191);background-color:rgb(40, 44, 52);"> </font><font style="color:rgb(198, 120, 221);background-color:rgb(40, 44, 52);">module</font>
模块,开发时跳过打包的过程,提升编译效率
5) preload
<link rel="preload" as="script" href="index.js">
link 标签的 preload 属性:用于提前加载一些需要的依赖,这些资源会优先加载(如下图红框)
async、defer 是 script 标签的专属属性,对于网页中的其他资源,可以通过 link 的 preload、prefetch 属性来预加载
如今现代框架已经将 preload、prefetch 添加到打包流程中了,通过灵活的配置,去使用这些预加载功能,同时我们也可以审时度势地向 script 标签添加 async、defer 属性去处理资源,这样可以显著提升性能
图片的懒加载
<img src="" alt="" data-src="./images/1.jpg">
<img src="" alt="" data-src="./images/2.jpg">
图片转 base64 格式
将小图片转换为 base64 编码字符串,并写入 HTML 或者 CSS 中,减少 http 请求
// 安装
npm install url-loader --save-dev
// 配置
module.exports = {
module: {
rules: [{
test: /.(png|jpg|gif)$/i,
use: [{
loader: 'url-loader',
options: {
// 小于 20kb 的图片转化为 base64
limit: 1024 * 20
}
}]
}]
}
};