From 27df65b0907ced326dd69ebffbd9c592f9d62e44 Mon Sep 17 00:00:00 2001 From: Gaubee Date: Wed, 21 Jan 2026 02:28:30 +0800 Subject: [PATCH 1/2] feat(vite): add runtime config for miniapps (wujie preparation) - Add wujie-react dependency - Extend MiniappManifest with runtime field - Extend vite plugins to inject runtime config per dev/build mode - Configure apps with server: iframe, build: wujie Note: This PR only adds the config infrastructure. The actual wujie runtime integration will be done in a separate PR with proper SOLID refactoring and tests. --- package.json | 1 + pnpm-lock.yaml | 43 ++++++++++++++++++++++++++ scripts/vite-plugin-miniapps.ts | 29 ++++++++++++----- scripts/vite-plugin-remote-miniapps.ts | 7 ++++- src/services/ecosystem/types.ts | 2 ++ vite.config.ts | 15 ++++++++- 6 files changed, 88 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index ca07169f2..3933b1209 100644 --- a/package.json +++ b/package.json @@ -130,6 +130,7 @@ "tweetnacl": "^1.0.3", "vaul": "^1.1.2", "viem": "^2.43.3", + "wujie-react": "^1.0.29", "yargs": "^18.0.0", "zod": "^4.1.13" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2b31c3d0c..624407793 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -194,6 +194,9 @@ importers: viem: specifier: ^2.43.3 version: 2.43.3(typescript@5.9.3)(zod@4.2.1) + wujie-react: + specifier: ^1.0.29 + version: 1.0.29(react@19.2.3) yargs: specifier: ^18.0.0 version: 18.0.0 @@ -6125,6 +6128,10 @@ packages: resolution: {integrity: sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==} engines: {node: '>=8.0'} + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + loupe@3.2.1: resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} @@ -6803,6 +6810,9 @@ packages: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + property-information@7.1.0: resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} @@ -6933,6 +6943,9 @@ packages: peerDependencies: react: ^18.0.0 || ^19.0.0 + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} @@ -8342,6 +8355,14 @@ packages: resolution: {integrity: sha512-3sFIGLiaDP7rTO4xh3g+b3AzhYDIUGGywE/WsmqzJWDxus5aJXVnPTNC/6L+r2WzrwXqVOdD262OaO+cEyPMSQ==} engines: {node: '>=20'} + wujie-react@1.0.29: + resolution: {integrity: sha512-6XKSpHwaRPKhmn6glkt4fKpVjnKWE2Lfd7hG/ACmmCsy+65aQCIrD91eJb/qL6hyPnpGQMiPCJeVhPFyDPpqzw==} + peerDependencies: + react: '>=16.0.0' + + wujie@1.0.29: + resolution: {integrity: sha512-u7PmBT4l5ov7ciwmZNG8DLIRKrYrnXDa0LnAFgRQikJ/ZrVZYzzSNz6duzEJoj8/o46dnMy/g9Hh/PBYGbgFUw==} + xml-name-validator@5.0.0: resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} engines: {node: '>=18'} @@ -14170,6 +14191,10 @@ snapshots: transitivePeerDependencies: - supports-color + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + loupe@3.2.1: {} lru-cache@10.4.3: {} @@ -14761,6 +14786,12 @@ snapshots: kleur: 3.0.3 sisteransi: 1.0.5 + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + property-information@7.1.0: {} proxy-addr@2.0.7: @@ -14878,6 +14909,8 @@ snapshots: dependencies: react: 19.2.3 + react-is@16.13.1: {} + react-is@17.0.2: {} react-refresh@0.18.0: {} @@ -16569,6 +16602,16 @@ snapshots: is-wsl: 3.1.0 powershell-utils: 0.1.0 + wujie-react@1.0.29(react@19.2.3): + dependencies: + prop-types: 15.8.1 + react: 19.2.3 + wujie: 1.0.29 + + wujie@1.0.29: + dependencies: + '@babel/runtime': 7.28.4 + xml-name-validator@5.0.0: {} xmlchars@2.2.0: {} diff --git a/scripts/vite-plugin-miniapps.ts b/scripts/vite-plugin-miniapps.ts index e35e670bf..357a94ab8 100644 --- a/scripts/vite-plugin-miniapps.ts +++ b/scripts/vite-plugin-miniapps.ts @@ -15,6 +15,13 @@ import { getRemoteMiniappsForEcosystem } from './vite-plugin-remote-miniapps'; // ==================== Types ==================== +type MiniappRuntime = 'iframe' | 'wujie'; + +interface MiniappRuntimeConfig { + server?: MiniappRuntime; + build?: MiniappRuntime; +} + interface MiniappManifest { id: string; dirName: string; @@ -36,6 +43,7 @@ interface MiniappManifest { themeColor: string; officialScore?: number; communityScore?: number; + runtime?: MiniappRuntime; } interface EcosystemJson { @@ -56,12 +64,13 @@ interface MiniappServer { interface MiniappsPluginOptions { miniappsDir?: string; + apps?: Record; } // ==================== Plugin ==================== export function miniappsPlugin(options: MiniappsPluginOptions = {}): Plugin { - const { miniappsDir = 'miniapps' } = options; + const { miniappsDir = 'miniapps', apps = {} } = options; let root: string; let isBuild = false; @@ -77,11 +86,9 @@ export function miniappsPlugin(options: MiniappsPluginOptions = {}): Plugin { async writeBundle(options) { if (isBuild && options.dir) { - // 构建完成后构建 miniapps await buildAllMiniapps(root, miniappsDir, options.dir); - // 生成 ecosystem.json 到 miniapps/ 目录 - const ecosystem = generateEcosystemDataForBuild(root, miniappsDir); + const ecosystem = generateEcosystemDataForBuild(root, miniappsDir, apps); const miniappsOutputDir = resolve(options.dir, 'miniapps'); mkdirSync(miniappsOutputDir, { recursive: true }); const outputPath = resolve(miniappsOutputDir, 'ecosystem.json'); @@ -121,17 +128,19 @@ export function miniappsPlugin(options: MiniappsPluginOptions = {}): Plugin { // 等待所有 miniapp 启动后,fetch 各自的 /manifest.json 生成 ecosystem const generateEcosystem = async (): Promise => { - // 本地 miniapps const localApps = await Promise.all( miniappServers.map(async (s) => { try { const manifest = await fetchManifest(s.port); + const appConfig = apps[manifest.id]; + const runtime = appConfig?.server ?? 'iframe'; return { ...manifest, dirName: s.dirName, icon: new URL(manifest.icon, s.baseUrl).href, url: new URL('/', s.baseUrl).href, screenshots: manifest.screenshots.map((sc) => new URL(sc, s.baseUrl).href), + runtime, }; } catch (e) { console.error(`[miniapps] Failed to fetch manifest for ${s.id}:`, e); @@ -140,7 +149,6 @@ export function miniappsPlugin(options: MiniappsPluginOptions = {}): Plugin { }), ); - // 远程 miniapps (从 vite-plugin-remote-miniapps 获取) const remoteApps = getRemoteMiniappsForEcosystem(); return { @@ -259,13 +267,19 @@ function scanScreenshots(root: string, shortId: string): string[] { .map((f) => `screenshots/${f}`); } -function generateEcosystemDataForBuild(root: string, miniappsDir: string): EcosystemJson { +function generateEcosystemDataForBuild( + root: string, + miniappsDir: string, + apps: Record, +): EcosystemJson { const miniappsPath = resolve(root, miniappsDir); const manifests = scanMiniapps(miniappsPath); const localApps = manifests.map((manifest) => { const shortId = manifest.id.split('.').pop() || ''; const screenshots = scanScreenshots(root, shortId); + const appConfig = apps[manifest.id]; + const runtime = appConfig?.build ?? 'iframe'; const { dirName, ...rest } = manifest; return { @@ -274,6 +288,7 @@ function generateEcosystemDataForBuild(root: string, miniappsDir: string): Ecosy url: `./${dirName}/`, icon: `./${dirName}/icon.svg`, screenshots: screenshots.map((s) => `./${dirName}/${s}`), + runtime, }; }); diff --git a/scripts/vite-plugin-remote-miniapps.ts b/scripts/vite-plugin-remote-miniapps.ts index 79737c06e..65b23c977 100644 --- a/scripts/vite-plugin-remote-miniapps.ts +++ b/scripts/vite-plugin-remote-miniapps.ts @@ -87,6 +87,7 @@ interface RemoteMiniappServer { server: ReturnType; baseUrl: string; manifest: MiniappManifest; + config: RemoteMiniappConfig; } // ==================== Plugin ==================== @@ -203,6 +204,7 @@ export function remoteMiniappsPlugin(options: RemoteMiniappsPluginOptions): Plug server: httpServer, baseUrl, manifest, + config, }; servers.push(serverInfo); @@ -384,13 +386,16 @@ export function getRemoteMiniappServers(): RemoteMiniappServer[] { /** * 获取远程 miniapps 用于 ecosystem.json 的数据 */ -export function getRemoteMiniappsForEcosystem(): Array { +export function getRemoteMiniappsForEcosystem(): Array< + MiniappManifest & { url: string; runtime?: 'iframe' | 'wujie' } +> { return globalRemoteServers.map((s) => ({ ...s.manifest, dirName: s.dirName, icon: new URL(s.manifest.icon, s.baseUrl).href, url: new URL('/', s.baseUrl).href, screenshots: s.manifest.screenshots?.map((sc) => new URL(sc, s.baseUrl).href) ?? [], + runtime: s.config.server?.runtime ?? 'iframe', })); } diff --git a/src/services/ecosystem/types.ts b/src/services/ecosystem/types.ts index 002438c66..25062f965 100644 --- a/src/services/ecosystem/types.ts +++ b/src/services/ecosystem/types.ts @@ -225,6 +225,8 @@ export interface MiniappManifest { sourceIcon?: string; /** 来源名称(运行时填充) */ sourceName?: string; + /** 运行时容器类型(由宿主注入,默认 'iframe') */ + runtime?: 'iframe' | 'wujie'; } /** Ecosystem source - JSON 文件格式 */ diff --git a/vite.config.ts b/vite.config.ts index f317b4822..f75e81865 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -97,7 +97,9 @@ export default defineConfig(({ mode }) => { { metadataUrl: 'https://iweb.xin/rwahub.bfmeta.com.miniapp/metadata.json', dirName: 'rwa-hub', + server: { runtime: 'iframe' }, build: { + runtime: 'wujie', rewriteBase: true, }, }, @@ -105,7 +107,18 @@ export default defineConfig(({ mode }) => { timeout: 60000, retries: 3, }), - miniappsPlugin(), + miniappsPlugin({ + apps: { + 'com.bfmeta.teleport': { + server: 'iframe', + build: 'wujie', + }, + 'com.bfmeta.forge': { + server: 'iframe', + build: 'wujie', + }, + }, + }), buildCheckPlugin(), ], resolve: { From 81defdeb900a8b02b4a29ea4362ef1d9b7ec3048 Mon Sep 17 00:00:00 2001 From: Gaubee Date: Wed, 21 Jan 2026 02:37:39 +0800 Subject: [PATCH 2/2] fix(vite): convert absolute paths to relative for base tag to work Base tag only affects relative paths. Absolute paths like /assets/xxx are resolved against origin, ignoring base href. This fix converts /assets/xxx to assets/xxx so base tag works correctly. --- scripts/vite-plugin-remote-miniapps.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/vite-plugin-remote-miniapps.ts b/scripts/vite-plugin-remote-miniapps.ts index 65b23c977..5ad40ff8f 100644 --- a/scripts/vite-plugin-remote-miniapps.ts +++ b/scripts/vite-plugin-remote-miniapps.ts @@ -328,8 +328,12 @@ function rewriteHtmlBase(targetDir: string, basePath: string): void { html = html.replace(/]*>/i, `$&\n \n ${baseTag}\n `); } + // Convert absolute paths to relative paths (base tag only works with relative paths) + // /assets/xxx -> assets/xxx, /css/xxx -> css/xxx, /images/xxx -> images/xxx + html = html.replace(/(src|href)="\/(?!\/)/g, '$1="'); + writeFileSync(indexPath, html); - console.log(`[remote-miniapps] Rewrote to "${normalizedBase}" in ${indexPath}`); + console.log(`[remote-miniapps] Rewrote and converted absolute paths to relative in ${indexPath}`); } /**