mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-18 10:36:22 +08:00
feat: isolated plugin system (#2742)
(cherry picked from commit f2ac2e5b84)
This commit is contained in:
@@ -1,20 +1,23 @@
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
import { rootStore } from '@affine/workspace/atom';
|
||||
import { atom } from 'jotai';
|
||||
import { atom, createStore } from 'jotai/vanilla';
|
||||
|
||||
import type { AffinePlugin, Definition } from './type';
|
||||
import type { AffinePlugin, Definition, ServerAdapter } from './type';
|
||||
import type { Loader, PluginUIAdapter } from './type';
|
||||
import type { PluginBlockSuiteAdapter } from './type';
|
||||
|
||||
const isServer = typeof window === 'undefined';
|
||||
const isClient = typeof window !== 'undefined';
|
||||
|
||||
// global store
|
||||
export const rootStore = createStore();
|
||||
|
||||
// todo: for now every plugin is enabled by default
|
||||
export const affinePluginsAtom = atom<Record<string, AffinePlugin<string>>>({});
|
||||
|
||||
const pluginLogger = new DebugLogger('affine:plugin');
|
||||
|
||||
export function definePlugin<ID extends string>(
|
||||
definition: Definition<ID>,
|
||||
uiAdapterLoader?: Loader<Partial<PluginUIAdapter>>,
|
||||
blockSuiteAdapter?: Loader<Partial<PluginBlockSuiteAdapter>>
|
||||
blockSuiteAdapter?: Loader<Partial<PluginBlockSuiteAdapter>>,
|
||||
serverAdapter?: Loader<ServerAdapter>
|
||||
) {
|
||||
const basePlugin = {
|
||||
definition,
|
||||
@@ -27,57 +30,70 @@ export function definePlugin<ID extends string>(
|
||||
[definition.id]: basePlugin,
|
||||
}));
|
||||
|
||||
if (blockSuiteAdapter) {
|
||||
const updateAdapter = (adapter: Partial<PluginBlockSuiteAdapter>) => {
|
||||
rootStore.set(affinePluginsAtom, plugins => ({
|
||||
...plugins,
|
||||
[definition.id]: {
|
||||
...basePlugin,
|
||||
blockSuiteAdapter: adapter,
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
blockSuiteAdapter
|
||||
.load()
|
||||
.then(({ default: adapter }) => updateAdapter(adapter))
|
||||
.catch(err => {
|
||||
pluginLogger.error('[definePlugin] blockSuiteAdapter error', err);
|
||||
});
|
||||
|
||||
if (import.meta.webpackHot) {
|
||||
blockSuiteAdapter.hotModuleReload(async _ => {
|
||||
const adapter = (await _).default;
|
||||
updateAdapter(adapter);
|
||||
pluginLogger.info('[HMR] Plugin', definition.id, 'hot reloaded.');
|
||||
if (isServer) {
|
||||
if (serverAdapter) {
|
||||
serverAdapter.load().then(({ default: adapter }) => {
|
||||
rootStore.set(affinePluginsAtom, plugins => ({
|
||||
...plugins,
|
||||
[definition.id]: {
|
||||
...basePlugin,
|
||||
serverAdapter: adapter,
|
||||
},
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (isClient) {
|
||||
if (blockSuiteAdapter) {
|
||||
const updateAdapter = (adapter: Partial<PluginBlockSuiteAdapter>) => {
|
||||
rootStore.set(affinePluginsAtom, plugins => ({
|
||||
...plugins,
|
||||
[definition.id]: {
|
||||
...basePlugin,
|
||||
blockSuiteAdapter: adapter,
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
if (uiAdapterLoader) {
|
||||
const updateAdapter = (adapter: Partial<PluginUIAdapter>) => {
|
||||
rootStore.set(affinePluginsAtom, plugins => ({
|
||||
...plugins,
|
||||
[definition.id]: {
|
||||
...basePlugin,
|
||||
uiAdapter: adapter,
|
||||
},
|
||||
}));
|
||||
};
|
||||
blockSuiteAdapter
|
||||
.load()
|
||||
.then(({ default: adapter }) => updateAdapter(adapter))
|
||||
.catch(err => {
|
||||
console.error('[definePlugin] blockSuiteAdapter error', err);
|
||||
});
|
||||
|
||||
uiAdapterLoader
|
||||
.load()
|
||||
.then(({ default: adapter }) => updateAdapter(adapter))
|
||||
.catch(err => {
|
||||
pluginLogger.error('[definePlugin] blockSuiteAdapter error', err);
|
||||
});
|
||||
if (import.meta.webpackHot) {
|
||||
blockSuiteAdapter.hotModuleReload(async _ => {
|
||||
const adapter = (await _).default;
|
||||
updateAdapter(adapter);
|
||||
console.info('[HMR] Plugin', definition.id, 'hot reloaded.');
|
||||
});
|
||||
}
|
||||
}
|
||||
if (uiAdapterLoader) {
|
||||
const updateAdapter = (adapter: Partial<PluginUIAdapter>) => {
|
||||
rootStore.set(affinePluginsAtom, plugins => ({
|
||||
...plugins,
|
||||
[definition.id]: {
|
||||
...basePlugin,
|
||||
uiAdapter: adapter,
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
if (import.meta.webpackHot) {
|
||||
uiAdapterLoader.hotModuleReload(async _ => {
|
||||
const adapter = (await _).default;
|
||||
updateAdapter(adapter);
|
||||
pluginLogger.info('[HMR] Plugin', definition.id, 'hot reloaded.');
|
||||
});
|
||||
uiAdapterLoader
|
||||
.load()
|
||||
.then(({ default: adapter }) => updateAdapter(adapter))
|
||||
.catch(err => {
|
||||
console.error('[definePlugin] blockSuiteAdapter error', err);
|
||||
});
|
||||
|
||||
if (import.meta.webpackHot) {
|
||||
uiAdapterLoader.hotModuleReload(async _ => {
|
||||
const adapter = (await _).default;
|
||||
updateAdapter(adapter);
|
||||
console.info('[HMR] Plugin', definition.id, 'hot reloaded.');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import { ProviderComposer } from '@affine/component/provider-composer';
|
||||
import { ThemeProvider } from '@affine/component/theme-provider';
|
||||
import { rootStore } from '@affine/workspace/atom';
|
||||
import { Provider } from 'jotai';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export function AffinePluginContext(props: PropsWithChildren) {
|
||||
return (
|
||||
<ProviderComposer
|
||||
contexts={useMemo(
|
||||
() =>
|
||||
[
|
||||
<Provider key="JotaiProvider" store={rootStore} />,
|
||||
<ThemeProvider key="ThemeProvider" />,
|
||||
].filter(Boolean),
|
||||
[]
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</ProviderComposer>
|
||||
);
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './context';
|
||||
@@ -141,6 +141,11 @@ export type Definition<ID extends string> = {
|
||||
* @example ReleaseStage.PROD
|
||||
*/
|
||||
stage: ReleaseStage;
|
||||
|
||||
/**
|
||||
* Registered commands
|
||||
*/
|
||||
commands: string[];
|
||||
};
|
||||
|
||||
// todo(himself65): support Vue.js
|
||||
@@ -171,12 +176,16 @@ export type PluginBlockSuiteAdapter = {
|
||||
uiDecorator: (root: EditorContainer) => Cleanup;
|
||||
};
|
||||
|
||||
export type PluginAdapterCreator = (
|
||||
context: AffinePluginContext
|
||||
) => PluginUIAdapter;
|
||||
type AFFiNEServer = {
|
||||
registerCommand: (command: string, fn: (...args: any[]) => any) => void;
|
||||
unregisterCommand: (command: string) => void;
|
||||
};
|
||||
|
||||
export type ServerAdapter = (affine: AFFiNEServer) => () => void;
|
||||
|
||||
export type AffinePlugin<ID extends string> = {
|
||||
definition: Definition<ID>;
|
||||
uiAdapter: Partial<PluginUIAdapter>;
|
||||
blockSuiteAdapter: Partial<PluginBlockSuiteAdapter>;
|
||||
serverAdapter?: ServerAdapter;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user