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