当前位置: 首页 > news >正文

uniapp-降低主包体积-分包js

网上大量篇幅在讲如何分包,降低图片资源等等策略,但针对一些陈年老项目大家会发现,这些策略都已做过,那么该如何降低包体积呢?

在 uni-app 小程序开发中,主包体积超限是最常见的痛点之一。官方默认的split-chunks策略存在一个硬伤:一个 JS 模块只要被 2 个及以上分包同时引用,即便主包完全不用,也会被强制打包进主包common/vendor,直接导致主包体积暴涨,甚至无法发布。

为了解决这个问题,写一个插件,专门针对小程序平台,自动将多分包共享的模块从主包剥离,分别下沉到对应分包的 vendor 中,精准瘦身主包。

一、先搞懂:为什么主包会莫名其妙变大

uni-app 小程序默认分包优化规则:

  1. 仅被单个分包使用的模块 → 自动打包到该分包 vendor(最优)
  2. 主包 + 任意分包使用的模块 → 打包进主包 vendor(合理)
  3. 2 个及以上分包使用、主包未使用的模块 → 强制打包进主包 vendor( 问题根源)

这就导致:很多公共工具类、组件、工具库,只要被多个分包共享,就会挤爆主包体积。

二、插件核心能力

SplitUtilPlugin专门解决上述第 3 种场景:

  • 仅针对小程序平台生效,不影响 H5/APP
  • 自动识别:多分包共享、主包未引用的模块
  • 自动剥离:从主包common/vendor移除
  • 自动分发:分别打包到对应分包的common/vendor
  • 安全兜底:保留主包引用的模块,不破坏业务逻辑
  • 可视化日志:清晰查看模块移动记录,方便排查

三、完整插件代码

/** * SplitUtilPlugin * 解决 uni-app 主包体积过大问题 * 核心:将【多分包共享、主包未使用】的模块从主包vendor移至对应分包vendor */ 'use strict' const path = require('path') const GraphHelpers = require('webpack/lib/GraphHelpers') const PLUGIN_NAME = 'SplitUtilPlugin' const MAIN_VENDOR = 'common/vendor' // 路径标准化 function normalizePath(p) { return (p || '').replace(/\\/g, '/') } // 根据名称查找chunk function findChunkByName(chunks, name) { return chunks.find((c) => c.name === name) } class SplitUtilPlugin { apply(compiler) { // 仅小程序平台生效 const platform = process.env.UNI_PLATFORM || process.env.VUE_APP_PLATFORM const isMP = platform && platform.startsWith('mp-') if (!isMP) { console.log(`[${PLUGIN_NAME}] 非小程序平台,插件跳过。`) return } compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => { // webpack优化阶段:splitChunks已完成,chunk归属已确定 compilation.hooks.optimizeChunkModules.tap( PLUGIN_NAME, (chunks, modules) => { // 获取所有分包根目录 const subPackageRoots = Object.keys( process.UNI_SUBPACKAGES || {}, ).map((root) => normalizePath(root) + '/') if (subPackageRoots.length === 0) { console.warn(`[${PLUGIN_NAME}] 未配置分包,跳过。`) return } const chunkArr = Array.from(chunks) // 查找主包vendor const mainVendorChunk = findChunkByName(chunkArr, MAIN_VENDOR) if (!mainVendorChunk) { console.log(`[${PLUGIN_NAME}] 未找到主包vendor,跳过。`) return } // 判断chunk所属分包 const getSubPackageRoot = (chunk) => { const name = normalizePath(chunk.name || '') return subPackageRoots.find((root) => name.startsWith(root)) || null } // 判断是否为主包chunk const isMainPackageChunk = (chunk) => !getSubPackageRoot(chunk) // 统计数据 let movedCount = 0 let skippedMainRef = 0 let skippedSinglePkg = 0 // 遍历所有模块 for (const mod of modules) { // 仅处理真实JS文件,跳过CSS/虚拟模块 if (!mod.resource || mod.type === 'css/mini-extract') continue const modChunks = mod.getChunks() // 仅处理当前只在主包vendor中的模块 if (modChunks.length !== 1 || modChunks[0].name !== MAIN_VENDOR) continue // 收集所有引用该模块的业务chunk const issuerChunks = new Set() if (mod.reasons) { for (const reason of mod.reasons) { if (reason.module) { for (const rc of reason.module.getChunks()) { issuerChunks.add(rc) } } } } // 兜底:获取真实引用chunk const referenceChunks = issuerChunks.size > 0 ? Array.from(issuerChunks) : chunkArr.filter(c => c.name !== MAIN_VENDOR && c.hasModule?.(mod)) // 安全规则:主包直接引用的模块,不移动 const hasMainRef = referenceChunks.some(isMainPackageChunk) if (hasMainRef) { skippedMainRef++ continue } // 收集所有引用该模块的分包 const targetPkgRoots = new Set() for (const rc of referenceChunks) { const root = getSubPackageRoot(rc) if (root) targetPkgRoots.add(root) } // 仅处理多分包共享模块(单分包uni已自动优化) if (targetPkgRoots.size < 2) { skippedSinglePkg++ continue } // 为每个目标分包创建/关联vendor chunk for (const pkgRoot of targetPkgRoots) { const targetChunkName = normalizePath( path.join(pkgRoot.replace(/\/$/, ''), MAIN_VENDOR), ) let pkgChunk = findChunkByName(chunkArr, targetChunkName) // 不存在则新建分包vendor if (!pkgChunk) { const group = compilation.addChunkInGroup(targetChunkName) pkgChunk = group.chunks[0] chunkArr.push(pkgChunk) } // 连接模块与分包vendor GraphHelpers.connectChunkAndModule(pkgChunk, mod) } // 从主包vendor移除该模块 GraphHelpers.disconnectChunkAndModule(mainVendorChunk, mod) movedCount++ // 打印移动日志 console.log(`[${PLUGIN_NAME}] 移动[${movedCount}]: ${normalizePath(mod.resource)} -> 分包: ${Array.from(targetPkgRoots).join(', ')}`) } // 最终统计 console.log(`[${PLUGIN_NAME}] 处理完成:移动=${movedCount} | 跳过(主包引用)=${skippedMainRef} | 跳过(单分包)=${skippedSinglePkg}`) }, ) }) } } module.exports = SplitUtilPlugin

四、vue.config.js 配置教程

  1. 将插件文件放在项目根目录src目录下
  2. 打开项目根目录的vue.config.js,添加配置:
const SplitUtilPlugin = require('./split-util-plugin') module.exports = { configureWebpack: { plugins: [ // 注入分包优化插件 new SplitUtilPlugin() ] } }
http://www.jsqmd.com/news/536850/

相关文章:

  • nanobot镜像沙盒体验:无需本地安装的OpenClaw快速验证方案
  • Llama-3.2V-11B-cot详细步骤:bf16精度下视觉权重加载稳定性验证
  • 别再只 apt-get install 了!手把手教你为Docker容器配置NVIDIA GPU支持(从nvidia-container-toolkit到实战)
  • ChatGPT代理模式实战:高并发场景下的架构设计与性能优化
  • 2026丨最火话题:关于java最新的进阶代码学习方法!+实战避坑!
  • LeetCode hot100——最长连续序列
  • ai-news-2026-03-25
  • 2026年热门的铠装网线/浙江工业网线/浙江屏蔽网线/超五类网线实力工厂推荐 - 品牌宣传支持者
  • 2026最新Java面试,必问的十个AI面试题!标准答案+实战避坑,先码住!
  • ChatGPT生成Word文档实战指南:从API调用到格式优化
  • 微信小程序连接MQTT避坑指南:从域名备案到ClientId冲突,这些雷我都帮你踩过了
  • weixin259基于微信小程序的医院综合服务平台的设计与实现ssm(文档+源码)_kaic
  • 2026丨科学大百科:Java面试时问在项目开发时遇到最难的是什么问题,?怎么解决的?
  • 不只是漏洞检测:用Joern+Neo4j在Windows下可视化你的C项目代码结构图
  • OpenClaw+GLM-4.7-Flash会议纪要:语音转文字与要点提取
  • 2026北京报废资产回收优质服务商推荐榜:防爆报废资产回收、防腐报废资产回收、低噪声报废资产回收、废金属回收、废金属回收选择指南 - 优质品牌商家
  • OpenClaw+nanobot隐私计算:本地化处理敏感信息方案
  • Yarle终极指南:3分钟完成Evernote到Markdown的无损迁移
  • HunyuanVideo-Foley效果展示:AI生成音效在Audition中后期处理兼容性验证
  • 2026大型人工气候室优质品牌推荐指南:小型人工气候室/恒温恒湿人工气候室/恒温恒湿植物工厂/恒温恒湿种子资源库/选择指南 - 优质品牌商家
  • 2026年质量好的浙江铠装网线/B1阻燃网线源头厂家推荐 - 品牌宣传支持者
  • RWKV7-1.5B-G1A效果展示:多风格创意文本生成作品集
  • OpenClaw创意应用:Qwen3-VL:30B生成飞书生日祝福海报
  • Element UI表格fixed列错位?5分钟搞定el-table滚动条与固定列对齐问题
  • 2026年质量好的模块化配线架/六类配线架公司选择指南 - 品牌宣传支持者
  • 想拥有专属的桌面宠物伙伴吗?DyberPet开源框架让个性化养成触手可及
  • Qwen3-VL-8B部署避坑指南:消费级GPU配置与常见问题解决
  • macOS下OpenClaw深度配置:GLM-4.7-Flash模型性能调优
  • OpenClaw技能扩展指南:基于Qwen3.5-4B-Claude-4.6-Opus-Reasoning-Distilled-GGUF开发自定义自动化
  • AI原生应用自适应界面,创造流畅交互体验