专栏首页佛曰不可说丶webpack优化解决项目体积大、打包时间长、刷新时间长问题!

webpack优化解决项目体积大、打包时间长、刷新时间长问题!

前言

在大家的日常开发中,特别是开发大型项目,大家有没有每次打包想要骂娘的冲动!反正我是很痛苦,每次打包20分钟起,这漫长的等待时间,让人非常焦虑,遇见一些特殊问题(比如测试微信分享),必须要打包部署,看效果,你会发现,一天时间全部浪费在打包上,真所谓改代码两分钟,打包代码两小时,于是闲暇之余,研究了一下webpck打包机制,并且通过几个小插件和一些技巧成功的减少公司项目的打包时间,虽然打包时间没有断崖式的减少,但是能少一分钟,是一分钟吧,下面我们一起来研究一下webpack的性能优化,以及体积优化!

1、提升基础环境版本

在项目中,如果发现脚手架打包缓慢,体积又臭又大,那么,升级技术环境是提升的最快方式,比如node环境,比如升级脚手架版本,效果会有显著提升,而且是最廉价的优化方式。

2、使用include或者exclude配置,来避免重复打包

在我们的日常开发中,我们引入的一些插件,类库,都是被打包过了的,那么我们用babel去做编译的时候,就需要配置一下,给已经编译过的语法剔除掉,这样就能减少打包时间,在此,科普一下,我们在使用插件的时候webpack是怎么做的,比如我们在项目中去引入jq插件

import $ from 'jquery'

首先当我们取用esmodel去引用jquery的时候npm搜索会先从当前目录的node_modules中找,找不到。就往上一级目录找node_modules。一直往上找到当前磁盘。如果都没有。才报错。当找到node_modules包之后他回去找package.json文件,在文件中确入口文件,然后找到入口文件去加载,当加载后发现他其实已经是一个已经编译过的文件了

确定入口

如上图,这是一个编译过的es5的代码,因为你可以看到熟悉的原型对象,那我我们应该怎么使用include,或者exclude,比如我们配置webpack的时候我们在babel-loader中去配置:

{ 
			test: /\.js$/, 
			//使用include来指定编译文件夹
			include: path.resolve(__dirname, '../src'),
			//使用exclude排除指定文件夹
			exclude: /node_modules/
			use: [{
				loader: 'babel-loader'
			}]
		}

当然还能使用noParse指定文件,避免重复打包

3、合理的利用缓存来减少打包时间

还拿这个babel-loader来举例

{ 
			test: /\.js$/,
			use: [{
			    //如果文件没被改动则使用缓存
				loader: 'babel-loader?chaheDirectory'
			}]
		}

所以,我们一些公用不改动的库,就是可使用当前方式去打包,除了有这个技巧呢还有个cache-loader,也能开启缓存,用法非常简单,在开销较大的loader前使用即可

 {
        test: /\.js$/,
        use: [
          'cache-loader',
          'babel-loader'
        ],
        include: path.resolve('src')
      }

4、合理的使用plugin,减少打包时间和体积

在我们搭建webpack脚手架的时候,我们会发现必须要使用一些plugin才能实现我们的需求,那我们应该怎么选择呢?

  • 首先我们尽量选择官方推荐的plugin,这些插件经过了官方的测试又可靠的性能
  • 其次合理的使用这些插件,避免出现引入无用模块和代码,

举一些例子,比如在我们使用moment的库的时候,在打包的手webpack默认会给整个库都引入进来,这样就会导致我们的库非常大,我们可以使用IgonrePlugin忽略插件的某个无用的文件夹,这样就能大大的减少打包体积,在比如,我们在使用压缩css的OptimizeCSSAssetsPlugin的时候只需要在生产环境下对代码做压缩,那么我们在开发环境下就不需要这个插件,这样就能有效的缩短压缩时间

5、合理配置relosve,防止减慢打包时间

我们在平常引入es6模块的时候,发现不用写文件后缀也能引用进来,其实,这就是webpack的relosve配置参数给我们提供的便利

//这样配置我们就不用写后缀了
resolve:{
    extensions:['js','jsx']
}

但是正由于这样的便利,大家为了方便,就会配置许多后缀,比如jpg、css、png 等,由于不写后缀,他在查找的时候,会给 extensions数组中的所有后缀遍历完了找不到才去报错,这样就大大增加了查找时间,所以,大家还是要合理配置

6、启用多进程打包(重点有坑)

开启多进程打包主要有三个方法,使用happy和使用thread-loader,

happypack

happypack本身就是对node开启了一个多进程打包,用法有点麻烦

//首先引入happypack
const HappyPack = require('happypack')
//然后在lorder中使用即可,以babel-lorder为例
 {
                test: /\.js$/,
                // 对 .js 文件的处理转交给 id 为 babel 的 HappyPack 实例
                use: ['happypack/loader?id=babel'],
                include: srcPath,
                // exclude: /node_modules/
            }
//需要在plugins中使用HappyPack
  // happyPack 开启多进程打包
        new HappyPack({
            // 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件
            id: 'babel',
            // 处理 .js 文件,用法和 Loader 配置中一样,也能开启缓存
            loaders: ['babel-loader?cacheDirectory']
        }),

当然由于webpack4中官方文档的极力推荐thread-loader,并且HappyPack将不再被维护,所以当我们使用多进程打包时首选thread-loader

thread-loader

hread-loader 使用起来也非常简单,只要把 thread-loader 放置在其他 loader 之前即可,这样一来,按照官方的解释之后的 loader 就会在一个单独的 worker 池(worker pool)中运行,并且还支持之定义配置,方便性能优化

 {
        test: /\.js$/,
        exclude: /node_modules/,
        // 创建一个 js worker 池
        use: [ 
        //直接在loader之前使用
          'thread-loader',
          'babel-loader'
        ] 
      },
    //自定义配置行
    use[
    {
    loader: "thread-loader",
    // loaders with equal options will share worker pools
    // 设置同样option的loaders会共享
    options: {
      // worker的数量,默认是cpu核心数
      workers: 2,
      // 一个worker并行的job数量,默认为20
      workerParallelJobs: 50,
      // 添加额外的node js 参数
      workerNodeArgs: ['--max-old-space-size=1024'],
      // 允许重新生成一个dead work pool
      // 这个过程会降低整体编译速度
      // 开发环境应该设置为false
      poolRespawn: false,
      //空闲多少秒后,干掉work 进程
      // 默认是500ms
      // 当处于监听模式下,可以设置为无限大,让worker一直存在
      poolTimeout: 2000,
      // pool 分配给workder的job数量
      // 默认是200
      // 设置的越低效率会更低,但是job分布会更均匀
      poolParallelJobs: 50,
      }
    }
    'babel-loader'
    ]

ParallelUglifyPlugin

我们知道压缩 JS 需要先将代码解析成 AST 语法树,然后需要根据复杂的规则去分析和处理 AST,最后将 AST 还原成 JS,这个过程涉及到大量计算,因此比较耗时,那么我们使用ParallelUglifyPlugin这个插件就能开启多进程压缩JS使用方式也非常简单

//引入插件
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin')
 plugins: [
  // 使用 ParallelUglifyPlugin 并行压缩输出的 JS 代码
        new ParallelUglifyPlugin({
            // 传递给 UglifyJS 的参数
            // (还是使用 UglifyJS 压缩,只不过帮助开启了多进程)
            uglifyJS: {
                output: {
                    beautify: false, // 最紧凑的输出
                    comments: false, // 删除所有的注释
                },
                compress: {
                    // 删除所有的 `console` 语句,可以兼容ie浏览器
                    drop_console: true,
                    // 内嵌定义了但是只用到一次的变量
                    collapse_vars: true,
                    // 提取出出现多次但是没有定义成变量去引用的静态值
                    reduce_vars: true,
                }
            }
        })
 ]

ok,基本我们的多进程的优化到这里了,之所以说有坑,是由于好多人配置以后发现打包时间不但没有加快反而慢了,这是由于如果项目较小,打包开启多进程会有额外的性能开销,反而将时间拖慢了,所以,在这里如果项目较大,那么我们推荐使用(我们公司的项目确实是快了一点),如果项目较小,还是算了,杀鸡焉能用牛刀。

7、开发中使用热更新替换自动刷新

我们在日常开发中,由于每次改完代码都要刷新页面,但是如果项目体积过大,没事刷新就得喝几口水,才能出来,那是相当痛苦。

其实我们可以使用热更新,来代替自动刷新,来提高开发体验,不要问我为什么,我体验过,那种每次改代码就要等十秒的感觉(我们公司的angular老项目就这样),那么热更新应该怎么使用呢?同样也非常简单引入插件即可

//引入webpack
const webpack = require('webpack');
//使用webpack提供的热更新插件
   plugins: [
   new webpack.HotModuleReplacementPlugin()
    ],
    //最后需要在我们的devserver中配置
     devServer: {
+     hot: true
    },

如果小伙伴使用的是angular项目可以使用 @angularclass/hmr插件

8、使用DllPlugin插件,优化提高打包时间

当我们使用一些社区的比较稳定的库的时候,比如react 比如vue,比如jquery 你会发现他几个月都不会更新一次,那么,我们就不需要重复打包了,只需要打包一次,下次打包只引用即可,那我应该怎么做呢,其实在webpack4的版本中,已经集成DllPlugin插件,我们只需要配置即可。

首先第一步我们要编写一个dll的配置文件,去打包dll文件

const path = require('path')
const DllPlugin = require('webpack/lib/DllPlugin')
const { srcPath, distPath } = require('./paths')

module.exports = {
  mode: 'development',
  // JS 执行入口文件
  entry: {
    // 以 React为例 模块的放到一个单独的动态链接库
    react: ['react', 'react-dom']
  },
  output: {
    // 输出的动态链接库的文件名称,[name] 代表当前动态链接库的名称,
    // 也就是 entry 中配置的 react 和 polyfill
    filename: '[name].dll.js',
    // 输出的文件都放到 dist 目录下
    path: distPath,
    // 存放动态链接库的全局变量名称,例如对应 react 来说就是 _dll_react
    // 之所以在前面加上 _dll_ 是为了防止全局变量冲突
    library: '_dll_[name]',
  },
  plugins: [
    // 接入 DllPlugin
    new DllPlugin({
      // 动态链接库的全局变量名称,需要和 output.library 中保持一致
      // 该字段的值也就是输出的 manifest.json 文件 中 name 字段的值
      // 例如 react.manifest.json 中就有 "name": "_dll_react"
      name: '_dll_[name]',
      // 描述动态链接库的 manifest.json 文件输出时的文件名称
      path: path.join(distPath, '[name].manifest.json'),
    }),
  ],
}

第二步在webpack中配置映射关系,防止打包时再次引用npm包

// 第一,引入 DllReferencePlugin
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin');
 plugins: [
  //告诉 Webpack 使用了哪些动态链接库
        new DllReferencePlugin({
            // 描述 react 动态链接库的文件内容
            manifest: require(path.join(distPath, 'react.manifest.json')),
        }),
 ]

第三步在我们的html中引用打包的公用模块,因为当我们在配置DllReferencePlugin的时候,他底层其实是在执行的时候在window中去寻找这个包,所以必须引入进来打包后的这个文件,那么相应的这个公用模块也会在window下挂一个全局变量,引入方式我们可以使用AddAssetHtmlWebpackPlugin插件引入,也可以手动引入

以上就可以大幅提高打包时间,但是由于dllPlugin的使用是为了解决开发时的打包时间缓慢问题,在线上环境时,建议还是不要使用,统一原流程打包即可

9、一些项目体积以及运行时的性能优化

部分来吗使用懒加载,加快首屏加载时间

懒加载是老生常谈的问题了,这是性能优化的必要手段,当页面中的大型,并且不太重要的代码,我们就可以使用懒加载的方式去异步加载进来,这样便可以提前达到渲染条件,具体懒加载怎么使用:

import('./util.js').then(data=>{  //  懒加载
        console.log(data.default)  //拿到加载的文件里面的数据信息  输出到控制台上
    })

小图片使用base64格式,不使用用网络请求

在我们打包时,如果遇见小型图片,我们可以直接转换成base64位格式,减少http请求就能达到前端性能优化的目的,使用方式非常简单

rules: [
            // 图片 - 考虑 base64 编码的情况
            {
                test: /\.(png|jpg|jpeg|gif)$/,
                use: {
                    loader: 'url-loader',
                    options: {
                        // 小于 5kb 的图片用 base64 格式产出
                        // 否则,依然延用 file-loader 的形式,产出 url 格式
                        limit: 5 * 1024,
                        // 打包到 img 目录下
                        outputPath: '/img1/',
                        // 设置图片的 cdn 地址(也可以统一在外面的 output 中设置,那将作用于所有静态资源)
                        // publicPath: 'http://cdn.abc.com'
                    }
                }
            },
        ]

打包时合理使用hash,如果没改动的文件,命中缓存

当我们在打包时,使用hash计算文件变动,如果文件被改变,则hash的值改变,在上线后,浏览器访问时没有改变的文件会命中缓存,从而达到性能优化的目的,使用方式如下:

output: {
        filename: 'bundle.[contentHash:8].js',  // 打包代码时,加上 hash 后缀
        path: distPath
    },

提取公用代码,代码分割

当我们在打包时,提取公用代码,并且实现代码分割,那么我便可以提取多个模块的公共代码,只需要打包一次,这样我们便能实现更小的代码体积!怎么使用呢,webpack给我们提供了optimization属性,去配置

    optimization: {
      
        // 分割代码块
       splitChunks: {
        chunks: "async”,//默认作用于异步chunk,值为all/initial/async/function(chunk),值为function时第一个参数为遍历所有入口chunk时的chunk模块,chunk._modules为chunk所有依赖的模块,通过chunk的名字和所有依赖模块的resource可以自由配置,会抽取所有满足条件chunk的公有模块,以及模块的所有依赖模块,包括css
        minSize: 30000,  //表示在压缩前的最小模块大小,默认值是30kb
        minChunks: 1,  // 表示被引用次数,默认为1;
         maxAsyncRequests: 5,  //所有异步请求不得超过5个
        maxInitialRequests: 3,  //初始话并行请求不得超过3个
        automaticNameDelimiter:'~',//名称分隔符,默认是~
        name: true,  //打包后的名称,默认是chunk的名字通过分隔符(默认是~)分隔
        cacheGroups: { //设置缓存组用来抽取满足不同规则的chunk,下面以生成common为例
         common: {
         name: 'common',  //抽取的chunk的名字
         chunks(chunk) { //同外层的参数配置,覆盖外层的chunks,以chunk为维度进行抽取
         },
         test(module, chunks) {  //可以为字符串,正则表达式,函数,以module为维度进行抽取,只要是满足条件的module都会被抽取到该common的chunk中,为函数时第一个参数是遍历到的每一个模块,第二个参数是每一个引用到该模块的chunks数组。自己尝试过程中发现不能提取出css,待进一步验证。
         },
        priority: 10,  //优先级,一个chunk很可能满足多个缓存组,会被抽取到优先级高的缓存组中
       minChunks: 2,  //最少被几个chunk引用
       reuseExistingChunk: true,//  如果该chunk中引用了已经被抽取的chunk,直接引用该chunk,不会重复打包代码
       enforce: true  // 如果cacheGroup中没有设置minSize,则据此判断是否使用上层的minSize,true:则使用0,false:使用上层minSize
       }
    }
}
        }

使用 tree-shaking 去除无用代码减少代码体积

tree-shaking 的目的就是去除被引用但是没有被使用的代码,在webpack4中,如果一个文件中引用多个函数,却使用一个函数,那么多个函数都会被打包,据说webpack5中,能解析出更强大的依赖图谱,去除最终没有被使用的代码

// a.js
export const a = 'a';
export const b = 'b';

// b.js
import * as c from "./a";
export { c };

// index.js
import * as module from "./b";
console.log(module.c.a);

比如上述代码最终是这个b变量是不会被打包的,说了半天怎么开启呢?

 mode: 'production',

一句代码,配置mode即可

总结

webpack的优化内容基本完成,本文是自己根据公司项目,参考大佬文章实践后总结而成,确属有效策略,不对之处请大佬指正!

我的博客即将同步至腾讯云+社区,邀请大家一同入驻:cloud.tencent.com/developer/s…

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 梳理 6 项 webpack 的性能优化

    webpack在启动后,会根据Entry配置的入口,递归解析所依赖的文件。这个过程分为「搜索文件」和「把匹配的文件进行分析、转化」的两个过程,因此可以从这两个角...

    Nealyang
  • 前端性能优化篇一:webpack性能优化

    当我们不用cli,而是自己搭建项目架子的时候,会用到webpack构建我们的项目,在用webpack构建项目的时候,过长的打包编译时间和庞大冗余的代码会让我们...

    用户6835371
  • 从 Bundleless 看前端构建

    Bundle or Bundleless?自 2015 年 ESM 标准发布后,路线之争就开始逐步升温。转眼间,时间已来到 2021 年。如果白酒的车你错过了,...

    coder_koala
  • Bundleless,前端工程构建的未来

    Bundle or Bundleless?自 2015 年 ESM 标准发布后,路线之争就开始逐步升温。转眼间,时间已来到 2021 年。如果白酒的车你错过了,...

    用户3806669
  • webpack实战——打包第一个应用

    作为前端开发者,我们以前在浏览器中运行 JavaScript ,会引用一些脚本来存放每个功能;此解决方案很难扩展,因为加载太多脚本会导致网络瓶颈;亦或使用一个包...

    流眸
  • 学好webpack,一名前端开发工程师的自我修养

    前言 webpack 前端工程中扮演的角色越来越重要,它也是前端工程化很重要的一环。本文将和大家一起按照项目流程学习使用 wwbpack,由浅入深的学习,妈妈再...

    企鹅号小编
  • Webpack知识点速记

    Webpack是一个模块打包工具,在Webpack里一切文件皆模块。通过loader转换文件,通过plugin注入钩子,最后输出由多个模块组合的文件。Webpa...

    Clearlove
  • 大作!webpack详细配置

    注意:在一个模块中,只允许使用export default向外默认暴露一次成员,不能写多个export default,否则会报错

    小丞同学
  • 让 F5 歇一会儿——laravel-mix 自动刷新之道

    原文链接:https://segmentfault.com/a/1190000018863373

    猿哥
  • 关于webpack的面试题总结

    本文首发于前端面试总结@知乎专栏,各位可以通过点击文章下方的阅读原来来访问原文地址

    用户1687375
  • React 16 加载性能优化指南(上)

    公司的新项目迁移到了 React 16 和 Webpack 4.0,写一篇文章来总结一下。

    腾讯NEXT学位
  • React 16 加载性能优化指南

    关于 React 应用加载的优化,其实网上类似的文章已经有太多太多了,随便一搜就是一堆,已经成为了一个老生常谈的问题。

    IMWeb前端团队
  • 刚刚,发布Webpack中级教程系列

    - 对于浏览器而言,html文件是用户访问的入口点,也是所有资源的挂载点,所有资源都是通过html中的标记来进行引用的。

    达达前端
  • 「吐血整理」再来一打Webpack面试题

    从头发的浓密程度和干练的走路姿势我察觉到,面前坐着的这位面试官也是一把好手。我像以往一样,准备花3分钟的时间进行自我介绍。在此期间,我的目光被16寸的MacBo...

    童欧巴
  • 为何webpack风靡全球?三大主流模块打包工具对比

    前端的模块系统经历了长久的演变,对应的模块打包方案也几经变迁。从最初简单的文件合并,到AMD 的模块具名化并合并,再到browserify将CommonJS 模...

    前朝楚水
  • webpack4大结局:加入腾讯IM配置策略,实现前端工程化环境极致优化

    Peter谭金杰
  • Webpack5 新特性业务落地实战

    Webpack5 在 2020 年 10 月 10 日正式发布,并且在过去的几个月中快速演进和迭代,截止 1 月 28 日,Webpack5 已经更新了 18 ...

    前端迷
  • webpack 4 的 30 个步骤打造优化到极致的 react 开发环境

    将 react 和 webpack4 进行结合,集 webpack 的优势于一身,从 0 开始构建一个强大的 react 开发环境。

    夜尽天明
  • 前端性能优化总结

    最近花了一些时间在项目的性能优化上,背后做了很多工作,但是最后依然没有达到自己想要的结果,有些失望,但是还是记录下自己的执着。

    前端迷

扫码关注云+社区

领取腾讯云代金券