feat: isolated plugin system (#2742)

This commit is contained in:
Himself65
2023-06-09 16:43:46 +08:00
committed by GitHub
parent af6f431c15
commit f2ac2e5b84
51 changed files with 489 additions and 209 deletions

View File

@@ -1,18 +1,23 @@
{
"name": "@toeverything/plugin-infra",
"private": true,
"type": "module",
"scripts": {
"build": "vite build"
"build": "vite build",
"dev": "vite build --watch"
},
"exports": {
"./manager": "./src/manager.ts",
"./type": "./src/type.ts",
"./react": "./src/react/index.ts"
"./manager": {
"type": "./dist/manager.d.ts",
"import": "./dist/manager.js",
"require": "./dist/manager.cjs"
},
"./type": {
"type": "./dist/type.d.ts",
"import": "./dist/type.js",
"require": "./dist/type.cjs"
}
},
"dependencies": {
"@affine/component": "workspace:*",
"@affine/env": "workspace:*",
"@affine/workspace": "workspace:*",
"@blocksuite/blocks": "0.0.0-20230607055421-9b20fcaf-nightly",
"@blocksuite/editor": "0.0.0-20230607055421-9b20fcaf-nightly",
"@blocksuite/global": "0.0.0-20230607055421-9b20fcaf-nightly",
@@ -20,7 +25,9 @@
"@blocksuite/store": "0.0.0-20230607055421-9b20fcaf-nightly"
},
"devDependencies": {
"jotai": "^2.1.1"
"jotai": "^2.1.1",
"vite": "^4.3.9",
"vite-plugin-dts": "^2.3.0"
},
"peerDependencies": {
"@blocksuite/blocks": "*",

View File

@@ -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.');
});
}
}
}
}

View File

@@ -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>
);
}

View File

@@ -1 +0,0 @@
export * from './context';

View File

@@ -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;
};

View File

@@ -8,10 +8,7 @@
},
"references": [
{
"path": "../component"
},
{
"path": "../workspace"
"path": "./tsconfig.node.json"
}
]
}

View File

@@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"outDir": "lib"
},
"include": ["vite.config.ts"]
}

View File

@@ -0,0 +1,35 @@
import { resolve } from 'node:path';
import { fileURLToPath } from 'url';
import { defineConfig } from 'vite';
import dts from 'vite-plugin-dts';
const root = fileURLToPath(new URL('.', import.meta.url));
export default defineConfig({
build: {
minify: false,
lib: {
entry: {
type: resolve(root, 'src/type.ts'),
manager: resolve(root, 'src/manager.ts'),
},
},
rollupOptions: {
external: [
'jotai',
'jotai/vanilla',
'@blocksuite/blocks',
'@blocksuite/store',
'@blocksuite/global',
'@blocksuite/editor',
'@blocksuite/lit',
],
},
},
plugins: [
dts({
insertTypesEntry: true,
}),
],
});