tencent cloud

文档反馈

分包加载

最后更新时间:2024-11-14 16:33:53
    某些情况下,开发者需要将小程序划分成不同的子包,在构建时打包成不同的分包,用户在使用时按需进行加载。
    在构建小程序分包项目时,构建会输出一个或多个分包。每个使用分包小程序必定含有一个主包。所谓的主包,即放置默认启动页面/TabBar 页面,以及一些所有分包都需用到公共资源/JS 脚本;而分包则是根据开发者的配置进行划分。
    在小程序启动时,默认会下载主包并启动主包内页面,当用户用户进入分包内某个页面时,客户端会把对应分包下载下来,下载完成后再进行展示。
    目前小程序分包大小有以下限制:
    整个小程序所有分包大小不超过 24M。
    单个分包/主包大小不能超过 2M。
    对小程序进行分包,可以优化小程序首次启动的下载时间,以及在多团队共同开发时可以更好的解耦协作。

    1. 使用分包

    1.1 配置方法

    假设支持分包的小程序目录结构如下:
    ├── app.js
    ├── app.json
    ├── app.wxss
    ├── packageA
    │ └── pages
    │ ├── cat
    │ └── dog
    ├── packageB
    │ └── pages
    │ ├── apple
    │ └── banana
    ├── pages
    │ ├── index
    │ └── logs
    └── utils
    开发者通过在 app.json subpackages 字段声明项目分包结构:
    写成 subPackages 也支持。
    {
    "pages":[
    "pages/index",
    "pages/logs"
    ],
    "subpackages": [
    {
    "root": "packageA",
    "pages": [
    "pages/cat",
    "pages/dog"
    ]
    }, {
    "root": "packageB",
    "name": "pack2",
    "pages": [
    "pages/apple",
    "pages/banana"
    ]
    }
    ]
    }
    subpackages 中,每个分包的配置有以下几项:
    字段
    类型
    说明
    root
    String
    分包根目录
    name
    String
    分包别名,分包预下载时可以使用
    pages
    StringArray
    分包页面路径,相对于分包根目录
    independent
    Boolean
    分包是否是独立分包

    1.2 打包原则

    声明 subpackages 后,将按 subpackages 配置路径进行打包,subpackages 配置路径外的目录将被打包到主包中。
    主包也可以有自己的 pages,即最外层的 pages 字段。
    subpackage 的根目录不能是另外一个 subpackage 内的子目录。
    tabBar 页面必须在主包内。

    1.3 引用原则

    packageA 无法 require packageB JS 文件,但可以 require 主包、packageA 内的 JS 文件;使用 分包异步化 时不受此条限制。
    packageA 无法 import packageB 的 template,但可以 require 主包、packageA 内的 template。
    packageA 无法使用 packageB 的资源,但可以使用主包、packageA 内的资源。

    2. 独立分包

    独立分包是小程序中一种特殊类型的分包,可以独立于主包和其他分包运行。从独立分包中页面进入小程序时,不需要下载主包。当用户进入普通分包或主包内页面时,主包才会被下载。
    开发者可以按需将某些具有一定功能独立性的页面配置到独立分包中。当小程序从普通的分包页面启动时,需要首先下载主包;而独立分包不依赖主包即可运行,可以很大程度上提升分包页面的启动速度。
    一个小程序中可以有多个独立分包。

    2.1 配置方法

    假设小程序目录结构如下:
    ├── app.js
    ├── app.json
    ├── app.wxss
    ├── moduleA
    │ └── pages
    │ ├── rabbit
    │ └── squirrel
    ├── moduleB
    │ └── pages
    │ ├── pear
    │ └── pineapple
    ├── pages
    │ ├── index
    │ └── logs
    └── utils
    开发者通过在app.jsonsubpackages字段中对应的分包配置项中定义independent字段声明对应分包为独立分包。
    {
    "pages": [
    "pages/index",
    "pages/logs"
    ],
    "subpackages": [
    {
    "root": "moduleA",
    "pages": [
    "pages/rabbit",
    "pages/squirrel"
    ]
    }, {
    "root": "moduleB",
    "pages": [
    "pages/pear",
    "pages/pineapple"
    ],
    "independent": true
    }
    ]
    }

    2.2 限制

    独立分包属于分包的一种。普通分包的所有限制都对独立分包有效。独立分包中自定义组件的处理方式同普通分包。
    此外,使用独立分包时要注意:
    独立分包中不能依赖主包和其他分包中的内容,包括 js 文件、template、wxss、自定义组件等(使用 分包异步化 时 js 文件、自定义组件不受此条限制);
    主包中的 app.wxss 对独立分包无效,应避免在独立分包页面中使用 app.wxss 中的样式;
    App 只能在主包内定义,独立分包中不能定义 App,会造成无法预期的行为;

    2.3 注意事项

    2.3.1 关于 getApp()
    与普通分包不同,独立分包运行时,App并不一定被注册,因此 getApp() 也不一定可以获得 App 对象:
    当用户从独立分包页面启动小程序时,主包不存在,App也不存在,此时调用 getApp() 获取到的是 undefined。 当用户进入普通分包或主包内页面时,主包才会被下载,App 才会被注册。
    当用户是从普通分包或主包内页面跳转到独立分包页面时,主包已经存在,此时调用getApp() 可以获取到真正的App
    由于这一限制,开发者无法通过 App 对象实现独立分包和小程序其他部分的全局变量共享。
    为了在独立分包中满足这一需求,getApp 支持 [allowDefault] 参数,在 App 未定义时返回一个默认实现。当主包加载,App被注册时,默认实现中定义的属性会被覆盖合并到真正的 App 中。
    示例代码:
    独立分包中
    const app = getApp({allowDefault: true}) // {}
    app.data = 456
    app.global = {}
    app.js中
    App({
    data: 123,
    other: 'hello'
    })
    
    console.log(getApp()) // {global: {}, data: 456, other: 'hello'}
    2.3.2 关于 App 生命周期
    当从独立分包启动小程序时,主包中 ApponLaunch 和首次 onShow 会在从独立分包页面首次进入主包或其他普通分包页面时调用。
    由于独立分包中无法定义 App,小程序生命周期的监听可以使用 wx.onAppShowwx.onAppHide 完成。App 上的其他事件可以使用 wx.onErrorwx.onPageNotFound 监听。

    3. 分包预下载

    开发者可以通过配置,在进入小程序某个页面时,由框架自动预下载可能需要的分包,提升进入后续分包页面时的启动速度。对于 独立分包,也可以预下载主包。
    分包预下载目前只支持通过配置方式使用,暂不支持通过调用 API 完成。
    vConsole 里有preloadSubpackages开头的日志信息,可以用来验证预下载的情况。

    3.1 配置方法

    预下载分包行为在进入某个页面时触发,通过在app.json增加preloadRule配置来控制。
    {
    "pages": ["pages/index"],
    "subpackages": [
    {
    "root": "important",
    "pages": ["index"],
    },
    {
    "root": "sub1",
    "pages": ["index"],
    },
    {
    "name": "hello",
    "root": "path/to",
    "pages": ["index"]
    },
    {
    "root": "sub3",
    "pages": ["index"]
    },
    {
    "root": "indep",
    "pages": ["index"],
    "independent": true
    }
    ],
    "preloadRule": {
    "pages/index": {
    "network": "all",
    "packages": ["important"]
    },
    "sub1/index": {
    "packages": ["hello", "sub3"]
    },
    "sub3/index": {
    "packages": ["path/to"]
    },
    "indep/index": {
    "packages": ["__APP__"]
    }
    }
    }
    preloadRule中,key是页面路径,value是进入此页面的预下载配置,每个配置有以下几项:
    字段
    类型
    必填
    默认值
    说明
    packages
    StringArray
    进入页面后预下载分包的 root 或 name,__APP__ 表示主包。
    network
    String
    wifi
    在指定网络下预下载,可选值为:
    all: 不限网络;
    wifi: 仅wifi下预下载。

    3.2 限制

    同一个分包中的页面享有共同的预下载大小限额2M,限额会在工具中打包时校验。
    如,页面 A 和 B 都在同一个分包中,A 中预下载总大小0.5M的分包,B中最多只能预下载总大小1.5M的分包。

    4. 分包异步化

    在小程序中,不同的分包对应不同的下载单元;因此,除了非独立分包可以依赖主包外,分包之间不能互相使用自定义组件或进行 require。「分包异步化」特性将允许通过一些配置和新的接口,使部分跨分包的内容可以等待下载后异步使用,从而一定程度上解决这个限制。

    4.1 跨分包自定义组件引用

    一个分包使用其他分包的自定义组件时,由于其他分包还未下载或注入,其他分包的组件处于不可用的状态。通过为其他分包的自定义组件设置 占位组件,我们可以先渲染占位组件作为替代,在分包下载完成后再进行替换。例如:
    // subPackageA/pages/index.json
    {
    "usingComponents": {
    "button": "../../commonPackage/components/button",
    "list": "../../subPackageB/components/full-list",
    "simple-list": "../components/simple-list",
    "plugin-comp": "plugin://pluginInSubPackageB/comp"
    },
    "componentPlaceholder": {
    "button": "view",
    "list": "simple-list",
    "plugin-comp": "view"
    }
    }
    在这个配置中,buttonlist 两个自定义组件是跨分包引用组件,其中 button 在渲染时会使用内置组件 view 作为替代,list 会使用当前分包内的自定义组件 simple-list 作为替代进行渲染;在这两个分包下载完成后,占位组件就会被替换为对应的跨分包组件。

    4.2 跨分包 JS 代码引用

    一个分包中的代码引用其它分包的代码时,为了不让下载阻塞代码运行,我们需要异步获取引用的结果。如:
    // subPackageA/index.js
    // 使用回调函数风格的调用
    require('../subPackageB/utils.js', utils => {
    console.log(utils.whoami) // Wechat MiniProgram
    }, ({mod, errMsg}) => {
    console.error(`path: ${mod}, ${errMsg}`)
    })
    // 或者使用 Promise 风格的调用
    require.async('../commonPackage/index.js').then(pkg => {
    pkg.getPackageName() // 'common'
    }).catch(({mod, errMsg}) => {
    console.error(`path: ${mod}, ${errMsg}`)
    })
    详情可参考 模块化 - require 章节
    
    联系我们

    联系我们,为您的业务提供专属服务。

    技术支持

    如果你想寻求进一步的帮助,通过工单与我们进行联络。我们提供7x24的工单服务。

    7x24 电话支持