Vite打包碎片化,如何化解?

news2024/10/22 4:49:09

背景

我们在使用Vite进行打包时,经常会遇到这个问题:随着业务的展开,版本迭代,页面越来越多,第三方依赖也越来越多,打出来的包也越来越大。如果把页面都进行动态导入,那么凡是几个页面共用的文件都会进行独立拆包,从而导致大量chunk碎片的产生。许多chunk碎片体积都很小,比如:1k,2k,3k,从而显著增加了浏览器的资源请求。

虽然可以通过rollupOptions.output.manualChunks定制分包策略,但是文件之间的依赖关系错综复杂,分包配置稍有不慎,要么导致初始包体积过大,要么导致出现循环依赖错误,因此心智负担很重。那么有没有自动化的分包机制来彻底解决打包碎片化的问题呢?

拆包合并的两种隐患

前面提到使用rollupOptions.output.manualChunks定制分包策略有两种隐患,这里展开说明一下:

1. 导致初始包体积过大

在这里插入图片描述
如图所示,文件A本来只依赖文件C,但是按照图中所示分包配置,导致在使用文件A之前必须先下载Chunk1和Chunk2。在稍微大一点的项目中,由于文件之间的依赖关系非常复杂,这种依赖关系会随着大量小文件的合并而快速蔓延,导致初始包体积过大。

2. 导致出现循环依赖错误

在这里插入图片描述

如图所示,由于文件之间的相互依赖,导致打包后的Chunk1和Chunk2出现循环依赖的错误。那么在复杂的项目中,业务之间相互依赖的情况就更加常见。

解决之道:模块化体系

由于分包配置会导致以上两个隐患,所以往往步履维艰,很难有一个可以遵循的简便易用的配置规则。因为分包配置与业务的当前状态密切相关。一旦业务有所变更,分包配置也需要做相应的改变。

为了解决这个难题,我在项目中引入了模块化体系。也就是将项目的代码依据业务特点进行拆分,形成若干个模块的组合。每一个模块都可以包含页面、组件、配置、语言、工具等等资源。然后一个模块就是一个天然的拆包边界,在 build构建时,自动打包成一个独立的异步chunk,告别Vite配置的烦恼,同时可以有效避免构建产物的碎片化。特别是在大型业务系统中,这种优势尤其明显。当然,采用模块化体系也有利于代码解耦,便于分工协作。

由于一个模块就是一个拆包边界,我们可以通过控制模块的内容和数量来控制产物chunk的大小和数量。而模块划分的依据是业务特点,具有现实的业务意义,相较于rollupOptions.output.manualChunks定制,显然心智负担很低。

文件结构

随着项目不断迭代演进,创建的业务模块也会随之膨胀。对于某些业务场景,往往需要多个模块的配合实现。因此,我还在项目中引入了套件的概念,一个套件就是一组业务模块的组合。这样,一个项目就是由若干套件和若干模块组合而成的。下面是一个项目的文件结构:

project
├── src
│  ├── module
│  ├── module-vendor
│  ├── suite
│  │  ├── a-demo
│  │  └── a-home
│  │    ├── modules
│  │    │  ├── home-base
│  │    │  ├── home-icon
│  │    │  ├── home-index
│  │    │  └── home-layout
│  └── suite-vendor
名称说明
src/module独立模块(不属于套件)
src/module-vendor独立模块(来自第三方)
src/suite套件
src/suite-vendor套件(来自第三方)
名称说明
a-demo测试套件:将测试代码放入一个套件中,从而方便随时禁用
a-home业务套件:包含4个业务模块

打包效果

下面就来看一下实际的打包效果:

以模块home-base为例,图左显示的就是该模块的代码,图右显示的就是该模块打包后的文件体积12K,压缩后是3K。要达到这种分包效果,不需要做任何配置。
在这里插入图片描述
再比如,我们还可以把布局组件集中放入模块home-layout进行管理。该模块打包成独立的Chunk,体积为29K,压缩后是6K。
在这里插入图片描述

源码分析

1. 动态导入模块

由于项目的模块目录结构都是有规律的,我们可以在项目启动之前提取所有的模块清单,然后生成一个js文件,集中实现模块的动态导入:

const modules = {};
...
modules['home-base'] = { resource: () => import('home-base')};
modules['home-layout'] = { resource: () => import('home-layout')};
...
export const modulesMeta = { modules };

由于所有模块都是通过import方法动态导入的,那么在进行Vite打包时就会自动拆分为独立的chunk。

2. 拆包配置

我们还需要通过rollupOptions.output.manualChunks定制拆包配置,从而确保模块内部的代码统一打包到一起,避免出现碎片化文件。

const __ModuleLibs = [
  /src\/module\/([^\/]*?)\//,
  /src\/module-vendor\/([^\/]*?)\//,
  /src\/suite\/.*\/modules\/([^\/]*?)\//,
  /src\/suite-vendor\/.*\/modules\/([^\/]*?)\//,
];

const build = {
  rollupOptions: {
    output: {
      manualChunks: id => {
        return customManualChunk(id);
      },
    },
  },
};

function customManualChunk(id: string) {
  for (const moduleLib of __ModuleLibs) {
    const matched = id.match(moduleLib);
    if (matched) return matched[1];
  }
  return null;
}

通过正则表达式匹配每一个文件路径,如果匹配成功就使用相应的模块名称作为chunk name。

两种隐患的解决之道

如果模块之间相互依赖,那么也有可能存在前面所言的两种隐患,如图所示:

在这里插入图片描述
为了防止两种隐患情况的发生,我们可以实现一种更精细的动态加载和资源定位的机制。简而言之,当我们在模块1中访问模块2的资源时,首先要动态加载模块2,然后找到模块2的资源,返回给使用方。

比如,在模块2中有一个Vue组件Card,模块1中有一个页面组件FirstPage,我们需要在页面组件FirstPage中使用Card组件。那么,我们需要这样来做:

// 动态加载模块
export async function loadModule(moduleName: string) {
  const moduleRepo = modulesMeta.modules[moduleName];
  return await moduleRepo.resource();
};

// 生成异步组件
export function createDynamicComponent(moduleName: string, name: string) {
  return defineAsyncComponent(() => {
    return new Promise(resolve => {
      // 动态加载模块
      loadModule(moduleName).then(moduleResource => {
        // 返回模块中的组件
        resolve(moduleResource.components[name]);
      });
    });
  });
}
const ZCard = createDynamicComponent('模块2', 'Card');

export class RenderFirstPage {
  render() {
    return (
      <div>
        <ZCard/>
      </div>
    );
  }
}

高级导入机制

虽然使用createDynamicComponent可以达到预期的目的,但是,代码不够简洁,无法充分利用Typescript提供的自动导入机制。我们希望仍然像常规的方式一样使用组件:

import { ZCard } from '模块2';

export class RenderFirstPage {
  render() {
    return (
      <div>
        <ZCard/>
      </div>
    );
  }
}

这样的代码,就是静态导入的形式,就会导致模块1模块2强相互依赖。那么,有没有两全其美的方式呢?有的。我们可以开发一个Babel插件,对AST语法树进行解析,自动将ZCard的导入改为动态导入形式。这样的话,我们的代码不仅简洁直观,而且还可以实现动态导入,规避分包时两种隐患的发生。为了避免主题分散,Babel插件如何开发不在这里展开,如果感兴趣,可以直接参考源代码:babel-plugin-zova-component

结语

本文对Vite打包碎片化的成因进行了分析,并且提出了模块化体系,从而简化分包配置,同时又采用动态加载机制,完美规避了分包时两种隐患的发生。

当然,实现一个完整的模块化系统,需要考虑的细节还有很多,如果想体验开箱即用的效果,可以访问我开源的Zova.js框架:https://github.com/cabloy/zova。可添加我的微信,入群交流:yangjian2025

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

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

相关文章

RHCSA的学习(5)

一、文本内容处理命令 1、数据流和重定向 1、数据流 标准输入&#xff08;standard input&#xff0c;简称stdin&#xff09;&#xff1a;默认情况下&#xff0c;标准输入指从键盘获取的输入标准输出&#xff08;standard output&#xff0c;简称stdout&#xff09;&#xff…

2020前端面试 - JavaScript2.0篇

前言&#xff1a; 个人觉得面试其实是一个自我学习的过程&#xff0c;如果说短时间内找不到工作&#xff0c;那一定是你面的还不够多&#xff0c;不要气馁&#xff0c;不要放弃&#xff0c;在心底告诉自己&#xff0c;下一次面试&#xff0c;再下一次面试&#xff0c;一定能够拿…

为什么inet_ntoa会返回错误的IP地址?

目录 1、调用inet_addr和inet_ntoa实现整型IP与点式字符串之间的转换 1.1、调用inet_addr将点式字符串IP转换成整型IP 1.2、调用inet_ntoa将整型IP转换成点式字符串IP 2、调用inet_ntoa返回错误点式字符串IP的原因分析 3、解决多线程调用inet_ntoa返回错误点式字符串IP的办…

在 Docker容器中安装 ROS-Melodic 并使用 rviz 进行图形化显示

文章目录 写在前面1. 背景描述2. 安装步骤2.1 允许本地机器上的用户或进程连接到 X server2.2 拉取 docker 镜像2.3 使用镜像osrf/ros:melodic-desktop-full创建并运行容器2.4 运行 roscore2.5 运行 rviz 参考链接 写在前面 自己的测试环境&#xff1a; Ubuntu20.04&#xff0…

基于SSM的微信小程序博客管理系统(博客1)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1、项目介绍 基于SSM的微信小程序博客管理系统实现与设计&#xff0c;实现了管理员与用户&#xff0c;管理员实现了用户管理、博文信息管理、博文类型管理、我的博文管理、个人名片分享管理、签到管理…

SAP S/4 HANA 销售返利

目录 1 简介 2 后台配置 3 主数据 4 业务操作 4.1 场景 1 - 返利应计 4.2 场景 2 - 最终结算 1 简介 在过去 SAP ECC 把“返利”功能集成在了 SD 模块当中&#xff0c;而 SAP S/4 HANA 把“返利”集成在了结算管理功能模块当中。究其原因&#xff0c;主要是 ECC “返利”…

深度解析LMS(Least Mean Squares)算法

目录 一、引言二、LMS算法简介三、LMS算法的工作原理四、LMS算法的特点五、LMS算法的应用场景六、LMS算法的局限性七、总结八、进一步探讨 一、引言 自适应滤波器是一种动态调整其参数以适应变化环境的信号处理工具&#xff0c;广泛应用于噪声消除、信道均衡和系统识别等领域。…

算法工程师重生之第二十五天(加油站 分发糖果 柠檬水找零 根据身高重建队列 )

参考文献 代码随想录 一、加油站 在一条环路上有 n 个加油站&#xff0c;其中第 i 个加油站有汽油 gas[i] 升。 你有一辆油箱容量无限的的汽车&#xff0c;从第 i 个加油站开往第 i1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发&#xff0c;开始时油箱为空。…

Mysql的Innodb的RR隔离级别到底有没有解决幻读问题?

InnoDB中的repeatable read这种隔离级别通过间隙锁MVCC解决了大部分的幻读问题&#xff0c;但是并不是所有的幻读都能解读&#xff0c;想要彻底解决幻读&#xff0c;需要使用Serializable的隔离级别。 MVCC解决幻读 在RC中&#xff0c;每次读取都会重新生成一个快照&#xff…

EDM平台排行榜与工具推荐

本文总结了EDM平台排行榜&#xff0c;包括ZohoCampaigns、Mailchimp等。各平台在集成性、自动化、模板库等方面各具优势&#xff0c;适合不同规模和需求的企业。选择适合的EDM平台对营销活动成功至关重要。 1. Zoho Campaigns 简介 Zoho Campaigns是Zoho的edm平台&#xff0c…

[LeetCode] 515. 在每个树行中找最大值

题目描述&#xff1a; 给定一棵二叉树的根节点 root &#xff0c;请找出该二叉树中每一层的最大值。 示例1&#xff1a; 输入: root [1,3,2,5,3,null,9] 输出: [1,3,9]示例2&#xff1a; 输入: root [1,2,3] 输出: [1,3]提示&#xff1a; 二叉树的节点个数的范围是 [0,10…

标准地图下载

官网&#xff0c;地图来源为中国地图出版社、国家基础地理信息中心 http://bzdt.ch.mnr.gov.cn/ 能够下载各个版本的中国地图 世界地图

python中的工具

一、Scapy Scapy是python语言编写的工具&#xff0c;也是一个强大的交付式数据包处理程序&#xff0c;能够伪造或者解码大量的网络协议数据包&#xff0c;能够发送、嗅探、剖析和伪造网络数据包&#xff0c;如端口扫描、路由跟踪、探测、攻击或网络发现等。 python3环境下&am…

2022年华为杯数学建模竞赛B题论文和代码

基于整数规划的方形件排样和组批优化问题研究 常见的板式产品如玻璃&#xff0c;PCB板&#xff0c;铝合金门窗等产品因其结构定制化程度高的特点&#xff0c;相关生产制造的企业往往采用“多品种小批量”的个性化生产模式。通过对客户订单的组批&#xff0c;预先规划好各个产品…

【2022统考真题】计算时间复杂度

目录 一、题目描述 二、思路分析 三、易错提醒 四、同级和嵌套的关系 一、题目描述 下列程序段的时间复杂度是&#xff08;&#xff09; int sum 0; for (int i 1; i < n; i * 2) for (int j 0; j < i; j) sum; A. O(logn) B. O(n) C. O(nlogn) D…

卡牌小程序开发,线上抽卡机的优势

今年以来&#xff0c;卡牌成为了一种新的潮玩方式&#xff0c;继盲盒后在度成为大众收藏娱乐的选择。卡牌的种类各异&#xff0c;以热门IP为原型&#xff0c;设计出了专属卡牌&#xff0c;具有较大的收藏价值&#xff0c;吸引了无数的消费者的关注。 随着互联网技术的发展&…

python常用的字符串方法

一、求字符串的长度 python最常用的内置函数 二、切片获取子串 获取前第5个字符 print(s[:5]) # wuzij 获取后6个字符 print(s[-6:]) # python 三、去掉多余空格 四、是否以某个串开头 五、是否包含某个子串 六、串联多串 七、分割多串 八、替换子串

施瓦辛格之女凯瑟琳和克里斯帕拉特的女儿养成了她们妈妈一个习惯

凯瑟琳施瓦辛格与克里斯帕拉特的长女莉拉和次女艾洛斯显然与其母亲极为相像&#xff0c;一张崭新且罕见的照片清晰地展露了这一情形&#xff01; 广告 10 月 13 日&#xff0c;施瓦辛格分享了一系列家人外出游玩的照片。她分享了这些甜蜜温馨的照片&#xff0c;并附上标题写道&…

链接伪类(:hover)CSS背景图片有闪动BUG的解决方法 vue3

现象&#xff1a; hover时候&#xff0c;图片还没加载出来&#xff0c;导致边框闪烁 在Vue 3中&#xff0c;如果你遇到了使用伪类(:hover)时背景图片出现闪烁的问题&#xff0c;可能是由于浏览器的渲染机制导致的。解决这个问题的方法可能包括&#xff1a; 使用background-pos…

【Go初阶】两万字快速入门Go语言

初见golang语法 package mainimport "fmt"func main() {/* 简单的程序 万能的hello world */fmt.Println("Hello Go")} 第一行代码package main定义了包名。你必须在源文件中非注释的第一行指明这个文件属于哪个包&#xff0c;如&#xff1a;package main…