Vue SPA初次加载的性能优化方法

829 阅读2分钟

优化缘由

我们通过vue-cli构建vue项目如果通过npm run build直接打包后的项目体积会比较大,尤其是生成的vendor.js以及app.css文件体积大至以MB作为单位。对于初次加载页面(浏览器没有缓存)的用户而言,加载页面的空档白屏时间长,体验极差!

优化方法

  1. 代码精简压缩 ==> 减少打包产物的体积
  2. 图片资源压缩 ==> 减少打包产物的体积
  3. 去除SourceMap ==> 减少打包产物的体积
  4. CDN引入替代import ==> 将打包后的vendor.js划分多块。
  5. 路由懒加载 ==> 减少初次加载的组件
  6. app.css拆分(TODO)

1. 代码精简压缩

  • 尽可能精简每个组件的体积,减少无意义/重复的第三方库的引入@import,减少console.log代码。
  • 在上线时通过工具将代码压缩成一行,去除所有的空格及注释。

方案1:去除console

在Webpack中的配置项中可以设置是否去除控制台输出。位于webpack.prod.conf.js中约38行位置,在UglifyJsPlugin-uglifyOptions-comporess中配置drop_console

image.png

文件的大致结构如下:

const webpackConfig = merge(baseWebpackConfig, {
  module: {
      ...
  },
  devtool: config.build.productionSourceMap ? config.build.devtool : false,
  output: {
        ...
  },
  plugins: [
    // http://vuejs.github.io/vue-loader/en/workflow/production.html
    new webpack.DefinePlugin({
      'process.env': env
    }),
    new UglifyJsPlugin({
      uglifyOptions: {
        compress: {
          warnings: false,
          drop_debugger: true,
          drop_console: true
        }
      },
      sourceMap: config.build.productionSourceMap,
      parallel: true
    }),
    ...

方案2:代码压缩

利用webpack工具本身就有压缩代码的功能,这里介绍下第三方可以压缩代码的网站。

在线压缩HTML/CSS/JS:tool.oschina.net/jscompress

2. 图片资源压缩

  • 利用Webpack或者第三方工具将项目资源里面的图片或者视频等资源进行压缩。减少静态资源的体积。

方案:引入loader——image-webpack-loader

首先安装loader:

npm install image-webpack-loader --save-dev

接着在webpack.base.conf.js中(约45行)配置loader的使用。大体结构如下:

  module: {
    rules: [
      ...
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        use:[
          {
            loader: 'url-loader',
            options: {
              limit: 10000,
              name: utils.assetsPath('img/[name].[hash:7].[ext]')
            }    
          },
          {
            loader: 'image-webpack-loader',
            options: {
              bypassOnDebug: true,
            }
          }
        ]
      },
      ...

此时重新打包,可能会出现错误😥: error:cannot find module "imagemin-gifsicle"

猜测:

  1. 某种东西的版本过高导致某种不兼容问题(安装的时候会说找不到什么什么模块)。
  2. 需要科学上网才能下载全?

解决方案:使用cnpm重装

# 安装cnpm
npm install -g cnpm --registry=https://registry.npm.taobao.org
# 重新安装
cnpm install image-webpack-loader --save-dev

重装虽然也提示了版本需要变动,但是此时不会出现缺失模块的问题了。

image.png

效果:重新打包后会发现原本打包出来较大的png文件(32KB)会消失,不会再单独作为一个外部的png文件引入。

image.png

image.png

3. 去除SourceMap

默认的打包配置中会生成一堆map文件,这些SourceMap文件主要记录了转换压缩后的代码所对应的转换前的源代码位置,是源代码和生产代码的映射。虽然方便线上调试,但是不可否认的是,它们的存储量往往比源代码要大得多。

下面是毫无任何优化的项目build出来的js文件夹😨。

image.png

为了性能更佳,我们可以在config/index.js通过配置productionSourceMap:false,取消SourceMap的生成。

image.png

4. cdn引入第三方库

上述我们看到打包出来的vendor.js文件非常大,原因一般是我们引入了较多的第三方库,而webpack又会将这些第三方库打包在一起。

方案:去除import,以cdn方式引入。

首先在index.html文件中引入我们需要的第三方库。比如elementui,vue-router。

<script src="https://cdn.bootcss.com/vue/2.5.3/vue.min.js"></script>
<script src="https://cdn.bootcss.com/vue-router/3.0.1/vue-router.min.js"></script>
<script src="https://cdn.bootcss.com/axios/0.26.0/axios.min.js"></script>
<link rel="stylesheet" href="https://cdn.bootcss.com/element-ui/2.15.6/theme-chalk/index.css">
<script src="https://cdn.bootcss.com/element-ui/2.15.6/index.js"></script>

其次在修改webpack.base.conf.js文件,添加externals配置:

module.exports = {
  ...
  module: {
    rules: [...]
  },
  // cdn引入第三方库,故排除import内容
  externals:{
    'vue': 'Vue',
    'vue-router': 'VueRouter',
    'ElementUI': 'ELEMENT',
    'axios': 'axios',
  }
}

最后在main.js或其他通过import引用这些第三方库的地方去除掉对应的引用,防止Webpack再次打包对应的模块。

// main.js
// import ElementUI from 'element-ui';
// import 'element-ui/lib/theme-chalk/index.css';
// http.js 
// import axios from "axios";

5. 路由懒加载

当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就会更加高效。

在vue中的路由文件夹中router/index.js,如果是通过import方式引入,那么在页面加载的时候是会加载完所有的路由组件才显示初始页面的。

//import MyComponent from '@/components/MyComponent'
//替换成
const MyComponent =()=> import ('@/components/MyComponent')

const router = createRouter({
  // ...
  routes: [{ path: '/users/:id', component: MyComponent }],
})

这种方案可以解决首页加载缓慢的问题,但是有一个小问题就是:如果你的动态加载的组件体量巨大,有可能在第一次切换路由(加载组件)的时候造成延时(等待1秒)。

至此,本篇文章的优化方法已经结束了。剩下的是失败的踩坑实录。也望大佬指点。

image.png

6. 抽离CSS文件

我们可以利用插件做各自模块的 css 文件提取,而不是全部都打包进一个app.css文件中。

webpack3 可以用 extract-text-webpack-plugin。

npm install extract-text-webpack-plugin --save-dev

webpack4 可以用 mini-css-extract-plugin

npm install mini-css-extract-plugin -D

Webpack4请见官网:www.npmjs.com/package/min…

踩坑实录

下面我们测试extract-text-webpack-plugin。 找到webpack.base.conf.js,引入

const ExtractTextPlugin = require("extract-text-webpack-plugin");

在对应的module.rules中配置如下:

image.png

  module: {
    plugins: [ //这个不添加allChunks参数的话,不会抽离chunk的css
        new ExtractTextPlugin({filename: 'css/[name].[hash:5].css', allChunks: true})
    ],
    rules: [
    ...
        {     //处理js中引入的css
        test: /\.css$/,
        loader: ExtractTextPlugin.extract({
            use: [
                {
                    loader: 'css-loader'
                }
            ]
        })
        },
        {
          test: /\.vue$/,
          loader: 'vue-loader',
          options: {loaders:{
              css: ExtractTextPlugin.extract({
                  use: 'css-loader',
                  fallback: 'vue-style-loader'
              })
          }}
        },
       ...

接着美滋滋地重新打包查看结果:

image.png

(当场吐血)

不但没有拆解,反而多了一个。

猜测:应该是vue-loader.conf.js默认配置,自己打包了一次所有组件的CSS,

image.png

在上述配置的css/[name].[hash:5].css中可以知道,我们实际上是对产物进行了二次打包。而vue-cli默认的CommonsChunkPlugin配置将所有的CSS都聚集在一起。

但是反复修改就是不会拆分成多个┭┮﹏┭┮

找了网上好多的资料,可以先参考:

vue-loader之CSS提取文档 : vue-loader.vuejs.org/zh/guide/ex…

segmentfault问答: segmentfault.com/q/101000001…

Webpack之CommonsChunkPlugin: webpack.js.org/plugins/com…