浅谈webpack 构建性能优化策略小结

前端之家收集整理的这篇文章主要介绍了浅谈webpack 构建性能优化策略小结前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

背景

如今前端工程化的概念早已经深入人心,选择一款合适的编译和资源管理工具已经成为了所有前端工程中的标配,而在诸多的构建工具中,webpack以其丰富的功能和灵活的配置而深受业内吹捧,逐步取代了grunt和gulp成为大多数前端工程实践中的首选,React,Vue,Angular等诸多知名项目也都相继选用其作为官方构建工具,极受业内追捧。但是,随者工程开发的复杂程度和代码规模不断地增加,webpack暴露出来的各种性能问题也愈发明显,极大的影响着开发过程中的体验。

问题归纳

历经了多个web项目的实战检验,我们对webapck在构建中逐步暴露出来的性能问题归纳主要有如下几个方面:

  • 代码全量构建速度过慢,即使是很小的改动,也要等待长时间才能查看到更新与编译后的结果(引入HMR热更新后有明显改进);
  • 随着项目业务的复杂度增加,工程模块的体积也会急剧增大,构建后的模块通常要以M为单位计算;
  • 多个项目之间共用基础资源存在重复打包,基础库代码复用率不高;
  • node的单进程实现在耗cpu计算型loader中表现不佳;

针对以上的问题,我们来看看怎样利用webpack现有的一些机制和第三方扩展插件来逐个击破。

慢在何处

作为工程师,我们一直鼓励要理性思考,用数据和事实说话,“我觉得很慢”,“太卡了”,“太大了”之类的表述难免显得太笼统和太抽象,那么我们不妨从如下几个方面来着手进行分析:

  • 从项目结构着手,代码组织是否合理,依赖使用是否合理;
  • 从webpack自身提供的优化手段着手,看看哪些api未做优化配置;
  • 从webpack自身的不足着手,做有针对性的扩展优化,进一步提升效率;

在这里我们推荐使用一个wepback的可视化资源分析工具:webpack-bundle-analyzer,在webpack构建的时候会自动帮你计算出各个模块在你的项目工程中的依赖与分布情况,方便做更精确的资源依赖和引用的分析。

从上图中我们不难发现大多数的工程项目中,依赖库的体积永远是大头,通常体积可以占据整个工程项目的7-9成,而且在每次开发过程中也会重新读取和编译对应的依赖资源,这其实是很大的的资源开销浪费,而且对编译结果影响微乎其微,毕竟在实际业务开发中,我们很少会去主动修改第三方库中的源码,改进方案如下:

方案一、合理配置 CommonsChunkPlugin

webpack的资源入口通常是以entry为单元进行编译提取,那么当多entry共存的时候,CommonsChunkPlugin的作用就会发挥出来,对所有依赖的chunk进行公共部分的提取,但是在这里可能很多人会误认为抽取公共部分指的是能抽取某个代码片段,其实并非如此,它是以module为单位进行提取

假设我们的页面中存在entry1,entry2,entry3三个入口,这些入口中可能都会引用如utils,loadash,fetch等这些通用模块,那么就可以考虑对这部分的共用部分机提取

通常提取方式有如下四种实现:

1、传入字符串参数,由chunkplugin自动计算提取

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

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

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

3、将entry下所有的模块的公共部分(可指定引用次数提取到一个通用的chunk中

提取所有node_modules中的模块至vendors中,也可以指定minChunks中的最小引用数;

4、抽取enry中的一些lib抽取到vendors中

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

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

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

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

external的配置相对比较简单,只需要完成如下三步:

1、在页面中加入需要引入的lib地址,如下:

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

这里要提到的一个细节是:此类文件在配置前,构建这些资源包时需要采用amd/commonjs/cmd相关的模块化进行兼容封装,即打包好的库已经是umd模式包装过的,如在node_modules/react-router中我们可以看到umd/ReactRouter.js之类的文件,只有这样webpack中的require和import * from 'xxxx'才能正确读到该类包的引用,在这类js的头部一般也能看到如下字样:

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

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

通过配置后,我们可以看到对应的资源信息已经可以在浏览器的source map中读到了。

对应的资源也可以直接由页面外链载入,有效地减小了资源包的体积。

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

我们的项目依赖中通常会引用大量的npm包,而这些包在正常的开发过程中并不会进行修改,但是在每一次构建过程中却需要反复的将其解析,如何来规避此类损耗呢?这两个插件就是干这个用的。

简单来说DllPlugin的作用是预先编译一些模块,而DllReferencePlugin则是把这些预先编译好的模块引用起来。这边需要注意的是DllPlugin必须要在DllReferencePlugin执行前先执行一次,dll这个概念应该也是借鉴了windows程序开发中的dll文件的设计理念。

相对于externals,DllPlugin有如下几点优势:

  1. dll预编译出来的模块可以作为静态资源链接库可被重复使用,尤其适合多个项目之间的资源共享,如同一个站点pc和手机版等;
  2. dll资源能有效地解决资源循环依赖的问题,部分依赖库如:react-addons-css-transition-group这种原先从react核心库中抽取的资源包,整个代码只有一句话:

却因为重新指向了react/lib中,这也会导致在通过externals引入的资源只能识别react,寻址解析react/lib则会出现无法被正确索引的情况。

由于externals的配置项需要对每个依赖库进行逐个定制,所以每次增加一个组件都需要手动修改,略微繁琐,而通过DllPlugin则能完全通过配置读取,减少维护的成本;

1、配置DllPlugin对应资源表并编译文件

那么externals该如何使用呢,其实只需要增加一个配置文件:webpack.dll.config.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
    };

后执行命令:

即可分别编译出支持调试版和生产环境中lib静态资源库,在构建出来的文件中我们也可以看到会自动生成如下资源:

文件说明:

  1. lib.js可以作为编译好的静态资源文件直接在页面中通过src链接引入,与externals的资源引入方式一样,生产与开发环境可以通过类似charles之类的代理转发工具来做路由替换;
  2. manifest.json中保存了webpack中的预编译信息,这样等于提前拿到了依赖库中的chunk信息,在实际开发过程中就无需要进行重复编译;

2、DllPlugin的静态资源引入

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

猜你在找的JavaScript相关文章