如何显著的提升前端加载性能

3,041 阅读12分钟

本文代码基于vue项目,代码源自vue不同环境打包命令配置,但优化方案适用于所有现代化前端框架

关于前端性能优化是个历史非常非常悠久的话题,百度搜一下前端性能优化,基本上都是在教我们如何提升执行性能,比如说尽量的减少DOM操作,js文件放在html的最底部,第三方文件使用CDN方式加载。但在现在Vue、react等现代化框架大行其道的时代下,我们第三方库基本上都是npm install XXXX安装到项目内了,这就使得我们的项目越来越大,哪怕使用Webpack压缩打包后,也会有十几兆的大小(实际大小根据来说,不做优化的情况下,项目越大,使用的第三方库越多,打包后的项目也越大),但我们服务器的带宽却不一定非常大,尤其是像Vue、React这样的单页面应用(本文讨论的以页面应用为主,它们虽然可以配置多页面,但作者对多页面不熟悉,本文不做讨论),如果我们的第三方库在都挂载到全局,就会造成主包文件非常大,首页加载超级慢。

所以我们本文就开始尝试将项目体积减小,提升首屏加载速度。

一,使用CDN加载第三方库

在现代化框架中我们使用第三方UI基本都是使用npm install的方式,在vue中如果一个UI库中的组件我们大量使用,我们会将其在main.js中全局引用,这样使用是没有问题的,但当项目进行打包的时候,UI库也会被打包进项目,使得项目包变得非常大,以iView来说,当我们不使用iView的时候我们的项目包打包出来是这个样子的。

我项目是在本地部的所以加载速度可参考性不大,但我们应该可以理解你的资源越大加载速度就越慢。

然后我们安装使用iView之后看一下有什么变化;

首先可以看到打包的时候先给我们报了警告,告诉我们文件过大可能影响性能。然后打包后的文件体积明显变得更大了,加载速度也更慢了,如果你自己做实验,你会发现打包速度也变慢了,而且这只是我们在全局只使用了iView,实际开发中我们可能还会使用moment、axios、jscookies、echarts等各种各样的可能需要在全局使用的第三方工具,那么项目越来越大,不做其他处理的情况下,性能只会越来越差,很明显的感受就是首页渲染超级慢。

所以我们开始进行优化,减小我们项目体积,提高资源加载速度。

1-1、通过CDN引入iView

首先由于我们的项目是单页面应用,所以我们在项目的html文件内通过CDN引入iView-UI库。iView的CDN传送门

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <!-- S 引入iView样式-->
    <link rel="stylesheet" href="//unpkg.com/iview/dist/styles/iview.css">
    <!-- E 引入iView样式-->
    <title>myproject</title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but myproject doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- S 引入VueJs和iView脚本-->
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    <script src="//unpkg.com/iview/dist/iview.min.js"></script>
    <!-- E 引入VueJs和iView脚本-->
  </body>
</html>

看到这里,可能你会想,不是引入iview吗,为什么把vue也用cdn的方式引入了?这个答案呢,你猜下,然后对比下去掉vue和加上vue有什么区别,然后再思考下,我想你可以猜到答案。

1-2、配置CDN使用

在项目根目录下新建一个vue.config.js文件,这个文件vue给我们提供的,我们可以在文件内定义一些webpack配置,代码如下vue.config.js配置指南

module.exports = {
  configureWebpack: {
    externals: {
      iview: 'iview', // '包名':'全局变量' 
   },
  }
}

如果是vue-cli 2.X创建的项目,可以在/build/webpack.base.conf.js文件内这么写入;webpackConfig对象是vue帮你已经定义好了,你只需要加入externals配置就行,千万别去又建个webpackConfig对象。

const webpackConfig = {
  externals: {
      iview: 'iview', // '包名':'全局变量' 
   },
}

如果是其他项目,并且使用了webpack,那么请找到项目的webpack配置文件,然后在配置对象内加入以下代码。webpack关于externals配置项的文档

externals: {
  iview: 'iview', // '包名':'全局变量' 
}

1-3、使用

main.js内这么引用

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import iView from 'iview';

Vue.config.productionTip = false
Vue.use(iView);

new Vue({
  router,
  render: h => h(App)
}).$mount('#app')

然后你就可以在项目中正常使用,使用方式和npm install安装的没有任何区别,请放心使用。

1-4、对比

然后我们继续进行打个包,然后部署,对比npm install安装在项目内有什么区别;

然后我们发现,咦!!!打包后的文件确实变小了,但是怎么加载速度还变慢了,尤其是iview相关的,加载速度超级慢啊,iview.min.js居然花费了1.14秒才加载完成。

这里就直接说答案了,由于iview的CDN是unpkg,如果在大陆访问的话,访问的节点是香港,所以呢,我们更换下iview的cdn地址,实际开发中各家公司应该是有自己的CDN的,所以可以直接把第三方资源放在自家CDN上,我这里换成了jsDelivr,然后对比一下;

可以明显看到这加载速度直接快了一半还多啊。

1-5 总结

所以我们就可以在实际项目开发过程直接使用这种方式来使用第三方资源,速度提升是可以用眼睛看到的,唰!唰!唰!另外我们在1-1中已经通过CDN的方式引用vue了,那我们可不可以在项目内npm uninstall vue移除vue,然后采用CDN方式使用呢,试一下呗!!

二,使用CDN加载项目静态资源

我们进行项目开发的时候不可避免地会使用到图片,字体等文件,当我们进行项目打包的时候,或者被打包成base64或者打包进静态资源目录,不可避免都在项目内,最后都上传到服务器上了,这样图片加载的时候,肯定会影响其他资源加载速度,造成首页渲染速度变慢;当然我们也可以把图片资源上传到CDN上,然后复制下地址在项目中使用,但是这样我们每使用一张就需要上传一次,严重影响开发效率;

但是我们如果可以在项目开发的时候从项目内加载图片,然后打包的时候从CDN上来加载图片,这样不就可以解决问题了。

1-1 首先图片打包进项目

强烈建议在服务器作此操作对比,我这里是在本地用PHPStudy模拟起的服务,资源由于在本地,所以会发现从CDN加载资源没有从本地快;在实际开发中,由于服务器性能带宽等各种因素影响,所以从CDN加载资源会非常明显的带来性能提升

我们先来看一下我们不使用CDN,把图片放在项目内,会有多大的影响。我在项目内引入20张图片,每个页面加载几张,先来截个图看看。

首先是我这几个图片就比较大,所以打的包也就很大,实际项目中一般不会有一个图片八九兆的情况,但如果项目中用到的图片资源多,那么包也会比较大。

1-2 图片通过CDN方式加载

我们希望开发环境用本地图片,生产环境用CDN资源,那么我们这里就需要用到Webpack的一个loader,url-loader,这是webpack推荐的一个loader,在vue、react等各种框架中也是使用频率非常高的一个loader,如果不准备很深入研究的话,看Webpack提供的这个文档就够了。

  1. 首先在vue.config.js中加入如下配置
module.exports = {
  configureWebpack: {
    externals: {
      iview: 'iview', // '包名':'全局变量' 
    },
  },
  // 这里是新加的
  chainWebpack: config => {
    config.module
      .rule('images')
      .use('url-loader')
        .options({
          limit: 10240,
          publicPath: process.env.NODE_ENV === 'development' ? '' : 'http://static.jindll.com/img',
          outputPath: 'img',
          name: '[name].[ext]',
        })
        .end()
  }
}
  1. 然后你就可以在项目正常使用你的图片了,无论是../的相对路径还是@/XXX都可以,开发环境都是用的是本地的图片资源,其他环境(测试环境或者生产环境或者预发布环境等等),图片路径会被替换成publicPath所设置的路径加上你的图片名字,以我上面的配置来说,假如我在项目中使用了@/assets/img/logo.png图片,那么打包之后路径就会是http://static.jindll.com/img/logo.png还有一个需要说,你打包之后图片不会自动就上传到CDN上,打包之后,在生成的dist文件夹下,会有一个img文件夹,这个文件夹内的图片就是你项目所使用的图片,然后将这个文件夹内的图片自行上传到CDN上就行,但是CDN的图片路径前缀一定要和publicPath设置的路径保持一致;为什么打包之后图片会被放进img文件夹,这个是因为我们通过outputPath属性设置的输出路径,如果你想改成其他的名称都随意;

  2. 对之上的配置做下解释,我之前有说过我们需要使用url-loader但是我并没有npm install安装url-loader,这是因为vue-cli内部使用了url-loader,所以我们可以通过vue-cli提供的chainWebpack属性对内部的webpack配置做修改;vue-cli对chainWebpack这个配置解释的并不多,但告诉了我们chainWebpack接收一个基于 webpack-chain 的 ChainableConfig实例,所以更多的使用或者api我们其实可以查看webpack-chain官方文档

    根据webpack-chain文档rule方法表示的是这个规则的名字,也就是任意的字符串,Vue-Cli内部对其定义的名字是images(仅针对url-loader),我们这里是修改内部配置,所以用这个名字来取对应的配置来做修改;use方法表示要使用的loader名字(如果使用plugin就是plugin名字),limit表示只有文件大于这个值的时候才会被打包进img文件夹,小于这个值文件将被处理成base64,name表示的是文件名字,它的值我设置的是[name].[ext],表示的是打包的文件名就用原名称,后缀也不改变,这个是占位符,其他的占位符可以看file-loader文档url-loader和file-loader作用相同,配置url-loader比file-loader多了limit配置,file-loader相当于打包的时候将文件进行移动,url-loader是将文件处理成base64,其他基本没有区别了

  3. 补充内容,在使用使用CDN加载第三方库中,我使用了configureWebpack配置,在使用CDN加载项目静态资源中我使用了chainWebpack配置,在Vue项目中,如果我们想要对webpack做修改,那么这两个配置就是很重要的配置,它们都可以帮助我们完成webpack修改;那这两个有什么区别呢?

    • configureWebpack可以接受一个对象,在这个对象内部我们可以写原生的webpack配置;简单说它就是加新的配置,但不能改已有的配置,但使用方便;
    • chainWebpack是一个函数,我们可以在这个函数中对Vue-Cli内部的webpack配置做修改;简单说它就是改已有的配置,也可以加新的配置,但需要看下webpack-chain文档才可以更好的使用

三、减少在全局挂载内容

这部分内容之后会在其他文章做更多描述

再通过CDN加载第三方库和静态资源通过CDN加载之后,我们项目体积已经可以大幅度的减小了,但在实际项目开发中,由于业务需要我们自己会写非常多的工具函数,比如后端返回的是state码,而不是翻译好的文字,我们需要封装一些方法来对这些状态码做处理,但这些工具函数的使用频率可能并不是非常高,有可能仅在项目的这个功能模块内用,其他功能模块并不使用;或者身份证校验等这些类似的方法,他们的使用频率可能也就五六各地方会用,但在我所遇到的项目中,非常多的开发人员是单独建立一个文件,文件内封装这些方法,最后将这个文件挂载到全局使用;这样会出现的问题就是,当项目打包的时候,这些方法会被打进主包内(vue项目是app.hash.js),这会增大主包的体积,无形中就造成了首屏渲染速度变慢;

所以对于这种场景,建议就是如果某几个方法仅在某一个功能模块中使用,那就给这个功能模块单独建个tool.js,然后在需要的地方引入具体的方法就好。

减少在全局挂载内容这部分暂时没有代码,这部分的内容之后也会在其他文章重点描述。

四、其他

。。。。。。。。。。。。。。。。。。。想到再更。。。。。。。。。。。。。。。。。