深圳幻海软件技术有限公司 欢迎您!

小程序分包的一些思考及 Uiniapp 分包优化逻辑的验证

2023-02-28

大家好,我是小智。今天,带来我在团队的最近一次分享。分包什么是分包分包指的是把一个完整的小程序项目,按照需求划分为不同的子包,在构建时打包成不同的分包,用户在使用时按需进行加载。分包的好处对小程序进行分包的好处主要有以下两点:可以优化小程序首次启动的下载时间在多团队共同开发时可以更好的解耦协作uni

大家好,我是小智。

今天,带来我在团队的最近一次分享。

分包

什么是分包

分包指的是把一个完整的小程序项目,按照需求划分为不同的子包,在构建时打包成不同的分包,用户在使用时按需进行加载。

分包的好处

对小程序进行分包的好处主要有以下两点:

  • 可以优化小程序首次启动的下载时间
  • 在多团队共同开发时可以更好的解耦协作

uniapp 分包配置

按官网,假设支持分包的 uni-app 目录结构如下:

┌─pages               
│  ├─index
│  │  └─index.vue    
│  └─login
│     └─login.vue    
├─pagesA   
│  ├─static
│  └─list
│     └─list.vue 
├─pagesB    
│  ├─static
│  └─detail
│     └─detail.vue  
├─static             
├─main.js       
├─App.vue          
├─manifest.json  
└─pages.json            


  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

则需要在 pages.json 中填写:

{
    "pages": [{
        "path": "pages/index/index",
        "style": { ...}
    }, {
        "path": "pages/login/login",
        "style": { ...}
    }],
    "subPackages": [{
        "root": "pagesA",
        "name": "分包的别名"
        "pages": [{
            "path": "list/list",
            "style": { ...}
        }]
    }, {
        "root": "pagesB",
        "pages": [{
            "path": "detail/detail",
            "style": { ...}
        }]
    }],
}


  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

动手实践:https://github.com/qq449245884/uniapp-subpackage-demo/tree/feature/01.%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%88%86%E5%8C%85%E9%85%8D%E7%BD%AE

运行后,可以在开发者详情里查看分包信息:

这样配置的目录结构真的好?

按官网的事例,如果我们要分两个包,则对在 pages 同级下建立两个目录 pagesA、 pagesB,这样划分真的好吗?假设我们有一个活动的业务模板,对应 的活动详情地址是 /pages/activity/detail。pages.json 配置如下:

 "pages": [
  {
   "path": "pages/index/index",
   "style": {
    "navigationBarTitleText": "首页"
   }
  },
  {
   "path": "pages/activity/detail",
   "style": {
    "navigationBarTitleText": "活动详情"
   }
  }
 ],


  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

随着业务的发展,该模块的功能也越来越多。哪天,我们要对该包进行分包的时候,如果按照前面的分包方式,我们如果在 pages 同级下建立一个子包的目录,假设我们这里叫 pagesA,然后把对应的活动模块的文件都挪动到该目录下,对应的pages.json配置如下:

"subPackages": [{
  "root": "pagesA",
  "pages": [{
    "path": "activity/detail"
   }]
}]


  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

此时,对应的 活动详情地址是 /pagesA/activity/detail,这样就会引发一个问题,此时活动详情路径已经变化了,所以分包要能正常工作,之前的路径都要改过来,如果有其它小程序有跳到该详情页面也得改,显然这种分包结构是很不靠谱的,为了一个分包得改多个文件,甚至多个小程序。

那要怎么解决这个问题了?显然,只要路径不变化,但能正确分包不就可以解决这个问题了。

在细想一下分包,无非就是在 subPackages 中指定一个分包名,分包页面对应该分包名下的文件。所以,我们可以指定原有 pages 下的模块作为一个子分包,这样配置就可以解决分包后路径不一致的问题,改写后的结构如下:

  "subPackages": [
    {
      "root": "pages/activity",
      "pages": [
        {
          "path": "detail"
        }
      ]
    }
  ],


  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

事例地址:https://github.com/qq449245884/uniapp-subpackage-demo/tree/feature/02.%E9%85%8D%E7%BD%AE%E7%9B%AE%E5%BD%95%E7%BB%93%E6%9E%84%E7%9A%84%E5%90%88%E7%90%86%E6%80%A7

分包预加载

分包预载配置。配置 preloadRule 后,在进入小程序某个页面时,由框架自动预下载可能需要的分包,提升进入后续分包页面时的启动速度。

假设,我们两个分包 pagesA 和 pagesB ,当我们进入详情页面,想预先加 pagesA ,对应的配置如下:

{
 "pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
  {
   "path": "pages/index/index",
   "style": {
    "navigationBarTitleText": "首页"
   }
  },
  {
   "path": "pages/detail/index",
   "style": {
    "navigationBarTitleText": "详情"
   }
  }
 ],
 "subPackages": [{
  "root": "pagesA",
  "pages": [{
   "path": "detail/index"
  }]
 }, {
  "root": "pagesB",
  "pages": [{
   "path": "detail/index"
  }]
 }],
 "preloadRule": {
  "pages/detail/index": {
   "network": "wifi",
   "packages": ["pagesA", "pagesB"]
  }
 }
}


  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.

preloadRule 中,key 是页面路径,value 是进入此页面的预下载配置, packages 是进入页面后预下载分包的 。network 在指定网络下预下载,可选值为:all(不限网络)、wifi(仅wifi下预下载)。

事例地址:

​​https://github.com/qq449245884/uniapp-subpackage-demo/tree/feature/03.%E5%88%86%E5%8C%85%E9%A2%84%E5%8A%A0%E8%BD%BD​​

分包的加载规则

分包需要注意一点是:tabBar 页面需要放在主包中,假设我们如两个 tabBar,配置如下:

{
 "pages": [
  {
      "path": "pages/channel/index",
      "style": {
        "disableScroll": true
      }
    },
    {
      "path": "pages/member/index",
      "style": {
        "disableScroll": true
        }
      }
 ],
 "tabBar": {
    "color": "#BBBBBD",
    "selectedColor": "#1C1C1C",
    "borderStyle": "white",
    "backgroundColor": "#ffffff",
    "list": [
      {
        "pagePath": "pages/channel/index",
        "iconPath": "static/tabbar_icon_channel.png",
        "selectedIconPath": "static/tabbar_icon_channel_active.png",
        "text": "会员专属"
      },
      {
        "pagePath": "pages/member/index",
        "iconPath": "static/tabbar_icon_member.png",
        "selectedIconPath": "static/tabbar_icon_member_active.png",
        "text": "掌通会员"
      }
    ]
  }
}


  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.

如果我们把 tabBar 页面配置到 subPackages,则会出错:

事例地址:https://github.com/qq449245884/uniapp-subpackage-demo/tree/feature/04.%E5%88%86%E5%8C%85%E7%9A%84%E9%99%90%E5%88%B6

分包优化

在对应平台的配置下添加"optimization":{"subPackages":true}开启分包优化。

分包优化具体逻辑

  • 静态文件:分包下支持 static 等静态资源拷贝,即分包目录内放置的静态资源不会被打包到主包中,也不可在主包中使用
  • js文件:当某个 js 仅被一个分包引用时,该 js 会被打包到该分包内,否则仍打到主包(即被主包引用,或被超过 1 个分包引用)
  • 自定义组件:若某个自定义组件仅被一个分包引用时,且未放入到分包内,编译时会输出提示信息

首先,我们来化验一下第一条规则,首页建立对应的配置:

{
 "pages": [
  {
   "path": "pages/index/index",
  }
 ],
 "subPackages": [{
  "root": "pagesA",
  "pages": [{
   "path": "detail/index"
  }]
 }],
}


  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

这里,我们有一个分包 pagesA 对应一个详情页面,详情页面使用一张图片,该图片放置当前包下static 文件下,内容如下:

  <view class="content">
    <image class="logo" src="../static/test.png" />
  </view>


  • 1.
  • 2.
  • 3.

然后,我们开始打包,然后点击开发者工具的 详情,点击 本地代码-> 依赖分析,就可以查看打包的详细信息,如下图所示:

可以看到,我们点击 主包 下的 static 是没有我们的在分包中用的 test.png 图片,相反,它被打包自己的分包下面。即分包目录内放置的静态资源不会被打包到主包中,成立。

接下来,在来验证一下,如果在主包中使用分包的test.png 图片会怎么样?改写一上我们主包的index 文件的内容:

<view class="content">
  <image class="logo" src="../../pagesA/static/test.png" />
</view>



  • 1.
  • 2.
  • 3.

运行后,控制台会给出错误:

所以,分包下支持 static 等静态资源拷贝,即分包目录内放置的静态资源不会被打包到主包中,也不可在主包中使用成立。

事例地址:https://github.com/qq449245884/uniapp-subpackage-demo/tree/feature/05.%E5%88%86%E5%8C%85%E4%BC%98%E5%8C%96__%E9%9D%99%E6%80%81%E6%96%87%E4%BB%B6

然后,我们来验证一下第二条规则:js文件:当某个 js 仅被一个分包引用时,该 js 会被打包到该分包内,否则仍打到主包(即被主包引用,或被超过 1 个分包引用)。

首先,我们在主包中的 src/utils中建立一个 common.ts,内容如下:

export const add = (a: number, b:number) => {
  return a + b
}


  • 1.
  • 2.
  • 3.

然后,我们在子包 pagesA 中导入使用:

<script lang="ts">
import Vue from 'vue'
import { add } from '@/utils/common'
export default Vue.extend({
  created() {
    console.log(add(1, 2))
  }
})
</script>


  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

同样,打包,然后点击开发者工具的 详情,点击本地代码-> 依赖分析:

从上图可以看到,我们在 主包 中的的 vender.js没有找到 src/utils/common.ts 中的 add 方法,但在分包 pagesA 找到了。

那如果某个 js被多个分包所引用呢?

我们再建立一个分包 pagesB,与同样的方式在子包 pagesB 中导入common.ts:

<script lang="ts">
import Vue from 'vue'
import { add } from '@/utils/common'
export default Vue.extend({
  created() {
    console.log(add(1, 2))
  }
})
</script>


  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

同样,打包,然后点击开发者工具的 详情,点击本地代码-> 依赖分析:

从图可以看到,我们在主包中的 vendor.js 可以找到我们使用的 add 方法,在分包中没有找到对应的 vendor.js,所以当某个 js 仅被多个分包引用时,该 js 会被打包到主包。

事例地址:https://github.com/qq449245884/uniapp-subpackage-demo/tree/feature/05.%E5%88%86%E5%8C%85%E4%BC%98%E5%8C%96__JS%E6%96%87%E4%BB%B6

最后,我们来验证一下第三条规则:自定义组件:若某个自定义组件仅被一个分包引用时,且未放入到分包内,编译时会输出提示信息。

首先,我们在主包中的 src/components 建立一个自定义组件 SayHello,内容如下:

<template>
 <view class="content">
    Hello World
 </view>
</template>



  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

然后,以同样的方式建个分包(步骤跟上面的例子一样),在分包的中引用我们的组件 SayHello:

<template>
  <view class="content">
    <SayHello />
  </view>
</template>

<script lang="ts">
import Vue from 'vue'
import SayHello from '@/components/SayHello.vue'

export default Vue.extend({
  components: {
    SayHello
  }
})
</script>



  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

然后,编译可以在控制台看到提示的信息:

那如果自定义组件被多个分包引用呢?提示应该会消失,为了严谨性,我们再建个分包 pagesB 以同样姿势引用组件,然后编译,再查看控制台:

可以看到,提示信息消失了。

至此,UniApp 官网提到分包优化具体逻辑我们都验证过,Nice。

事例地址:https://github.com/qq449245884/uniapp-subpackage-demo/tree/feature/0.5.%E5%88%86%E5%8C%85%E4%BC%98%E5%8C%96__%E7%BB%84%E4%BB%B6