diff --git a/packages/ui-next/api.ts b/packages/ui-next/api.ts
new file mode 100644
index 000000000..930999c04
--- /dev/null
+++ b/packages/ui-next/api.ts
@@ -0,0 +1,2 @@
+export type { Context } from './src/context';
+export type { ComponentName } from './src/registry';
diff --git a/packages/ui-next/index.ts b/packages/ui-next/index.ts
index c3577227e..7611e4d5b 100644
--- a/packages/ui-next/index.ts
+++ b/packages/ui-next/index.ts
@@ -29,7 +29,7 @@ function hydroPlugins(): Plugin {
if (fs.existsSync(uiEntry)) entries.push(uiEntry);
}
if (!entries.length) return 'export default [];';
- const imports = entries.map((e, i) => `import plugin${i} from '${e}';`).join('\n');
+ const imports = entries.map((e, i) => `import * as plugin${i} from '${e}';`).join('\n');
const exports = `export default [${entries.map((_, i) => `plugin${i}`).join(', ')}];`;
return `${imports}\n${exports}`;
}
@@ -39,6 +39,7 @@ function hydroPlugins(): Plugin {
export async function apply(ctx: Context) {
if (process.env.HYDRO_CLI) return;
+ // 现在只是开发环境的实现,生产环境的实现还未完成
const vite = await createServer({
server: {
middlewareMode: true,
diff --git a/packages/ui-next/package.json b/packages/ui-next/package.json
index 30d7d39b8..179b82a56 100644
--- a/packages/ui-next/package.json
+++ b/packages/ui-next/package.json
@@ -2,6 +2,8 @@
"name": "@hydrooj/ui-next",
"version": "0.0.0",
"type": "module",
+ "main": "index.ts",
+ "types": "api.ts",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
diff --git a/packages/ui-next/src/App.tsx b/packages/ui-next/src/App.tsx
index 6f74d94b5..b0fcd4e9e 100644
--- a/packages/ui-next/src/App.tsx
+++ b/packages/ui-next/src/App.tsx
@@ -1,5 +1,5 @@
-function App() {
- return
;
-}
+import { Component } from './registry';
-export default App;
+const AppInner = Component('page:app', () => app
);
+
+export default AppInner;
diff --git a/packages/ui-next/src/context.ts b/packages/ui-next/src/context.ts
new file mode 100644
index 000000000..eae2b5951
--- /dev/null
+++ b/packages/ui-next/src/context.ts
@@ -0,0 +1,16 @@
+import {
+ after, before, Component, patch,
+} from './registry';
+
+export interface Context {
+ before: typeof before;
+ after: typeof after;
+ patch: typeof patch;
+ register: typeof Component;
+}
+
+export function createContext(): Context {
+ return {
+ before, after, patch, register: Component,
+ };
+}
diff --git a/packages/ui-next/src/main.tsx b/packages/ui-next/src/main.tsx
index 8b9bc3ccd..7c8beca82 100644
--- a/packages/ui-next/src/main.tsx
+++ b/packages/ui-next/src/main.tsx
@@ -1,7 +1,16 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
+import plugins from 'virtual:hydro-plugins';
import App from './App';
+import { createContext } from './context';
-createRoot(document.getElementById('root')!).render(
-
-);
+const ctx = createContext();
+for (const plugin of plugins) {
+ plugin.apply(ctx);
+}
+
+createRoot(document.getElementById('root')!).render(
+
+
+ ,
+);
diff --git a/packages/ui-next/src/registry.tsx b/packages/ui-next/src/registry.tsx
new file mode 100644
index 000000000..99105f9ba
--- /dev/null
+++ b/packages/ui-next/src/registry.tsx
@@ -0,0 +1,76 @@
+/* eslint-disable react-refresh/only-export-components */
+
+import React from 'react';
+
+type Scope = 'page' | 'component';
+export type ComponentName = `${Scope}:${string}`;
+
+interface BeforeAfterEntry {
+ id: string;
+ Comp: React.FC;
+}
+
+interface PatchEntry {
+ id: string;
+ cb: (props: any) => any;
+}
+
+const registry = {
+ components: {} as Record>,
+ before: {} as Record,
+ after: {} as Record,
+ patch: {} as Record,
+};
+
+let idCounter = 0;
+function genId() { return `__auto_${++idCounter}`; }
+
+function upsert(list: T[], entry: T) {
+ const idx = list.findIndex((e) => e.id === entry.id);
+ if (idx !== -1) list[idx] = entry;
+ else list.push(entry);
+}
+
+export function Component>(
+ name: ComponentName,
+ Inner: React.FC
,
+): React.FC
{
+ if (registry.components[name]) {
+ console.warn(`[Registry] Component "${name}" is being re-registered.`);
+ }
+ const Wrapped: React.FC
= (props) => {
+ let patched = props;
+ for (const { cb } of registry.patch[name] ?? []) {
+ try {
+ patched = cb(patched);
+ } catch (e) {
+ console.error(`[Registry] patch error in "${name}":`, e);
+ }
+ }
+ return (
+ <>
+ {(registry.before[name] ?? []).map(({ id, Comp }) => )}
+
+ {(registry.after[name] ?? []).map(({ id, Comp }) => )}
+ >
+ );
+ };
+ Wrapped.displayName = `Registry(${name})`;
+ registry.components[name] = Wrapped;
+ return Wrapped;
+}
+
+export function before(name: ComponentName, Comp: React.FC, id?: string) {
+ const list = registry.before[name] ??= [];
+ upsert(list, { id: id ?? genId(), Comp });
+}
+
+export function after(name: ComponentName, Comp: React.FC, id?: string) {
+ const list = registry.after[name] ??= [];
+ upsert(list, { id: id ?? genId(), Comp });
+}
+
+export function patch(name: ComponentName, cb: (props: any) => any, id?: string) {
+ const list = registry.patch[name] ??= [];
+ upsert(list, { id: id ?? genId(), cb });
+}
diff --git a/packages/ui-next/src/vite-env.d.ts b/packages/ui-next/src/vite-env.d.ts
index 11f02fe2a..617da4f3f 100644
--- a/packages/ui-next/src/vite-env.d.ts
+++ b/packages/ui-next/src/vite-env.d.ts
@@ -1 +1,7 @@
///
+
+declare module 'virtual:hydro-plugins' {
+ import type { RegistryContext } from './registry';
+ const plugins: Array<{ apply: (ctx: RegistryContext) => void }>;
+ export default plugins;
+}