在去年年末参与的一个项目中,项目技术栈使用
,生产环境全量构建将近三分钟,项目业务模块多达数百个,项目依赖数千个,并且该项目协同前后端开发人员较多,提高 webpack 构建效率,成为了改善团队开发效率的关键之一。
- react+es6+ant-design+webpack+babel
下面我将在项目中遇到的问题和技术方案沉淀出来与大家做个分享
我们的项目是将 js 分离,不同页面加载不同的 js。然而分析 webpack 打包过程并针对性提出优化方案是一个比较繁琐的过程,首先我们需要知道 webpack 打包的流程,从而找出时间消耗比较长的步骤,进而逐步进行优化。
在优化前,我们需要找出性能瓶颈在哪,代码组织是否合理,优化相关配置,从而提升 webpack 构建速度。
1. 使用 yarn 而不是 npm
由于项目使用 npm 安装包,容易导致在多关联依赖关系中,很可能某个库在指定依赖时没有指定版本号,进而导致不同设备上拉到的 package 版本不一。yarn 不管安装顺序如何,相同的依赖关系将以相同的方式安装在任何机器上。当关联依赖中包括对某个软件包的重复引用,在实际安装时将尽量避免重复的创建。yarn 不仅可以缓存它安装过的包,而且安装速度快,使用 yarn 无疑可以很大程度改善工作流和工作效率
2. 删除没有使用的依赖
很多时候,我们由于项目人员变动比较大,参与项目的人也比较多,在分析项目时,我发现了一些问题,诸如:有些文件引入进来的库没有被使用到也没有及时删除,例如:
- import a from 'abc';
在业务中并没有使用到
模块,但 webpack 会针对该
- a
进行打包一遍,这无疑造成了性能的浪费。
- import
1. 打包过程分析
我们知道,webpack 在打包过程中会针对不同的资源类型使用不同的 loader 处理,然后将所有静态资源整合到一个 bundle 里,以实现所有静态资源的加载。webpack 最初的主要目的是在浏览器端复用符合 CommonJS 规范的代码模块,而 CommonJS 模块每次修改都需要重新构建 (rebuild) 后才能在浏览器端使用。
那么, webpack 是如何进行资源的打包的呢?总结如下:
我们的项目使用的就是多入口文件。在入口文件中,webpack 会对每个资源文件进行配置一个 id,即使多次加载,它的 id 也是一样的,因此只会打包一次。
实例如下:
main.js 引用了 chunk1、chunk2,chunk1 又引用了 chunk2,打包后:bundle.js:
- ...省略webpack生成代码
- /************************************************************************/
- /******/
- ([
- /* 0 */
- /***/
- function(module, exports, __webpack_require__) {
- __webpack_require__(1); //webpack分配的id
- __webpack_require__(2);
- /***/
- },
- /* 1 */
- /***/
- function(module, exports, __webpack_require__) {
- //chunk1.js文件
- __webpack_require__(2);
- var chunk1 = 1;
- exports.chunk1 = chunk1;
- /***/
- },
- /* 2 */
- /***/
- function(module, exports) {
- //chunk2.js文件
- var chunk2 = 1;
- exports.chunk2 = chunk2;
- /***/
- }
- /******/
- ]);
2. 如何定位 webpack 打包速度慢的原因
我们首先需要定位 webpack 打包速度慢的原因,才能因地制宜采取合适的方案。我么可以在终端中输入:
- $ webpack --profile --json > stats.json
然后将输出的 json 文件到如下两个网站进行分析
这两个网站可以将构建后的组成用可视化的方式呈现出来,可以让你清楚的看到模块的组成部分,以及在项目中可能存在的多版本引用的问题,对于分析项目依赖有很大的帮助
针对 webpack 构建大规模应用的优化往往比较复杂,我们需要抽丝剥茧,从性能提升点着手,可能没有一套通用的方案,但大体上的思路是通用的,核心思路可能包括但不限于如下:
1):拆包,限制构建范围,减少资源搜索时间,无关资源不要参与构建
2):使用增量构建而不是全量构建
3):从 webpack 存在的不足出发,优化不足,提升效率
webpack+react 的项目打包出来的文件经常动则几百 kb 甚至上兆,究其原因有:
针对第一种情况,我们可以使用
,但缺点是会产生更长时间的编译,也没有 HMR,还会增加额外的 HTTP 请求。对于 css 文件不是很大的情况最好还是不要使用该插件。
- extract-text-webpack-plugin
针对第二种情况,我们可以通过提取公共代码块,这也是比较普遍的做法:
- new webpack.optimize.CommonsChunkPlugin('common.js');
通过这种方法,我们可以有效减少不同入口文件之间重叠的代码,对于非单页应用来说非常重要。
针对第三种情况,我们可以把 React、ReactDOM 缓存起来:
- entry: {
- vendor: ['react', 'react-dom']
- },
- new webpack.optimize.CommonsChunkPlugin('vendor', 'common.js'),
我们在开发环境使用 react 的开发版本,这里包含很多注释,警告等等,部署线上的时候可以通过
来切换生产版本。
- webpack.DefinePlugin
当然,我们还可以将 React 直接放到 CDN 上,以此来减少体积。
webpack 提供的 UglifyJS 插件由于采用单线程压缩,速度很慢 ,
插件可以并行运行 UglifyJS 插件,这可以有效减少构建时间,当然,该插件应用于生产环境而非开发环境,配置如下:
- webpack-parallel-uglify-plugin
- var ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
- new ParallelUglifyPlugin({
- cacheDir: '.cache/',
- uglifyJS:{
- output: {
- comments: false
- },
- compress: {
- warnings: false
- }
- }
- })
的原理是让 loader 可以多进程去处理文件,原理如图示:
此外,happypack 同时还利用缓存来使得 rebuild 更快
- var HappyPack = require('happypack'),
- os = require('os'),
- happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
- modules: {
- loaders: [
- {
- test: /\.js|jsx$/,
- loader: 'HappyPack/loader?id=jsHappy',
- exclude: /node_modules/
- }
- ]
- }
- plugins: [
- new HappyPack({
- id: 'jsHappy',
- cache: true,
- threadPool: happyThreadPool,
- loaders: [{
- path: 'babel',
- query: {
- cacheDirectory: '.webpack_cache',
- presets: [
- 'es2015',
- 'react'
- ]
- }
- }]
- }),
- //如果有单独提取css文件的话
- new HappyPack({
- id: 'lessHappy',
- loaders: ['style','css','less']
- })
- ]
由于项目中主要使用的是 react.js 和 es6,结合 webpack 的 babel-loader 加载器进行编译,每次重新构建都需要重新编译一次,我们可以针对这个进行增量构建,而不需要每次都全量构建。
可以缓存处理过的模块,对于没有修改过的文件不会再重新编译,
- babel-loader
有着 2 倍以上的速度提升,这对于 rebuild 有着非常大的性能提升。
- cacheDirectory
- var node_modules = path.resolve(__dirname, 'node_modules');
- var pathToReact = path.resolve(node_modules, 'react/react');
- var pathToReactDOM = path.resolve(node_modules,'react-dom/index');
- {
- test: /\.js|jsx$/,
- include: path.join(__dirname, 'src'),
- exclude: /node_modules/,
- loaders: ['react-hot','babel-loader?cacheDirectory'],
- noParse: [pathToReact,pathToReactDOM]
- }
让除了
- babel-loader
目录下的 js 文件都支持 es6 语法,注意
- node_modules
很重要,否则 babel 可能会把
- exclude: /node_modules/
中所有模块都用 babel 编译一遍!
- node_modules
当然,你还需要一个像这样的
文件,配置如下:
- .babelrc
- {
- "presets": ["es2015", "stage-0", "react"],
- "plugins": ["transform-runtime"]
- }
这是一劳永逸的做法,何乐而不为呢?除此之外,我们还可以使用 webpack 自带的 cache,以缓存生成的模块和 chunks 以提高多个增量构建的性能。
在 webpack 的整个构建过程中,有多个地方提供了缓存的机会,如果我们打开了这些缓存,会大大加速我们的构建
而针对增量构建 ,我们一般使用:
webpack-dev-server 或 webpack-dev-middleware,这里我们使用
:
- webpack-dev-middleware
- webpackDevMiddleware(compiler, {
- publicPath: webpackConfig.output.publicPath,
- stats: {
- chunks: false,
- colors: true
- },
- debug: true,
- hot: true,
- lazy: false,
- historyApiFallback: true,
- poll: true
- })
通过设置
,可以将控制台输出的代码块信息关闭
- chunks:false
为了加快 webpack 打包时对资源的搜索速度,有很多的做法:
大多数路径应该使用
,只对嵌套的路径使用
- resolve.root
,这可以获得显著的性能提升
- Resolove.moduledirectories
原因是
是取相对路径,所以比起
- Resolove.moduledirectories
会多 parse 很多路径:
- resolve.root
- resolve: {
- root: path.resolve(__dirname, 'src'),
- modulesDirectories: ['node_modules']
- },
针对第三方 NPM 包,这些包我们并不会修改它,但仍然每次都要在 build 的过程消耗构建性能,我们可以通过 DllPlugin 来前置这些包的构建,具体实例:
是 webpack 的一个配置项,它的作用是把用户的一个请求重定向到另一个路径。 比如:
- resolve.alias
- resolve: { // 显示指出依赖查找路径
- alias: {
- comps: 'src/pages/components'
- }
- }
这样我们在要打包的脚本中的使用
其实就等价于
- require('comps/Loading.jsx');
。
- require('src/pages/components/Loading.jsx')
webpack 默认会去寻找所有 resolve.root 下的模块,但是有些目录我们是可以明确告知 webpack 不要管这里,从而减轻 webpack 的工作量。这时会用到
参数
- module.noParse
在项目中合理使用 alias 和 noParse 可以有效提升效率,虽然不是很明显
以上配置均由本人给出,仅供参考(有些插件的官方文档给的不是那么明晰)
- //css-loader 0.16.0
- Hash: 8d3652a9b4988c8ad221
- Version: webpack 1.11.0
- Time: 51612ms
- //以下是css-loader 0.14.5
- Hash: bd471e6f4aa10b195feb
- Version: webpack 1.11.0
- Time: 6121ms
插件来按需加载模块
- babel-plugin-import
虽然上面的做法减少了文件体积,加快了编译速度,整体构建 (initial build) 从最初的三分多钟到一分钟,rebuild 十多秒,优化效果明显。但对于 Webpack + React 项目来说,性能优化方面远不止于此,还有很多的优化空间,比如服务端渲染,首屏优化,异步加载模块,按需加载,代码分割等等
来源: http://www.tuicool.com/articles/iMnyuyy