ui-next: add component registry

This commit is contained in:
Baoshuo committed 2026-05-10 01:12:46 +08:00
1 parent 8d7a48bf25
commit 5b20f42de7
8 files changed
+120 -8

No files matched your search

+2
View File
@@ -0,0 +1,2 @@
export type { Context } from './src/context';
export type { ComponentName } from './src/registry';
+2 -1
View File
@@ -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,
+2
View File
@@ -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",
+4 -4
View File
@@ -1,5 +1,5 @@
function App() {
return <div></div>;
}
import { Component } from './registry';
export default App;
const AppInner = Component('page:app', () => <div>app</div>);
export default AppInner;
+16
View File
@@ -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,
};
}
+12 -3
View File
@@ -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(<StrictMode>
<App />
</StrictMode>);
const ctx = createContext();
for (const plugin of plugins) {
plugin.apply(ctx);
}
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
);
+76
View File
@@ -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<any>;
}
interface PatchEntry {
id: string;
cb: (props: any) => any;
}
const registry = {
components: {} as Record<string, React.FC<any>>,
before: {} as Record<string, BeforeAfterEntry[]>,
after: {} as Record<string, BeforeAfterEntry[]>,
patch: {} as Record<string, PatchEntry[]>,
};
let idCounter = 0;
function genId() { return `__auto_${++idCounter}`; }
function upsert<T extends { id: string }>(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<P extends Record<string, any>>(
name: ComponentName,
Inner: React.FC<P>,
): React.FC<P> {
if (registry.components[name]) {
console.warn(`[Registry] Component "${name}" is being re-registered.`);
}
const Wrapped: React.FC<P> = (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 }) => <Comp key={id} {...patched} />)}
<Inner {...patched} />
{(registry.after[name] ?? []).map(({ id, Comp }) => <Comp key={id} {...patched} />)}
</>
);
};
Wrapped.displayName = `Registry(${name})`;
registry.components[name] = Wrapped;
return Wrapped;
}
export function before(name: ComponentName, Comp: React.FC<any>, id?: string) {
const list = registry.before[name] ??= [];
upsert(list, { id: id ?? genId(), Comp });
}
export function after(name: ComponentName, Comp: React.FC<any>, 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 });
}
+6
View File
@@ -1 +1,7 @@
/// <reference types="vite/client" />
declare module 'virtual:hydro-plugins' {
import type { RegistryContext } from './registry';
const plugins: Array<{ apply: (ctx: RegistryContext) => void }>;
export default plugins;
}