优化 Webpack 构建结果

本文作者:IMWeb nixzheng 原文出处:IMWeb社区 未经同意,禁止转载

Webpack应该是当下流行度最广的JavaScript构建、打包工具了。我们团队中大部分项目也在使用Webpack构建。项目的是传统的非SPA页面,我们使用了CommonsChunkPlugin来提取公共模块,保证各页面之间部分公共库可以复用缓存,同时使用UglifyJS等来保证输出文件体积的减小。原本Webpack这部分的配置是这样的:

{
  // ...
  plugins: [
    // ...
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks: 5,
    })
  ],
}

但这么做粒度还太粗,无法对打包的文件做更精细的控制。庆幸的是Webpack生态圈是如此的丰富,有不少好工具可以利用。

1. 分析打包结果

webpack-bundle-analyzer是一个非常好用的Webpack包分析工具。可以将每个文件包含的内容通过非常漂亮的图片表现出来。

我们在production模式的webpack配置下引入插件:

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

// ...
plugins: [
  // ...
  new BundleAnalyzerPlugin(),
]
// ...

通过插件生成的包结构如下:

可以发现这里有两个严重的问题:

  • 有部分可以共用的脚本并没有公用。
  • areaData_min.js是项目中显示地区的一个JavaScript数据文件,体积很大,用到的页面也很多,但并不是强依赖,却被打包到了bundle里。

其他常见的问题包括vendor中打包入了过多文件,多次引入了不同版本的库文件等。

2. 模块异步化

首先我们来把非强依赖的模块异步化。

Webpack 1.0中可以通过 require.ensure 来实现异步文件的剥离和加载。Webpack2则通过更标准的 import() 来实现同样的功能。我们首先将 areaData_min.js 异步化:

import('assets/areaData_min').then((data) => {
  this.setState({areaData: data});
})

异步后的结果:

独立了一个0_{hash}_.js,同时减少整体体积的效果也很明显。

对于React组件我们可以添加一个AsyncWrapper来复用异步加载逻辑。不过由于Webpack的动态引入其实依赖了静态的分析,所以我们不可以使用 const target='lodash'; import(target) 这种方式来实现动态加载。需要额外定义一个AsyncComponent加载所需代码:

import React, {Component, createElement} from 'react'

export default class AsyncWrapper extends Component {
  constructor(props) {
    super(props)
    this.state = {
      component: null,
    }
  }

  componentDidMount() {
    const {load} = this.props

    load().then(module => {
      this.setState({component: module.default})
    })

    load(component => {
      this.setState({component})
    })
  }

  componentWillUnMount() {
    this.setState({component: null})
  }

  render() {
    const {component} = this.state
    return component ? createElement(component, this.props) : null
  }
}

// Target Async Component
import React from 'react'
import AsyncWrapper from './AsyncWrapper'

const AsyncComponent = (props) =>
  <AsyncWrapper load={() => import('./TargetComponent')} {...props} />

// Use
<AsyncComponent xxx="1" />

也可以使用react-loadable等库实现同样的效果。

3. 调整CommonsChunkPlugin配置

可以发现部分可以公用的文件并没有合到vendor里来。简单调整一下配置:

{
  // ...
  plugins: [
    // ...
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks: 4,
    })
  ],
}

值得注意的是,我们需要谨慎权衡minChunks的取值,过小的取值将造成vendor无意义的变大。大部分情况下我并不推荐使用 ~minChunks~。这是由于我们一般希望vendor是稳定的,缓存可长时间使用。如果合并入太多业务代码,vendor的缓存复用率会大为降低,对更新频繁的项目来说对性能反而有损耗。

最后的结果如下图,相比优化前已经大幅改善了。

4. 其他性能优化点

  • 将NODE~ENV设置为 production。一般也需要增加 DefinePlugin 设置。
  • 使用DllPlugin。不仅有利于提升vendor的稳定性,同时也会减少每次编译时间。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏吴裕超

datalist标签小结

在Web设计中,经常会用到如输入框的自动下拉提示,这将大大方便用户的输入。在以前,如果要实现这样的功能,必须要求开发者使用一些Javascript的技巧或相关的...

3415
来自专栏草根专栏

使用VS Code开发asp.net core (上)

本文是基于Windows10的. 下载地址: https://code.visualstudio.com/ insider 版下载地址: https://cod...

3425
来自专栏移动端开发

告诉你 iOS9.0 之后的Bitcode到底是什么!!

 用Xcode 7 beta 3在真机(iOS 8.3)上运行一下工程,结果发现工程编译不过。看了下问题,报的是以下错误: ld: ‘/Users/**/Fr...

3818
来自专栏西安-晁州

golang学习之select用法

早期的select函数是用来监控一系列的文件句柄,一旦其中一个文件句柄发生IO操作,该select调用就会被返回。golang在语言级别直接支持select,用...

4720
来自专栏程序生活

Python爬虫系列(六)外国图库Unsplash图片自动化下载

再做一个网站,要找一些高清图片,然后同学推荐了这个网站:Unsplash 。但是每张图片下载要手动点,然后下拉加载更多图片,效率不高,所以自己写了爬虫程序,进...

5989
来自专栏小狼的世界

Mac下安装Android模拟器

像iPhone的iOS或者其他的手机操作系统一样,Android的开发者非常需要一个模拟器,以在设备上实地测试前对自己开发的应用进行测试。这需要借住SDK来实现...

2992
来自专栏葡萄城控件技术团队

SpreadJS使用进阶指南 - 使用 NPM 管理你的项目

1062
来自专栏Debian社区

Debian 9.2 发布,大量问题修复

Debian 9.2 发布了。此次发布情况特殊,使用”apt-get“工具执行升级的用户将需要确保使用”dist-upgrade“命令,以便更新到最新的内核软件...

771
来自专栏Youngxj

修改Windows 7 开机启动画面

2262
来自专栏云计算

在Ubuntu 16.04上安装WordPress

在本指南中,您将学习如何在运行Ubuntu 16.04的Linode上安装WordPress。WordPress是一个流行的动态内容管理系统,专注于博客。Wor...

4432

扫码关注云+社区

领取腾讯云代金券