mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-06 03:49:56 +08:00
refactor(electron): server side plugin (#3360)
This commit is contained in:
@@ -9,13 +9,12 @@ import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-
|
||||
import { useBlockSuiteWorkspacePage } from '@toeverything/hooks/use-block-suite-workspace-page';
|
||||
import type { CallbackMap } from '@toeverything/plugin-infra/entry';
|
||||
import {
|
||||
affinePluginsAtom,
|
||||
contentLayoutAtom,
|
||||
editorItemsAtom,
|
||||
rootStore,
|
||||
windowItemsAtom,
|
||||
} from '@toeverything/plugin-infra/manager';
|
||||
import type { AffinePlugin, LayoutNode } from '@toeverything/plugin-infra/type';
|
||||
import type { LayoutNode } from '@toeverything/plugin-infra/type';
|
||||
import clsx from 'clsx';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import type { CSSProperties, FC, ReactElement } from 'react';
|
||||
@@ -164,7 +163,6 @@ const PluginContentAdapter = memo<{
|
||||
type LayoutPanelProps = {
|
||||
node: LayoutNode;
|
||||
editorProps: PageDetailEditorProps;
|
||||
plugins: AffinePlugin<string>[];
|
||||
};
|
||||
|
||||
const LayoutPanel = memo(function LayoutPanel(
|
||||
@@ -189,21 +187,13 @@ const LayoutPanel = memo(function LayoutPanel(
|
||||
>
|
||||
<Panel defaultSize={node.splitPercentage}>
|
||||
<Suspense>
|
||||
<LayoutPanel
|
||||
node={node.first}
|
||||
editorProps={props.editorProps}
|
||||
plugins={props.plugins}
|
||||
/>
|
||||
<LayoutPanel node={node.first} editorProps={props.editorProps} />
|
||||
</Suspense>
|
||||
</Panel>
|
||||
<PanelResizeHandle />
|
||||
<Panel defaultSize={100 - node.splitPercentage}>
|
||||
<Suspense>
|
||||
<LayoutPanel
|
||||
node={node.second}
|
||||
editorProps={props.editorProps}
|
||||
plugins={props.plugins}
|
||||
/>
|
||||
<LayoutPanel node={node.second} editorProps={props.editorProps} />
|
||||
</Suspense>
|
||||
</Panel>
|
||||
</PanelGroup>
|
||||
@@ -219,16 +209,11 @@ export const PageDetailEditor: FC<PageDetailEditorProps> = props => {
|
||||
}
|
||||
|
||||
const layout = useAtomValue(contentLayoutAtom);
|
||||
const affinePluginsMap = useAtomValue(affinePluginsAtom);
|
||||
const plugins = useMemo(
|
||||
() => Object.values(affinePluginsMap),
|
||||
[affinePluginsMap]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Suspense>
|
||||
<LayoutPanel node={layout} editorProps={props} plugins={plugins} />
|
||||
<LayoutPanel node={layout} editorProps={props} />
|
||||
</Suspense>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -15,7 +15,6 @@ if (process.platform === 'win32') {
|
||||
|
||||
async function buildLayers() {
|
||||
const common = config();
|
||||
await esbuild.build(common.workers);
|
||||
await esbuild.build({
|
||||
...common.layers,
|
||||
define: {
|
||||
|
||||
@@ -15,7 +15,7 @@ const DEV_SERVER_URL = process.env.DEV_SERVER_URL;
|
||||
/** @type 'production' | 'development'' */
|
||||
const mode = (process.env.NODE_ENV = process.env.NODE_ENV || 'development');
|
||||
|
||||
/** @return {{layers: import('esbuild').BuildOptions, workers: import('esbuild').BuildOptions}} */
|
||||
/** @return {{layers: import('esbuild').BuildOptions}} */
|
||||
export const config = () => {
|
||||
const define = Object.fromEntries([
|
||||
['process.env.NODE_ENV', `"${mode}"`],
|
||||
@@ -52,23 +52,5 @@ export const config = () => {
|
||||
assetNames: '[name]',
|
||||
treeShaking: true,
|
||||
},
|
||||
workers: {
|
||||
entryPoints: [
|
||||
resolve(electronDir, './src/main/workers/plugin.worker.ts'),
|
||||
],
|
||||
entryNames: '[dir]/[name]',
|
||||
outdir: resolve(electronDir, './dist/workers'),
|
||||
bundle: true,
|
||||
target: `node${NODE_MAJOR_VERSION}`,
|
||||
platform: 'node',
|
||||
external: ['@toeverything/plugin-infra', 'async-call-rpc'],
|
||||
define: define,
|
||||
format: 'cjs',
|
||||
loader: {
|
||||
'.node': 'copy',
|
||||
},
|
||||
assetNames: '[name]',
|
||||
treeShaking: true,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -96,37 +96,8 @@ async function watchLayers() {
|
||||
});
|
||||
}
|
||||
|
||||
async function watchWorkers() {
|
||||
return new Promise(async resolve => {
|
||||
let initialBuild = false;
|
||||
|
||||
const buildContext = await esbuild.context({
|
||||
...common.workers,
|
||||
plugins: [
|
||||
...(common.workers.plugins ?? []),
|
||||
{
|
||||
name: 'electron-dev:reload-app-on-workers-change',
|
||||
setup(build) {
|
||||
build.onEnd(() => {
|
||||
if (initialBuild) {
|
||||
console.log(`[workers] has changed, [re]launching electron...`);
|
||||
spawnOrReloadElectron();
|
||||
} else {
|
||||
resolve();
|
||||
initialBuild = true;
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
await buildContext.watch();
|
||||
});
|
||||
}
|
||||
|
||||
async function main() {
|
||||
await watchLayers();
|
||||
await watchWorkers();
|
||||
|
||||
if (watchMode) {
|
||||
console.log(`Watching for changes...`);
|
||||
|
||||
@@ -16,18 +16,10 @@ const outputList = [
|
||||
'preload.js',
|
||||
'affine.darwin-arm64.node',
|
||||
'plugins',
|
||||
'workers',
|
||||
],
|
||||
],
|
||||
['dist/plugins', ['bookmark-block']],
|
||||
['dist/plugins/bookmark-block', ['index.mjs']],
|
||||
['dist/workers', ['plugin.worker.js']],
|
||||
[
|
||||
'node_modules/@toeverything/plugin-infra/dist',
|
||||
['manager.js', 'manager.cjs'],
|
||||
],
|
||||
['node_modules/@blocksuite/global/dist', ['utils.js']],
|
||||
['node_modules/jotai', ['vanilla.js']],
|
||||
['dist/plugins', ['bookmark']],
|
||||
['dist/plugins/bookmark', ['index.js']],
|
||||
] as [entry: string, expected: string[]][];
|
||||
|
||||
await Promise.all(
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import { join, resolve } from 'node:path';
|
||||
import { Worker } from 'node:worker_threads';
|
||||
|
||||
import { logger } from '@affine/electron/main/logger';
|
||||
import { AsyncCall } from 'async-call-rpc';
|
||||
import { ipcMain } from 'electron';
|
||||
|
||||
import { MessageEventChannel } from './utils';
|
||||
|
||||
declare global {
|
||||
// fixme(himself65):
|
||||
// remove this when bookmark block plugin is migrated to plugin-infra
|
||||
@@ -15,56 +11,25 @@ declare global {
|
||||
}
|
||||
|
||||
export function registerPlugin() {
|
||||
const pluginWorkerPath = join(__dirname, './workers/plugin.worker.js');
|
||||
const asyncCall = AsyncCall<
|
||||
Record<string, (...args: any) => PromiseLike<any>>
|
||||
>(
|
||||
{
|
||||
log: (...args: any[]) => {
|
||||
logger.log('Plugin Worker', ...args);
|
||||
},
|
||||
},
|
||||
{
|
||||
channel: new MessageEventChannel(new Worker(pluginWorkerPath)),
|
||||
}
|
||||
);
|
||||
globalThis.asyncCall = asyncCall;
|
||||
logger.info('import plugin manager');
|
||||
import('@toeverything/plugin-infra/manager')
|
||||
.then(({ rootStore, affinePluginsAtom }) => {
|
||||
logger.info('import plugin manager');
|
||||
const bookmarkPluginPath = join(
|
||||
process.env.PLUGIN_DIR ?? resolve(__dirname, './plugins'),
|
||||
'./bookmark-block/index.mjs'
|
||||
);
|
||||
logger.info('bookmark plugin path:', bookmarkPluginPath);
|
||||
import('file://' + bookmarkPluginPath);
|
||||
let dispose: () => void = () => {
|
||||
// noop
|
||||
};
|
||||
rootStore.sub(affinePluginsAtom, () => {
|
||||
dispose();
|
||||
const plugins = rootStore.get(affinePluginsAtom);
|
||||
Object.values(plugins).forEach(plugin => {
|
||||
logger.info('register plugin', plugin.definition.id);
|
||||
plugin.definition.commands.forEach(command => {
|
||||
logger.info('register plugin command', command);
|
||||
ipcMain.handle(command, (event, ...args) =>
|
||||
asyncCall[command](...args)
|
||||
);
|
||||
});
|
||||
});
|
||||
dispose = () => {
|
||||
Object.values(plugins).forEach(plugin => {
|
||||
plugin.definition.commands.forEach(command => {
|
||||
logger.info('unregister plugin command', command);
|
||||
ipcMain.removeHandler(command);
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error('import plugin manager error', error);
|
||||
});
|
||||
globalThis.asyncCall = {};
|
||||
const bookmarkPluginPath = join(
|
||||
process.env.PLUGIN_DIR ?? resolve(__dirname, './plugins'),
|
||||
'./bookmark/index.js'
|
||||
);
|
||||
logger.info('bookmark plugin path:', bookmarkPluginPath);
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { entry } = require(bookmarkPluginPath);
|
||||
|
||||
entry({
|
||||
registerCommand: (command: string, handler: (...args: any[]) => any) => {
|
||||
logger.info('register plugin command', command);
|
||||
ipcMain.handle(command, (event, ...args) => handler(...args));
|
||||
globalThis.asyncCall[command] = handler;
|
||||
},
|
||||
registerCommands: (command: string) => {
|
||||
ipcMain.removeHandler(command);
|
||||
delete globalThis.asyncCall[command];
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
import { join, resolve } from 'node:path';
|
||||
import { parentPort } from 'node:worker_threads';
|
||||
|
||||
import { AsyncCall } from 'async-call-rpc';
|
||||
|
||||
import { MessageEventChannel } from '../utils';
|
||||
|
||||
const commandProxy: Record<string, (...args: any[]) => Promise<any>> = {};
|
||||
|
||||
if (!parentPort) {
|
||||
throw new Error('parentPort is undefined');
|
||||
}
|
||||
|
||||
const mainThread = AsyncCall<{
|
||||
log: (...args: any[]) => Promise<void>;
|
||||
}>(commandProxy, {
|
||||
channel: new MessageEventChannel(parentPort),
|
||||
});
|
||||
|
||||
globalThis.console.log = mainThread.log;
|
||||
globalThis.console.error = mainThread.log;
|
||||
globalThis.console.info = mainThread.log;
|
||||
globalThis.console.debug = mainThread.log;
|
||||
globalThis.console.warn = mainThread.log;
|
||||
|
||||
console.log('import plugin infra');
|
||||
|
||||
import('@toeverything/plugin-infra/manager')
|
||||
.then(({ rootStore, affinePluginsAtom }) => {
|
||||
const bookmarkPluginPath = join(
|
||||
process.env.PLUGIN_DIR ?? resolve(__dirname, '../plugins'),
|
||||
'./bookmark-block/index.mjs'
|
||||
);
|
||||
|
||||
console.log('import bookmark plugin', bookmarkPluginPath);
|
||||
|
||||
import('file://' + bookmarkPluginPath).catch(console.log);
|
||||
rootStore.sub(affinePluginsAtom, () => {
|
||||
const plugins = rootStore.get(affinePluginsAtom);
|
||||
Object.values(plugins).forEach(plugin => {
|
||||
console.log('handle plugin', plugin.definition.id);
|
||||
if (plugin.serverAdapter) {
|
||||
try {
|
||||
plugin.serverAdapter({
|
||||
registerCommand: (command, fn) => {
|
||||
console.log('register command', command);
|
||||
commandProxy[command] = fn;
|
||||
},
|
||||
unregisterCommand: command => {
|
||||
console.log('unregister command', command);
|
||||
delete commandProxy[command];
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(
|
||||
'error when handle plugin',
|
||||
plugin.definition.id,
|
||||
`${e}`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
console.log('no server adapter, skipping.');
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
Reference in New Issue
Block a user