[前端进阶] 首屏优化知识梳理

前言

首屏优化的重要性不用多说,是所有面向大众产品的一个硬核指标。这里带大家梳理下常用的首屏优化策略

无论你是使用webpack打包,还是vite打包,本文都列举了相关示例,希望对大家有帮助😀😀😀

一、体积优化

1. 路由懒加载

原理 :使用import()函数语句,将路由组件打包成独立的模块。只有访问到对应路由时,才去加载对应的组件内容。这种方式可以有效地减少首次加载页面时需要加载的代码量,提高页面的加载速度。

示例

js 复制代码
const LoginPage = () => import('@/views/login/LoginPage.vue');

分析工具

webpack使用webpack-bundle-analyzer进行分析;

vite使用rollup-plugin-visualizer进行分析;

效果图

可以看到各个路由组件都被分别打包进不同的js文件。后续也可根据此分析图来进行代码优化。

2. npm包按需导入

在项目中只引入所需的特定模块或功能,而不是引入整个包。这样可以减小项目的体积,提高加载速度

示例:Element Plus自动按需导入

  1. 安装插件
arduino 复制代码
yarn add unplugin-vue-components unplugin-auto-import -D
  1. 配置vite.config.ts
js 复制代码
// vite.config.ts
import { defineConfig } from 'vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

export default defineConfig({
  // ...
  plugins: [
    // ...
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],
})

3. 提取公共包

当一个npm包在多个路由页面中被引用,vite和webpack都会把公共包提取到一个公共的chunk(块)中。例如vite会把公共包打包进index-hash.js中,如下图所示:

现象:index-hash.js中包含了vue-i18n,vue-router,@vue等。在我的demo项目中,index-hash.js体积891KB,占比38.66%。在企业项目中,随着更多公共包的引入,体积只会越来越大。而且其中任何一个npm包进行了更新,文件的hash值就会改变,缓存失效,浏览器得重新从服务器下载资源

解决:webpack使用splitChunks,vite基于rollup使用manualChunks提取公共包

webpack:修改webpack.config.js,将echarts提取到名为echarts.vendor的js文件中

js 复制代码
module.exports = {
  //...
  optimization: {
    splitChunks: {
      chunks: "all",
      minSize: 30000,
      name: false,
      maxAsyncRequests: 5,
      maxInitialRequests: 3,
      cacheGroups: {
        "echarts.vendor": {
          name: "echarts.vendor",
          priority: 40,
          test: /[\\/]node_modules[\\/](echarts|zrender)[\\/]/,
          chunks: "all",
        },
      },
    },
  },
};

vite:修改vite.config.ts,将vue,pinia,vue-router提取到名为vue的js文件中

js 复制代码
export default () => {
  return defineConfig({
    //...
    build: {
      rollupOptions: {
        output: {
          manualChunks: {
            vue: ['vue', 'pinia', 'vue-router'],
          },
        },
      },
    },
  });
};

二、图片优化

1. 压缩图片

  1. 使用在线压缩图片网站手动压缩图片:tinypng.com

  2. 在构建流程中加入压缩图片插件,例如 image-webpack-loader

2. 转为webp图片

webp是谷歌推出的新图片格式(2010),与PNG,JPG相比,体积更小

  1. 使用在线网站webP-converter 等手动压缩图片:cloudconvert.com/png-to-webp

  2. 使用插件自动化生成,例如 image-min-webp

一张420K的png图片,转为webp后,体积仅为24K,大约提升了94%。如果图片本身就比较小,就没有转webp的必要了。

兼容性:除了IE浏览器不支持外,其他浏览器支持度都还可以。这年头不会还有要支持IE的项目吧(手动狗头🤣)

3. 使用字体图标代替图片

使用阿里字体图标库

4. 图片懒加载

  1. 将图片的src属性设置到占位符上,例如data-src

  2. 通过 IntersectionObserver 判断图片是否进入可视区域

  3. 进入可视区后,将占位符替换为真实的图片地址

或者直接使用第三方库,例如vue的vue-lazyload、react的vanilla-lazyload

三、传输优化

1. 脚本延迟加载

defer:异步加载,能保证执行顺序。在DOM解析之后,DOMContentLoaded触发之前执行

async:异步加载,不能保证执行顺序。浏览器会尽快解析和执行

示例:在index.html中引入public文件夹下三个本地资源 echarts.min.js,china.js与go.js

js 复制代码
//index.html
<body>
  <div id="app"></div>
  <script type="module" src="/src/main.ts"></script>
  <script src="/static/echarts.min.js"></script>
  <script src="/static/china.js"></script>
  <script src="/static/go.js"></script>
</body>

使用Performance进行分析:

DOMContentLoaded用时439.13ms

现在给script标签增加异步加载标识,因为china.js依赖于echarts.min.js(需先加载完echarts.min.js),因此使用defer,go.js是个独立的第三方流程图模块,使用async

js 复制代码
<body>
  <div id="app"></div>
  <script type="module" src="/src/main.ts"></script>
  <script src="/static/echarts.min.js" defer></script>
  <script src="/static/china.js" defer></script>
  <script src="/static/go.js" async></script>
</body>

使用Performance进行分析:

DOMContentLoaded用时220.98ms,少了接近一半的时间,提升很明显

2. 资源预加载

preload:预加载,预先下载本页面关键资源

prefetch:预加载,在浏览器空闲时预先下载其他页面可能要用到的资源

优先级:preload > prefetch

示例:在index.html中引入element-plus的css资源

js 复制代码
<head>
  <link rel="stylesheet" href="//unpkg.com/element-plus@2.5.5/dist/index.css" />
</head>

使用Performance进行分析:

DOMContentLoaded用时354.69ms

现在给link标签增加预加载标识,因为element-plus的css资源在登录页就需要用到,因此使用preload。如果是其他页面的资源,可以使用prefetch。

js 复制代码
<head>
  <link rel="preload" href="//unpkg.com/element-plus@2.5.5/dist/index.css" as="style"/>
</head>

href:指定资源路径

as:指定资源类型

使用Performance进行分析:

DOMContentLoaded用时166.31ms,少了接近一半的时间,提升很明显

3. DNS预连接

DNS预连接在DNS预解析的基础上,提前与目标服务器建立TCP连接,从而进一步加速页面的加载速度。

场景:提前解析跨源资源地址

js 复制代码
<link rel="preconnect" href="https://example.com" />

4. 使用HTTP2

在HTTP1中,浏览器对于同一域名的请求数量是有限制的。例如同时请求6条,更多的http请求只能排队。而HTTP2引入了多路复用的技术,在同一TCP连接上并发多个请求和响应,很好的解决了这个问题

开启也很简单,以Nginx为例,在443端口后面添加http2即可

js 复制代码
// nginx.conf
listen 443 http2;

之后重启Nginx,在NetWork面板,可以看到接口的Protocol栏看到h2

5. 开启Gzip压缩

压缩文件体积,提高文件传输效率。gzip压缩通常能减少2/3的体积,例如echarts.min.js原741K,被压缩后只有184k。对于小文件,也能减少1/2的体积。

配置Nginx

js 复制代码
http{
  gzip on; # 是否开启gzip
  gzip_min_length  1k;# 开始压缩的最小长度(再小就不要压缩了,意义不在)
  gzip_buffers     4 16k; # 缓冲(压缩在内存中缓冲几块? 每块多大?)
  gzip_http_version 1.1;# 开始压缩的http协议版本(可以不设置,目前几乎全是1.1协议)
  gzip_comp_level 2;# 推荐6 压缩级别(级别越高,压的越小,越浪费CPU计算资源)
  gzip_types     text/plain application/javascript application/x-javascript text/javascript text/css application/xml;# 对哪些类型的文件用压缩 如txt,xml,html ,css
  gzip_vary on; # 是否传输gzip压缩标志
  gzip_proxied   expired no-cache no-store private auth; #Nginx做为反向代理的时候启用 例如如果header中包含"Expires"头信息,启用压缩
  gzip_disable   "MSIE [1-6]\."; #正则匹配UA,配置禁用gzip条件。此处表示ie6及以下不启用gzip(因为ie低版本不支持)
  gzip_static on; #开启后会寻找以.gz结尾的文件,直接返回,不会占用cpu进行压缩,如果找不到则不进行压缩
}

尽管Nginx已配置为在响应时压缩并返回Gzip格式的数据,但压缩操作会消耗服务器CPU资源和时间。压缩级别越高,资源消耗越大。因此,我们通常会上传已压缩的gzip文件,以便服务器直接返回,从而节省资源。Nginx配置:gzip_static on

构建时生成gzip文件

webpack

  1. 安装
js 复制代码
yarn add compression-webpack-plugin -D
  1. 修改webpack.prod.js
js 复制代码
const CompressionPlugin = require("compression-webpack-plugin");

module.exports = {
  plugins: [new CompressionPlugin()],
};

vite

  1. 安装
js 复制代码
yarn add vite-plugin-compression -D
  1. 修改vite.config.ts

默认情况下插件在开发 (serve) 和生产 (build) 模式中都会调用,使用 apply 属性指明它们仅在 'build' 或 'serve' 模式时调用

这里打包生成 .gz 插件仅需在打包时使用

js 复制代码
import viteCompression from 'vite-plugin-compression'

  plugins: [
    //...
    {
      ...viteCompression(),
      apply: 'build',
    },
  ],

6. 使用CDN

CDN 加速原理:通过在网络不同节点部署缓存服务器,就近返回资源给用户,以提高网站的访问速度

示例:引入echarts

在index.html中使用cdn引入

js 复制代码
<script src="https://cdn.bootcdn.net/ajax/libs/echarts/4.4.0/echarts.min.js"></script>

使用CDN之后,还需配置externals(webpack)、external(vite),告诉打包工具忽略对某些库的打包,因为这些库已经通过CDN链接在浏览器中直接引用了

webpack

修改webpack.common.js,配置externals

js 复制代码
module.exports = {
  //...
  //externals中的key是用于import,value表示在全局中访问到该对象
  externals: {
    'echarts': 'echarts'
  },
};

vite

修改vite.config.ts,配置external

js 复制代码
export default () => {
  return defineConfig({
    //...
    build: {
      rollupOptions: {
        //...
        external: ['echarts'],
      },
    },
  });
};

7. 服务端渲染(SSR)

服务器会预先生成完整的HTML页面,包括接口请求数据,然后将这个已经渲染好的页面直接返回给客户端。这样用户在首次访问页面时,可以直接看到已经渲染好的内容,而不需要等待客户端JavaScript代码加载和执行完毕后再进行渲染

熟悉Vue的,使用Nuxt

熟悉React的,使用Next

四、交互优化

1. 骨架屏

例如antd的官网首页,就使用了骨架屏

2. loading动画

在JS没解析执行前,让用户能看到Loading动画,减轻等待焦虑。

示例:vue使用加载进度条

1.安装

js 复制代码
yarn add nprogress

2.使用

js 复制代码
import NProgress from 'nprogress';

import 'nprogress/nprogress.css';

//...省略其他路由配置相关代码

NProgress.configure({ showSpinner: false }); //关闭加载旋转器

//在路由全局前置钩子中开始
router.beforeEach((to) => {
  NProgress.start();
});

//在路由全局后置钩子中结束
router.afterEach(() => {
  NProgress.done();
});

例如对我的demo项目进行Performance分析,如下图所示:

FP(First Paint):首屏绘制为794.02ms

DCL(DOMContentLoaded):HTML文档加载完成为1.57s

加载进度条在1s内就出现在了视野区,提升了用户的交互体验

示例:React使用旋转动画

js 复制代码
import { Spin } from 'antd';

const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render(
  <Suspense fallback={<Spin delay={1000} size="large" />}>
    <RouterProvider router={router} />
  </Suspense>,
);

结尾

文本涉及的内容较多,包含vite,webpack,rollup,vue,react等,可参考我以前的文章结合阅读:

  1. Vue2性能优化

  2. webpack5详细教程(5.68.0版本)

  3. vue-router(4.1.6)最新使用指南

  4. Rollup炼金术:打造NPM包提高开发效率!

  5. 性能优化篇--Performance(工具)

  6. Vite4.3+Typescript+Vue3+Pinia 最新搭建企业级前端项目

  7. Vite5.0+Typescript+React18+Zustand 最新搭建企业级前端项目

近期文章推荐:

  1. 2种纯前端检测版本更新提示

  2. [React 进阶] 掌握 React18 全部 Hooks

  3. [React 进阶] 掌握 React TypeScript

  4. [Vite 进阶] 配置环境变量(包含多工程、多环境配置)

  5. 微信小程序《入门级教程》

相关推荐
Martin -Tang32 分钟前
vite和webpack的区别
前端·webpack·node.js·vite
迷途小码农零零发33 分钟前
解锁微前端的优秀库
前端
王解1 小时前
webpack loader全解析,从入门到精通(10)
前端·webpack·node.js
我不当帕鲁谁当帕鲁2 小时前
arcgis for js实现FeatureLayer图层弹窗展示所有field字段
前端·javascript·arcgis
那一抹阳光多灿烂2 小时前
工程化实战内功修炼测试题
前端·javascript
放逐者-保持本心,方可放逐2 小时前
微信小程序=》基础=》常见问题=》性能总结
前端·微信小程序·小程序·前端框架
毋若成5 小时前
前端三大组件之CSS,三大选择器,游戏网页仿写
前端·css
红中马喽5 小时前
JS学习日记(webAPI—DOM)
开发语言·前端·javascript·笔记·vscode·学习
Black蜡笔小新6 小时前
网页直播/点播播放器EasyPlayer.js播放器OffscreenCanvas这个特性是否需要特殊的环境和硬件支持
前端·javascript·html
秦jh_6 小时前
【Linux】多线程(概念,控制)
linux·运维·前端