记前端项目首屏加载优化(打包篇)

记前端项目首屏加载优化(打包篇)

看了一下我司官网的webpack打包出来的大小状况,发现有不少能够优化的点,好比 lodash、moment.js、antd等等;
本文主要围绕webpack的打包优化,并根据业务状况适当的作减法。html

优化前分析

优化前必定要有一个界面能记录目前的打包状况,推荐用webpack-bundle-analyzer这个包, 它能够看到打包后每一个模块的大小,还能给出gizp压缩后的大小,在生产环境中加载的模块都是通过gzip压缩过的,能够做为真实访问的大小依据。
安装也很简单:前端

// cli
npm install --save-dev webpack-bundle-analyzer

注意生产环境(production)是表明线上真实的环境,因此analyzer要对生产环境的包进行分析的,因此我配置了一下本地打包生产环境的构建配置,在package.json加入下面的配置:react

"scripts": {
    ...
    "local_production": "cross-env NODE_ENV=local_production npm run build"
}

而后在webpack配置里面判断process.env.NODE_ENV === 'local_production',构建production环境的构建而且加入analyzer分析生产环境打包出来的状况。webpack

// webpack.config.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;


if(process.env.NODE_ENV === 'local_production') {
  webpack_config.plugins.push(
    new BundleAnalyzerPlugin(
      {
        analyzerMode: 'server',
        analyzerHost: '127.0.0.1',
        analyzerPort: 8889,
        reportFilename: 'report.html',
        defaultSizes: 'parsed',
        openAnalyzer: true,
        generateStatsFile: false,
        statsFilename: 'stats.json',
        statsOptions: null,
        logLevel: 'info'
      }
    )
  );
}

这里是个人项目用analyzer生成出来的包大小状况(打包前)
git

主要看index.xxxx.js,它包含了全部的公共依赖,咱们要作的就是减小没必要要的公共资源的体积,能够减小大量没必要要的代码。github

逐个击破

分析antd

从上面的能够看出来antd.less占了很大部分面积,由于我要在项目中自定义theme,可是官方的那套配置的形式来自定义theme只能修改变量,不能改组件,因此我先加载全部的antd.less再在后面接着加载一个theme.less用于修改主题变量和修改antd组件样式。web

  • 多是我当时搭项目的时候想太多了,因为是官网项目,全部的组件都是根据ui来本身写的,不多用到antd的组件,项目开发了几十个页面了也没有用到这种自定义组件的状况,因此其实能够不加载这个庞大的antd.less,而后antd按需加载是必须的。
  • 后来发现我项目中用到的antd组件只有两个(轮播和单选框),其实轮播是能够用react-slick替代的,而单选框更是能够本身实现的,因此大胆的直接把antd给移除掉了,用其余插件替代便可。

移除了antd以后index包小了三百多k,这还远远不够,接着看下面的优化点npm

优化lodash

lodash也是须要优化按需加载的方式的,推荐这篇教程Webpack按需打包Lodash的几种方式, 按照教程改进后,lodash 小了500多k。json

优化moment

其实moment引进来的时候会带有不少语言包的,咱们只用到了其中一个中文的包,因此其余语言包均可以去掉,网络

plugins: [
    new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /zh-cn/),
]

后来又发现项目中只用到了moment().format()这个方法,因为moment.js只有一个大的moment.js模块,没有按模块分开写,没法按需打包,那么其实咱们能够本身实现个简易版的moment来替代moment.js,下面是我找到的实现简易版moment代码:

// 简易版moment代替moment.js
class Moment {
  private date:Date;
  constructor(arg = new Date().getTime()) {
    this.date = new Date(arg);
  }
  padStart(num) {
    num = String(num);
    if (num.length < 2) {
      return '0' + num;
    } else {
      return num;
    }
  }
  unix() {
    return Math.round(this.date.getTime() / 1000);
  }
  static unix(timestamp) {
    return new Moment(timestamp * 1000);
  }
  format(formatStr) {
      const date = this.date;
      const year = date.getFullYear();
      const month = date.getMonth() + 1;
      const day = date.getDate();
      const week = date.getDay();
      const hour = date.getHours();
      const minute = date.getMinutes();
      const second = date.getSeconds();
      const weeks = ['一', '二', '三', '四', '五', '六', '日'];

      return formatStr.replace(/Y{2,4}|M{1,2}|D{1,2}|d{1,4}|h{1,2}|m{1,2}|s{1,2}/g, (match) => {
          switch (match) {
          case 'YY':
              return String(year).slice(-2);
          case 'YYY':
          case 'YYYY':
              return String(year);
          case 'M':
              return String(month);
          case 'MM':
              return this.padStart(month);
          case 'D':
              return String(day);
          case 'DD':
              return this.padStart(day);
          case 'd':
              return String(week);
          case 'dd':
              return weeks[week];
          case 'ddd':
              return '周' + weeks[week];
          case 'dddd':
              return '星期' + weeks[week];
          case 'h':
              return String(hour);
          case 'hh':
              return this.padStart(hour);
          case 'm':
              return String(minute);
          case 'mm':
              return this.padStart(minute);
          case 's':
              return String(second);
          case 'ss':
              return this.padStart(second);
          default:
              return match;
          }
      });
  }
}

export const moment = (arg) => {
  return new Moment(arg);
};

这样就直接能够把moment.js 干掉了,包体积又小了很多。

下面是优化后的analyzer生成出来的包大小状况

包体从2.7M优化到了1.7M,gzip从297k减少到212k,访问虽然只是快了一点点,但在低网速环境下访问仍是看获得区别的。

首屏加载视觉优化

接下来说一个跟包大小无关又很重要的优化点,就是单页应用的第一个入口html,正常状况下入口html只是用来加载js包,等js加载完以后才渲染出相关界面出来,这个入口html自己没有内容展现,但它是整个网站的第一个请求,取到这个入口html以后才开始加载js,等到加载完js才开始渲染界面,这段时间是占网站总体加载时间最多的,以下图:

第一个请求只要128ms,直到加载完公共js渲染出界面须要1s左右,这时候若是入口index没内容的话那就是纯粹的白屏时间了,因此咱们应该好好利用这个入口index.html,能够作一个骨架屏或者loading动画,能让用户在等白屏时间里可以有个界面能看到,停留时间会更长一些,也能让用户觉得这个网站一下就刷出来看到东西的感受。

对于这个入口index的利用,我是加入了顶部导航栏进去的,让用户能够第一眼看到导航栏知道有什么导航项,并且也是能够点进去的,而内容区对于不一样的路径访问会有不一样的界面,因此我就简单的弄个loading便可。

至此,这一版优化减小了加载的时间,同时合理利用了入口index做为loading页,提升用户体验。

总结

前端优化工做是一个长期且复杂的工做,有不少能够考虑的地方,能够根据网络环境、框架、用户群体、业务状况、代码结构等多个方面合理地安排选择优化方案,本文只是我对于现有公司官网的优化的一部分,在这里分享给你们,若是以为有用就点个赞吧👍

相关文章
相关标签/搜索