前端白屏/首屏优化

1,919 阅读4分钟

白屏优化

  1. 路由懒加载

    {
        path: '/login',
        component: () => import('@aicc/components/login/index')
    }
    
  2. 使用v-if代替v-show

    好处是可以减少首屏渲染dom数量(除非是一些会频繁变换显隐的元素,这样的元素应该用v-show)

  3. 不直接显示的组件做懒加载

    常见的场景是:el-tabs组件内从第二个tab开始渲染的组件都可以懒加载,目的是减少根据此路由完成首屏渲染时需加载的代码量

    比如vue项目内的

     import pageComponent from './pageComponent'
    
     export default {
       name: 'Recharge',
       components: {
         pageComponent,
       },
     ...
    

    改成

    export default {
       name: 'Recharge',
       components: {
         pageComponent: () => import('./pageComponent'),
       },
     ...
    
  4. 使用resolutions统一依赖版本

    假设主项目依赖包A@1.1.0,主项目依赖的包B依赖A@1.1.1,此时yarn会在node_modules根路径和node_modules/B/node_modules分别安装A的两个版本,并都会打包到最终bundle。此时可以在package.json中使用resolutions强行统一A的版本

    "resolutions": {
         "a": "1.1.1"
     }
    
  5. 组件库按需加载

    包括项目使用的第三方组件库、公司内部组件库、项目components目录内的组件 这些都可以使用babel-plugin-import插件实现,babel.config.js如下:

    const babelPluginImportForComponents = [
       'import',
       {
         libraryName: '@/components',
         libraryDirectory: '',
         camel2DashComponentName: false,
         customName: name => {
           return `@/components/${name}`
         }
       }
     ]
    
     module.exports = {
       ...
       'plugins': [
         ...
         babelPluginImportForComponents
       ]
     }
    
  6. 资源预加载

    场景比如:使用element的项目打包出来的css内会通过@font-face去加载字体,也就是说字体会等css加载完并开始解析了再去加载,这样我们可以预加载字体,无需等待css

    这个一般是处理字体资源:<link rel="preload" href="/ai-engine/static/fonts/element-icons.woff" as="font" type="font/woff" crossorigin="anonymous">或者其他任何首屏必须加载,却要等待其他资源先加载的资源

  7. defer

    一些对渲染影响不大的js资源可以在script上加defer,这样这些js不会阻塞渲染也不影响加载,比如iconfont的彩色字体js文件

  8. CDN

    部分静态资源可以放CDN上,好处是请求这些资源时不会带本网站的cookie,减少流量。一些优秀的CDN也会加快资源加载速度,比如七牛、ali

  9. 减少css加载

    原因是css的加载会阻塞dom的渲染,因为浏览器去渲染dom会先构建两棵树:dom树和css规则树,然后再做渲染。 所以应该只加载首屏需要的css,并且最好先通过<style>内置到index.html

  10. gzip

    打包的时候就打成gzip包,不需要服务器再去做压缩

  11. 静态文件缓存

    目前前端资源一般通过hash区分版本,如果hash没变,可以一直缓存,在服务端设置相应的缓存策略

  12. 服务端渲染/预渲染

这个可以说是终极杀器,因为访问url直接就请求下来一段html和样式,可以直接渲染出来,其他的资源非阻塞式加载就好了。可以说使用此方案会减少很多优化工作量

首屏优化

其实上面的方案也都是首屏优化方案,这里重在强调白屏结束后的处理

  1. 骨架屏/loading动效

    假如首屏上有些内容要通过接口请求数据再渲染,可以先渲染出骨架屏或者loading效果,让用户觉得页面是在加载的

  2. 后端数据层缓存

    一些频繁查询的数据可以设置缓存,比如淘宝首页商品列表

  3. 图片懒加载(可以配合骨架屏方案)

  4. 图片压缩

  5. 去除或缓存重复请求

    tinyPng

-------------------22/4/7更新---------------------

最近发现一种方式,虚拟代理。对于一些占用系统资源较多或者加载时间较长的对象比如echart,可以给这些对象提供一个虚拟代理。在真实对象创建成功之前虚拟代理扮演真实对象的替身,而当真实对象创建之后,虚拟代理将用户的请求转发给真实对象,比如echart懒加载:

// 用于处理echart懒加载
let handler
let echartFactory
const echart = {
  init: (...args) => {
    let container = {
      // initFlag标志着echart是否已初始化,false代表未初始化,true代表已初始化
      initFlag: false,
      // 记录在未初始化完之前,主函数中要执行的方法都会存放在fns里面
      fns: [],
      // 待初始化的实例,在加载后所有方法都会在在echart中实现
      echart: undefined
    }
    echartFactory.create(args).then(echart => {
      container.echart = echart
      container.fns.forEach(({ fn, args }) => {
        if (typeof echart[fn] === 'function') echart[fn].apply(echart, args)
      })
      container.initFlag = true
    })
    return new Proxy(container, handler)
  }
}

handler = {
  get: function(target, key) {
    if (!target.initFlag) {
      // container中的echart未加载时,收集要执行的程序的名称和参数以对象的形式存放在fns中
      return (...args) => {
        target.fns.push({
          fn: key,
          args
        })
      }
    } else {
      // 当echarts初始化完成后,直接返回方法
      if (key in target.echart) {
        return target.echart[key]
      } else {
        return undefined
      }
    }
  }
}

echartFactory = {
  // 存放从外部加载的echarts模块
  _echart: undefined,
  create: async function(args) {
    if (!this._echart) {
      import('@aicc/utils/macarons.js')
      this._echart = await import(/* webpackChunkName: "echarts" */'echarts')
    }
    return this._echart.init.apply(undefined, args)
  }
}

export default echart

-------------------22/4/28更新---------------------

请求合并

如果是通过nginx启动服务,可以使用nginx-http-concat模块做请求合并; 如果是通过node起的服务,可以通过识别路由参数后,动态获取文件,拼接内容后返回的方式合并请求