diff --git a/apps/core/.webpack/template.html b/apps/core/.webpack/template.html index bb59e2420..b7045d454 100644 --- a/apps/core/.webpack/template.html +++ b/apps/core/.webpack/template.html @@ -38,7 +38,6 @@ rel="shortcut icon" href="https://affine.pro/favicon.ico" /> -
diff --git a/apps/core/src/bootstrap/register-plugins.ts b/apps/core/src/bootstrap/register-plugins.ts index 521959a67..7d7dadd44 100644 --- a/apps/core/src/bootstrap/register-plugins.ts +++ b/apps/core/src/bootstrap/register-plugins.ts @@ -81,9 +81,6 @@ const customRequire = (id: string) => { if (id === 'jotai/utils') { return JotaiUtils; } - if (id === '../plugin.js') { - return entryCompartment.evaluate('exports'); - } throw new Error(`Cannot find module '${id}'`); }; @@ -121,35 +118,48 @@ const createGlobalThis = () => { }; const group = new DisposableGroup(); -const pluginList = await ( +const pluginList = (await ( await fetch(new URL(`./plugins/plugin-list.json`, window.location.origin)) -).json(); -const builtInPlugins: string[] = pluginList.map((plugin: any) => plugin.name); -const pluginGlobalThis = createGlobalThis(); -const pluginEntry = await fetch('/plugins/plugin.js').then(res => res.text()); -const entryCompartment = new Compartment(pluginGlobalThis, {}); -entryCompartment.evaluate(pluginEntry, { - __evadeHtmlCommentTest__: true, -}); +).json()) as { name: string; assets: string[]; release: boolean }[]; + await Promise.all( - builtInPlugins.map(plugin => { + pluginList.map(({ name: plugin, release, assets }) => { + if (!release && process.env.NODE_ENV !== 'development') { + return Promise.resolve(); + } const pluginCompartment = new Compartment(createGlobalThis(), {}); const pluginGlobalThis = pluginCompartment.globalThis; const baseURL = new URL(`./plugins/${plugin}/`, window.location.origin); - const packageJsonURL = new URL('package.json', baseURL); - return fetch(packageJsonURL).then(async res => { - const packageJson = await res.json(); - const pluginConfig = packageJson['affinePlugin']; - if ( - pluginConfig.release === false && - process.env.NODE_ENV !== 'development' - ) { - return; + const entryURL = new URL('index.js', baseURL); + rootStore.set(registeredPluginAtom, prev => [...prev, plugin]); + return fetch(entryURL).then(async res => { + if (assets.length > 0) { + await Promise.all( + assets.map(asset => { + if (asset.endsWith('.css')) { + return fetch(new URL(asset, baseURL)).then(res => { + if (res.ok) { + // todo: how to put css file into sandbox? + return res.text().then(text => { + console.log('text', text); + const style = document.createElement('style'); + style.setAttribute('plugin-id', plugin); + style.textContent = text; + document.head.appendChild(style); + }); + } + return null; + }); + } else { + return Promise.resolve(); + } + }) + ); } - rootStore.set(registeredPluginAtom, prev => [...prev, plugin]); - const coreEntry = new URL(pluginConfig.entry.core, baseURL.toString()); - const codeText = await fetch(coreEntry).then(res => res.text()); - pluginCompartment.evaluate(codeText); + const codeText = await res.text(); + pluginCompartment.evaluate(codeText, { + __evadeHtmlCommentTest__: true, + }); pluginGlobalThis.__INTERNAL__ENTRY = { register: (part, callback) => { if (part === 'headerItem') { diff --git a/apps/core/src/components/affine/setting-modal/general-setting/plugins/index.tsx b/apps/core/src/components/affine/setting-modal/general-setting/plugins/index.tsx index 4ecacf836..8ad3e9acd 100644 --- a/apps/core/src/components/affine/setting-modal/general-setting/plugins/index.tsx +++ b/apps/core/src/components/affine/setting-modal/general-setting/plugins/index.tsx @@ -9,7 +9,6 @@ import { useAtomValue } from 'jotai'; export const Plugins = () => { const t = useAFFiNEI18N(); const allowedPlugins = useAtomValue(registeredPluginAtom); - console.log('allowedPlugins', allowedPlugins); return ( <> path.resolve(allPluginDir, plugin); +const pluginDir = getPluginDir(plugin); +const packageJsonFile = path.resolve(pluginDir, 'package.json'); + +const json: z.infer = await readFile( + packageJsonFile, + { + encoding: 'utf-8', + } +) + .then(text => JSON.parse(text)) + .then(async json => { + const { success } = await packageJsonSchema.safeParseAsync(json); + if (success) { + return json; + } else { + throw new Error('invalid package.json'); + } + }); + +type Metadata = { + assets: Set; +}; + +const metadata: Metadata = { + assets: new Set(), +}; + +const outDir = path.resolve( + projectRoot, + 'apps', + 'core', + 'public', + 'plugins', + plugin +); + +const pluginListJsonPath = path.resolve(outDir, '..', 'plugin-list.json'); + +const coreEntry = path.resolve(pluginDir, json.affinePlugin.entry.core); + +await build({ + build: { + watch: isWatch ? {} : undefined, + minify: false, + outDir, + emptyOutDir: true, + lib: { + entry: coreEntry, + fileName: 'index', + formats: ['cjs'], + }, + rollupOptions: { + output: { + assetFileNames: chunkInfo => { + if (chunkInfo.name) { + metadata.assets.add(chunkInfo.name); + return chunkInfo.name; + } else { + throw new Error('no name'); + } + }, + manualChunks: () => 'plugin', + }, + external, + }, + }, + plugins: [ + vanillaExtractPlugin(), + react(), + { + name: 'generate-list-json', + async generateBundle() { + const file = await open(pluginListJsonPath, 'as+'); + const txt = await file.readFile({ + encoding: 'utf-8', + }); + if (!txt) { + console.log('generate plugin-list.json'); + await file.write( + JSON.stringify([ + { + release: json.affinePlugin.release, + name: plugin, + assets: [...metadata.assets], + }, + ]) + ); + } else { + console.log('modify plugin-list.json'); + const list = JSON.parse(txt); + const index = list.findIndex((item: any) => item.name === plugin); + if (index === -1) { + list.push({ + release: json.affinePlugin.release, + name: plugin, + assets: [...metadata.assets], + }); + } else { + list[index].assets = [...metadata.assets]; + } + await file.write(JSON.stringify(list), 0); + } + }, + }, + ], +}); diff --git a/scripts/build-plugins.mjs b/scripts/build-plugins.mjs new file mode 100644 index 000000000..7ac7f2986 --- /dev/null +++ b/scripts/build-plugins.mjs @@ -0,0 +1,16 @@ +import { spawn } from 'node:child_process'; + +spawn('yarn', ['-T', 'run', 'dev-plugin', '--plugin', 'bookmark'], { + stdio: 'inherit', + shell: true, +}); + +spawn('yarn', ['-T', 'run', 'dev-plugin', '--plugin', 'hello-world'], { + stdio: 'inherit', + shell: true, +}); + +spawn('yarn', ['-T', 'run', 'dev-plugin', '--plugin', 'copilot'], { + stdio: 'inherit', + shell: true, +}); diff --git a/vite.config.ts b/vite.config.ts deleted file mode 100644 index 0e66984e7..000000000 --- a/vite.config.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { createRequire } from 'node:module'; -import { resolve } from 'node:path'; -import { fileURLToPath } from 'node:url'; - -import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin'; -import react from '@vitejs/plugin-react-swc'; -import { defineConfig } from 'vite'; - -const root = fileURLToPath(new URL('.', import.meta.url)); - -const require = createRequire(import.meta.url); - -const builtInPlugins = ['hello-world', 'bookmark', 'copilot']; - -const outputJson: [pluginName: string, output: string][] = []; - -const entry = builtInPlugins.reduce( - (acc, plugin) => { - const packageJson = require(resolve( - root, - 'plugins', - plugin, - 'package.json' - )); - const entry = packageJson.affinePlugin.entry.core; - acc[`${plugin}/index`] = resolve(root, 'plugins', plugin, entry); - packageJson.affinePlugin.entry.core = './index.js'; - outputJson.push([plugin, JSON.stringify(packageJson, null, 2)]); - return acc; - }, - {} as Record -); - -export default defineConfig({ - build: { - outDir: resolve(root, 'apps', 'core', 'public', 'plugins'), - emptyOutDir: true, - minify: false, - lib: { - entry, - formats: ['cjs'], - }, - rollupOptions: { - output: { - manualChunks: () => 'plugin', - entryFileNames: '[name].js', - chunkFileNames: '[name].js', - }, - external: [ - // built-in packages - /^@affine/, - /^@blocksuite/, - /^@toeverything/, - - // react - /^react/, - /^react-dom/, - - // store - /^jotai/, - - // css - /^@vanilla-extract/, - ], - plugins: [ - vanillaExtractPlugin(), - { - name: 'generate-manifest', - generateBundle() { - this.emitFile({ - type: 'asset', - fileName: `plugin-list.json`, - source: JSON.stringify( - builtInPlugins.map(plugin => ({ - name: plugin, - })) - ), - }); - outputJson.forEach(([name, json]) => { - this.emitFile({ - type: 'asset', - fileName: `${name}/package.json`, - source: json, - }); - }); - }, - }, - ], - }, - }, - plugins: [react()], -}); diff --git a/yarn.lock b/yarn.lock index eb8f057ee..fb87ed711 100644 --- a/yarn.lock +++ b/yarn.lock @@ -97,6 +97,7 @@ __metadata: bin: build-core: ./src/bin/build-core.mjs dev-core: ./src/bin/dev-core.mjs + dev-plugin: ./src/bin/dev-plugin.mjs languageName: unknown linkType: soft