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
  • 安全兜底:保留主包引用的模块,不破坏业务逻辑
  • 可视化日志:清晰查看模块移动记录,方便排查

三、完整插件代码

javascript 复制代码
/**
 * 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,添加配置:
javascript 复制代码
const SplitUtilPlugin = require('./split-util-plugin')

module.exports = {
  configureWebpack: {
    plugins: [
      // 注入分包优化插件
      new SplitUtilPlugin()
    ]
  }
}
相关推荐
专科3年的修炼2 小时前
uni-app移动应用开发第三章
uni-app
00后程序员张2 小时前
iPhone 无需越狱文件管理 使用Keymob查看导出文件
android·ios·小程序·https·uni-app·iphone·webview
2501_916008892 小时前
Unity3D iOS 应用防篡改实战 资源校验、 IPA 二进制保护
android·ios·小程序·https·uni-app·iphone·webview
2501_915909063 小时前
MachObfuscator全面解析:Apple平台Mach-O应用程序混淆技术指南
macos·ios·小程序·uni-app·objective-c·cocoa·iphone
『 时光荏苒 』3 小时前
2026跨端新纪元:深入解析 uni-app x 与 UTS 的原生革命
uni-app
TE-茶叶蛋3 小时前
uni-app 中,`uni_modules` 和 `node_modules`
uni-app
2501_9159184116 小时前
苹果App Store上架审核卡住原因分析与解决方案指南
android·ios·小程序·https·uni-app·iphone·webview
嵌入式-老费20 小时前
vivado hls的应用(axis接口)
前端·webpack·node.js
HWL56791 天前
uni-app的生命周期
前端·vue.js·uni-app