锁定微信壳页面包装层结构

原生壳门禁新增微信页面目录和页面文件清单反查

宿主壳方案文档同步微信页面包装层和微信能力清单

共享记忆补充三端壳结构和能力文档反查口径
This commit is contained in:
2026-06-19 07:15:12 +08:00
parent c04c7d4d8d
commit db153b0dde
4 changed files with 78 additions and 23 deletions
@@ -2453,7 +2453,7 @@
## 2026-06-19 原生壳 capability profile 来源收口
- 背景:Expo 移动壳、Tauri 桌面壳和方案文档都需要维护真实 capability 子集;如果移动端源码、桌面 Rust 镜像和文档各自手写完整清单,后续新增能力时容易出现入口 URL、`host.getRuntime` 回包、文档和门禁漂移。
- 决策:`packages/shared/src/contracts/hostBridge.ts` 中的 `HOST_BRIDGE_WECHAT_MINI_PROGRAM_CAPABILITIES``HOST_BRIDGE_EXPO_MOBILE_BASE_CAPABILITIES``HOST_BRIDGE_EXPO_MOBILE_IOS_CAPABILITIES``HOST_BRIDGE_TAURI_DESKTOP_CAPABILITIES` 是三端宿主壳 capability profile 来源。Expo 移动壳只通过 `apps/mobile-shell/src/host-bridge/capabilities.ts` 引用共享 profile 并选择平台差异;微信小程序 `miniprogram/host-bridge/protocol.js` 和 Tauri 桌面壳 `capabilities.rs` 仍保留运行时镜像,但 `miniprogram/host-bridge/protocol.test.js``apps/desktop-shell/scripts/check-config.mjs``npm run check:native-shells` 必须反查对应共享 profile。新增 capability 必须先进入共享白名单和对应平台 profile,再补真实壳实现、H5 fallback、测试和文档。
- 决策:`packages/shared/src/contracts/hostBridge.ts` 中的 `HOST_BRIDGE_WECHAT_MINI_PROGRAM_CAPABILITIES``HOST_BRIDGE_EXPO_MOBILE_BASE_CAPABILITIES``HOST_BRIDGE_EXPO_MOBILE_IOS_CAPABILITIES``HOST_BRIDGE_TAURI_DESKTOP_CAPABILITIES` 是三端宿主壳 capability profile 来源。Expo 移动壳只通过 `apps/mobile-shell/src/host-bridge/capabilities.ts` 引用共享 profile 并选择平台差异;微信小程序 `miniprogram/host-bridge/protocol.js` 和 Tauri 桌面壳 `capabilities.rs` 仍保留运行时镜像,但 `miniprogram/host-bridge/protocol.test.js``apps/desktop-shell/scripts/check-config.mjs``npm run check:native-shells` 必须反查对应共享 profile。根级门禁同时反查宿主壳方案文档里的微信 / Expo / Tauri 能力清单,新增 capability 必须先进入共享白名单和对应平台 profile,再补真实壳实现、H5 fallback、测试和文档。
- 影响范围:`packages/shared/src/contracts/hostBridge.ts``miniprogram/host-bridge/protocol.js``miniprogram/host-bridge/protocol.test.js``apps/mobile-shell/src/host-bridge/capabilities.ts``apps/mobile-shell/scripts/check-config.mjs``apps/desktop-shell/scripts/check-config.mjs``scripts/check-native-shells.mjs`、HostBridge 方案文档。
- 验证方式:`npm run check:native-shells``npm run mobile-shell:typecheck``npm run desktop-shell:typecheck``npm run test -- packages/shared/src/contracts/hostBridge.test.ts miniprogram/host-bridge/protocol.test.js``npm run check:encoding``git diff --check`
@@ -2523,7 +2523,7 @@
## 2026-06-19 三端宿主桥接层结构文档反查
- 背景:三端桥接层已经拆出移动 `scanner.ts`、桌面 `menu.rs``window_state.rs` 等职责文件,但如果只更新代码和目录门禁,`宿主壳能力统一协议``ExpoReactNative与Tauri宿主壳方案` 可能继续保留旧清单,后续开发者按文档扩展时仍会把能力放回错误 owner。
- 决策:`scripts/check-native-shells.mjs` 的三端桥接层目录清单同时作为文档反查来源。根级门禁会确认两份前端架构文档都显式列出微信桥接层、微信 shell、移动桥接层、移动 shell、桌面入口、桌面桥接层和桌面 shell 的当前生产文件;新增、删除或改名这些职责文件时,必须同时更新脚本清单、两份架构文档和相关实现,不允许只改一端。
- 决策:`scripts/check-native-shells.mjs` 的三端桥接层目录清单同时作为文档反查来源。根级门禁会确认两份前端架构文档都显式列出微信桥接层、微信 shell、微信页面包装层、移动桥接层、移动 shell、桌面入口、桌面桥接层和桌面 shell 的当前生产文件;新增、删除或改名这些职责文件时,必须同时更新脚本清单、两份架构文档和相关实现,不允许只改一端。
- 影响范围:`scripts/check-native-shells.mjs``docs/【前端架构】宿主壳能力统一协议-2026-06-17.md``docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md`、三端宿主壳源码布局。
- 验证方式:`npm run check:native-shells``npm run check:encoding``git diff --check`
@@ -66,7 +66,7 @@ src/
三端宿主桥接层按职责对齐命名:微信小程序页面路由仍保留在 `miniprogram/pages/*``miniprogram/host-bridge/protocol.js` 只沉淀微信壳能力、页面 URL、结果 hash / storage key 和分享消息类型等常量,`dispatch.js` 只作为 `protocol``webView``payment``shareGrid``subscribeMessage` 的薄索引,真实协议归一、支付 / 订阅 / 分享结果编解码仍分别在 `webView.js``payment.js``shareGrid.js``subscribeMessage.js`,不把微信小程序硬改成 Expo / Tauri 的 request 总线;Page 生命周期、`wx.*` 容器调用、WebView 容器行为和页面工厂统一放在 `miniprogram/shell/webView.js``payment.js``shareGrid.js``subscribeMessage.js`,页面入口只做 `Page(createWechat...Page())` 装配。Expo 移动壳使用 `apps/mobile-shell/src/host-bridge/protocol.ts` 承接 envelope、request 校验、ok / failure 响应和 replay 基础类型,`capabilities.ts` 只引用共享 HostBridge capability profile 并选择 iOS 差异能力,`dispatch.ts` 承接 method 分发和宿主能力调用,`files.ts` / `share.ts` / `scanner.ts` 分别承接文件、分享和扫码能力,`bridge.ts` 只作为 WebView message 入口、request id replay 编排和对外 facade`apps/mobile-shell/App.tsx` 只装配 `apps/mobile-shell/src/shell/ShellApp.tsx`,由 `apps/mobile-shell/src/shell/*.ts(x)` 承接 WebView 容器、URL、导航、网络、生命周期、安全区、扫码 overlay 和 WebView policy。Tauri 桌面壳使用 `apps/desktop-shell/src-tauri/src/host_bridge/protocol.rs` 承接 envelope、method 白名单、request 校验和 replay 状态,`capabilities.rs` 承接共享桌面 capability profile 的 Rust 运行时镜像,`dispatch.rs` 承接 method 分发和宿主能力调用,`files.rs` / `share.rs` 分别承接文件和分享能力,`mod.rs` 只保留模块声明、必要 re-export、`host_bridge_request` command facade 和 replay 编排;`apps/desktop-shell/src-tauri/src/shell/runtime.rs``url.rs``navigation.rs``network.rs``lifecycle.rs``file_drop.rs``events.rs``deep_link.rs``tray.rs``menu.rs``window_state.rs``webview.rs` 分别承接运行态、入口 URL、导航 / 下载、网络、生命周期、拖拽图片、HostBridge 事件注入、深链、托盘、应用菜单、窗口状态持久化和 WebView 门面,`apps/desktop-shell/src-tauri/src/app.rs` 承接 Tauri builder / plugin / window 装配,`main.rs` 只保留薄入口并调用 `app::run()`
当前 `npm run check:native-shells` 锁定的生产文件清单为:微信桥接层 `dispatch.js``payment.js``protocol.js``shareGrid.js``subscribeMessage.js``webView.js`;微信 shell 层 `payment.js``shareGrid.js``subscribeMessage.js``webView.js`;移动桥接层 `bridge.ts``capabilities.ts``dispatch.ts``files.ts``protocol.ts``scanner.ts``share.ts`;移动 shell 层 `QrScannerOverlay.tsx``ShellApp.tsx``deepLink.ts``lifecycle.ts``loadFailure.ts``navigation.ts``network.ts``runtime.ts``safeArea.ts``url.ts``webViewGlobals.d.ts``webViewHistory.ts``webViewPolicy.ts`;桌面入口 `app.rs``main.rs`;桌面桥接层 `capabilities.rs``dispatch.rs``files.rs``mod.rs``protocol.rs``share.rs`;桌面 shell 层 `deep_link.rs``events.rs``file_drop.rs``lifecycle.rs``menu.rs``mod.rs``navigation.rs``network.rs``runtime.rs``tray.rs``url.rs``webview.rs``window_state.rs`
当前 `npm run check:native-shells` 锁定的生产文件清单为:微信桥接层 `dispatch.js``payment.js``protocol.js``shareGrid.js``subscribeMessage.js``webView.js`;微信 shell 层 `payment.js``shareGrid.js``subscribeMessage.js``webView.js`微信页面包装层 `share-grid/index.js``share-grid/index.json``share-grid/index.wxml``share-grid/index.wxss``subscribe-message/index.js``subscribe-message/index.json``subscribe-message/index.wxml``subscribe-message/index.wxss``web-view/index.js``web-view/index.json``web-view/index.wxml``web-view/index.wxss``wechat-pay/index.js``wechat-pay/index.json``wechat-pay/index.wxml``wechat-pay/index.wxss`移动桥接层 `bridge.ts``capabilities.ts``dispatch.ts``files.ts``protocol.ts``scanner.ts``share.ts`;移动 shell 层 `QrScannerOverlay.tsx``ShellApp.tsx``deepLink.ts``lifecycle.ts``loadFailure.ts``navigation.ts``network.ts``runtime.ts``safeArea.ts``url.ts``webViewGlobals.d.ts``webViewHistory.ts``webViewPolicy.ts`;桌面入口 `app.rs``main.rs`;桌面桥接层 `capabilities.rs``dispatch.rs``files.rs``mod.rs``protocol.rs``share.rs`;桌面 shell 层 `deep_link.rs``events.rs``file_drop.rs``lifecycle.rs``menu.rs``mod.rs``navigation.rs``network.rs``runtime.rs``tray.rs``url.rs``webview.rs``window_state.rs`
## HostBridge 消息协议
@@ -281,6 +281,8 @@ GameBridge 禁止:
当前状态:已新增 `src/services/host-bridge/nativeAppHostBridge.ts`,支持 React Native WebView `postMessage` 和 Tauri `invoke('host_bridge_request')` 两种真实 transport。两条 transport 都会按 `timeoutMs` 在 H5 侧释放请求,超时统一抛出 `timeout / host_bridge_timeout`。登录、支付和原生页跳转如果宿主明确返回 `unsupported_method` / `unsupported_capability`,H5 回退到原有路径;生产代码不返回 mock 成功。
2026-06-19 追加:微信小程序壳当前真实能力完整清单为 `auth.requestLogin``payment.request``share.setTarget``share.open`
### Phase 2Expo 移动壳 MVP
- 新增 `apps/mobile-shell/`
@@ -39,7 +39,7 @@ AI H5 sandbox
桥接层文件结构按宿主统一为“协议 / 能力清单 / 分发 / 宿主容器行为”四类职责。微信小程序不硬套 Expo / Tauri 的 request 总线:`miniprogram/host-bridge/protocol.js` 只沉淀微信壳能力、页面 URL、结果 hash / storage key 和分享消息类型等常量,`dispatch.js` 只作为 `protocol``webView``payment``shareGrid``subscribeMessage` 的薄索引,真实协议归一、支付 / 订阅 / 分享结果编解码仍分别放在 `webView.js``payment.js``shareGrid.js``subscribeMessage.js``miniprogram/shell/webView.js``payment.js``shareGrid.js``subscribeMessage.js` 承接 Page 生命周期、`wx.*` 容器调用、WebView 容器行为、支付页和订阅页装配,页面目录只保留 `Page(createWechat...Page())` 装配。Expo 移动壳使用 `apps/mobile-shell/src/host-bridge/protocol.ts` 承接 envelope、request 校验、ok / failure 响应和 replay 基础类型,`capabilities.ts` 只引用共享 HostBridge capability profile 并选择 iOS 差异能力,`dispatch.ts` 承接 method 分发和宿主能力调用,`files.ts` / `share.ts` / `scanner.ts` 分别承接文件、分享和扫码能力,`bridge.ts` 只作为 WebView message 入口、request id replay 编排和对外 facade`apps/mobile-shell/App.tsx` 只装配 `apps/mobile-shell/src/shell/ShellApp.tsx`,由 `apps/mobile-shell/src/shell/*.ts(x)` 承接 WebView 容器、URL、导航、网络、生命周期、安全区、扫码 overlay 和 WebView policy。Tauri 桌面壳使用 `apps/desktop-shell/src-tauri/src/host_bridge/protocol.rs` 承接 envelope、method 白名单、request 校验和 replay 状态,`capabilities.rs` 承接共享桌面 capability profile 的 Rust 运行时镜像,`dispatch.rs` 承接 method 分发和宿主能力调用,`files.rs` / `share.rs` 分别承接文件和分享能力,`mod.rs` 只保留模块声明、必要 re-export、`host_bridge_request` command facade 和 replay 编排;`apps/desktop-shell/src-tauri/src/shell/runtime.rs``url.rs``navigation.rs``network.rs``lifecycle.rs``file_drop.rs``events.rs``deep_link.rs``tray.rs``menu.rs``window_state.rs``webview.rs` 分别承接运行态、入口 URL、导航 / 下载、网络、生命周期、拖拽图片、HostBridge 事件注入、深链、托盘、应用菜单、窗口状态持久化和 WebView 门面,`apps/desktop-shell/src-tauri/src/app.rs` 承接 Tauri builder / plugin / window 装配,`main.rs` 只保留薄入口并调用 `app::run()``npm run check:native-shells` 会检查这些目录清单。
当前 `npm run check:native-shells` 锁定的生产文件清单为:微信桥接层 `dispatch.js``payment.js``protocol.js``shareGrid.js``subscribeMessage.js``webView.js`;微信 shell 层 `payment.js``shareGrid.js``subscribeMessage.js``webView.js`;移动桥接层 `bridge.ts``capabilities.ts``dispatch.ts``files.ts``protocol.ts``scanner.ts``share.ts`;移动 shell 层 `QrScannerOverlay.tsx``ShellApp.tsx``deepLink.ts``lifecycle.ts``loadFailure.ts``navigation.ts``network.ts``runtime.ts``safeArea.ts``url.ts``webViewGlobals.d.ts``webViewHistory.ts``webViewPolicy.ts`;桌面入口 `app.rs``main.rs`;桌面桥接层 `capabilities.rs``dispatch.rs``files.rs``mod.rs``protocol.rs``share.rs`;桌面 shell 层 `deep_link.rs``events.rs``file_drop.rs``lifecycle.rs``menu.rs``mod.rs``navigation.rs``network.rs``runtime.rs``tray.rs``url.rs``webview.rs``window_state.rs`
当前 `npm run check:native-shells` 锁定的生产文件清单为:微信桥接层 `dispatch.js``payment.js``protocol.js``shareGrid.js``subscribeMessage.js``webView.js`;微信 shell 层 `payment.js``shareGrid.js``subscribeMessage.js``webView.js`微信页面包装层 `share-grid/index.js``share-grid/index.json``share-grid/index.wxml``share-grid/index.wxss``subscribe-message/index.js``subscribe-message/index.json``subscribe-message/index.wxml``subscribe-message/index.wxss``web-view/index.js``web-view/index.json``web-view/index.wxml``web-view/index.wxss``wechat-pay/index.js``wechat-pay/index.json``wechat-pay/index.wxml``wechat-pay/index.wxss`移动桥接层 `bridge.ts``capabilities.ts``dispatch.ts``files.ts``protocol.ts``scanner.ts``share.ts`;移动 shell 层 `QrScannerOverlay.tsx``ShellApp.tsx``deepLink.ts``lifecycle.ts``loadFailure.ts``navigation.ts``network.ts``runtime.ts``safeArea.ts``url.ts``webViewGlobals.d.ts``webViewHistory.ts``webViewPolicy.ts`;桌面入口 `app.rs``main.rs`;桌面桥接层 `capabilities.rs``dispatch.rs``files.rs``mod.rs``protocol.rs``share.rs`;桌面 shell 层 `deep_link.rs``events.rs``file_drop.rs``lifecycle.rs``menu.rs``mod.rs``navigation.rs``network.rs``runtime.rs``tray.rs``url.rs``webview.rs``window_state.rs`
Tauri 桌面壳启动时必须按 `label="main"` 解析 `tauri.conf.json` 主窗口配置,并在创建 WebView 前补写 `native_app``tauri_desktop` 和真实 capability 上下文;缺少主窗口配置时启动直接失败,不允许按 `windows[0]` 兜底或无主窗口静默运行。
+72 -19
View File
@@ -115,6 +115,18 @@ const expectedWechatShellFiles = [
'webView.js',
'webView.test.js',
];
const expectedWechatPageFilesByRoute = {
'share-grid': ['index.js', 'index.json', 'index.wxml', 'index.wxss'],
'subscribe-message': ['index.js', 'index.json', 'index.wxml', 'index.wxss'],
'web-view': [
'index.js',
'index.json',
'index.style.test.js',
'index.wxml',
'index.wxss',
],
'wechat-pay': ['index.js', 'index.json', 'index.wxml', 'index.wxss'],
};
const expectedMobileHostBridgeFiles = [
'bridge.test.ts',
'bridge.ts',
@@ -186,6 +198,15 @@ const documentedShellLayerGroups = [
(fileName) => !fileName.includes('.test.'),
),
},
{
label: 'wechat page wrapper files',
files: Object.entries(expectedWechatPageFilesByRoute).flatMap(
([route, files]) =>
files
.filter((fileName) => !fileName.includes('.test.'))
.map((fileName) => `${route}/${fileName}`),
),
},
{
label: 'mobile host bridge files',
files: expectedMobileHostBridgeFiles.filter(
@@ -215,6 +236,7 @@ const capabilityListMarkers = {
desktop: '桌面壳当前真实能力完整清单为',
mobile: '移动壳当前通用真实能力完整清单为',
mobileIosExtra: '移动壳 iOS 额外真实能力为',
wechat: '微信小程序壳当前真实能力完整清单为',
};
const sharedHostBridgeContractPath =
'packages/shared/src/contracts/hostBridge.ts';
@@ -579,6 +601,28 @@ function assertSameList(actual, expected, label) {
}
}
function readDirectoryFileList(directory, label) {
const files = [];
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
if (!entry.isFile()) {
throw new Error(`${label} must not contain nested entries: ${entry.name}`);
}
files.push(entry.name);
}
return files.sort();
}
function readDirectoryNameList(directory, label) {
const directories = [];
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
if (!entry.isDirectory()) {
throw new Error(`${label} must not contain root files: ${entry.name}`);
}
directories.push(entry.name);
}
return directories.sort();
}
function assertShellLayerLayoutDocumented(source, label) {
for (const group of documentedShellLayerGroups) {
for (const fileName of group.files) {
@@ -806,6 +850,11 @@ function assertNativeShellCapabilityPlan() {
],
'native shell documented method table',
);
assertSameList(
extractDocumentCapabilityList(planSource, capabilityListMarkers.wechat),
sharedWechatCapabilities,
'wechat mini program documented capabilities',
);
assertSameList(
extractDocumentCapabilityList(planSource, capabilityListMarkers.mobile),
mobileCapabilities,
@@ -976,35 +1025,39 @@ function assertWechatMiniProgramRouteParity() {
}
function assertHostBridgeLayerLayout() {
const wechatBridgeFiles = fs
.readdirSync('miniprogram/host-bridge', { withFileTypes: true })
.filter((entry) => entry.isFile())
.map((entry) => entry.name)
.sort();
assertSameList(
wechatBridgeFiles,
readDirectoryFileList(
'miniprogram/host-bridge',
'wechat host bridge files',
),
expectedWechatHostBridgeFiles,
'wechat host bridge files',
);
const wechatShellLayerFiles = fs
.readdirSync('miniprogram/shell', { withFileTypes: true })
.filter((entry) => entry.isFile())
.map((entry) => entry.name)
.sort();
assertSameList(
wechatShellLayerFiles,
readDirectoryFileList('miniprogram/shell', 'wechat shell files'),
expectedWechatShellFiles,
'wechat shell files',
);
const wechatPagePaths = [
'miniprogram/pages/web-view/index.js',
'miniprogram/pages/wechat-pay/index.js',
'miniprogram/pages/share-grid/index.js',
'miniprogram/pages/subscribe-message/index.js',
];
for (const pagePath of wechatPagePaths) {
assertSameList(
readDirectoryNameList('miniprogram/pages', 'wechat page directories'),
Object.keys(expectedWechatPageFilesByRoute).sort(),
'wechat page directories',
);
for (const [route, expectedFiles] of Object.entries(
expectedWechatPageFilesByRoute,
)) {
assertSameList(
readDirectoryFileList(
`miniprogram/pages/${route}`,
`wechat ${route} page wrapper files`,
),
expectedFiles,
`wechat ${route} page wrapper files`,
);
const pagePath = `miniprogram/pages/${route}/index.js`;
const source = fs.readFileSync(pagePath, 'utf8');
if (!source.includes("require('../../shell/")) {
throw new Error(`${pagePath} must import from miniprogram/shell`);