Skip to content
Merged
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 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
43 changes: 43 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 22 additions & 7 deletions scripts/vite-plugin-miniapps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -36,6 +43,7 @@ interface MiniappManifest {
themeColor: string;
officialScore?: number;
communityScore?: number;
runtime?: MiniappRuntime;
}

interface EcosystemJson {
Expand All @@ -56,12 +64,13 @@ interface MiniappServer {

interface MiniappsPluginOptions {
miniappsDir?: string;
apps?: Record<string, MiniappRuntimeConfig>;
}

// ==================== Plugin ====================

export function miniappsPlugin(options: MiniappsPluginOptions = {}): Plugin {
const { miniappsDir = 'miniapps' } = options;
const { miniappsDir = 'miniapps', apps = {} } = options;

let root: string;
let isBuild = false;
Expand All @@ -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');
Expand Down Expand Up @@ -121,17 +128,19 @@ export function miniappsPlugin(options: MiniappsPluginOptions = {}): Plugin {

// 等待所有 miniapp 启动后,fetch 各自的 /manifest.json 生成 ecosystem
const generateEcosystem = async (): Promise<EcosystemJson> => {
// 本地 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);
Expand All @@ -140,7 +149,6 @@ export function miniappsPlugin(options: MiniappsPluginOptions = {}): Plugin {
}),
);

// 远程 miniapps (从 vite-plugin-remote-miniapps 获取)
const remoteApps = getRemoteMiniappsForEcosystem();

return {
Expand Down Expand Up @@ -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<string, MiniappRuntimeConfig>,
): 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 {
Expand All @@ -274,6 +288,7 @@ function generateEcosystemDataForBuild(root: string, miniappsDir: string): Ecosy
url: `./${dirName}/`,
icon: `./${dirName}/icon.svg`,
screenshots: screenshots.map((s) => `./${dirName}/${s}`),
runtime,
};
});

Expand Down
13 changes: 11 additions & 2 deletions scripts/vite-plugin-remote-miniapps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ interface RemoteMiniappServer {
server: ReturnType<typeof createServer>;
baseUrl: string;
manifest: MiniappManifest;
config: RemoteMiniappConfig;
}

// ==================== Plugin ====================
Expand Down Expand Up @@ -203,6 +204,7 @@ export function remoteMiniappsPlugin(options: RemoteMiniappsPluginOptions): Plug
server: httpServer,
baseUrl,
manifest,
config,
};

servers.push(serverInfo);
Expand Down Expand Up @@ -326,8 +328,12 @@ function rewriteHtmlBase(targetDir: string, basePath: string): void {
html = html.replace(/<html[^>]*>/i, `$&\n <head>\n ${baseTag}\n </head>`);
}

// 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 <base> to "${normalizedBase}" in ${indexPath}`);
console.log(`[remote-miniapps] Rewrote <base> and converted absolute paths to relative in ${indexPath}`);
}

/**
Expand Down Expand Up @@ -384,13 +390,16 @@ export function getRemoteMiniappServers(): RemoteMiniappServer[] {
/**
* 获取远程 miniapps 用于 ecosystem.json 的数据
*/
export function getRemoteMiniappsForEcosystem(): Array<MiniappManifest & { url: string }> {
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',
}));
}

Expand Down
2 changes: 2 additions & 0 deletions src/services/ecosystem/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,8 @@ export interface MiniappManifest {
sourceIcon?: string;
/** 来源名称(运行时填充) */
sourceName?: string;
/** 运行时容器类型(由宿主注入,默认 'iframe') */
runtime?: 'iframe' | 'wujie';
}

/** Ecosystem source - JSON 文件格式 */
Expand Down
15 changes: 14 additions & 1 deletion vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,15 +97,28 @@ 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,
},
},
],
timeout: 60000,
retries: 3,
}),
miniappsPlugin(),
miniappsPlugin({
apps: {
'com.bfmeta.teleport': {
server: 'iframe',
build: 'wujie',
},
'com.bfmeta.forge': {
server: 'iframe',
build: 'wujie',
},
},
}),
buildCheckPlugin(),
],
resolve: {
Expand Down