Webpack - 首屏性能优化(splitChunks/externals/gzip/路由懒加载)

首屏加载慢原因: Vue只有第一次会加载页面, 以后的每次页面切换,只需要进行组件替换。因为Vue 是SPA,所以首页第一次加载时会把所有的组件以及组件相关的资源全都加载了,造成网站首页打开速度变慢的问题 (打包构建时 js包会很大)*

1、路由懒加载

懒加载: 把代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后,立即引用或即将引用另外一些新的代码块。

通过import()引用的子模块会被单独分离出来,打包成一个单独的文件(打包出来的文件被称为chunk )依照webpack原本的打包规则打包项目,我们就无法确定子模块在打包出来的哪个JS文件中,而且子模块的代码会和其他代码混合在同一个文件中。这样就无法进行懒加载操作。所以,要实现懒加载,就得保证懒加载的子模块代码单独打包在一个文件中。

懒加载(按需加载)原理分两步:

  1. 将需要进行懒加载的子模块打包成独立的文件 children chunk
  2. 借助函数来实现延迟执行子模块的加载代码

实现方法:

// 在进入路由后才会加载这个 chunk 文件
component: () => import('@/views/emOrderHandle/eeOrderTxzSocTwoHandle') // 每个组件打包成一个js文件
// 指定了相同的webpackChunkName,会合并打包成一个js文件。 把组件按组分块
() => import(/* webpackChunkName: "orderHandleRoute" */ './UserDetails.vue')
() => import(/* webpackChunkName: "orderHandleRoute" */ './eeOrderTxzSocTwoHandle.vue')

2、CDN链接& externals

默认的打包规则会将开发环境中 npm 引入的依赖打入项目公共模块集合中 即 chunk-vendors.js在页面首次加载时会被下载。通过减小公共依赖包体积达到优化首屏速度的目的

externals 的作用在于防止将某些 import 的包(package)打包到 bundle 中,而是在运行时(runtime)再去从外部(CDN方式)获取这些扩展依赖,我们可以在 bootcdn网站上找到相关包的CDN链接

// externals 排除不需要打包的依赖包
module.exports = {
  // vue/cli 中在此处管理 webpack 配置
  configureWebpack: (config) => {
    externals: {
      'vue': 'Vue',
      'vue-router': 'VueRouter',
      'vuex': 'Vuex'
      'element-ui': 'ELEMENT',
      'axios': 'axios'
    }
  }
}

externals 处理之后就不会出现在打包之后的 js 包中了, 接下来还需要在vue的入口html中加入CDN链接


<script src="https://cdn.bootcdn.net/xxx.min.js">script>
<script src="https://cdn.bootcdn.net/xxx.min.js">script>
<script src="https://cdn.bootcdn.net/xxx.min.js">script>
<link href="https://xxx/index.css">

3、使用 splitChunks 插件,配置分离规则

SplitChunks插件来控制Webpack打包生成的js文件的内容的精髓就在于,防止模块被重复打包,拆分过大的js文件,合并零散的js文件。最终的目的就是减少请求资源的大小和请求次数。

// splitChunks 的默认配置
module.exports = {
  chainWebpack(config) {
    config.optimization.splitChunks({
      chunks: 'async',
      minSize: 30000,
      maxSize: 0,
      minChunks: 1,
      maxAsyncRequests: 6, // 同时也限制了同一 priority 最大分块次数
      maxInitialRequests: 4,
      automaticNameDelimiter: '~',
      cacheGroups: {
        vendors: {
          name: 'chunk-vendors',
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          chunks: 'all'
        },
        common: {
          name: 'chunk-common',
          minChunks: 2,
          priority: -20,
          chunks: 'initial',
          reuseExistingChunk: true
        }
      }
    }
  }
}

在默认配置下(chunks === 'async')main.js中异步加载或间接异步加载的模块,都会被另外打包生成一个js文件

// 异步加载的模块:通过import('xxx') 或 require(['xxx'],() =>{}) 加载的模块。
// 同步加载的模块:通过 import xxx 或 require('xxx') 加载的模块。

根据 cacheGroups中对vendors的设定:

  • test匹配项目从node_modules中加载的模块并根据 chunks的设定提取打包
  • chunks: 'initial'时表示:node_modules在项目中被异步或同步加载多少次,那这个模块就被提取多少次,分别打包到不同文件中。
  • chunks: 'all'时表示:node_modules不管异步加载还是同步加载的模块都是提取打包到一个文件中。

可以看到所有的 node_modules模块都被提取到chunk-vendors文件中去了

下一步继续提取chunk-vendors减小它的体积:可以将诸如element-uixlsx等模块提取为一个个独立的文件

cacheGroups: {
  vendors: {
    name: 'chunk-vendors',
    test: /[\\/]node_modules[\\/]/,
    priority: -10,
    chunks: 'all'
  },
  common: {
    name: 'chunk-common',
    minChunks: 2,
    priority: -20,
    chunks: 'initial',
    reuseExistingChunk: true
  },
  // 继续添加其他提取规则 注意 priority 要比 vendors 大才能提取出来
  'element-ui': {
  	name: 'element-ui',
    test: /[\\/]element-ui[\\/]/,
    chunks: 'all',
    priority: 0
  },
  'moment': {
    name: 'moment',
    test: /[\\/]moment[\\/]/,
    chunks: 'all',
    priority: 0
  },
  'xlsx': {
    name: 'xlsx',
    test: /[\\/]xlsx[\\/]/,
    chunks: 'all',
    priority: 0
  },
  'lodash': {
    name: 'lodash',
    test: /[\\/]lodash[\\/]/,
    chunks: 'all',
    priority: 0
  }
}

还可以继续提取出现在多个chunk中的文件

CoEffectReportComponents: {
  name: 'CoEffectReportComponents',
  test: /[\\/]CoEffectReportComponents[\\/]/,
  reuseExistingChunk: true,
  priority: 0
},
dataEntryInput: {
  name: 'dataEntryInput',
  test: /[\\/]dataEntryInput[\\/]/,
  reuseExistingChunk: true,
  priority: 0
}

SplitChunks插件配置选项

  • chunks选项,决定要提取那些模块。

    • 默认是async:只提取异步加载的模块出来打包到一个文件中。

      • 异步加载的模块:通过import('xxx')require(['xxx'],() =>{})加载的模块。
    • initial:提取同步加载和异步加载模块,如果xxx在项目中异步加载了,也同步加载了,那么xxx这个模块会被提取两次,分别打包到不同的文件中。

      • 同步加载的模块:通过 import xxxrequire('xxx')加载的模块。
    • all:不管异步加载还是同步加载的模块都提取出来,打包到一个文件中。
  • minSize选项:规定被提取的模块在压缩前的大小最小值,单位为字节,默认为30000,只有超过了30000字节才会被提取。

  • maxSize选项:把提取出来的模块打包生成的文件大小不能超过maxSize值,如果超过了,要对其进行分割并打包生成新的文件。单位为字节,默认为0,表示不限制大小。

  • minChunks选项:表示要被提取的模块最小被引用次数,引用次数超过或等于minChunks值,才能被提取。

  • maxAsyncRequests选项:最大的按需(异步)加载次数,默认为 6。

  • maxInitialRequests选项:打包后的入口文件加载时,还能同时加载js文件的数量(包括入口文件),默认为4。

  • 先说一下优先级 maxInitialRequests / maxAsyncRequests <maxSize<minSize

  • automaticNameDelimiter选项:打包生成的js文件名的分割符,默认为~

  • name选项:打包生成js文件的名称。

  • cacheGroups选项,核心重点,配置提取模块的方案。里面每一项代表一个提取模块的方案。下面是cacheGroups每项中特有的选项,其余选项和外面一致,若cacheGroups每项中有,就按配置的,没有就使用外面配置的。

    • test选项:用来匹配要提取的模块的资源路径或名称。值是正则或函数。
    • priority选项:方案的优先级,值越大表示提取模块时优先采用此方案。默认值为0。
    • reuseExistingChunk选项:true/false。为true时,如果当前要提取的模块,在已经在打包生成的js文件中存在,则将重用该模块,而不是把当前要提取的模块打包生成新的js文件。
    • enforce选项:true/false。为true时,忽略minSizeminChunksmaxAsyncRequestsmaxInitialRequests外面选项

4、开启 gzip 压缩

gzip压缩能够缩小一倍多的体积,是非常有效的优化手段,兼容性也很好

响应头里显示有Content-Encoding: gzip,表示浏览器支持并且启用了Gzip压缩的资源

  • 代码层压缩
// CompressionPlugin 插件在 vue/cli 中的使用
module.exports = {
  configureWebpack: (config) => {
    if (process.env.NODE_ENV === 'production') {
      return {
		plugins: [
          new CompressionPlugin({
            test: /\.js$|\.html$|\.css$|\.jpg$|\.jpeg$|\.png/, // 需要压缩的文件类型
            threshold: 10240, // 归档需要进行压缩的文件大小最小值 10k
            deleteOriginalAssets: false // 是否删除原文件
          })
        ]
      }
    }
  }
}

  • 服务端压缩 (nginx)
http {
	gzip on; # 开启gzip压缩
    gzip_min_length 4k; # 小于4k的文件不会被压缩,大于4k的文件才会去压缩
	gzip_buffers 16 8k; # 处理请求压缩的缓冲区数量和大小,比如8k为单位申请16倍内存空间;使用默认即可,不用修改
	gzip_http_version 1.1; # 早期版本http不支持,指定默认兼容,不用修改
	gzip_comp_level 2; # gzip 压缩级别,1-9,理论上数字越大压缩的越好,也越占用CPU时间。实际上超过2的再压缩,只能压缩一点点了,但是cpu确是有点浪费。因为2就够用了
	gzip_types text/plain application/x-javascript application/javascript text/javascript text/css application/xml application/x-httpd-php image/jpeg image/gif image/png application/vnd.ms-fontobject font/x-woff font/ttf; # 压缩的文件类型 MIME类型
	gzip_vary on; # 是否在http header中添加Vary: Accept-Encoding,一般情况下建议开启
}

多层 nginx 代理时 gzip 不生效的坑:

# 第一层
http {
    gzip_static on;
    gzip_http_version 1.1;
    gzip_proxied expired no-cache no-store private auth;
    gzip_vary on;
}

# 第二层
http {
    gzip_static on;
    gzip_http_version 1.0;
    gzip_proxied expired no-cache no-store private auth;
    gzip_vary on;
}

5、服务端渲染 SSR (未完待续)

你可能感兴趣的:(vue实际运用,性能优化,vue.js,javascript,webpack,nginx)