## 1. 小程序存在的分包痛点
在小程序开发中,随着业务功能的增加,代码体积往往会迅速膨胀。微信小程序对包体积有严格限制(目前主包限制 2MB,整包 20MB)。为了解决这个问题,通常会采用分包机制。然而,原生分包机制存在以下痛点:
* **引用限制**:主包无法直接引用分包中的组件/资源,分包之间也无法直接相互引用。只有分包可以引用主包的资源。
* **主包压力**:Tabbar 页面(如首页)必须在主包中。如果首页使用了大量自定义组件(如装修组件 `diyPage`),这些组件及其依赖都必须打包在主包中,导致主包体积极易超标,功能无法落地。
## 2. 解决方案:分包异步化
分包异步化(Subpackage Async)是微信小程序提供的一种能力,允许跨分包引用组件和 JS 代码。
* **原理**:通过配置“占位组件”(Placeholder),在分包组件下载并注入之前,先渲染一个占位组件(如 Loading 或骨架屏)。待分包下载完成后,自动替换为实际组件。
* **优势**:彻底打破了“主包不能引用分包组件”的限制。我们可以将庞大的业务组件(如 `diyPage`)放入分包,只在主包首页保留一个轻量级的引用配置。
### 相关文档
* [分包结构配置](https://developers.weixin.qq.com/miniprogram/dev/framework/subpackages/basic.html)
* [分包异步化](https://developers.weixin.qq.com/miniprogram/dev/framework/subpackages/async.html)
## 3. 优化效果对比
通过实施分包异步化,将原本必须在主包的 `diy` 组件抽离至分包:
* **使用前**:主包包含所有首页装修组件逻辑,体积接近或超过 2MB 限制,无法新增业务代码。
以下图为例,一个主包页面直接引用了diy组件,打包之后可以看到diy组件的代码被打包进了主包中,占据了主包相关体积。

我们可以将引用到的 Diy 组件代码移出主包,抽离到分包之中,如下:

但是此时小程序页面中并没有正常渲染出对应组件,这是因为小程序跨分包引用组件,必须配置一个占位组件,等分包下载完成后,会自动替换掉这个占位组件,我们可以添加一个占位组件,如下:

添加了占位组件之后,可以看到组件内容正常渲染了。

* **使用后**:`diy` 组件及其庞大的子组件群被移出主包,主包仅保留少量基础代码。**主包体积显著下降**,释放了大量空间用于核心业务逻辑,彻底解决了空间不足的问题。
可以看到 Diy 组件被打包进了分包之中,不再占据主包空间。

## 4. 原生分包异步化的不足
虽然分包异步化功能强大,但在 uni-app 工程中使用时存在不便:
* **配置繁琐**:需要在 `pages.json` 或各个页面的配置文件中手动编写 `componentPlaceholder` 字段。
* **维护成本高**:组件的引用在 `.vue` 文件中,而异步化配置在 `.json` 中,两者分离,容易遗漏或配置错误(例如组件重命名时)。
* **命名规范**:Vue 组件通常使用驼峰命名(如 `diyPage`),而小程序 JSON 配置要求使用短横线命名(如 `diy-page`),手动转换容易出错。
## 5. 自研 Webpack 插件解决方案
为了解决上述工程化痛点,我们自研了 Webpack 插件 `UniComponentPlaceholder`。
### 插件核心逻辑
1. **自动扫描**:在构建过程中(`emit` 钩子),插件会扫描 `pages` 目录下的所有 `.vue` 文件。
2. **配置提取**:自动读取 `.vue` 文件中的 `componentPlaceholder` 对象配置。
3. **自动转换**:
* 处理路径引用,确保生成的 JSON 配置路径正确。
4. **自动注入**:将处理后的配置自动写入最终生成的页面 `json` 文件中。
### 解决了什么问题
* **开发体验一致**:开发者只需在 Vue 组件内通过 `componentPlaceholder` 属性配置,无需切换文件。
* **低侵入性**:无需修改 uni-app 核心构建逻辑,作为插件挂载。
* **自动化**:避免了手动维护 JSON 文件的繁琐和错误风险。
## 6. 插件使用说明
### 步骤一:组件代码编写
在需要使用分包异步组件的页面(例如 `pages/index/index.vue`)中,直接在 `script` 部分增加 `componentPlaceholder` 属性。

**示例代码 (`pages/index/index.vue`):**
```javascript
<script>
export default {
// ... 其他配置
components: {
diyPage, // 分包中的组件
diySkeleton // 主包中的占位组件(骨架屏)
},
// #ifdef MP
// 插件会自动识别此配置并注入到 page.json
componentPlaceholder: {
"diy-page": "diy-skeleton"
},
// #endif
// ...
}
</script>
```
### 步骤二:分包组件配置
确保分包组件(如 `sub-packages/diy/diyPage/index.vue`)已正确放置在分包目录下,并且在 `pages.json` 中配置了分包规则。
### 注意事项
* **Key 值对应**:`componentPlaceholder` 中的 Key 必须对应组件标签名(支持插件自动转短横线)。
* **条件编译**:建议包裹在 `// #ifdef MP` 中,因为该特性主要针对小程序平台。
* **占位组件**:建议准备一个轻量级的 Loading 或骨架屏组件(如 `diy-skeleton`)作为 value 值,提升用户体验。

