【uniapp】分包异步化

1,297 阅读2分钟

作用

默认情况下只有非独立分包可以调用主包的内容,分包异步化的出现可以通过配置与接口进行部分内容的跨分包调用

环境

uniapp(vue cli + vite + vue3) + 微信小程序

应用场景

  • 跨分包调用代码
  • 跨分包调用 npm 包
    • uniapp 打包默认会将 npm 包打进主包的 vendor.js 中,导致主包过大
    • 可手动将 npm 包移入分包中
    • 通过分包异步化的方式跨分包调用 npm 包
  • 跨分包调用组件

示例代码

跨分包调用代码

使用微信自带的 require 函数,此处写的代码在编译后会原样调用,因此需使用相对路径,文件后缀也是 js

<script setup lang="ts">
require
  .async('../../sub/pkgA/logicA.js')
  .then((pkg) => {
    pkg.tst('main')
  })
  .catch(({ mod, errMsg }) => {
    console.error(`path: ${mod}, ${errMsg}`)
  })
</script>

分包的业务代码

// sub/pkgA/logicA.ts
export function tst(name: string) {
  console.log('tst55667788', name)
}

为了让分包业务代码打包进产物,需要在分包页面中进行引用

// sub/pkgA/pkgA.vue
<script setup lang="ts">
import { tst } from './logicA.ts'
</script>

<template>
  <div>
    {{ tst }}
  </div>
</template>

跨分包调用 npm 包

使用方式与跨分包调用业务代码一致,唯一的区别就是需要将 node_modules 中的 npm 包移入子包代码中,并且需要被子包页面所引用

- sub
  - pkgA
    - dayjs // npm 包代码
    - pkgA.vue
跨分包调用组件

配置 componentPlaceholder

// pages.json
"pages": [
  {
    "path": "pages/index/index",
    "style": {
      "navigationStyle": "custom"
      "componentPlaceholder": {
        "comp-a": "view" // 组件名: 占位内容
      }
    }
  }
]

调用分包中的组件

<script setup lang="ts">
import compA from '@/sub/pkgA/compA.vue'
</script>

<template>
  <comp-a />
</template>

为了让分包中的组件代码能打包进产物,需要在分包页面中进行引用

// pkgA/pkgA.vue
<script setup lang="ts">
import CompA from './compA.vue'
</script>

<template>
  <div>
    <CompA />
  </div>
</template>

PS:pages.json 中可以给页面配置 componentPlaceholder 引用其他分包的异步组件; 组件中没法配置 componentPlaceholder,因此在组件中引用另一个分包的组件会产生错误 ❌

解决方案:通过 vite 插件对打包产物进行修改,添加上 componentPlaceholder

export default () => {
  return {
    name: 'vite-uni-comp-placeholder',
    enforce: 'post' as any,
    generateBundle: async (_, bundle) => {
      const keys = Object.keys(bundle)
      for (let i = 0; i < keys.length; i++) {
        const key = keys[i]
        const item = bundle[key]
        if (item.type === 'asset' && key.endsWith('.json')) {
          const obj = JSON.parse(item.source as string)
          if (!obj.usingComponents) continue
          Object.keys(obj.usingComponents).forEach((key) => {
            const value = obj.usingComponents[key]
            
            // 使用了子包中的组件,此处路径可自行替换为子包路径
            if (value.startsWith('../../sub')) {
              // 自行添加 componentPlaceholder 配置
              obj.componentPlaceholder = obj.componentPlaceholder || {}
              obj.componentPlaceholder[key] = 'view'
            }
          })
          
          item.source = JSON.stringify(obj)
        }
      }
    }
  }
}