Webpack应该是当下流行度最广的JavaScript构建、打包工具了。我们团队中大部分项目也在使用Webpack构建。项目的是传统的非SPA页面,我们使用了CommonsChunkPlugin来提取公共模块,保证各页面之间部分公共库可以复用缓存,同时使用UglifyJS等来保证输出文件体积的减小。原本Webpack这部分的配置是这样的:
{
// ...
plugins: [
// ...
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: 5,
})
],
}
但这么做粒度还太粗,无法对打包的文件做更精细的控制。庆幸的是Webpack生态圈是如此的丰富,有不少好工具可以利用。
webpack-bundle-analyzer是一个非常好用的Webpack包分析工具。可以将每个文件包含的内容通过非常漂亮的图片表现出来。
我们在production模式的webpack配置下引入插件:
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
// ...
plugins: [
// ...
new BundleAnalyzerPlugin(),
]
// ...
通过插件生成的包结构如下:
可以发现这里有两个严重的问题:
其他常见的问题包括vendor中打包入了过多文件,多次引入了不同版本的库文件等。
首先我们来把非强依赖的模块异步化。
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等库实现同样的效果。
可以发现部分可以公用的文件并没有合到vendor里来。简单调整一下配置:
{
// ...
plugins: [
// ...
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: 4,
})
],
}
值得注意的是,我们需要谨慎权衡minChunks的取值,过小的取值将造成vendor无意义的变大。大部分情况下我并不推荐使用 ~minChunks~。这是由于我们一般希望vendor是稳定的,缓存可长时间使用。如果合并入太多业务代码,vendor的缓存复用率会大为降低,对更新频繁的项目来说对性能反而有损耗。
最后的结果如下图,相比优化前已经大幅改善了。
production
。一般也需要增加 DefinePlugin
设置。