这样优化前端项目的加载速度,老大看了都对你连连称赞

1,047 阅读4分钟

一、借助工具实现代码拆包、压缩

拆包工具有两个: rollup-plugin-visualizervite-plugin-chunk-split

其中,rollup-plugin-visualizer 是开启打包分析的,我们需要借助打包分析图去规划如何拆包。vite-plugin-chunk-split 是拆包用的,我们通过他实现打包拆包

vite 本身支持拆包配置的,这里借助成熟的第三方插件实现简易配置、快速上手

压缩工具为 vite-plugin-compression。值得注意的是,如果开启了打包压缩,需要服务端配合调整配置的,一般来说服务端常用的 nginx 默认会有开启压缩文件解析的,如果开启了压缩部署后访问失败,问问后端小哥看他是否开启了服务器解析压缩文件配置。

import { visualizer } from "rollup-plugin-visualizer";
import { chunkSplitPlugin } from 'vite-plugin-chunk-split';
import viteCompression from 'vite-plugin-compression';


export default defineConfig(() => ({
    plugins: [
          // 开启依赖包分析
          visualizer({
            gzipSize: true,
            brotliSize: true,
            emitFile: false,
            filename: "bundle.html", //分析图生成的文件名
            open: false //如果存在本地服务端口,将在打包后自动展示
          }),
          // 开启拆包
          chunkSplitPlugin({
            // 指定拆包策略。拆包后,资源会被无差别预加载。
            customSplitting: {
              'react-virtualized': [/react-virtualized/],
              'xlsx': [/xlsx/],
              'recoil': [/recoil/],
              'lodash': [/lodash/],
              'ahook': [/ahook/],
              'antd': [/antd/],
              'echarts':[/echarts/],
            },
          }),
          // 开启压缩。
          viteCompression({
              verbose: true, // 是否在控制台输出压缩结果
              disable: false,
              threshold: 10240, // 体积大于 threshold 才会被压缩,单位 b
              algorithm: 'gzip', //压缩算法,可选 [ 'gzip' , 'brotliCompress' ,'deflate' , 'deflateRaw']
              ext: '.gz', // 生成的压缩包后缀
              deleteOriginFile: true //是否删除源文件
          })
    ]
}))

二、懒加载资源预取

我们都知道 react 是支持懒加载的。但它有个缺点,开启了懒加载的组件,他只有被动唤醒。啥意思呢,就是如果某个组件被懒加载了,只有用户在应用里主动访问了这个组件所在的场景,对应的组件资源才会被加载进来。这很合常理,但我们今天还去探索一个场景,就是能不能主动的在代码里手动唤醒,先预测一波用户的行为,比用户更先一步把懒加载的组件资源加载进来,用户等待资源加载的时间更短了,从而提升应用使用体验。

这个工具是 Loadable Components

image.png

为了实现上面的目的,我们需要利用 loadable 的一个钩子:preload

import loadable from '@loadable/component'
const Infos = loadable(() => import('./Infos'))

function App() {
  const [show, setShow] = useState(false)
  return (
    <div>
      <a onMouseOver={() => Infos.preload()} onClick={() => setShow(true)}>
        Show Infos
      </a>
      {show && <Infos />}
    </div>
  )
}

有了这个功能,我们便可以更加主动地控制那些需要精确提升用户体验的场景。比如,在路由系统里将组件统一使用 loadalbe 的导入,然后封装一个函数,在需要的地方通过调用这个函数对某些页面级组件进行提前资源预取。

import { lazy } from '@loadable/component'

// 需要预取的路由导入统一放在这
const Home = lazy(() => import('@/pages/home'))
const User = lazy(() => import('@/pages/user'))

export const preloadCop = (name: string) => {
    switch (name) {
        case '': {
            /** todo: 某些分裂下的组件资源预取 */
            break;
        }
    }
}

三、首屏样式资源速度优化

我们要知道,样式资源的加载是会阻塞页面的渲染的。这样就会存在一个什么情况呢,假设我们要非常高精确地提升用户体验,在样式资源文件没回来之前也不能让用户看到空白页,那么我们在模板HTML文件里写的默认 loading 效果也是无法被渲染的,需要等到首屏所有的样式资源文件加载完成后才能正常渲染。

现在常规的 SPA 应用尽管拆包后仍会存在一个蛮大的入口文件,而用户的网络情况我们是无法控制的,内容分发和缓存策略需要后端进行配合,纯前端能做的只有是缩小资源文件的加载体量和控制资源文件的加载方式和加载时机。

既然资源文件会阻塞,那我们让它变成不是资源文件不就好了。 我们通过写一个插件,将打包后的首屏css文件换成 meta 标签。

function previewPlugin(): Plugin {
  return {
    name: 'vite-plugin-preload-css',
    transformIndexHtml(html) {
      return html.replace(
        /<link rel="stylesheet" href="(.+\.css)">/g,
        '<meta name="base-css" content="$1" />'
      );
    }
  };
}

export default defineConfig(() => ({
    plugins: [
        // 首屏预渲染
        previewPlugin(),
    ]
}))

然后在 模板HTML 里监听 DOMContentLoaded 事件,再将其转换回来。

<script>
    document.addEventListener('DOMContentLoaded', function () {
      // debugger
      const cssMeta = document.querySelector('meta[name="base-css"]')
      if (!cssMeta) return
      const cssLink = cssMeta.content;
      const styleSheet = document.createElement("link");
      styleSheet.rel = "styleSheet";
      styleSheet.href = cssLink;
      document.head.appendChild(styleSheet);
      cssMeta.remove()
    })
</script>

这样就不会因为外部资源的加载阻塞我们在模板HTML上写的静态loading效果了。

其他

在第一点的代码分包后,对应的资源会变成使用modulepreload来无差别异步加载。这和传统的script异步加载方式有以下不同:

  • modulepreload是一种预加载模块的方式,它可以让浏览器提前加载代码,并平行执行,缩短加载时间。 和 preload 的区别是,它只是针对 JS 模块化的应用场景。
  • modulepreload只会预加载代码,不会执行代码。代码会在import时才执行。
  • modulepreload强制异步加载,脚本之间不会阻塞。
  • modulepreload需要浏览器支持ES6模块和preload功能。

具体来说,这样拆包后,其资源会在首屏时一起预加载

所以我们要权衡好拆分的颗粒度,以免适得其反。