webpack 项目打包优化

2,273 阅读11分钟

环境

  • vue-cli 3.11.0
  • nginx 1.16.1

写在前面的总结

点我直接跳转到总结部分

项目开发过程

  • ...此处省略前面的什么需求分析、技术选型、技术架构验证等等步骤,直接进入正题
  • 通过脚手架(vue-cli)创建项目
  • 搭建前端架构
  • 封装基础组件、公共的业务组件、公共模块、方法等
  • 正式开发(编码)
  • 开发完成,本地测试、打包、部署测试环境、测试,这时你突然发现了一些问题

部署完成

发现,诶,不错,可以跑起来,有点小开心。但是很快就发现了问题:

  • 这个性能是不是有点问题?
  • 首屏加载怎么这么慢?
  • 我是不是没做性能方面的优化?
  • 打包优化?
  • 雅虎军规?
  • 。。。我去,我好像啥都没做

以前用 apache 部署前端应用(jQuery + html + css)时还经常做相关的性能优化,比如用的最多的雅虎军规,但是现在用 vue 技术栈 + nginx 呢?

好吧,现在开始优化吧

分析

vue 技术栈 + nginx (当然其它也一样,只要用到类似 webpack 这样的打包工具) 优化可以分为哪些呢?可以简单分为以下三种 :

  • 编码层面的优化
  • 打包优化
  • nginx 服务器配置

编码层面的优化

这一层面的优化就多了去了,比如我们所熟知的 v-if、v-show的使用、虚拟列表的使用、动态加载、懒加载、禁用响应式(有些地方是不需要响应式的,比如单纯的数据展示,即数据不会变)等等方法

打包优化

本篇文章主要介绍 打包优化

nginx 服务器配置

nginx 服务器的配置,参考另外一篇文章,nginx 服务器的安装和配置

打包优化

分析

我们要进行打包优化,首先得知道我们需要优化哪些地方,这就需要我们对打包结果进行分析

webpack-bundle-analyzer

这是一个 webpack 的插件,可以对打包结果进行分析

如何使用

安装插件

npm i webpack-bundle-analyzer -D

配置 webpack

vue-cli3 我们通过 vue.config.js 文件对 webpack 进行配置

module.exports = {
  ...
  // vue-cli3 提供的一种高级技巧,链式操作
  chainWebpack (config) {
    ...
    // 意思只在打包时起作用
    if (process.env.NODE_ENV === 'production') {
      config.plugin('webpack-bundle-analyzer')
        .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin)
        .end()
    }
    ...
  }  
  ...
}

打包

npm run build

这时候就开始了打包操作,它会在本地启动一个服务器,默认端口号为 8888,我们在浏览器访问 172.0.0.1:8888 就可以看到打包的分析结果,当然命令行也会输出一些信息,比如我们的包有哪些,有哪些包是不是有问题,比如包太大?或者有 console log 信息等等,有不理解的信息可以继续向下看

打包结果分析

未优化前的打包结果 未优化前的打包结果

以上两张图片是一样的,只是第二张有每个包的信息

鼠标可以在页面上滑动,发现该页面是一个模块图,对整个应用的包进行分析,有哪些部分组成,以及每部分的一个信息

我们对该页面进行分析,发现 :

  • 打包后的数据包好像有点太大了吧,页面分析显示居然有 7.95 MB,然后查看 dist 目录,更大,居然有 13 MB
  • dist 目录大小

着手优化

刚才经过一番分析,发现主要问题是资源包太大了,居然有 13 MB,这不是一个大项目,打包结果怎么可能这么大

分析 dist 目录

source map

查看dist目录下的各个目录,发现居然有 .map 文件,这是 vue-cli3 默认会开启 source map,这样便于我们开发时调 bug,准确定位错误的位置,在打包时我们需要手动关闭

dist/js

关闭 source map
module.exports = {
  ...
  productionSourceMap: false,
  ...
}

重新打包,运行 npm run build, 再次查看 dist 目录,瞬间小了,但是 webpack-bundle-analyzer 分析结果还是没变,我们只是把 打包后的 dist 目录的大小减少了,当然这也是一个优化,这样可以保证部署到生产环境的代码体积,以及代码的安全性

GZip 压缩

我们会发现执行 npm run build 时有一些提示信息

提示信息

说明资源和入口文件太大,超出了webpack的默认限制,这样会影响 web 的性能,这个是webpack给出的提示,webpack的性能篇给了详细的介绍

有两种解决办法,改掉webpack的默认配置,比如将大小限制调大,或者直接关闭提示,不建议这么做,因为这样做等于你没有做任何的优化,我们采取 资源压缩 的方式

怎么做

两种方式

  • nginx 服务器进行压缩
  • webpack 打包时压缩

这里我们选择第二种方式,理由如下:

nginx 服务压缩,也可以,但是每次请求资源是,服务器其实是在动态实时进行压缩,这其实是对服务器的一种压力,比如 CPU 进行大量的计算,特别是当负载比较高时,请求过来,服务器进行压缩,请求开始到压缩结束,这段时间内用户是看不到任何东西的,体验不好,用户还以为卡了呢,重复发请求,重复执行压缩,负载越来越高,恶性循环,所以这里采用 webpack 打包时压缩,ngixn 放的时压缩过后的资源,性能就会好很多,当然**(划重点:这种方式 nginx 服务器也需要做点配置,请往下看)**

安装插件

备注:最新的 7.1.0 有问题

npm i compression-webpack-plugin@5.0.1 -D

修改 vue.config.js
const CompressionPlugin = require('compression-webpack-plugin')

module.exports = {
  ...
  configureWebpack: config => {
    if (process.env.NODE_ENV === 'production') {
      config.plugin('compressionPlugin')
        .use(
          new CompressionPlugin({
            filename: '[path].gz[query]',
            algorithm: 'gzip',
            test: /\.(js|json|css|jp?eg|png)$/,
            // 当资源大于该值时进行压缩
            threshold: 0,
            // 压缩率小于这个值的资源会被压缩,默认为 0.8
            minRatio: 1,
            // 删除源文件, 配合webpack-bundle-analyzer时不能删除源文件,且需要配置静态服务器
            deleteOriginalAssets: true
          })
        )
    }
  }
  ...
}
打包

npm run build

压缩后的结果(看效果)

压缩后的资源包

这里我往存了压缩之前的截图了,至少少了好几兆的数据,读者在实战的时候可以自己试试

配置 nginx 服务器

将打包后的资源包上传到 nginx 服务器进行部署,然后访问页面,发现啥都没有,打开控制一看,各种 404,意思是资源未找到,what ?? 原来我们还需要配置一下 nginx 服务器,开启 nginx 的压缩模式,nginx 的压缩分为两种 :

  • 动态压缩

就是我们开头说的,需要服务器实时压缩,会提高服务器的负荷,浪费 CPU

  • 静态压缩 (我们采用的方式)

利用 nginx 的 GZip Precompression 模块,这个模块的作用是对于支持 gzip 的请求,直接读取已经压缩好的文件(文件名以 .gz 结尾,查看我们打包后的文件,会发现这些文件也都是以 .gz 结尾的文件),而不是动态压缩,对于不支持 gzip 的请求,则读取源文件

说明

nginx 动态压缩需要用到 ngx_http_gzip_module 模块,该模块默认安装;静态压缩用的是 http_gzip_static_module 模块,该模块允许发送以 .gz 作为扩展的预压缩文件代替普通文件,我们需要检测 nginx 是否已经有该模块,我的环境已经有了,没有的话需要重新编译,并指定该模块

查看是否已经安装了该模块:

nginx -V

没有的话则安装:

./configure --with-http_gzip_static_module make && make install

niginx 配置 gzip, gzip 静态压缩

http {
  # 开启gzip
  # gzip on;
  gzip_static on;

  # 启用gzip压缩的最小文件;小于设置值的文件将不会被压缩
  gzip_min_length 1k;

  # gzip 压缩级别 1-10 
  gzip_comp_level 2;

  # 进行压缩的文件类型,静态压缩设置 gzip_types 没用
  # gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;

  # 是否在http header中添加Vary: Accept-Encoding,建议开启
  gzip_vary on;
}

** 查看是否配置成功 **

游览器发送请求,查看请求信息,如图所示,则成功

支持 gzip 的请求

OK,压缩的事情终于搞定了,继续查看分析结果,进行下一步的优化

按需加载

经过以上的压缩处理之后,打包后的文件已经小了很多了,现在总共也才 1.4 MB,但是我们还是需要再分析分析有哪些地方始都可以继续优化 继续分析打包结果,我们会发现,其中的第三库的体积占大头,从中可以看到 element-ui 和 echarts(项目中用的 v-charts)占大头,分别 1.49 MB 和 2.42 MB,这是未压缩的体积,如下图所示 :

element-ui 没有按需加载 echarts 没有按需加载

分析,这两个为什么这么大,难道在开发时没有 配置按需加载 ?经过一番检查,果然,没有做按需加载

element-ui 按需加载

这里我们参照 element-ui 官网快速上手章节的按需引入部分

** 注意 **

.babelrc 文件对应的是 vue-cli3 中的 babel.config.js 文件 其中的 es2015 需要改成 '@babel/preset-env', 用 es2015 会如下错误:

// babel 版本冲突,查看 package.json 文件会发现 babel-core 等都是 7.0
Plugin/Preset files are not allowed to export objects, only functions.

Babel 7 废弃了 babel-preset-es201x 而采用新的 env 插件。

所以 element-ui 按需加载最后的配置是:

// babel.config.js
module.exports = {
  presets: [
    '@vue/app',
    ['@babel/preset-env', { modules: false }]
  ],
  plugins: [
    [
      'component',
      {
        libraryName: 'element-ui',
        styleLibraryName: 'theme-chalk'
      }
    ]
  ]
}

** element-ui 配置按需加载后的效果 **

element-ui 按需加载后

v-charts 按需加载 (echarts 也有对应的方案)

v-charts的按需加载倒是不用额外的配置,只需要按要求进行编码即可

...
// v-charts 按需加载
import VeLine from 'v-charts/lib/line'
import VePie from 'v-charts/lib/pie'
import VeGauge from 'v-charts/lib/gauge'
import VeHistogram from 'v-charts/lib/histogram'
Vue.component(VeLine.name, VeLine)
Vue.component(VePie.name, VePie)
Vue.component(VeGauge.name, VeGauge)
Vue.component(VeHistogram.name, VeHistogram)
...

按照以上的方式,即可实现 v-charts 的按需加载

** v-charts 按需加载后的效果 **

echarts v-charts

element-ui、v-charts实现按需加载后,效果显著,只打包了我们实际用到的模块,优化效果显著

路由懒加载

该项目没有进行路由懒加载方面的优化,因为在项目架构初始时就做好了,直接上代码:

export const constRoutes = [
  {
    path: '/login',
    name: 'login',
    // 路由的懒加载
    component: () => import('@/views/Login.vue'),
    hidden: true
  },
  {
    path: '/',
    component: () => import('@/views/Home.vue')
  },
  {
    path: '/dashboard',
    redirect: '/',
    meta: {
      roles: ['admin', 'editor'],
      // title: 'dashboard'
      title: '首页',
      icon: 'dashboard'
    }
  }
]

路由懒加载实现其实很简单,只需要动态的引入我们的组件即可

优化效果

最后优化后的包体积

优化后的分析图: 优化后的分析图 优化后的分析图 浏览器加载耗时

包体积明显减小,从刚开始的 13MB 减小到现在的 1MB,体积降低了高达92% 首屏加载时间也可以达到毫秒级,相比优化之前提高了50%的加载效率

总结

项目优化总的可以分为三类,分别是编码层级的优化、打包优化和 配置nginx(静态资源)服务器

编码层级的优化

文档没有介绍,但是可以简单给大家挪列一些

  • v-if vs v-show
  • 虚拟列表、长列表优化
  • 动态加载(懒加载)
  • 你是不是真的需要响应式(响应式带来便利的同时也是对性能的损耗)
  • 渲染方面的优化(减少重绘、重排)
  • 网络方面的优化
  • ...

打包优化

通过一些工具查看我们需要进行哪些优化,比如 webpack-bundle-analyzer、lighthouse、performance、coverage等

  • 移除死代码,即永远不会被执行到的代码

UglifyJS、Tree Shaking,能自动把项目中没有用到的代码从打包中去掉,UglifyJS也可以去掉项目中各种console.log

  • 懒加载

移除死代码后发现资源包体积还是大,这时候就需要考虑是不是有些资源被重复打包了?特别是多入口的项目,很容易出现这种情况,一个包在多个入口被引用,可以用code spliting方法进行公共代码的抽离,webpack 4采用的是 splitChunks,可以实现拆包以及打包公共模块等功能;

  • 生产环境关闭 source map
  • 资源压缩,gzip
  • 资源按需加载
  • 如果有条件使用 CDN 的话,可以大幅度减少打包体积

可以通过 CDN 去加载一些外部资源(保罗全局变量的库),比如 vue、vuex、vue-router、axios、echars、element-ui 等,需要配置 webpack 的 externals 属性

  • 有些资源是否要拆分

这个就仁者见仁,智者见智了,比如 CSS 的拆分、小图片是否需要 base64 编码等 拆分带来的好处是:可以做缓存; 缺点:首次加载耗时,HTTP 请求数多了,内联的同时代码体积变大了 建议根据实际场景做决策

  • 打包设置缓存,增加首次打包的时间,但是减少后续的打包时间
  • 像一些不经常变的资源单独打包,也是借用 splitChunks 实现

配置 nginx 服务器

参考另外一篇文章,nginx 服务器的安装和配置

结束了,点我可以直接回到开始的位置