前端优化首屏加载时间

我接手vue2的一个旧项目,因为首屏加载很慢,要求我对此进行优化。我在此进行记录

一、分析加载慢的原因

推荐使用webpack-bundle-analyzer插件,可以对打包后的文件大小进行可视化分析。简单好用

  1. 安装插件

    1
    npm install webpack-bundle-analyzer --save-dev
  2. vue.config.js文件中添加对应配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    // 引入插件
    const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin

    module.exports = {
    publicPath: '/',
    lintOnSave: true,
    productionSourceMap: false,
    filenameHashing: true,
    configureWebpack: (config) => {
    const plugins = []
    // 配置BundleAnalyzerPlugin
    plugins.push(
    new BundleAnalyzerPlugin({
    analyzerMode: 'server',
    analyzerHost: '127.0.0.1',
    analyzerPort: 8888, // 运行后的端口号
    reportFilename: 'report.html',
    defaultSizes: 'parsed',
    openAnalyzer: true,
    generateStatsFile: false,
    statsFilename: 'stats.json',
    statsOptions: null,
    logLevel: 'info',
    })
    )
    return { plugins }
    }
    }

    配置完成后执行npm run build则自动打开页面展示文件大小。如下图:
    请添加图片描述

可以看到优化前node_modules模块占据了整个页面的将近70%大小(页面中占据面积越大的,也就是打包后体积越大的),其中显眼的插件有tinymcexlsxavue.min.jstheme.jsswiper.jshtml2canvas.js后面优化只要对症下药即可。优化掉对应的插件。网上提供的思路是使用cdn进行优化。下面进行详细记录

二、使用CDN进行优化

使用cdn优化即需要使用外链引入对应插件,那么就需要在index.html文件中添加对应的引入链接。然后需要在打包的时候把通过外链引入的插件排除在外,不进行打包。这里推荐使用这个网站搜索你要的插件,进行CDN外链引入
https://www.jsdelivr.com/

  1. index.html引入外链

    index.html文件中添加如下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <!-- 使用CDN的CSS文件 -->
    <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %>
    <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="preload" as="style" />
    <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="stylesheet" />
    <% } %>
    <!-- 使用CDN加速的JS文件,配置在vue.config.js下 -->
    <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
    <script type="text/javascript" src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
    <% } %>
  2. 配置不打包对应的插件

    vue.config.js文件中添加以下配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    // 配置不打包的模块
    const externals = {
    // 左边是引入的参数,例如 import Vue from 'vue' 这里面的 'vue'
    // 右边是CDN模块的导出对象字段
    vue: 'Vue',
    'vue-router': 'VueRouter',
    vuex: 'Vuex',
    axios: 'axios',
    '@smallwei/avue': 'AVUE',
    'element-ui': 'ELEMENT',
    xlsx: 'XLSX',
    tinymce: 'tinymce',
    html2canvas: 'html2canvas',
    echarts: 'echarts',
    }

    // CDN外链,会插入到index.html中
    const cdn = {
    // 开发环境
    dev: {
    css: [],
    js: [],
    },
    // 生产环境
    build: {
    css: [],
    js: [
    // cdn.jsdelivr.net域名下的cdn文件
    'https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.min.js',
    'https://cdn.jsdelivr.net/npm/vue-router@3.1.3/dist/vue-router.min.js',
    'https://cdn.jsdelivr.net/npm/vuex@3.2.0/dist/vuex.min.js',
    'https://cdn.jsdelivr.net/npm/axios@0.19.0/dist/axios.min.js',
    'https://cdn.jsdelivr.net/npm/element-ui@2.15.6/lib/index.js',
    'https://cdn.jsdelivr.net/npm/@smallwei/avue@2.6.18/lib/avue.min.js',
    'https://cdn.jsdelivr.net/npm/xlsx@0.17.5/xlsx.js',
    'https://cdn.jsdelivr.net/npm/tinymce@5.10.2/tinymce.min.js',
    'https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.js',
    'https://cdn.jsdelivr.net/npm/echarts@4.8.0/dist/echarts.min.js'
    ],
    },
    }

    module.exports = {
    publicPath: publicPath,
    lintOnSave: true,
    productionSourceMap: false,
    filenameHashing: true,
    configureWebpack: (config) => {
    const plugins = []
    // 配置BundleAnalyzerPlugin
    plugins.push(
    new BundleAnalyzerPlugin({
    analyzerMode: 'server',
    analyzerHost: '127.0.0.1',
    analyzerPort: 8888, // 运行后的端口号
    reportFilename: 'report.html',
    defaultSizes: 'parsed',
    openAnalyzer: true,
    generateStatsFile: false,
    statsFilename: 'stats.json',
    statsOptions: null,
    logLevel: 'info',
    })
    )
    if (process.env.NODE_ENV === 'production') {
    // externals里的模块不打包
    config.externals = externals
    }
    return { plugins }
    },
    chainWebpack: (config) => {
    // 添加自定义参数cdn
    config.plugin('html').tap((args) => {
    if (process.env.NODE_ENV === 'production') {
    args[0].cdn = cdn.build
    } else {
    args[0].cdn = cdn.dev
    }
    return args
    })
    },
    }

    完成以上配置后,再运行npm run build得出以下结果:
    请添加图片描述

可以看到优化前node_modules模块打包后占了12.48M,优化后nodee_modules模块打包后占了4.02M。

首屏加载的时间从原来的5s变成了3s,还有很多插件也可以使用cdn进行优化,只需要将上图占面积大的插件进行优化即可。另外,由于老项目,经手了很多人,太多重复性代码,所以上图左边views模块占了很大空间。漫漫优化路,无穷无尽。

/————————————————————– 分割线 ————————————————————————/

三、路由懒加载

按照上面的优化思路实现后发现还是不够快,需要更进一步优化。分析其原因,发现在加载首页的时候将打包后的所有的js文件,css文件都引入了。这就会导致花费了大量时间加载不必要的文件。欸,没错。这时就需要使用路由懒加载进行拆分,只加载当前页面需要的js和css文件,减少不必要的文件请求。

网上可以查到多种不同的懒加载方式,这里使用官方介绍的把组件按组分块来进行路由懒加载

1
const UserDashboard = () => import(/* webpackChunkName: "group-user" */ './UserDashboard.vue')

配置不同的webpackChunkName就可以将对应的组件打包进对应文件中,轻松实现分块打包。

原以为这就结束了,但是打包后一看index.html文件,发现事情不简单
请添加图片描述

打包确实分块了,但是入口文件还是将所有的js和css文件都引入了,这是为什么?

原因是vue-cli3 默认会把所有通过import()按需加载的javascript文件加上 prefetch,也就是上图中link标签后面的属性rel=prefetch

prefetch链接会消耗宽带,因此我们要将prefetch手动关掉,使入口文件只有必要的js和css文件。

关闭prefetch代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// vue.config.js
module.exports = {
chainWebpack: config => {
// 移除 prefetch 插件
config.plugins.delete('prefetch')
// 或者
// 修改它的选项:
config.plugin('prefetch').tap(options => {
options[0].fileBlacklist = options[0].fileBlacklist || []
options[0].fileBlacklist.push(/myasyncRoute(.)+?\.js$/)
return options
})
}
}

再进行打包,观察入口文件结果如下:
请添加图片描述

完美!至此完成路由懒加载~

四、插件按需引入

这一块不用多说,像ElementUIecharts等等,体积较大的插件,如果只用到其中的几个模块但全局引入了,会导致文件过大。

  1. echarts的按需引入直接参照官网即可:
    https://echarts.apache.org/handbook/zh/basics/import

  2. elementUI按需引入过程中,配置中会存在一点小问题。
    如果脚手架比较新没有 .babelrc文件,则修改babel.config.js文件,添加配置项:

1
2
3
4
5
6
7
8
9
10
11
12
{
"presets": [["es2015", { "modules": false }]], //如果配置后报错,把这里的数组项改成右边的即可 ["@babel/preset-env", { "modules": false }]
"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}

还有一个小问题就是element的隐藏组件scrollBar并没有对外暴露,这个组件无法按需引入。

五、压缩图片

至此首页快了非常多,观察首页的所有请求,发现还有两张图片请求较大,便将图片进行压缩(原来的我以为图片压缩会导致像素变低,直到我在网上尝试了压缩图片,压缩前后的观感完全一样,但是文件大小直接降低了近70%。不过大部分压缩图片的网站是需要会员或者收费的)

下面是我常用的一个免费的压缩图片的网站:

https://kt.fkw.com/yasuo.html?_ta=8780

压缩完图片进行替换,请求再次变小,速度再次提升。

最后

最后如果服务器有nginx代理的话,可以配置gzip压缩传输,速度会更快,但是这里服务器没有使用nginx代理,故不能配置gzip了,不然我想这个网站肯定可以更快