webpack前段构建性能优化策略小结

时间:2022-12-19 15:21:46
方案一、合理配置CommonsChunkPlugin
   webpack的资源入口通常是以entry为单元进行编译提取,那么当多entry共存的时候,CommonsChunkPlugin的作用就会发挥出来,对所有依赖的chunk进行公共部分的提取,但是在这里可能很多人会误认为抽取公共部分指的是能抽取某个代码片段,其实并非如此,它是以module为单位进行提取。假设我们的页面中存在entry1,entry2,entry3三个入口,这些入口中可能都会引用如utils,loadash,fetch等这些通用模块,那么就可以考虑对这部分的共用部分机提取。通常提取方式有如下四种实现:
1、传入字符串参数,由chunkplugin自动计算提取

new webpack.optimize.CommonsChunkPlugin('common.js')

这种做法默认会把所有入口节点的公共代码提取出来, 生成一个common.js

2、有选择的提取公共代码

new webpack.optimize.CommonsChunkPlugin('common.js',['entry1','entry2']);  

只提取entry1节点和entry2中的共用部分模块, 生成一个common.js

3、将entry下所有的模块的公共部分(可指定引用次数)提取到一个通用的chunk中
new webpack.optimize.CommonsChunkPlugin({  
    name: 'vendors',
    minChunks: function (module, count) {
       return (
          module.resource &&
          /.js$/.test(module.resource) &&
          module.resource.indexOf(
            path.join(__dirname, '../node_modules')
          ) === 0
       )
    }
});
提取所有node_modules中的模块至vendors中,也可以指定minChunks中的最小引用数;
4、抽取enry中的一些lib抽取到vendors中
entry = {  
    vendors: ['fetch', 'loadash']
};
new webpack.optimize.CommonsChunkPlugin({  
    name: "vendors",
    minChunks: Infinity
});

添加一个entry名叫为vendors,并把vendors设置为所需要的资源库,CommonsChunk会自动提取指定库至vendors中。

方案二:通过externals配置来提取常用库

在实际项目开发过程中,我们并不需要实时调试各种库的源码,这时候就可以考虑使用external选项了。

简单来说external就是把我们的依赖资源声明为一个外部依赖,然后通过script外链脚本引入。这也是我们早期页面开发中资源引入的一种翻版,只是通过配置后可以告知webapck遇到此类变量名时就可以不用解析和编译至模块的内部文件中,而改用从外部变量中读取,这样能极大的提升编译速度,同时也能更好的利用CDN来实现缓存。
1、在页面中加入需要引入的lib地址如下:
webpack前段构建性能优化策略小结

2 在webpack.config.js中加入external配置项:

webpack前段构建性能优化策略小结


3 非常重要的是一定要在output选项中加入如下一句话:

output:{

   libraryTarget:"umd"

}

 由于通过external提取过的js模块是不会被记录到webapck的chunk信息中,通过libraryTarget可告知我们构建出来的业务模块,当读到了externals中的key时,需要以umd的方式去获取资源名,否则会有出现找不到module的情况。


 方案三、利用DllPlugin和DllReferencePlugin预编译资源模块

    简单来说DllPlugin的作用是预先编译一些模块,而DllReferencePlugin则是把这些预先编译好的模块引用起来。这边需要注意的是DllPlugin必须要在DllReferencePlugin执行前先执行一次,dll这个概念应该也是借鉴了windows程序开发中的dll文件的设计理念。相对于externals,dllPlugin有如下几点优势:dll预编译出来的模块可以作为静态资源链接库可被重复使用,尤其适合多个项目之间的资源共享,如同一个站点pc和手机版等;dll资源能有效地解决资源循环依赖的问题,部分依赖库如:react-addons-css-transition-group这种原先从react核心库中抽取的资源包,整个代码只有一句话:

module.exports = require('react/lib/ReactCSSTransitionGroup');  

 却因为重新指向了react/lib中,这也会导致在通过externals引入的资源只能识别react,寻址解析react/lib则会出现无法被正确索引的情况。由于externals的配置项需要对每个依赖库进行逐个定制,所以每次增加一个组件都需要手动修改,略微繁琐,而通过dllPlugin则能完全通过配置读取,减少维护的成本;
1、配置dllPlugin对应资源表并编译文件
那么externals该如何使用呢,其实只需要增加一个配置文件:webpack.dll.config.js:

const webpack = require('webpack');  
const path = require('path');  
const isDebug = process.env.NODE_ENV === 'development';  
const outputPath = isDebug ? path.join(__dirname, '../common/debug')  
: path.join(__dirname, '../common/dist');
const fileName = '[name].js';
 
// 资源依赖包,提前编译
const lib = [  
  'react',
  'react-dom',
  'react-router',
  'history',
  'react-addons-pure-render-mixin',
  'react-addons-css-transition-group',
  'redux',
  'react-redux',
  'react-router-redux',
  'redux-actions',
  'redux-thunk',
  'immutable',
  'whatwg-fetch',
  'byted-people-react-select',
  'byted-people-reqwest'
];
 
const plugin = [  
  new webpack.DllPlugin({
    /**
     * path
     * 定义 manifest 文件生成的位置
     * [name]的部分由entry的名字替换
     */
    path: path.join(outputPath, 'manifest.json'),
    /**
     * name
     * dll bundle 输出到那个全局变量上
     * 和 output.library 一样即可。
     */
    name: '[name]',
    context: __dirname
  }),
  new webpack.optimize.OccurenceOrderPlugin()
];
 
if (!isDebug) {  
  plugin.push(
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('production')
    }),
    new webpack.optimize.UglifyJsPlugin({
      mangle: {
        except: ['$', 'exports', 'require']
      },
      compress: { warnings: false },
      output: { comments: false }
    })
  )
}
 
module.exports = {  
  devtool: '#source-map',
  entry: {
    lib: lib
  },
  output: {
    path: outputPath,
    filename: fileName,
    /**
     * output.library
     * 将会定义为 window.${output.library}
     * 在这次的例子中,将会定义为`window.vendor_library`
     */
    library: '[name]',
    libraryTarget: 'umd',
    umdNamedDefine: true
  },
  plugins: plugin
};

然后执行命令:

$ NODE_ENV=development webpack --config  webpack.dll.lib.js --progress
$ NODE_ENV=production webpack --config  webpack.dll.lib.js --progress


 2、dllPlugin的静态资源引入
 lib.js和manifest.json存在一一对应的关系,所以我们在调用的过程也许遵循这个原则,如当前处于开发阶段,对应我们可以引入common/debug文件夹下的lib.js和manifest.json,切换到生产环境的时候则需要引入common/dist下的资源进行对应操作,这里考虑到手动切换和维护的成本,我们推荐使用add-asset-html-webpack-plugin进行依赖资源的注入,可得到如下结果:

<head>  
<script src="/static/common/lib.js"></script>  
</head>

在webpack.config.js文件中增加如下代码:

const isDebug = (process.env.NODE_ENV === 'development');  
const libPath = isDebug ? '../dll/lib/manifest.json' :  
'../dll/dist/lib/manifest.json';
 
// 将mainfest.json添加到webpack的构建中
 
module.export = {  
  plugins: [
       new webpack.DllReferencePlugin({
       context: __dirname,
       manifest: require(libPath),
        })
  ]
}

多个工程之间如果需要使用共同的lib资源,也只需要引入对应的lib.js和manifest.js即可,plugin配置中也支持多个webpack.DllReferencePlugin同时引入使用,如下:

module.export = {  
  plugins: [
     new webpack.DllReferencePlugin({
        context: __dirname,
        manifest: require(libPath),
        }),
        new webpack.DllReferencePlugin({
        context: __dirname,
        manifest: require(ChartsPath),
        })
  ]
}

方案四 使用Happypack加速你的代码构建

 众所周知,webpack中为了方便各种资源和类型的加载,设计了以loader加载器的形式读取资源,但是受限于node的编程模型影响,所有的loader虽然以async的形式来并发调用,但是还是运行在单个 node的进程以及在同一个事件循环中,这就直接导致了当我们需要同时读取多个loader文件资源时,比如babel-loader需要transform各种jsx,es6的资源文件。在这种同步计算同时需要大量耗费cpu运算的过程中,node的单进程模型就无优势了,那么happypack就针对解决此类问题而生。
开启happypack的线程池
happypack的处理思路是将原有的webpack对loader的执行过程从单一进程的形式扩展多进程模式,原本的流程保持不变,这样可以在不修改原有配置的基础上来完成对编译过程的优化,具体配置如下:

var HappyPack = require('happypack');  
var happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
 
module: {  
  loaders: [
      {
        test: /.js[x]?$/,
        // loader: 'babel-loader?presets[]=es2015&presets[]=react'
        loader: 'happypack/loader?id=happybabel'
      }
    ]
  },
  plugins: [
      new HappyPack({
        id: 'happybabel',
        loaders: ['babel-loader'],
        threadPool: happyThreadPool,
        cache: true,
        verbose: true
      })
  ]

以上内容通过参考其他类似文章以及结合自身的所学汇总而成,不是很全面后期学到新的知识会继续补全,不断学习希望自己能够在前端这条路上越走越远!!!!