likes
comments
collection
share

提高webpack的打包速度:happypack和dll打包

作者站长头像
站长
· 阅读数 6

提高webpack的打包速度:happypack和dll打包

背景

本人一直在用React开发一个后台管理系统,那天不小心加了个富文本编辑器(github.com/jpuri/react…) 之后,webpack打包的速度就直线下降,觉得是时候要开始一波优化了。

当前的配置是8G内存,i5CPU的mac,node v8.2.1 , webpack 3.5.5,在这个配置下打包一次的数据是

Compiled successfully in 46.5s.

File sizes after gzip:

  296.77 KB         dist/js/common.js
  149.49 KB (-1 B)  dist/js/atadd.997ff5.js
  34.06 KB          dist/css/index.css
  15.32 KB (-8 B)   dist/js/cpadd.5c7f29.js
  4.45 KB           dist/js/cpsupple.9fe38b.js
  3.3 KB (+13 B)    dist/js/cplist.3a5f02.js
  3.08 KB           dist/js/cpview.e68fe9.js
  2.91 KB           dist/js/atview.2a5aac.js
  2.89 KB           dist/js/atlist.6d166c.js
  2.24 KB           dist/js/atuser.30b019.js
  295 B             dist/js/activity.4e286a.js
  291 B             dist/js/coupon.3dd30f.js

happypack 多线程打包

一般情况下,js是单线程执行的,但node不是。利用node提供的多线程环境,happypack 是可以多线程打包的。基本上打开官网看了一下readme就可以配置了,特别是我只针对js的编译进行优化,配置还是比较简单的。

    {
      test:/\.js$/,
      loader:'happypack/loader',
    }
    //...上面修改了js的loader,下面也相应增加一个即可
    new HappyPack({
        loaders:....,//原来的babel相关配置
    })

这里 有一篇happypack原理的解释文章,就不赘言了。应用了happypack后的打包一次的数据如下:

Compiled successfully in 45.7s.

File sizes after gzip:

  296.82 KB               dist/js/common.js
  150.06 KB (+144.13 KB)  dist/js/atadd.5afe2c.js
  34.06 KB                dist/css/index.css
  15.32 KB                dist/js/cpadd.5c7f29.js
  4.45 KB                 dist/js/cpsupple.9fe38b.js
  3.3 KB                  dist/js/cplist.3a5f02.js
  3.08 KB                 dist/js/cpview.e68fe9.js
  2.91 KB                 dist/js/atview.2a5aac.js
  2.89 KB                 dist/js/atlist.6d166c.js
  2.24 KB                 dist/js/atuser.30b019.js
  295 B                   dist/js/activity.4e286a.js
  291 B                   dist/js/coupon.3dd30f.js

好像并没有多大区别 😂

dll打包

回归到原始的问题,在加富文本编辑器后特别的慢,所以我们应该要把这个富文本编辑器特别提取出来,不要每次打包都分析它。webpack本身提供这种功能,叫dll打包,需要两个插件 DllPluginDllReferencePlugin 。前者是用来打包dll.js,后者是用在打包主流程时引用刚才的dll.js的。具体配置可以参考官网。

我应用dll打包了两个文件,一个是vendor文件,一个是editor文件,相关配置如下

//webpack.config.dll.js
const path = require('path'); 
const webpack=require('webpack');
const env = 'production';

const vendors=[
    'react',
    'react-dom',
    'react-router',
    'history',
    'redux',
    'dva',
    'axios',
    'qs',
    'moment',
    'styled-components'
];

const editor = [
    'react-draft-wysiwyg',
    'draftjs-to-html',
    'html-to-draftjs',
    'draft-js'
]

const libname = '[name]_lib'

module.exports={
    entry:{
        'vendor':vendors,
        'editor':editor,
    },
    output:{
        path:path.join(__dirname, 'dll'),
        filename:'[name].[hash:4].dll.js',
        library:libname,
    },
    plugins:[
        new webpack.HashedModuleIdsPlugin(), //保持其他包的hash不会变
        new webpack.optimize.CommonsChunkPlugin({
            names:['vendor'],
            minChunks:2, //把编辑器里面关于react那部分代码提取到vendor
          }),
        new webpack.DllPlugin({
            path: path.resolve(__dirname, 'dll', 'manifest-[name].json'),
            name: libname,
            context:process.cwd() //是解析包路径的上下文,这个要和 webpack.config.js 一致。
        }),
        ...uglify, //uglifyjs及其他在生产环境应该要用的插件配置
    ]
}

这样再运行 webpack -p --config ./config/webpack.config.dll.js即可生成 vendor.dll.jseditor.dll.js

其中vendor.dll.js是基础文件,一进入页面就要引入的,所以直接在html里面加入即可

//webpack.config.js

const dir = fs.readdirSync(path.resolve(__dirname,'dll')); //因为文件名带hash,所以要用程序读出来

new HtmlWebpackPlugin({
    ...otherConfig,
    scripts: env === 'production' ? dir.map(file=>{
      if(/vendor\..{4}\.dll\.js$/.test(file)){
        return `${publicPath}js/${file}` //publicPath是全局配置的publicPath
      }
    }): [xxx],

动态载入dll脚本

还有一个editor.dll.js文件,gzip后有160多k,是需要富文本编辑器的页面才需要用到,我并不想在页面一载入的时候就用,于是就想了个黑方法,在页面第一次加载编辑器时,异步引入该dll文件再进行渲染。关键代码如下:

import Async from 'react-code-splitting';

let editorScriptloaded = __DEV__; //编辑器的dll脚本载入完成,在开发状态下永远为真
let editorScriptStartload = false; //编辑器的dll脚本开始载入

export default class AsyncEditor extends React.Component {
  render(){
    return <Async load={new Promise((res,rej)=>{
      if(typeof window !== 'undefined'){
        if(editorScriptloaded){
          resolveEditor(res, rej);
        }else if(!editorScriptStartload){
          editorScriptStartload = true;
          var doc = window.document;
          var s = doc.createElement('script');
          s.src=__EDITOR_URL__; //这是一个用DefinePlugin定义的变量
          s.onload = function(){
            editorScriptloaded = true;
            resolveEditor(res, rej);
          };
          doc.body.appendChild(s);
        }
      }else{
        res(null)
      }
    })} componentProps={this.props}/>
  }
}

function resolveEditor(res, rej){
  @res
  class RichEditor extends React.Component {
  //......
}

这样只需要引入AsyncEditor,像正常的React组件那样使用,就会自动把依赖的dll文件动态加载进来,而且只会加载一次。

优化结果

不计两个dll文件的打包,时间一下子就减少很多了,结果如下

Compiled successfully in 25.3s.

File sizes after gzip:

  172.04 KB             dist/js/common.js
  160.28 KB             dist/js/editor.7517.dll.js
  130.92 KB             dist/js/vendor.7517.dll.js
  34.06 KB              dist/css/index.css
  15.32 KB              dist/js/cpadd.be002d.js
  5.93 KB (-144.13 KB)  dist/js/atadd.18beb0.js
  4.45 KB               dist/js/cpsupple.9fe38b.js
  3.3 KB                dist/js/cplist.3a5f02.js
  3.08 KB               dist/js/cpview.e68fe9.js
  2.91 KB               dist/js/atview.2a5aac.js
  2.89 KB               dist/js/atlist.6d166c.js
  2.24 KB               dist/js/atuser.30b019.js
  295 B                 dist/js/activity.4e286a.js
  291 B                 dist/js/coupon.3dd30f.js

效率提高了46%! 😀 细心的同学会发现后来的打包的文件会有细微的增大,但是相比起打包的时间,这点还是可以接受的。如果想要减少体积,react-draft-wysiwyg中有很多国际化的文件,还有draft-js也不支持tree shaking,我们可以在后续的时候再挖掘这些优化点。