手把手教Vue打包优化实践-12M减少到3M

3,742 阅读3分钟

背景

一个老pc项目,基于vue-cli3,由于依赖了中element-ui,echarts,xlsx,monaco-editor,moment, jquery等常见比较大的库。导致项目打包完成的dist重达12M

使用webpack-bundle-analyzer分析图如下,大块头们我都标记了下

作为一个web项目build之后12M还是比较笨重的,作为一个有追求的前端攻城狮是很难接受的。于是开启了我的优化之路

正文

1、CDN抽离不经常更新的库

比如 element-ui,echarts,xlsx,moment以及vue的三大件都可以使用cdn缓存,以提高我们的速度以及缩小包大小

(1) 头部配置cdn对象

externals:外部扩展,详细了解查看看官网文档,这个操作同时会将chunk-vendors.js体积缩小,减少首屏白屏时间

// cdn配置
const cdn = {
  externals: {
    vue: 'Vue',
    vuex: 'Vuex',
    'vue-router': 'VueRouter',
    'element-ui': 'ELEMENT',
    echarts: 'Echarts',
    moment: 'moment',
    xlsx: 'xlsx',
    jquery: 'jquery',
  },
  // cdn的css
  css: [
    'https://cdn.bootcdn.net/ajax/libs/element-ui/2.13.0/theme-chalk/index.css',
    
  ],
  // cdn的js
  js: [
    'https://cdn.staticfile.org/vue/2.6.10/vue.min.js',
    'https://cdn.staticfile.org/vuex/3.0.1/vuex.min.js',
    'https://cdn.staticfile.org/vue-router/3.0.3/vue-router.min.js',
    'https://cdn.bootcdn.net/ajax/libs/element-ui/2.13.0/index.js',
    'https://cdn.bootcdn.net/ajax/libs/echarts/4.2.1/echarts.min.js',
    "https://cdn.bootcss.com/moment.js/2.20.1/moment.min.js",
    "https://cdn.bootcss.com/moment.js/2.20.1/locale/zh-cn.js",
    'https://cdn.bootcdn.net/ajax/libs/xlsx/0.14.1/xlsx.min.js',
    "http://libs.baidu.com/jquery/2.0.0/jquery.min.js"

  ]
}
//

在配置elementui的时候,要稍微注意点,我一开始是element-ui:ElementUi,结果报错,我仔细研究,发现https://cdn.bootcdn.net/ajax/libs/element-ui/2.13.0/index.js中有一句exports.ELEMENT = t(require("vue")) : e.ELEMENT = t(e.Vue),其中e传入的window,那么最后的全局变量名称就是ELEMENT

(2) public/index.html调整

css、js采用<link href= rel="preload" as="style">的方式对cdn缓存资源进行预加载,这样可以减少首屏白屏时间

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="renderer" content="webkit">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">

     <!-- 使用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文件 -->
    <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
      <link href="<%= htmlWebpackPlugin.options.cdn.js[i] %>" rel="preload" as="script">
    <% } %>
    <title><%= webpackConfig.name %></title>
  </head>
  <body>    
    <div id="app"></div>
    <!-- 使用CDN的JS文件 -->
    <% for (var i in htmlWebpackPlugin.options.cdn &&
    htmlWebpackPlugin.options.cdn.js) { %>
    <script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
    <% } %>

    
    <!-- built files will be auto injected -->
  </body>
</html>

(3)vue.config.jschainWebpack调整

这个地方主要是将cdn资源注入到html

config.when(process.env.NODE_ENV !== 'development', //生产环境才做处理
    config => {
      config.plugin('html').tap(args => {
        args[0].cdn = cdn
        return args
      })
      
    }
  )

(4)vue.config.jsconfigureWebpack调整

这个地方主要是将前面配置的cdn配置到webpack

if (process.env.NODE_ENV !== 'development') config.externals = cdn.externals

这一套打下去,基本把我这个项目的大肥肉从build包里面拿出去了。

在实际操作过程中,还是要根据项目实际情况做一些取舍,优化的目标是较少http请求资源体积,如果过度使用externals可能会导致http数量增多,而且导致体验感下降

2、productionSourceMap 设为 false

(1) 在vue.config.js调整

productionSourceMap: process.env.NODE_ENV !== 'production'

3、抽离公共代码

这个操作主要用来切割代码,缓存公共部分,在chainWebpack配置如下, 具体每个参数如何配置可以查看官方 SplitChunksPlugin

config.when(process.env.NODE_ENV !== 'development',
    config => {
      config
        .optimization.splitChunks({
          chunks: 'all',
          cacheGroups: {
            libs: {
              name: 'chunk-libs',
              test: /[\\/]node_modules[\\/]/,
              priority: 10,
              chunks: 'initial' 
            },
            commons: {
              name: 'chunk-commons',
              test: resolve('src/components'), 
              minChunks: 3, 
              priority: 5,
              reuseExistingChunk: true
            }
          }
        })
     
    }
  )

4、开启双端gzip

这个操作build包瘦身超级大绝杀,压缩后通常能帮我们减少响应 70% 左右的大小。但是不建议图片也进行gzip压缩,经测试效果不理想。

(1) 前端部分

首先安装依赖:cnpm install --save-dev compression-webpack-plugin

vue.config.js configureWebpack 里面新增

//头部引入
const CompressionWebpackPlugin = require('compression-webpack-plugin')


// 生产环境相关配置
if (process.env.NODE_ENV !== 'development') {
   
    // gzip压缩
    const productionGzipExtensions = ['html', 'js', 'css']
    config.plugins.push(
        new CompressionWebpackPlugin({
            filename: '[path].gz[query]',
            algorithm: 'gzip',
             test: /\.(js|css|svg|woff|ttf|json|html)$/,
            threshold: 10240, 
            minRatio: 0.8, 
            deleteOriginalAssets: false 
        })
    )
}

(2) 服务端部分

nginx为例,修改配置如下:

server {
    gzip on;
    gzip_static on;  
    
    gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

  
}

说明:

Nginx的动态压缩是对每个请求先压缩再输出,但是在我们优化过程中,我们已经通过webpack进行了压缩处理,故不需要再进行动态压缩,减少资源浪费。在ngx配置中

gzip_static on; 就开启优先加载gz结尾的文件

gzip on;开启gzip

大招一发,效果还是特别明显,文件都变得特别零碎了,而且很小个

而整体包大小得到了飞跃的成长

5、图片压缩

gzip说过图片不建议使用gzip压缩,但是可以通过image-webpack-loader插件可将大的图片进行压缩从而缩小打包体积,不过我这个项目中,图片很少,但是我有做过测试,这个压缩效果还是理想,优化配置如下:

vue.config.jschainWebpack中调整

config.module
    .rule('images')
    .use('image-webpack-loader')
    .loader('image-webpack-loader')
    .options({ bypassOnDebug: true })
    .end()

6、完成版如下(去除了一些自己项目中的东西)

实例中的cdn都是公共的,优化自己可以根据公司情况选择

'use strict'
const path = require('path')
const webpack = require('webpack')
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin')
function resolve(dir) {
  return path.join(__dirname, dir)
}

const port = '1024' // dev port

const CompressionWebpackPlugin = require('compression-webpack-plugin')

const cdn = {
  externals: {
    vue: 'Vue',
    vuex: 'Vuex',
    'vue-router': 'VueRouter',
    'element-ui': 'ELEMENT',
    'echarts': 'Echarts',
    moment: 'moment',
    xlsx: 'xlsx',
  },
  // cdn的css链接
  css: [
    'https://cdn.bootcdn.net/ajax/libs/element-ui/2.13.0/theme-chalk/index.css',
    
  ],
  // cdn的js链接
  js: [
    'https://cdn.staticfile.org/vue/2.6.10/vue.min.js',
    'https://cdn.staticfile.org/vuex/3.0.1/vuex.min.js',
    'https://cdn.staticfile.org/vue-router/3.0.3/vue-router.min.js',
    'https://cdn.bootcdn.net/ajax/libs/element-ui/2.13.0/index.js',
    'https://cdn.bootcdn.net/ajax/libs/echarts/4.2.1/echarts.min.js',
    "https://cdn.bootcss.com/moment.js/2.20.1/moment.min.js",
    "https://cdn.bootcss.com/moment.js/2.20.1/locale/zh-cn.js",
    'https://cdn.bootcdn.net/ajax/libs/xlsx/0.14.1/xlsx.min.js',
  ]
}
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports = {
 
  publicPath: './',
  outputDir: 'dist',
  assetsDir: 'static',
  lintOnSave: 'error',
  productionSourceMap: process.env.NODE_ENV !== 'production',
  devServer: {
    port: port,
    open: true,
    overlay: {
      warnings: false,
      errors: true
    },
    proxy: {
     
      '/api-dev/*': {
        target: ``,
        changeOrigin: true,
        pathRewrite: {
          '^/api-dev/': ''
        },
        logLevel: 'debug'
      }
    }
    
  },
  configureWebpack: config => {
    
    config.externals = cdn.externals
    return {
      name: name,
      resolve: {
        alias: {
          '@': resolve('src'),
          'src': resolve('src')
        }
      },
      plugins: [
        new MonacoWebpackPlugin({
          features: ['coreCommands']
        }),
       
        new CompressionWebpackPlugin({
          filename: '[path].gz[query]',
          algorithm: 'gzip',
          threshold: 10240, // 只有大小大于该值的资源会被处理 10240
          minRatio: 0.8, // 只有压缩率小于这个值的资源才会被处理
          deleteOriginalAssets: true // 删除原文件
        })
      ]
    }
  },
  chainWebpack(config) {
   
    config.plugin('provide')
      .use(webpack.ProvidePlugin, [{
        $: 'jquery',
        jquery: 'jquery',
        jQuery: 'jquery',
        'window.jQuery': 'jquery'
      }])
    config.module
      .rule('vue')
      .use('vue-loader')
      .loader('vue-loader')
      .tap(options => {
        options.compilerOptions.preserveWhitespace = true
        return options
      })
      .end()
    config
      .when(process.env.NODE_ENV === 'development',
        config => config.devtool('cheap-source-map')
      )
    config
      .when(process.env.NODE_ENV !== 'development',
        config => {
         
          config
            .optimization.splitChunks({
              chunks: 'all',
              cacheGroups: {
                libs: {
                  name: 'chunk-libs',
                  test: /[\\/]node_modules[\\/]/,
                  priority: 10,
                  chunks: 'initial' 
                },
                commons: {
                  name: 'chunk-commons',
                  test: resolve('src/components'), 
                  minChunks: 3, 
                  priority: 5,
                  reuseExistingChunk: true
                }
              }
            })
        
          config.plugin('html').tap(args => {
            args[0].cdn = cdn
            return args
          })
        }
      )
  }
}

结语

在经历了上述5步骤之后,优化效果从12.1M => 3.4M。