mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
feat: plugin system with isolated bundles (#2660)
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -66,6 +66,7 @@ i18n-generated.ts
|
|||||||
# Cache
|
# Cache
|
||||||
.eslintcache
|
.eslintcache
|
||||||
next-env.d.ts
|
next-env.d.ts
|
||||||
|
.rollup.cache
|
||||||
|
|
||||||
# Rust
|
# Rust
|
||||||
target
|
target
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
|
import { join } from 'node:path';
|
||||||
|
|
||||||
import { app, BrowserWindow, nativeTheme } from 'electron';
|
import { app, BrowserWindow, nativeTheme } from 'electron';
|
||||||
|
|
||||||
import type { NamespaceHandlers } from '../type';
|
import type { NamespaceHandlers } from '../type';
|
||||||
import { isMacOS } from '../utils';
|
import { isMacOS } from '../utils';
|
||||||
import { getMetaData } from './get-meta-data';
|
|
||||||
import { getGoogleOauthCode } from './google-auth';
|
import { getGoogleOauthCode } from './google-auth';
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const handlers = require(join(
|
||||||
|
process.env.PLUGIN_DIR ?? '../../plugins',
|
||||||
|
'./bookmark-block/server'
|
||||||
|
)) as NamespaceHandlers;
|
||||||
|
|
||||||
export const uiHandlers = {
|
export const uiHandlers = {
|
||||||
handleThemeChange: async (_, theme: (typeof nativeTheme)['themeSource']) => {
|
handleThemeChange: async (_, theme: (typeof nativeTheme)['themeSource']) => {
|
||||||
nativeTheme.themeSource = theme;
|
nativeTheme.themeSource = theme;
|
||||||
@@ -40,11 +47,5 @@ export const uiHandlers = {
|
|||||||
getGoogleOauthCode: async () => {
|
getGoogleOauthCode: async () => {
|
||||||
return getGoogleOauthCode();
|
return getGoogleOauthCode();
|
||||||
},
|
},
|
||||||
getBookmarkDataByLink: async (_, url: string) => {
|
...handlers,
|
||||||
return getMetaData(url, {
|
|
||||||
shouldReGetHTML: metaData => {
|
|
||||||
return !metaData.title && !metaData.description;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
} satisfies NamespaceHandlers;
|
} satisfies NamespaceHandlers;
|
||||||
|
|||||||
@@ -56,7 +56,6 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"better-sqlite3": "^8.4.0",
|
"better-sqlite3": "^8.4.0",
|
||||||
"cheerio": "^1.0.0-rc.12",
|
|
||||||
"chokidar": "^3.5.3",
|
"chokidar": "^3.5.3",
|
||||||
"electron-updater": "^5.3.0",
|
"electron-updater": "^5.3.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
#!/usr/bin/env zx
|
#!/usr/bin/env zx
|
||||||
import 'zx/globals';
|
import 'zx/globals';
|
||||||
|
|
||||||
|
import { spawnSync } from 'node:child_process';
|
||||||
|
import { resolve } from 'node:path';
|
||||||
|
|
||||||
import * as esbuild from 'esbuild';
|
import * as esbuild from 'esbuild';
|
||||||
|
|
||||||
import { config } from './common.mjs';
|
import { config, rootDir } from './common.mjs';
|
||||||
|
|
||||||
const NODE_ENV =
|
const NODE_ENV =
|
||||||
process.env.NODE_ENV === 'development' ? 'development' : 'production';
|
process.env.NODE_ENV === 'development' ? 'development' : 'production';
|
||||||
@@ -17,6 +20,12 @@ async function buildLayers() {
|
|||||||
const common = config();
|
const common = config();
|
||||||
await esbuild.build(common.preload);
|
await esbuild.build(common.preload);
|
||||||
|
|
||||||
|
console.log('build plugins');
|
||||||
|
spawnSync('yarn', ['build'], {
|
||||||
|
cwd: resolve(rootDir, './plugins/bookmark-block'),
|
||||||
|
stdio: 'inherit',
|
||||||
|
});
|
||||||
|
|
||||||
await esbuild.build({
|
await esbuild.build({
|
||||||
...common.main,
|
...common.main,
|
||||||
define: {
|
define: {
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ import { resolve } from 'node:path';
|
|||||||
|
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
export const root = fileURLToPath(new URL('..', import.meta.url));
|
export const electronDir = fileURLToPath(new URL('..', import.meta.url));
|
||||||
|
|
||||||
|
export const rootDir = resolve(electronDir, '..', '..');
|
||||||
|
|
||||||
export const NODE_MAJOR_VERSION = 18;
|
export const NODE_MAJOR_VERSION = 18;
|
||||||
|
|
||||||
// hard-coded for now:
|
// hard-coded for now:
|
||||||
@@ -33,10 +36,13 @@ export const config = () => {
|
|||||||
return {
|
return {
|
||||||
main: {
|
main: {
|
||||||
entryPoints: [
|
entryPoints: [
|
||||||
resolve(root, './layers/main/src/index.ts'),
|
resolve(electronDir, './layers/main/src/index.ts'),
|
||||||
resolve(root, './layers/main/src/workers/merge-update.worker.ts'),
|
resolve(
|
||||||
|
electronDir,
|
||||||
|
'./layers/main/src/workers/merge-update.worker.ts'
|
||||||
|
),
|
||||||
],
|
],
|
||||||
outdir: resolve(root, './dist/layers/main'),
|
outdir: resolve(electronDir, './dist/layers/main'),
|
||||||
bundle: true,
|
bundle: true,
|
||||||
target: `node${NODE_MAJOR_VERSION}`,
|
target: `node${NODE_MAJOR_VERSION}`,
|
||||||
platform: 'node',
|
platform: 'node',
|
||||||
@@ -50,8 +56,8 @@ export const config = () => {
|
|||||||
treeShaking: true,
|
treeShaking: true,
|
||||||
},
|
},
|
||||||
preload: {
|
preload: {
|
||||||
entryPoints: [resolve(root, './layers/preload/src/index.ts')],
|
entryPoints: [resolve(electronDir, './layers/preload/src/index.ts')],
|
||||||
outdir: resolve(root, './dist/layers/preload'),
|
outdir: resolve(electronDir, './dist/layers/preload'),
|
||||||
bundle: true,
|
bundle: true,
|
||||||
target: `node${NODE_MAJOR_VERSION}`,
|
target: `node${NODE_MAJOR_VERSION}`,
|
||||||
platform: 'node',
|
platform: 'node',
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
/* eslint-disable no-async-promise-executor */
|
/* eslint-disable no-async-promise-executor */
|
||||||
import { spawn } from 'node:child_process';
|
import { spawn } from 'node:child_process';
|
||||||
import { readFileSync } from 'node:fs';
|
import { readFileSync } from 'node:fs';
|
||||||
import path from 'node:path';
|
import path, { resolve } from 'node:path';
|
||||||
|
|
||||||
import electronPath from 'electron';
|
import electronPath from 'electron';
|
||||||
import * as esbuild from 'esbuild';
|
import * as esbuild from 'esbuild';
|
||||||
|
|
||||||
import { config, root } from './common.mjs';
|
import { config, electronDir, rootDir } from './common.mjs';
|
||||||
|
|
||||||
// this means we don't spawn electron windows, mainly for testing
|
// this means we don't spawn electron windows, mainly for testing
|
||||||
const watchMode = process.argv.includes('--watch');
|
const watchMode = process.argv.includes('--watch');
|
||||||
@@ -21,7 +21,10 @@ const stderrFilterPatterns = [
|
|||||||
|
|
||||||
// these are set before calling `config`, so we have a chance to override them
|
// these are set before calling `config`, so we have a chance to override them
|
||||||
try {
|
try {
|
||||||
const devJson = readFileSync(path.resolve(root, './dev.json'), 'utf-8');
|
const devJson = readFileSync(
|
||||||
|
path.resolve(electronDir, './dev.json'),
|
||||||
|
'utf-8'
|
||||||
|
);
|
||||||
const devEnv = JSON.parse(devJson);
|
const devEnv = JSON.parse(devJson);
|
||||||
Object.assign(process.env, devEnv);
|
Object.assign(process.env, devEnv);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -65,7 +68,18 @@ function spawnOrReloadElectron() {
|
|||||||
|
|
||||||
const common = config();
|
const common = config();
|
||||||
|
|
||||||
function watchPreload() {
|
function watchPlugins() {
|
||||||
|
const cp = spawn('yarn', ['dev'], {
|
||||||
|
cwd: resolve(rootDir, './plugins/bookmark-block'),
|
||||||
|
stdio: 'inherit',
|
||||||
|
});
|
||||||
|
|
||||||
|
process.once('beforeExit', () => {
|
||||||
|
cp.kill();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function watchPreload() {
|
||||||
return new Promise(async resolve => {
|
return new Promise(async resolve => {
|
||||||
let initialBuild = false;
|
let initialBuild = false;
|
||||||
const preloadBuild = await esbuild.context({
|
const preloadBuild = await esbuild.context({
|
||||||
@@ -122,6 +136,7 @@ async function watchMain() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
|
watchPlugins();
|
||||||
await watchMain();
|
await watchMain();
|
||||||
await watchPreload();
|
await watchPreload();
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,16 @@
|
|||||||
"main": "./src/index.ts",
|
"main": "./src/index.ts",
|
||||||
"module": "./src/index.ts",
|
"module": "./src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts"
|
".": "./src/index.ts",
|
||||||
|
"./server": "./src/server.ts"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "vite build",
|
||||||
|
"dev": "vite build --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@toeverything/plugin-infra": "workspace:*"
|
"@toeverything/plugin-infra": "workspace:*",
|
||||||
|
"cheerio": "^1.0.0-rc.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"react": "18.3.0-canary-16d053d59-20230506",
|
"react": "18.3.0-canary-16d053d59-20230506",
|
||||||
|
|||||||
11
plugins/bookmark-block/src/server.ts
Normal file
11
plugins/bookmark-block/src/server.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { getMetaData } from './server/get-meta-data';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getBookmarkDataByLink: async (_: unknown, url: string) => {
|
||||||
|
return getMetaData(url, {
|
||||||
|
shouldReGetHTML: metaData => {
|
||||||
|
return !metaData.title && !metaData.description;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import urlparse from 'url';
|
import { parse, resolve } from 'node:url';
|
||||||
|
|
||||||
export function makeUrlAbsolute(base: string, relative: string): string {
|
export function makeUrlAbsolute(base: string, relative: string): string {
|
||||||
const relativeParsed = urlparse.parse(relative);
|
const relativeParsed = parse(relative);
|
||||||
|
|
||||||
if (relativeParsed.host === null) {
|
if (relativeParsed.host === null) {
|
||||||
return urlparse.resolve(base, relative);
|
return resolve(base, relative);
|
||||||
}
|
}
|
||||||
|
|
||||||
return relative;
|
return relative;
|
||||||
@@ -15,7 +15,7 @@ export function makeUrlSecure(url: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function parseUrl(url: string): string {
|
export function parseUrl(url: string): string {
|
||||||
return urlparse.parse(url).hostname || '';
|
return parse(url).hostname || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getProvider(host: string): string {
|
export function getProvider(host: string): string {
|
||||||
@@ -1,4 +1,13 @@
|
|||||||
{
|
{
|
||||||
"extends": "../../tsconfig.json",
|
"extends": "../../tsconfig.json",
|
||||||
"include": ["./src"]
|
"compilerOptions": {
|
||||||
|
"rootDir": "./src",
|
||||||
|
"types": ["electron"]
|
||||||
|
},
|
||||||
|
"include": ["**.ts", "**.tsx"],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.node.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
10
plugins/bookmark-block/tsconfig.node.json
Normal file
10
plugins/bookmark-block/tsconfig.node.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"outDir": "lib"
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts", "scripts"]
|
||||||
|
}
|
||||||
33
plugins/bookmark-block/vite.config.ts
Normal file
33
plugins/bookmark-block/vite.config.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { resolve } from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
|
const rootDir = fileURLToPath(new URL('.', import.meta.url));
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
minify: false,
|
||||||
|
lib: {
|
||||||
|
entry: resolve(__dirname, 'src/server.ts'),
|
||||||
|
fileName: 'server',
|
||||||
|
formats: ['cjs'],
|
||||||
|
},
|
||||||
|
emptyOutDir: true,
|
||||||
|
rollupOptions: {
|
||||||
|
external: ['cheerio', 'electron', 'node:url'],
|
||||||
|
output: {
|
||||||
|
dir: resolve(
|
||||||
|
rootDir,
|
||||||
|
'..',
|
||||||
|
'..',
|
||||||
|
'apps',
|
||||||
|
'electron',
|
||||||
|
'dist',
|
||||||
|
'plugins',
|
||||||
|
'bookmark-block'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
14
scripts/setup/build-plugins.ts
Normal file
14
scripts/setup/build-plugins.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { resolve } from 'node:path';
|
||||||
|
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import { build } from 'vite';
|
||||||
|
import { beforeAll } from 'vitest';
|
||||||
|
|
||||||
|
export const rootDir = fileURLToPath(new URL('../..', import.meta.url));
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const { default: config } = await import(
|
||||||
|
resolve(rootDir, './plugins/bookmark-block/vite.config.ts')
|
||||||
|
);
|
||||||
|
await build(config);
|
||||||
|
});
|
||||||
@@ -6,6 +6,7 @@ import react from '@vitejs/plugin-react';
|
|||||||
import { defineConfig } from 'vitest/config';
|
import { defineConfig } from 'vitest/config';
|
||||||
|
|
||||||
const rootDir = fileURLToPath(new URL('.', import.meta.url));
|
const rootDir = fileURLToPath(new URL('.', import.meta.url));
|
||||||
|
const pluginOutputDir = resolve(rootDir, './apps/electron/dist/plugins');
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react(), vanillaExtractPlugin()],
|
plugins: [react(), vanillaExtractPlugin()],
|
||||||
@@ -16,8 +17,12 @@ export default defineConfig({
|
|||||||
'next/config': resolve(rootDir, './scripts/vitest/next-config-mock.ts'),
|
'next/config': resolve(rootDir, './scripts/vitest/next-config-mock.ts'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
define: {
|
||||||
|
'process.env.PLUGIN_DIR': JSON.stringify(pluginOutputDir),
|
||||||
|
},
|
||||||
test: {
|
test: {
|
||||||
setupFiles: [
|
setupFiles: [
|
||||||
|
resolve(rootDir, './scripts/setup/build-plugins.ts'),
|
||||||
resolve(rootDir, './scripts/setup/lit.ts'),
|
resolve(rootDir, './scripts/setup/lit.ts'),
|
||||||
resolve(rootDir, './scripts/setup/i18n.ts'),
|
resolve(rootDir, './scripts/setup/i18n.ts'),
|
||||||
resolve(rootDir, './scripts/setup/search.ts'),
|
resolve(rootDir, './scripts/setup/search.ts'),
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ __metadata:
|
|||||||
resolution: "@affine/bookmark-block@workspace:plugins/bookmark-block"
|
resolution: "@affine/bookmark-block@workspace:plugins/bookmark-block"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@toeverything/plugin-infra": "workspace:*"
|
"@toeverything/plugin-infra": "workspace:*"
|
||||||
|
cheerio: ^1.0.0-rc.12
|
||||||
react: 18.3.0-canary-16d053d59-20230506
|
react: 18.3.0-canary-16d053d59-20230506
|
||||||
react-dom: 18.3.0-canary-16d053d59-20230506
|
react-dom: 18.3.0-canary-16d053d59-20230506
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -183,7 +184,6 @@ __metadata:
|
|||||||
"@types/fs-extra": ^11.0.1
|
"@types/fs-extra": ^11.0.1
|
||||||
"@types/uuid": ^9.0.1
|
"@types/uuid": ^9.0.1
|
||||||
better-sqlite3: ^8.4.0
|
better-sqlite3: ^8.4.0
|
||||||
cheerio: ^1.0.0-rc.12
|
|
||||||
chokidar: ^3.5.3
|
chokidar: ^3.5.3
|
||||||
cross-env: 7.0.3
|
cross-env: 7.0.3
|
||||||
electron: 25.0.0
|
electron: 25.0.0
|
||||||
|
|||||||
Reference in New Issue
Block a user