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

看了一下我司官网的webpack打包出来的大小情况,发现有很多可以优化的点,比如 lodash、moment.js、antd等等;
本文主要围绕webpack的打包优化,并根据业务情况适当的做减法。

优化前分析

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

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

// webpack.config.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
 
module.exports = {
  plugins: [
    new BundleAnalyzerPlugin()
  ]
}

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

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

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

这里是我的项目用analyzer生成出来的包大小情况(打包前)


image

主要看index.xxxx.js,它包含了所有的公共依赖,我们要做的就是减少不必要的公共资源的体积,可以减少大量不必要的代码。

逐个击破

分析antd

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

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

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

优化lodash

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

优化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生成出来的包大小情况


image

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

首屏加载视觉优化

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


image

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

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


image

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

总结

前端优化工作是一个长期且复杂的工作,有很多可以考虑的地方,可以根据网络环境、框架、用户群体、业务情况、代码结构等多个方面合理地安排选择优化方案,本文只是我对于现有公司官网的优化的一部分,在这里分享给大家,如果觉得有用就点个赞吧👍

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容