webpack的性能优化(一)——分包优化

news2024/11/20 0:46:56

1.什么是分包?为什么要分包?

        默认情况下,Webpack 会将所有代码构建成一个单独的包,这在小型项目通常不会有明显的性能问题,但伴随着项目的推进,包体积逐步增长可能会导致应用的响应耗时越来越长。归根结底这种将所有资源打包成一个文件的方式存在两个弊端:

  • 资源冗余:客户端必须等待整个应用的代码包都加载完毕才能启动运行,但可能用户当下访问的内容只需要使用其中一部分代码
  • 缓存失效:将所有资源达成一个包后,所有改动 —— 即使只是修改了一个字符,客户端都需要重新下载整个代码包,缓存命中率极低

        这些问题都可以通过代码分离解决,例如 node_modules 中的资源通常变动较少,可以抽成一个独立的包,那么业务代码的频繁变动不会导致这部分第三方库资源被无意义地重复加载。

代码分离(Code Splitting) 是webpack一个非常重要的特性:
  • 它主要的目的是将代码分离到不同的bundle中,之后我们可以按需加载,或者并行加载这些文件; 比如默认情况下,所有的JavaScript代码(业务代码、第三方依赖、暂时没有用到的模块)在首页全部都加载, 就会影响首页的加载速度;
  • 代码分离可以分出出更小的bundle,以及控制资源加载优先级,提供代码的加载性能; 
Webpack中常用的代码分离有三种:
  • 入口起点:使用entry配置手动分离代码;
  • 防止重复:使用Entry Dependencies或者SplitChunksPlugin去重和分离代码;
  • 动态导入:通过模块的内联函数调用来分离代码;

2.配置多入口

入口起点的含义非常简单,就是配置多入口:
  • 比如配置一个index.js和main.js的入口;
  • 他们分别有自己的代码逻辑;
const path = require('path'); 
module.exports = { 
    entry: { 
        main: './src/main.js', // 第一个入口起点 
        app: './src/app.js' // 第二个入口起点 
    }, 
   output: { 
       filename: '[name].bundle.js', // 使用[name]占位符将生成的文件名与入口起点名称对应 
       path: path.resolve(__dirname, 'build') 
    } ,
   };
  • 假如我们的index.js和main.js都依赖两个库:lodash、dayjs
  • 如果我们单纯的进行入口分离,那么打包后的两个bunlde都有会有一份lodash和dayjs;
事实上我们可以对他们进行共享;
const path = require('path'); 
module.exports = { 
    entry: { 
        main: { import: './src/main.js', dependOn: 'shared' }, // 第一个入口起点 
        app:  { import: './src/app.js', dependOn: 'shared' }, // 第二个入口起点 
        shared: ['dayjs', 'lodash'] // 共享的库
    }, 
   output: { 
       filename: '[name].bundle.js', // 使用[name]占位符将生成的文件名与入口起点名称对应 
       path: path.resolve(__dirname, 'dist') } 
   };

3.SplitChunks

        Webpack 提供了 SplitChunkPlugin 进行分包优化。SplitChunksPlugin 插件可以将应用程序中共享的代码拆分成单独的块,以便将其从应用程序代码中分离出来,从而提高性能和加载速度。         该插件webpack已经默认安装和集成,所以我们并不需要单独安装和直接使用该插件,只需要提供SplitChunksPlugin 相关的配置信息即可。

  • Webpack提供了SplitChunksPlugin默认的配置,我们也可以手动来修改它的配置:
  • 比如默认配置中,chunks仅仅针对于异步(async)请求,我们可以设置为initial或者all;

SplitChunksPlugin默认的配置

const { resolve } = require('path');

module.exports = {
  entry: './src/main.js',
  output: {
    filename: 'bundle.js',
    path: resolve(__dirname, 'build')
  },
  optimization: {
    splitChunks: {
      chunks: 'async',
      minSize: 20000,
      minRemainingSize: 0,
      minChunks: 1,
      maxAsyncRequests: 30,
      maxInitialRequests: 30,
      enforceSizeThreshold: 50000,
      cacheGroups: {
        defaultVendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          namw:'', // 用在filename中的name占位符
          fileName: 'vender_[id]_[name].js', //打包之后的文件名
          reuseExistingChunk: true, 
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true,
        },
      },
    },

  },
};

一些常见的配置项

属性名
splitChunks.chunks
  • async:只拆分异步导入的模块。
  • initial:只拆分同步导入的模块。
  • all:拆分所有模块,无论是同步还是异步导入的。
splitChunks.minSize

拆分出来的这个包的最小尺寸为minSize(以 bytes 为单位)

如果一个包拆分出来达不到minSize,那么这个包就不会拆分;
splitChunks.maxSize

将大于maxSize的包, 拆分成不小于minSize的包

splitChunks.minChunks
至少被引入的次数,默认是1;
如果我们写一个2,但是引入了一次,那么不会被单独拆分;
splitChunks.maxAsyncRequests最大的初始化请求数量
splitChunks.cacheGroups用于对拆分的包进行分组

cacheGroups

        用于对拆分的包就行分组,比如一个lodash在拆分之后,并不会立即打包,而是会等到有没有其他符合规则的包一起来打包
  • test属性:匹配符合规则的包;
  • name属性:拆分包的name属性;
  • filename属性:拆分包的名称,可以自己使用placeholder属性;
  • priority一个模块可以属于多个cacheGroups。优化将优先考虑具有更高 priority(优先级)的cacheGroups。默认组的优先级为负,自定义组的默认值为 0;
  • reuseExistingChunk:如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块。这可能会影响 chunk 的结果文件名;

更多的配置可以参考:

SplitChunksPlugin | webpack 中文文档 (docschina.org) 

4.动态导入

        当代码中存在不确定会被使用的模块时,最佳做法是将其分离为一个独立的 JavaScript 文件。

  • 这样可以确保在不需要该模块时,浏览器不会加载或处理该文件的 JavaScript 代码。
  • 我们平时使用的路由懒加载的就是这个原理,都是为了优化性能而延迟加载资源。

实现动态导入的方式是使用ES6的import()语法来完成。

注意:使用动态导入bar.js:
  • 在webpack中,通过动态导入获取到一个对象
  • 真正导出的内容,在该对象的default属性中,所以我们需要做一个简单的解构;

4.1 路由懒加载

动态导入最常见的使用场景就是路由懒加载

// main.js文件中
const homeBtn = document.createElement('button')
const aboutBtn = document.createElement('button')
homeBtn.textContent = '加载home文件'
aboutBtn.textContent = '加载about文件'

document.body.appendChild(homeBtn)
document.body.appendChild(aboutBtn)

homeBtn.addEventListener('click', () => {
    import('./views/home.js')
})

aboutBtn.addEventListener('click', () => {
    import('./views/about.js')
})

打包之后的资源: 

4.2 配置打包文件的名称

但是我们会发现一个问题,从包名中无法区分是哪个文件构建后的包,我们可以通过以下方式修改打包之后的文件名:

  • output.chunkFilename
const { resolve } = require('path');

module.exports = {
  entry: './src/main.js',
  output: {
    filename: 'bundle.js',
    path: resolve(__dirname, 'build'),
    chunkFilename: 'chunk_[name]_[id].js',
  },
};

默认情况下我们获取到的 [name][id] 的名称保持一致的,如果我们希望修改name的值,可以通过magic comments(魔法注释)的方式;

  • webpackChunkName 魔法注释
// main.js
homeBtn.addEventListener('click', () => {
    import(/* webpackChunkName: "home" */'./views/home.js') // 让webpack读取的魔法注释,固定写法
})

aboutBtn.addEventListener('click', () => {
    import(/* webpackChunkName: "about" */'./views/about.js')
})

打包之后的资源,可以从名称看出原始文件是哪一个 

4.3 prefetch(预获取)

prefetch被用于懒加载策略。它会在浏览器空闲时,即浏览器已经加载主要资源并且有剩余带宽时,开始加载。这意味着它不会影响初始页面加载时间,因为它是在后台加载的。通常用于加载将来可能需要的资源,例如懒加载的代码块或其他不太紧急的资源。

4.4 preload(预加载)

preload用于立即加载重要资源。它会在当前页面加载时立即开始加载,而不管浏览器的空闲状态如何。因此,preload可能会影响初始页面加载性能,因为它可以竞争主要资源的带宽。通常用于加载当前页面渲染所必需的关键资源,如字体、样式表或脚本

import(/* webpackPrefetch: true */ './view/home');
import(/* webpackPreload: true */ './view/about');

5.runtime代码的分包

配置 runtime 相关的代码是否抽取到一个单独的chunk中:
  • runtime相关的代码指的是在运行环境中,对模块进行解析加载、模块信息相关的代码;
  • 比如我们的component、bar两个通过import函数相关的代码加载,就是通过runtime代码完成的;
runtime抽离出来后,有利于浏览器的缓存策略
  • 比如我们修改了业务代码(main),那么runtime和component、bar的chunk是不需要重新加载的;
  • 比如我们修改了component、bar的代码,那么main中的代码是不需要重新加载的;
const { resolve } = require('path');

module.exports = {
  entry: './src/main.js',
  output: {
    filename: 'bundle.js',
    path: resolve(__dirname, 'build')
  },
  optimization: {
    runtimeChunk: 'true/multiple' //针对每个入口打包一个runtime文件
    runtimeChunk: 'single'  //打包一个runtime文件
    runtimeChunk: {
      name: function(entrypoint) {
        return `my-${entrypoint.name}` // 决定runtimeChunk的名称
      }
    }

  },
};
设置的值:
  • true/multiple:针对每个入口打包一个runtime文件;
  • single:打包一个runtime文件;
  • 对象:name属性决定runtimeChunk的名称;

6.将css提取到一个独立的css文件

我们平时在打包css文件时,是通过css-loaderstyle-loader进行如下配置,最终会把css注入到页面中,

const { resolve } = require('path');

module.exports = {
  entry: './src/main.js',
  output: {
    filename: 'bundle.js',
    path: resolve(__dirname, 'build'),
  },
  module: {
    rules: [
        {	//通过正则告诉webpack匹配是什么文件
            test: /\.css$/,
            use: [
             //  因为loader的执行顺序是从右向左(或者说从下到上,或者说从后到前							 的),所以我们需要将style-loader写到css-loader的前面;
                { loader: 'style-loader' },
                { loader: 'css-loader' }
            ]
        }
    ]
},
};

如果将css单独打包到一个css文件中有如下好处:

  • 分离结构和样式:将CSS独立出来可以将网页的结构(HTML)和样式(CSS)分开,使代码更加模块化和易于维护。
  • 缓存优化:独立的CSS文件可以被浏览器缓存,当用户再次访问网站时,可以减少加载时间,提高性能。
MiniCssExtractPlugin 可以帮助我们将css提取到一个独立的css文件中,该插件需要在webpack4+才可以使用。
  • 首先,我们需要安装 mini-css-extract-plugin:
npm install mini-css-extract-plugin -D
  • 配置rules和plugins:
const { resolve } = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

module.exports = {
  entry: './src/main.js',
  output: {
    filename: 'bundle.js',
    path: resolve(__dirname, 'build'),
  },
  module: {
    rules: [
        {
            test: /\.css$/,
            use: [
                // 将CSS样式提取为单独的CSS文件,通过链接方式(link)引入到HTML中
                { loader: MiniCssExtractPlugin.loader },  
                { loader: 'css-loader' }
            ]
        }
    ]
},
  plugins: [
      new MiniCssExtractPlugin({ // 使用MiniCssExtractPlugin插件
                filename: "css/[name]_[id].css",  // 打包后的css文件放到css文件夹中
                chunkFilename: "css/[name]_[id].css"
              }
           )
        ],
};

打包之后,css被单独打包到build/css/main_179.css 

 

参考:

Webpack 优化实操(十一):页面分包优化 - 知乎 (zhihu.com)

webpack性能优化(一):分包 - 掘金 (juejin.cn)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1385668.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

什么是google算法?

谷歌算法本身指的是谷歌针对搜索引擎做的规定 要想在别人的地盘玩,那肯定要了解这个地盘的规定,不然做了什么违反了规定,谷歌肯定不会让你继续玩下去 要想做谷歌,那肯定要了解谷歌的算法,然而谷歌的算法也不是一成不变…

代码随想录 Leetcode202. 快乐数

题目&#xff1a; 代码(首刷自解 2024年1月15日&#xff09;&#xff1a; class Solution { public:bool isHappy(int n) {unordered_set<int> hash;while(n ! 1) {int sum 0;while(n/10 ! 0) {sum (n % 10)*(n % 10);n/10;}sum n*n;if (hash.find(sum) ! hash.end()…

SQL备忘--集合运算

前言 本文讨论的是两个子查询结果的合并问题&#xff0c; 是行维度下的合并处理 例如子查询A查出5条记录、子查询B查出3条记录&#xff0c;那么将两个结果合并&#xff0c;则共返回8条记录 行维度上要能进行合并&#xff0c;前置要求是&#xff1a;子查询的列字段是相同的&…

关于你不知道的前端文件上传方式

1、原生方法 我们开发中比较常用的有使用 input 属性的 type 设置为 file,这里我们就不进行过多的阐述&#xff0c;已经老生常谈了。 今天我们主要介绍两个新的属性 showDirectoryPicker 和 showOpenFilePicker 这里是官方文档 1、showDirectoryPicker showDirectoryPicker方…

20240115-【UNITY 学习】第一人称移动增加斜坡移动、冲刺和蹲伏功能

直接修改或者替换PlayerMovement_01.cs using System.Collections; using System.Collections.Generic; using UnityEngine;public class PlayerMovement_02 : MonoBehaviour {private float moveSpeed; // 玩家移动速度public float walkSpeed 7; // 行走速度public float sp…

内网搭建文件服务器

文件下载地址 Releases rejetto/hfs GitHubHFS is a web file server to run on your computer. Share folders or even a single file thanks to the virtual file system. - Releases rejetto/hfshttps://github.com/rejetto/hfs/releases http://192.168.100.33/apk/ 就…

.net core IResultFilter 的 OnResultExecuted和OnResultExecuting的区别

//全局过滤器 builder.Services.AddMvc(m > { m.Filters.Add<AllResultFilter>(); }); 1、实现过滤器 public class AllResultFilter : IResultFilter {/// <summary>/// 结果执行后方法/// 不可更改结果/// </summary>/// <param name"con…

spring常见漏洞(2)

Spring Web Flow框架远程代码执行(CVE-2017-4971)漏洞&#xff0c;是由于Spring Web Flow的数据绑定问题带来的表达式注入&#xff0c;从而导致任意代码执行。 影响版本 2.4.0-2.4.4、Older unsupported versions are also affected 漏洞分析 view对象处理用户事件&#xf…

【外汇天眼】误入假冒Ctrl Investments无法出金,投资者:太相信网友了!

在当下这个互联网迅速发展的时代&#xff0c;各类交友类APP成为人们拓展社交圈的新渠道。一方面这样的交友软件在满足了用户基础的社交要求&#xff0c;另一方面网络世界所交往的朋友能给用户带来的神秘感和新鲜感&#xff0c;所以导致一部分年轻人离不开这些交友软件。然而&am…

STM32快速复制MX25L1606E系列Flash

去年做了一个使用RS485对PIC18F45K80系列单片机进行在线升级的程序&#xff0c;如果是小批量的出厂烧录程序和升级验证&#xff08;出厂前肯定要测试单片机是否能正常读写Flash&#xff09;是可以的&#xff0c;但是后来产品订单量很大&#xff0c;生产线的烧录及升级验证就很缓…

QT上位机开发(多线程处理)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 目前大部分cpu都是多核cpu&#xff0c;所以如果不用多线程进行数据处理的话&#xff0c;实在是太浪费资源了。另外&#xff0c;很多的操作都是阻塞…

数据中心温湿度监测,这个细节你真的看懂了吗?

在当今数字化和智能化的时代&#xff0c;温湿度监控系统成为了各行业中不可或缺的关键技术&#xff0c;其在维护设备、保障产品质量和确保生产环境稳定性方面发挥着至关重要的作用。 随着科技的不断进步&#xff0c;企业和机构越来越意识到对环境参数的实时监测对于提高效率、降…

采样次数与频率的关系

采样次数&#xff08;Sampling Points&#xff09; 在给定时间内记录信号值的次数。 假设在1秒内对一个连续信号采样10次&#xff0c;这意味着每0.1秒记录一次信号值。 假设在1秒内对一个连续信号采样100次&#xff0c;这意味着每0.01秒记录一次信号值。 频率&#xff08;Fre…

GitLab CI 实现项目A更新代码自动触发项目B更新错误码文档

一、CI/CD简介 CI/CD 是持续集成&#xff08;Continuous Integration&#xff09;和持续交付/持续部署&#xff08;Continuous Delivery/Continuous Deployment&#xff09;的缩写&#xff0c;是一种软件开发和交付的最佳实践。这两个概念通常一起使用&#xff0c;但有些时候它…

XCTF:Hidden-Message[WriteUP]

使用Wireshark打开文件 分析能分析的流&#xff0c;这里直接选择UDP流 分别有两段流&#xff0c;内容都是关于物理的 和flag没啥关系&#xff0c;只能从别的方面下手 分析&#xff1a;整个数据包&#xff0c;全部由UDP协议组成 其中发送IP和接收IP固定不变&#xff0c;数据长…

[python]pyside6安装和在pycharm配置

安装命令&#xff1a; pip install PySide6 -i https://mirror.baidu.com/pypi/simple Pycharm配置Pyside6 打开Pycharm点击File -> Settings -> Tools -> External Tools&#xff0c;点击&#xff0b;。需要添加 Pyside6-Designer 、 Pyside6-UIC 和 Pyside6-rcc三…

第9章 通信动力与环境

文章目录 9.1.1 动力与环境的组成9.1.2 动力与环境的特点9.1.3 动力与环境的地位与作用9.1.4 动力与环境的基本要求9.2.1 通信电源的组成和结构9.2.2 交流供电系统1、交流供电系统的组成2、市电交流供电的质量指标3、常用高压电电器4、电力变压器5、常用低压电器6、油机发电机组…

Mac电脑采样器软Native Instruments Kontakt 7

Native Instruments Kontakt 7是一款功能强大的采样器软件&#xff0c;专为音乐制作和声音设计而设计。它提供了一个直观的界面和丰富的功能&#xff0c;使用户能够轻松地加载、编辑和演奏采样样本。Kontakt 7支持多种采样格式&#xff0c;包括WAV、AIFF、 Kontakt .nki等&…

CTFhub-phpinfo

CTFhub-Web-信息泄露-“phpinfo” 题目信息 解题过程 ctrlF搜索关键字…

JMeter定时器之同步定时器

JMeter定时器之同步定时器 1. 背景2. 目的3. 介绍4. 例子4.1单个请求4.2多个请求 1. 背景 在实际生活中大家肯定遇到过一种场景&#xff0c;就是在某一时间或某一时刻&#xff0c;某件商品进行抢购&#xff0c;相当于秒杀&#xff1b;但是用JMeter进行测试的时候&#xff0c;如…