重点!!记小程序分包异步化的注意点
一、分包异步化
微信小程序在2.11.2之后就已经支持分包异步化了,因此引入了俩个特性,支持 跨分包自定义组件引用 与 跨分包 JS 代码引用,应用这俩个特性我们可以去优化主包体积。
1、自定义组件的使用限制
自定义组件的使用受到其所在包的限制:
- 如果组件位于主包中,则它可以被主包和所有分包中的页面引用。
- 如果组件位于某个分包中,则它只能被该分包内的页面引用,无法被其他分包或主包中的页面直接引用,但是可以使用跨分包自定义组件引用。
- 分包异步原理允许主包异步加载并使用子包的组件,未加载完成的时候,使用占位组件展示,加载完成则会替换为真实的组件
- 分包没法直接访问另一个分包的资源,除非两个分包都已经加载过了。这个问题可以使用「分包异步化」解决。
2、跨分包自定义组件引用
跨分包自定义组件引用,需要搭配占位组件的配置才可以实现,根据微信小程序官方文档描述如下:

配置完成之后,可以在对应的主包页面直接使用该组件(),实际上还是存在部分问题的,主要如下:
(1) 在分包未加载完成之前,该组件为占位组件,如果是view,text等基础组件,则slots的内容会直接显示出来,影响用户体验
(2) 该方案只支持直接在页面使用分包组件,页面内的子组件引入均无效
(3) 该方案在遇到分包比较大的时候容易造成过长的加载时间等待
虽然存在着部分问题,但是我们依然可以将部分业务组件分离到分包中,然后使用跨包组件,从而减少主包的体积,在我这个项目中我对接的腾讯云IM就是通过这种形式,将消息列表对接到tabbar中,否则按照以往的经验这一部分肯定占据主包大量的空间。
3、以下是一个简单的分包配置示例:
subPackages是专门存放分包页面的,一个分包为一项,所以它是集合,root则是当前分包的跟路径
{
"pages": ["pages/home/home"], // 主包页面
"subpackages": [
{
"root": "pages-three", // 分包A
"pages": [
{
"path" : "three-render/three-render",
"style" : { "navigationBarTitleText" : "子包pages-three的测试页面" }
}
]
},
{ "root": "packageB", // 分包B
"pages": ["pages/pageB/pageB"]
}
]
} // 主包想要加载分包A里面的three-render页面的组件,修改page.json如下: {
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首页",
"usingComponents": { "three-js": "/pages-three/threeJs/threeJs" // 组件的路径 },
"componentPlaceholder": { "three-js": "view" // 未完成时的占位组件 }
}
}
]
}
注意点
重点:我们必须在子包的某个页面中,去使用这个组件,否则打包的时候会找不到文件,只要在子包的某个页面,导入并渲染了组件,打包的时候threeJs就会在文件中了
4、解决主包页面引入子包页面组件,打包体积大问题
因为uniapp开发的小程序使用的node_modules里面的包永远会打包到主包的vendor.js中,即使是你没有在主包中直接使用。
解决方案
要解决也很简单,把node_modules中的npm包移动到子包的目录中

然后可以修改npm的导入路径,或者在配置文件中统一设置别名,这样会更方便
// vite.config.js import path from "path"; import { defineConfig } from "vite"; import uni from "@dcloudio/vite-plugin-uni"; export default defineConfig({ plugins: [ uni(), ], resolve: { alias: { "three-platformize": path.resolve(__dirname, "./pages-three/npm/three-platformize"), }, }, });
这样打包three就会进入子包了
二、三方框架无法直接使用分包异步化,微信原生语法无影响。如一定要在第三方框架中使用「异步化」这个特性,可以使用-分包插件异步化
1、requirePlugin
// 使用回调函数风格的调用 requirePlugin( 'live-player-plugin', (livePlayer) => { console.log(livePlayer.getPluginVersion()); }, ({ mod, errMsg }) => { console.error(`path: ${mod}, ${errMsg}`); }, ); // 或者使用 Promise 风格的调用 requirePlugin .async('live-player-plugin') .then((livePlayer) => { console.log(livePlayer.getPluginVersion()); }) .catch(({ mod, errMsg }) => { console.error(`path: ${mod}, ${errMsg}`); });
如上,解决思路:微信官方提供的 requirePlugin,Webpack 不会进行编译,则可以正常访问小程序宿主环境的的 requirePlugin API,从而达成异步加载异步的目的。
2、既然要插件,先去微信官方注册一个插件,这部分可以搜官方文档,主要代码如下:
// xxx-plugin/index.js,插件代码只是加载 SDK,并且导出 import TIM from 'tim-wx-sdk'; module.exports = { TIM, };
3、在分包页面中引入插件。
{ "plugins": { "xxx-plugin": { "version": "dev-01055b63731de071ffb850464bd5c7b1", "provider": "xxx-plugin appid" } } }
4、上面的 utils 封装改一下。
// utils/im.js // let TIM = null; requirePlugin.async('xxx-plugin').then(({ TIM: modTIM }) => { TIM = modTIM; }) .catch(({ mod, errMsg }) => { console.error(`direct-service-plugin path: ${mod}, ${errMsg}`); }); // 暴露出去 export { TIM }; //更新一下更好的写法 // utils/im.js let TIM = {}; import { loadPluginPackage } from '@/utils/async-load'; const TIMSdk = loadPluginPackage('xxx-plugin'); TIMSdk.then((mod) => { TIM = mod.TIM; }); // 导出出去 export { TIM, TIMSdk };
5、加载插件的封装
// @/utils/async-load.ts /**@param pluginName * @returns Promise<any> * 加载插件的方法 **/ export async function loadPluginPackage(pluginName: string): Promise<any> { try { // @ts-ignore const mod = await requirePlugin.async(pluginName); return mod; } catch ({ mod, errMsg }) { console.error( `loadPluginPackage '${pluginName}' errpr path: ${mod}, ${errMsg}`, ); return {}; } }
6、插件使用的时候
// app.js ; (async init( ) { const { TIM } = await loadPluginPackage('xxx-plugin') TIM.login({ userID: 'xxxxx', userSig: 'userSig', }) })()
这样还是有问题,如果有多个地方都是用这个 JS,每个地方都要写一下加载插件的方法,可以在小程序启动的时候做一次加载就可以,后面所有用到的地方都用同一个 Promise 就行。
7、修改utils/im.js
let TIM = {}; import { loadPluginPackage } from '@/utils/async-load'; const TIMSdk = loadPluginPackage('xxx-plugin'); TIMSdk.then((mod) => { TIM = mod.TIM; }); // 导出出去 export { TIM, TIMSdk };
TIMSdk,是一个默认执行一次的 Promise,加载过一次之后,后续调用 TIMSdk 拿到的都是同一个结果。
使用方式
// app.js import { TIM, TIMSdk } from '@/utils/im'; /** * * IM初始化 */ const init = async ( ) => { // 要用的时候 await 一下即可 a wait TIMSdk; const tim = TIM.create({ SDKAppID: config.SDKAppID, }); }; init();
8、上面写法太繁琐,完美方式,顶部直接、await
// app.js const { TIM } = await loadPluginPackage('xxx-plugin');
一定要注意基础库的依赖,要用这个方案,必须要把最低基础库限制拉到 2.11.2。
三、控制小程序代码包大小主要几个手段
- 静态资源,能走 CDN 的,全部走 CDN。
- 能分包的页面或者组件,全部放到分包里面去,主包只留不能拆分的,提升分包加载速度可以使开启分包预下载。(preloadRule)
- 如果资源一定要在主包引用且大小不可控,那就使用「分包异步化」或者「分包插件异步化」来处理。「分包异步化」和「分包插件异步化」两者的选择建议: