性能优化篇之Webpack构建速度优化的建议


Posted in Javascript onApril 03, 2019

如何输出Webpack构建分析

输出Webpack构建信息的.json文件:webpack --profile --json > stats.json

  1. --profile:记录构建中的耗时信息
  2. --json:以json格式输出构建结果,最后只输出一个json文件(包含所有的构建信息)

web可视化查看构建分析:得到了webpack构建信息文件stats.json,如何进行很好的可视化查看?

  1. 方案一:通过可视化分析工具Webpack Analyse,是个在线Web应用,上传stats.json文件就可以;不过好像需要翻墙;
  2. 方案二:安装webpack-bundle-analyzer工具npm i -g webpack-bundle-analyzer,生成stats.json后直接在其文件夹目录执行webpack-bundle-analyzer后,浏览器会打开对应网页并展示构建分析webpack-bundle-analyzer stats.json -p 8888文档地址webpack-bundle-analyzer
  3. webpack-dashboard是一款统计和优化webpack日志的工具,可以以表格形势展示日志信息。其中包括构建过程和状态、日志以及涉及的模块列表
  4. jarvis是一款基于webapck-dashboard的webpack性能分析插件,性能分析的结果在浏览器显示,比webpack-bundler-anazlyer更美观清晰GitHub文档地址

npm i -D webpack-jarvis

webpack.config.js配置:

const Jarvis = require("webpack-jarvis");

plugins: [
 new Jarvis({
  watchOnly: false,
  port: 3001 // optional: set a port
 })
];

port:监听的端口,默认1337,监听面板将监听这个端口,通常像http://localhost:port/

host:域名,默认localhost,不限制域名。

watchOnly:仅仅监听编译阶段。默认为true,如果高为false,jarvis不仅仅运行在编译阶段,在编译完成后还保持运行状态。

界面:看到构建时间为:Time: 11593ms(作为优化时间对比)

性能优化篇之Webpack构建速度优化的建议

webpack配置优化

webpack在启动时会从配置的Entry出发,解析出文件中的导入语句,再递归解析。

对于导入语句Webpack会做出以下操作:

  1. 根据导入语句寻找对应的要导入的文件;
  2. 在根据要导入的文件后缀,使用配置中的Loader去处理文件(如使用ES6需要使用babel-loader处理)

针对这两点可以优化查找途径

1、优化Loader配置

Loader处理文件的转换操作是很耗时的,所以需要让尽可能少的文件被Loader处理

{
  test: /\.js$/,
  use: [
    'babel-loader?cacheDirectory',//开启转换结果缓存
  ],
  include: path.resolve(__dirname, 'src'),//只对src目录中文件采用babel-loader
  exclude: path.resolve(__dirname,' ./node_modules'),//排除node_modules目录下的文件
},

2、优化resolve.modules配置

resolve.modules用于配置webpack去哪些目录下寻找第三方模块,默认是['node_modules'],但是,它会先去当前目录的./node_modules查找,没有的话再去../node_modules最后到根目录;

所以当安装的第三方模块都放在项目根目录时,就没有必要安默认的一层一层的查找,直接指明存放的绝对位置

resolve: {
    modules: [path.resolve(__dirname, 'node_modules')],
  }

3、优化resolve.extensions配置

在导入没带文件后缀的路径时,webpack会自动带上后缀去尝试询问文件是否存在,而resolve.extensions用于配置尝试后缀列表;默认为extensions:['js','json'];

及当遇到require('./data')时webpack会先尝试寻找data.js,没有再去找data.json;如果列表越长,或者正确的后缀越往后,尝试的次数就会越多;

所以在配置时为提升构建优化需遵守:

  1. 频率出现高的文件后缀优先放在前面;
  2. 列表尽可能的小;
  3. 书写导入语句时,尽量写上后缀名

因为项目中用的jsx较多,所以配置extensions: [".jsx",".js"],

基本配置后查看构建速度:Time: 10654ms;配置前为Time: 11593ms

使用DllPlugin优化

在使用webpack进行打包时候,对于依赖的第三方库,如react,react-dom等这些不会修改的依赖,可以让它和业务代码分开打包;

只要不升级依赖库版本,之后webpack就只需要打包项目业务代码,遇到需要导入的模块在某个动态链接库中时,就直接去其中获取;而不用再去编译第三方库,这样第三方库就只需要打包一次。

接入需要完成的事:

  1. 将依赖的第三方模块抽离,打包到一个个单独的动态链接库中
  2. 当需要导入的模块存在动态链接库中时,让其直接从链接库中获取
  3. 项目依赖的所有动态链接库都需要被加载

接入工具(webpack已内置)

DllPlugin插件:用于打包出一个个单独的动态链接库文件;

DllReferencePlugin:用于在主要的配置文件中引入DllPlugin插件打包好的动态链接库文件

配置webpack_dll.config.js构建动态链接库

const path = require('path');
const DllPlugin = require('webpack/lib/DllPlugin');

module.exports = {
  mode: 'production',
  entry: {
    // 将React相关模块放入一个动态链接库
    react: ['react','react-dom','react-router-dom','react-loadable'],
    librarys: ['wangeditor'],
    utils: ['axios','js-cookie']
  },
  output: {
    filename: '[name]-dll.js',
    path: path.resolve(__dirname, 'dll'),
    // 存放动态链接库的全局变量名,加上_dll_防止全局变量冲突
    library: '_dll_[name]'
  },
  // 动态链接库的全局变量名称,需要可output.library中保持一致,也是输出的manifest.json文件中name的字段值
  // 如react.manifest.json字段中存在"name":"_dll_react"
  plugins: [
    new DllPlugin({
      name: '_dll_[name]',
      path: path.join(__dirname, 'dll', '[name].manifest.json')
    })
  ]
}

webpack.pro.config.js中使用

const DllReferencePlugin = require('webpack/lib/DllReferencePlugin');
...
plugins: [
  // 告诉webpack使用了哪些动态链接库
    new DllReferencePlugin({
      manifest: require('./dll/react.manifest.json')
    }),
    new DllReferencePlugin({
      manifest: require('./dll/librarys.manifest.json')
    }),
    new DllReferencePlugin({
      manifest: require('./dll/utils.manifest.json')
    }),
]

注意:在webpack_dll.config.js文件中,DllPlugin中的name参数必须和output.library中的一致;因为DllPlugin的name参数影响输出的manifest.json的name;而webpack.pro.config.js中的DllReferencePlugin会读取manifest.json的name,将值作为从全局变量中获取动态链接库内容时的全局变量名

执行构建

webpack --progress --colors --config ./webpack.dll.config.js

webpack --progress --colors --config ./webpack.prod.js

html中引入dll.js文件

构建时间对比:["11593ms","10654ms","8334ms"]

HappyPack并行构建优化

核心原理:将webpack中最耗时的loader文件转换操作任务,分解到多个进程中并行处理,从而减少构建时间。

HappyPack

接入HappyPack

安装:npm i -D happypack

重新配置rules部分,将loader交给happypack来分配:

const HappyPack = require('happypack');
const happyThreadPool = HappyPack.ThreadPool({size: 5}); //构建共享进程池,包含5个进程
...
plugins: [
  // happypack并行处理
  new HappyPack({
    // 用唯一ID来代表当前HappyPack是用来处理一类特定文件的,与rules中的use对应
    id: 'babel',
    loaders: ['babel-loader?cacheDirectory'],//默认设置loader处理
    threadPool: happyThreadPool,//使用共享池处理
  }),
  new HappyPack({
    // 用唯一ID来代表当前HappyPack是用来处理一类特定文件的,与rules中的use对应
    id: 'css',
    loaders: [
      'css-loader',
      'postcss-loader',
      'sass-loader'],
      threadPool: happyThreadPool
  })
],
module: {
  rules: [
  {
    test: /\.(js|jsx)$/,
    use: ['happypack/loader?id=babel'],
    exclude: path.resolve(__dirname,' ./node_modules'),
  },
  {
    test: /\.(scss|css)$/,
    //使用的mini-css-extract-plugin提取css此处,如果放在上面会出错
    use: [MiniCssExtractPlugin.loader,'happypack/loader?id=css'],
    include:[
      path.resolve(__dirname,'src'),
      path.join(__dirname, './node_modules/antd')
    ]
  },
}

参数:

threads:代表开启几个子进程去处理这一类文件,默认是3个;

verbose:是否运行HappyPack输出日志,默认true;

threadPool:代表共享进程池,即多个HappyPack示例使用一个共享进程池中的子进程去处理任务,以防资源占有过多

代码压缩用ParallelUglifyPlugin代替自带的 UglifyJsPlugin插件

自带的JS压缩插件是单线程执行的,而webpack-parallel-uglify-plugin可以并行的执行

配置参数:

  • uglifyJS: {}:用于压缩 ES5 代码时的配置,Object 类型
  • test: /.js$/g:使用正则去匹配哪些文件需要被 ParallelUglifyPlugin 压缩,默认是 /.js$/
  • include: []:使用正则去包含被压缩的文件,默认为 [].
  • exclude: []: 使用正则去包含不被压缩的文件,默认为 []
  • cacheDir: '':缓存压缩后的结果,下次遇到一样的输入时直接从缓存中获取压缩后的结果并返回,默认不会缓存,开启缓存设置一个目录路径
  • workerCount: '':开启几个子进程去并发的执行压缩。默认是当前运行电脑的 CPU 核数减去1
  • sourceMap: false:是否为压缩后的代码生成对应的Source Map, 默认不生成
...
minimizer: [
  // webpack:production模式默认有配有js压缩,但是如果这里设置了css压缩,js压缩也要重新设置,因为使用minimizer会自动取消webpack的默认配置
  new optimizeCssPlugin({
    assetNameRegExp: /\.css$/g,
    cssProcessor: require('cssnano'),
    cssProcessorOptions: { discardComments: { removeAll: true } },
    canPrint: true
  }),
  new ParallelUglifyPlugin({
    cacheDir: '.cache/',
    uglifyJS:{
      output: {
      // 是否输出可读性较强的代码,即会保留空格和制表符,默认为输出,为了达到更好的压缩效果,可以设置为false
        beautify: false,
    //是否保留代码中的注释,默认为保留,为了达到更好的压缩效果,可以设置为false
        comments: false
      },
      compress: {
      //是否在UglifyJS删除没有用到的代码时输出警告信息,默认为输出
        warnings: false,
      //是否删除代码中所有的console语句,默认为不删除,开启后,会删除所有的console语句
        drop_console: true,
      //是否内嵌虽然已经定义了,但是只用到一次的变量,比如将 var x = 1; y = x, 转换成 y = 1, 默认为否
        collapse_vars: true,
      }
    }
}),
]

构建结果对比:["11593ms","10654ms","8334ms","7734ms"]

整体构建速度从12000ms降到现在的8000ms

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
js利用事件的阻止冒泡实现点击空白模态框的隐藏
Jan 24 Javascript
js调用后台、后台调用前台等方法总结
Apr 17 Javascript
BootStrap的alert提示框的关闭后再显示怎么解决
May 17 Javascript
基于Bootstrap的后台管理面板 Bootstrap Metro Dashboard
Jun 17 Javascript
js友好的时间返回函数
Aug 24 Javascript
JavaScript队列函数和异步执行详解
Jun 19 Javascript
浅析JS中常用类型转换及运算符表达式
Jul 23 Javascript
详解React中setState回调函数
Jun 14 Javascript
vue使用监听实现全选反选功能
Jul 06 Javascript
vue.js单文件组件中非父子组件的传值实例
Sep 13 Javascript
JavaScript模板引擎原理与用法详解
Dec 24 Javascript
vue自定义右键菜单之全局实现
Apr 09 Vue.js
elementUI多选框反选的实现代码
Apr 03 #Javascript
vue生命周期的探索
Apr 03 #Javascript
用原生 JS 实现 innerHTML 功能实例详解
Apr 03 #Javascript
详释JavaScript执行环境与执行栈
Apr 02 #Javascript
mongodb初始化并使用node.js实现mongodb操作封装方法
Apr 02 #Javascript
koa大型web项目中使用路由装饰器的方法示例
Apr 02 #Javascript
vue中v-text / v-html使用实例代码详解
Apr 02 #Javascript
You might like
如何对PHP程序中的常见漏洞进行攻击(上)
2006/10/09 PHP
Smarty foreach控制循环次数的一些方法
2015/07/01 PHP
详谈配置phpstorm完美支持Codeigniter(CI)代码自动完成(代码提示)
2017/04/07 PHP
让textarea自动调整大小的js代码
2011/04/12 Javascript
JQuery select控件的相关操作实现代码
2012/09/14 Javascript
innerHTML与jquery里的html()区别介绍
2012/10/12 Javascript
JS上传前预览图片实例
2013/03/25 Javascript
jQuery中ajax的使用与缓存问题的解决方法
2013/12/19 Javascript
JavaScript italics方法入门实例(把字符串显示为斜体)
2014/10/17 Javascript
javascript 使用for循环时该注意的问题-附问题总结
2015/08/19 Javascript
JS实现IE状态栏文字缩放效果代码
2015/10/24 Javascript
JavaScript实现带播放列表的音乐播放器实例分享
2016/03/07 Javascript
html、css和jquery相结合实现简单的进度条效果实例代码
2016/10/24 Javascript
用js制作淘宝放大镜效果
2020/10/28 Javascript
jqGrid翻页时数据选中丢失问题的解决办法
2017/02/13 Javascript
Bootstrap警告框(Alert)插件使用方法
2017/03/21 Javascript
JS实现简单抖动效果
2017/06/01 Javascript
微信小程序 页面跳转传值实现代码
2017/07/27 Javascript
js实现1,2,3,5数字按照概率生成
2017/09/12 Javascript
在Vue项目中取消ESLint代码检测的步骤讲解
2019/01/27 Javascript
python 实现文件的递归拷贝实现代码
2012/08/02 Python
详解Python的Django框架中的模版继承
2015/07/16 Python
python 获取文件下所有文件或目录os.walk()的实例
2018/04/23 Python
Python自然语言处理 NLTK 库用法入门教程【经典】
2018/06/26 Python
python中的协程深入理解
2019/06/10 Python
python实现把两个二维array叠加成三维array示例
2019/11/29 Python
HTML5的新特性(1)
2016/03/03 HTML / CSS
Luxplus荷兰:以会员价购买美容产品等,独家优惠
2019/08/30 全球购物
介绍一下ICMP(Internet Control Message Protocol)Internet控制信息协议
2016/11/26 面试题
中专生自我鉴定
2013/12/17 职场文书
学校办公室主任职责
2013/12/27 职场文书
教学实习自我评价
2014/01/28 职场文书
小学生竞选班干部演讲稿
2014/04/24 职场文书
反腐倡廉演讲稿
2014/05/22 职场文书
网站推广策划方案
2014/06/04 职场文书
MySQL笔记 —SQL运算符
2022/01/18 MySQL