Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,581 changes: 1,581 additions & 0 deletions .agents/skills/mpx-rn-style-guide/references/rn-api-reference.md

Large diffs are not rendered by default.

433 changes: 383 additions & 50 deletions .agents/skills/mpx-rn-style-guide/references/rn-json-reference.md

Large diffs are not rendered by default.

699 changes: 560 additions & 139 deletions .agents/skills/mpx-rn-style-guide/references/rn-script-reference.md

Large diffs are not rendered by default.

323 changes: 171 additions & 152 deletions .agents/skills/mpx-rn-style-guide/references/rn-style-practice.md

Large diffs are not rendered by default.

193 changes: 116 additions & 77 deletions .agents/skills/mpx-rn-style-guide/references/rn-style-reference.md

Large diffs are not rendered by default.

1,173 changes: 719 additions & 454 deletions .agents/skills/mpx-rn-style-guide/references/rn-template-reference.md

Large diffs are not rendered by default.

112 changes: 96 additions & 16 deletions .agents/skills/mpx-rn-style-guide/references/single-file-component.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 单文件开发 {#single-file-development}

小程序规范中每个页面和组件都是由四个文件描述组成的,wxml/js/wxss/json,分别描述了组件/页面的视图模板,执行逻辑,样式和配置,由于这四个部分彼此之间存在相关性,比如模板中的组件需要在json中注册,数据需要在js中定义,这种离散的文件结构在实际开发的时候体验并不理想;受Vue单文件开发的启发,Mpx也提供了类似的单文件开发模式,拓展名为.mpx。
小程序规范中每个页面和组件都是由四个文件描述组成的,wxml/js/wxss/json,分别描述了组件/页面的视图模板,执行逻辑,样式和配置,由于这四个部分彼此之间存在相关性,比如模板中的组件需要在 json 中注册,数据需要在 js 中定义,这种离散的文件结构在实际开发的时候体验并不理想;受 Vue 单文件开发的启发,Mpx 也提供了类似的单文件开发模式,拓展名为.mpx。

Mpx 单文件组件(SFC)格式 `.mpx` 是一种将组件的视图模板、组件逻辑、组件样式和组件配置封装在一个文件中的开发模式。每个 `.mpx` 文件都包含了以下几个部分:

Expand All @@ -9,35 +9,115 @@ Mpx 单文件组件(SFC)格式 `.mpx` 是一种将组件的视图模板、
- style 区块:定义组件的样式,支持 CSS 预处理(如 Stylus、Sass、Less 等),跨端输出 RN 时存在较强约束限制,对应微信小程序的 `.wxss` 部分
- json 区块:定义组件的配置,支持微信小程序的组件配置选项,对应微信小程序的 `.json` 部分

简单示例如下
示例如下

```html
<!--对应.wxml文件-->
<template>
<list></list>
<!--动态样式-->
<view class="container" wx:style="{{dynamicStyle}}">
<!--数据绑定-->
<view class="title">{{title}}</view>
<!--计算属性数据绑定-->
<view class="title">{{reversedTitle}}</view>
<view class="list">
<!--循环渲染,动态类名,事件处理内联传参-->
<view wx:for="{{list}}" wx:key="id" class="list-item" wx:class="{{ {active:item.active} }}"
bindtap="toggleActive(index)">
<view>{{item.content}}</view>
</view>
</view>
<!-- wx:ref 标记 ref 名;布局完成后在 setup 中用 context.refs.ci、选项式中用 this.$refs.ci 取自定义组件实例 -->
<custom-input wx:ref="ci" wx:model="{{customInfo}}" wx:model-prop="info" wx:model-event="change"/>
<!--动态组件,is传入组件名字符串,可使用的组件需要在json中注册,全局注册也生效-->
<component is="{{current}}"></component>
<!--显示/隐藏dom-->
<view class="bottom" wx:show="{{showBottom}}">
<!-- 模板节点条件编译,非目标平台的节点不会进入产物 -->
<view @wx>wx mode</view>
<view @ali>ali mode</view>
</view>
</view>
</template>
<!--对应.js文件-->

<script>
import { createPage } from '@mpxjs/core'
import { createPage, ref, computed, watch, onMounted } from '@mpxjs/core'

createPage({
onLoad () {
},
onReady () {
setup (props, context) {
const dynamicStyle = ref({
fontSize: '16px',
color: 'red'
})
const title = ref('hello world')
const list = ref([
{
content: '全军出击',
id: 0,
active: false
},
{
content: '猥琐发育,别浪',
id: 1,
active: false
}
])
const customInfo = ref({
title: 'test',
content: 'test content'
})
const current = ref('com-a')
const showBottom = ref(false)

const reversedTitle = computed(() => title.value.split('').reverse().join(''))

watch(title, (val, oldVal) => {
console.log(val, oldVal)
}, { immediate: true })

function toggleActive (index) {
list.value[index].active = !list.value[index].active
}

// 页面下 onMounted 对应小程序页面的 onReady(组件对应 ready);此时可安全读取 wx:ref
onMounted(() => {
const ciIns = context.refs.ci
if (ciIns) {
// 拿到 custom-input 组件实例,可调用其对外暴露的 methods 等(以子组件实际导出为准)
// 例如:ciIns.focus()
console.log('custom-input instance', ciIns)
}
setTimeout(() => {
title.value = '你好,世界'
current.value = 'com-b'
}, 1000)
})

return {
dynamicStyle,
title,
list,
customInfo,
current,
showBottom,
reversedTitle,
toggleActive
}
}
})
</script>
<!--对应.wxss文件-->
<style lang="stylus">
</style>
<!--对应.json文件-->

<script type="application/json">
{
"usingComponents": {
"list": "../components/list"
"custom-input": "../components/custom-input",
"com-a": "../components/com-a",
"com-b": "../components/com-b"
}
}
</script>
```


<style lang="stylus">
.container
flex 1
</style>
```
7 changes: 6 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@

## 偏好指引

- 开发新特性时尽量参考具有相似性的已有的流程实现,尽可能复用当前已有的流程实现,或者保持与已有流程实现的相似性
- 设计技术方案时尽量参考具有相似性的已有流程实现,尽可能复用当前已有流程实现,或者保持与已有流程实现的相似性
- 技术实现时尽可能复用现有流程与工具方法,追求最小的变动进行实现
- 编写代码时尽可能保持简洁精炼,避免无效的冗余代码,例如:
- 避免声明不必要的中间变量
- 避免编写不必要的中间步骤
- 避免添加不必要的兜底值
- 避免类似 `|| undefined` 的冗余兜底写法
- 兜底逻辑统一添加到实现侧而不是调用侧,避免多重兜底
- 避免添加不必要的容错判断
- 代码风格尽可能模仿当前仓库中现有的写法,例如:
- 遍历数组用.forEach而不是for()


## 强制约束
Expand Down
25 changes: 18 additions & 7 deletions docs-vitepress/api-proxy/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ mpx.use(apiProxy, options)
| --------------- | ----------------- | ---------------------------------------------------------- | -------- | ------- | ----------------------------------------------------------- |
| ~~platform~~ | ~~Object~~ | ~~各平台之间的转换~~ | ~~否~~ | | 已删除 |
| ~~exclude~~ | ~~Array(String)~~ | ~~跨平台时不需要转换的 api~~ | ~~否~~ | ~~[]~~ | 已删除 |
| usePromise | Boolean | 是否启用 Promise 化风格调用 | 否 | `false` | 开启后,异步 API 将返回 Promise 对象 |
| usePromise | Boolean | 是否启用 Promise 化风格调用 | 否 | `false` | 开启后,符合 Promise 化规则的异步 API 会返回 Promise 对象 |
| whiteList | Array(String) | **强制启用** Promise 化的 API 名单 | 否 | `[]` | 仅在 usePromise: true 时有效,可覆盖 blackList |
| blackList | Array(String) | **强制禁用** Promise 化的 API 名单 | 否 | `[]` | 即使在 usePromise: true 时,名单内的 API 也不会返回 Promise |
| ~~fallbackMap~~ | ~~Object~~ | ~~对于不支持的 API,允许配置一个映射表,接管不存在的 API~~ | ~~否~~ | ~~{}~~ | 已删除 |
Expand Down Expand Up @@ -54,12 +54,12 @@ mpx.showModal({

### usePromise

启用 `usePromise` 选项后,所有异步 `API` 将返回 `Promise` 对象。需要注意的是,部分小程序 `API`(如 `uploadFile`)的返回值本身具有特定意义(例如返回一个 `uploadTask` 对象用于监听上传进度或取消任务)。为了兼容这种情况,这些 `API` 的原始返回值会被挂载到返回的 `Promise` 对象的 `__returned` 属性上,开发者仍可正常访问和使用。
启用 `usePromise` 选项后,在 Promise 化规则范围内的异步 `API` 会返回 `Promise` 对象。部分小程序 `API`(如 `uploadFile`)的返回值本身具有特定意义(例如返回一个 `uploadTask` 对象用于监听上传进度或取消任务)。为了兼容这种情况,这些 `API` 的原始返回值会被挂载到返回的 `Promise` 对象的 `__returned` 属性上,开发者仍可正常访问和使用。

> [!important] ⚠️ 注意
>
> - **与原生微信小程序差异**
> - 在 `Mpx` 中,开启 `usePromise` 后,**所有**接收 `success` 或 `fail` 参数的 `API` 都可返回 `Promise`,统一支持 `await` 或 `.then ()` 语法。
> - 在 `Mpx` 中,开启 `usePromise` 后,在 Promise 化规则范围内的异步 `API` 会返回 `Promise`,统一支持 `await` 或 `.then()` 语法(部分 API 因语义或命名规则默认不参与 Promise 化,见下文「whiteList 与 blackList」)
> - 而微信小程序原生环境中则存在差异:大部分接收 `success` 和 `fail` 回调的 `API` 已支持 `Promise` 化写法,**但存在个别特例——** 这些 `API` 虽然同样接收 `success` 和 `fail` 参数,却不支持 `promisify` 风格调用。
> - **混用风险**
> - API 调用同时支持 `success/fail` 回调与 `Promise` 的 `then/catch` 写法,且两者两种方式可以共存使用。虽然支持共存,但还是**强烈建议开启 `usePromise` 后,支持 `promisify` 风格的 `API` 都走 `then/catch` 写法**以避免产生下面提示的第三项的影响
Expand All @@ -74,6 +74,7 @@ import apiProxy from "@mpxjs/api-proxy"

// 启用 Promise 风格
mpx.use(apiProxy, {
usePromise: true,
blackList: ["getSystemInfo"],
})

Expand All @@ -90,7 +91,17 @@ mpx.getSystemInfo({

### whiteList 与 blackList {#whitelist-and-blacklist}

当微信平台新增了某些 API,而 Mpx 框架暂未及时适配时,您可以通过配置 whiteList 或 blackList 来明确指定这些 API 是否应被 Promise 化,从而避免因框架未及时更新而导致的调用异常或兼容性问题。
在 `usePromise: true` 时,除你在 `whiteList` / `blackList` 中的配置外,实现里还会按命名与语义过滤一部分 **默认不** 做 Promise 化的方法,例如:

- 名称以 `Sync` 结尾的同步风格 API;
- 名称以 `on`、`off` 开头的监听类 API;
- 名称匹配 `get*Manager` 的 API;
- 名称匹配 `create*` 的 API(`createBLEConnection`、`createBLEPeripheralServer` 等少数例外会参与 Promise 化);
- 另有少量特殊 API(如 `nextTick`、`canIUse`、部分信息获取类方法等)在实现中固定不参与 Promise 化。

`whiteList` 中的名称会 **强制** 参与 Promise 化(可覆盖上述默认规则);`blackList` 中的名称会与上述内置规则 **合并**,用于强制保持回调风格。

当平台侧新增了带 `success` / `fail` 的 API,而本地 `@mpxjs/api-proxy` 版本尚未跟进时,也可通过 `whiteList` / `blackList` 显式控制是否 Promise 化,减少行为不确定带来的问题。

### custom

Expand Down Expand Up @@ -129,8 +140,8 @@ if (__mpx_mode__ === "ali" || __mpx_mode__ === "ios") {
作为 Mpx 框架的内置能力,也支持独立调用,无需显式挂载在 Mpx 实例上

```js
import apiProxy from "@mpxjs/api-proxy"
// 获取代理实例,底层仍基于 Mpx 框架的转换机制
import { getProxy } from "@mpxjs/api-proxy"
// 获取代理实例,与 mpx.use(apiProxy, options) 使用相同的 install 逻辑
const proxy = getProxy(options)

// 调用方式与原生 API 一致,代理层会自动处理跨平台转换
Expand All @@ -144,7 +155,7 @@ proxy.navigateTo({

### 按需导入单个 API {#import-single-api}

除了使用完整的代理实例,你也可以直接从 `@mpxjs/api-proxy` 中按需导入特定的 API 方法
除了使用完整的代理实例,也可以从 `@mpxjs/api-proxy` 中按需 **具名导入** 包内已实现并导出的 API(导出集合与 `getProxy()` 挂到目标上的方法一致)。未导出或未在本文档子页中出现的接口,通常表示尚未做跨端适配或需直接使用各端原生对象。

```js
// 直接导入所需的 API 方法
Expand Down
116 changes: 108 additions & 8 deletions docs-vitepress/api/compile.md
Original file line number Diff line number Diff line change
Expand Up @@ -818,20 +818,49 @@ module.exports = defineConfig({
})
```

### webConfig
### webConfig {#web-config}

`{transRpxFn(match:string, $1:number): string}`
**`mode === 'web'`** 输出时使用的编译期配置对象,在 `plugin.webConfig`(如 `vue.config.js` / `mpx.config.js` 的 `pluginOptions.mpx.plugin` 下)中传入,由 `MpxWebpackPlugin` 读取并参与 Web 侧 rpx 转换、SSR、页面切换动画与内建组件等编译逻辑。

transRpxFn 配置用于自定义输出 web 时对于 rpx 样式单位的转换逻辑,常见的方式有转换为 vw 或转换为 rem
#### webConfig.transRpxFn {#webconfig-transrpxfn}

`{useSSR: boolean}`
`(match: string, $1: number) => string`

useSSR 默认值为 `false`,当 SSR 模式下使用异步分包时,需要将 useSSR 设置为 `true`, 其他场景不需要
自定义 Web 输出时对 **rpx** 单位的转换规则,常见写法为转为 **vw** 或 **rem**

`{disablePageTransition: boolean}`
#### webConfig.useSSR {#webconfig-usessr}

用于配置禁用/开启页面切换动画,默认禁用
`boolean = false`

在 **SSR** 模式下若使用 **异步分包**,需将 `useSSR` 设为 **`true`**;其它场景保持默认即可。

#### webConfig.disablePageTransition {#webconfig-disablepagetransition}

`boolean = true`

为 `true` 时 **禁用** 页面切换动画;设为 **`false`** 可 **开启** 切换过渡效果。

#### webConfig.customBuiltInComponents {#webconfig-custombuiltincomponents}

`Record<string, string> | undefined`

在 **Web 输出**(**`mode === 'web'`**)、且 **非 app 入口**(存在参与编译的用户模版)时生效,用于 **替换或扩展** 框架对某一微信基础标签在 Web 侧的内建实现。

**key**

- 须为 **微信小程序侧基础标签名**(如 `view`、`text`、`scroll-view`),与模版中写的标签一致。
- **不要** 使用 **`mpx-*`** 作为 key。

**value**(文档约定,插件 **不在编译期校验** 格式)

- 建议使用 **绝对路径**(POSIX 以 **`/`** 开头,或 Windows 下 **`path.isAbsolute` 为真**),或 **以 npm 包名开头的模块路径**(如 **`@scope/pkg/...`**、**`my-pkg/...`**,首段须为合法包名,避免写成易被误认为工程相对路径的 **`src/...`**)。
- **不要** 使用 **`./`、`../`** 及 **`~`** 前缀;不符合约定时一般由 **webpack 解析** 等环节报错。

**注意事项**

- 某标签一旦在配置中声明,Web 侧将 **优先使用你提供的模块** 作为该基础标签的实现;**属性、事件、子节点等与微信文档的差异** 需在你的组件内 **自行对齐**。
- **app 入口** 仅内置 **`mpx-keep-alive`**,**不使用** 本配置项。
- **小程序等其它输出形态** 不读取 `webConfig.customBuiltInComponents`;仅在 **Web / RN** 输出下配置有效。RN 侧请使用 **`rnConfig.customBuiltInComponents`**,约定与本节相同,见 [rnConfig.customBuiltInComponents](#rnconfig-custombuiltincomponents)。

```js
// mpx.config.js
Expand All @@ -847,7 +876,78 @@ module.exports = defineConfig({
// 当 SSR 模式下使用异步分包时
useSSR: true,
// 开启页面切换动画
disablePageTransition: false
disablePageTransition: false,
customBuiltInComponents: {
view: require('path').resolve(__dirname, 'src/builtin/MpxView.vue')
}
}
}
}
}
})
```

### rnConfig {#rn-config}

**`mode` 为输出 React Native(如 `react`)时**使用的编译期配置对象,由 `MpxWebpackPlugin` 传入 loader 上下文,并会挂到运行时的 `mpx.config.rnConfig` 上供 RN 逻辑读取(与小程序 / Web 无关)。

#### rnConfig.projectName

`string | undefined`

若配置,则在入口脚本中调用 `AppRegistry.registerComponent(projectName, () => app)`,用于注册 RN 根组件名称。

#### rnConfig.supportSubpackage

`boolean = true`

为 `true` 时,RN 输出下页面与组件可走异步分包与 `import()` 等逻辑;为 `false` 时关闭相关能力。插件初始化时若未传入则默认为 `true`。

#### rnConfig.asyncChunk

`{ timeout?: number, fallback?: string, loading?: string } | undefined`

异步分包相关:

- **timeout**:传给内部 `LoadAsyncChunkModule` 的超时时间(毫秒级用途,见 `@mpxjs/webpack-plugin` 实现)。
- **fallback** / **loading**:异步页面/组件的占位与 loading 组件资源路径(经 `addQuery(..., { isComponent: true })` 参与打包),在 `react/script-helper.js` 中生成异步包装代码时使用。

#### rnConfig.customBuiltInComponents {#rnconfig-custombuiltincomponents}

`Record<string, string> | undefined`

在 **RN 输出**(**`mode`** 为 **`ios` / `android` / `harmony`** 等)时生效。与 [webConfig.customBuiltInComponents](#webconfig-custombuiltincomponents) 的 **key、value 约定及注意事项** 相同,仅在 **`rnConfig`** 中配置,便于与 Web 使用不同的模块路径。

**注意事项**

- 页面/组件 **主模版** 以及 **子模版**(如通过 import 引入的模版)均会应用本配置。
- **key / value** 及路径书写要求与 Web 一节一致,此处不再重复;详见 [webConfig.customBuiltInComponents](#webconfig-custombuiltincomponents)。

#### rnConfig.loadChunkAsync(运行时)

编译插件不会在选项里“实现”该函数;异步分包下载 chunk 时,运行时代码会调用 **`mpx.config.rnConfig.loadChunkAsync`**(若存在)。需在 RN 应用启动后自行挂载,例如与原生下载、热更新方案对接。与 **asyncChunk** 编译配置配合使用。

```js
// vue.config.js / mpx.config.js 片段
const path = require('path')

module.exports = defineConfig({
pluginOptions: {
mpx: {
plugin: {
mode: 'react',
rnConfig: {
projectName: 'MyMpxApp',
supportSubpackage: true,
asyncChunk: {
timeout: 10000,
fallback: path.resolve(__dirname, 'src/rn/PageFallback.mpx'),
loading: path.resolve(__dirname, 'src/rn/PageLoading.mpx')
},
customBuiltInComponents: {
view: '@your-org/mpx-rn-builtin/MpxView.mpx',
text: path.resolve(__dirname, 'src/builtin/MpxText.mpx')
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion docs-vitepress/guide/advance/pinia.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { createPinia } from '@mpxjs/pinia'
const pinia = createPinia()
```

如果你的应用想使用 SSR 渲染模式,请将 pinia 的创建放在 `onAppInit` 钩子中执行
如果你的应用需要输出 Web 并使用 SSR 渲染模式,请将 pinia 的创建放在 `onAppInit` 钩子中执行并返回。
```js
// app.mpx

Expand Down
2 changes: 1 addition & 1 deletion packages/api-proxy/src/platform/api/system/index.ios.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const getSystemInfo = function (options = {}) {
try {
const systemInfo = getSystemInfoSync()
Object.assign(systemInfo, {
errMsg: 'setStorage:ok'
errMsg: 'getSystemInfo:ok'
})
successHandle(systemInfo, success, complete)
} catch (err) {
Expand Down
2 changes: 1 addition & 1 deletion packages/webpack-plugin/lib/platform/run-rules.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ module.exports = function runRules (rules = [], input, options = {}) {
Object.assign(data, {
mode
})
if (tester(testInput, meta) && processor) {
if (tester(testInput, meta, data) && processor) {
const result = processor.call(rule, input, data, meta)
meta.processed = true
if (result !== undefined) {
Expand Down
Loading
Loading