diff --git a/apps/core/.webpack/config.ts b/apps/core/.webpack/config.ts index 0519258db1..ca9a758adb 100644 --- a/apps/core/.webpack/config.ts +++ b/apps/core/.webpack/config.ts @@ -1,7 +1,6 @@ import { join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { createRequire } from 'node:module'; -import HTMLPlugin from 'html-webpack-plugin'; import type { Configuration as DevServerConfiguration } from 'webpack-dev-server'; import { PerfseePlugin } from '@perfsee/webpack'; import { sentryWebpackPlugin } from '@sentry/webpack-plugin'; @@ -253,14 +252,6 @@ export const createConfiguration: ( ignoreOrder: true, }), ]), - new HTMLPlugin({ - template: join(rootPath, '.webpack', 'template.html'), - inject: 'body', - scriptLoading: 'module', - minify: false, - chunks: ['index', 'plugin', 'polyfill-ses'], - filename: 'index.html', - }), new VanillaExtractPlugin(), new webpack.DefinePlugin({ 'process.env': JSON.stringify({}), diff --git a/apps/core/.webpack/webpack.config.ts b/apps/core/.webpack/webpack.config.ts index ddf671e983..a08b054513 100644 --- a/apps/core/.webpack/webpack.config.ts +++ b/apps/core/.webpack/webpack.config.ts @@ -1,8 +1,9 @@ import { createConfiguration, rootPath } from './config.js'; import { merge } from 'webpack-merge'; -import { resolve } from 'node:path'; +import { join, resolve } from 'node:path'; import type { BuildFlags } from '@affine/cli/config'; import { getRuntimeConfig } from './runtime-config.js'; +import HTMLPlugin from 'html-webpack-plugin'; export default async function (cli_env: any, _: any) { const flags: BuildFlags = JSON.parse( @@ -28,6 +29,29 @@ export default async function (cli_env: any, _: any) { dependOn: ['polyfill-ses', 'plugin'], import: resolve(rootPath, 'src/index.tsx'), }, + '_plugin/index.test': { + asyncChunks: false, + dependOn: ['polyfill-ses', 'plugin'], + import: resolve(rootPath, 'src/_plugin/index.test.tsx'), + }, }, + plugins: [ + new HTMLPlugin({ + template: join(rootPath, '.webpack', 'template.html'), + inject: 'body', + scriptLoading: 'module', + minify: false, + chunks: ['index', 'plugin', 'polyfill-ses'], + filename: 'index.html', + }), + new HTMLPlugin({ + template: join(rootPath, '.webpack', 'template.html'), + inject: 'body', + scriptLoading: 'module', + minify: false, + chunks: ['_plugin/index.test', 'plugin', 'polyfill-ses'], + filename: '_plugin/index.html', + }), + ], }); } diff --git a/apps/core/src/_plugin/index.test.tsx b/apps/core/src/_plugin/index.test.tsx new file mode 100644 index 0000000000..dd045064b5 --- /dev/null +++ b/apps/core/src/_plugin/index.test.tsx @@ -0,0 +1,42 @@ +import { assertExists } from '@blocksuite/global/utils'; +import { + registeredPluginAtom, + rootStore, +} from '@toeverything/plugin-infra/atom'; +import { use } from 'foxact/use'; +import { useAtomValue } from 'jotai'; +import { Provider } from 'jotai/react'; +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; + +import { _pluginNestedImportsMap } from '../bootstrap/plugins/setup'; +import { pluginRegisterPromise } from '../bootstrap/register-plugins'; + +const root = document.getElementById('app'); +assertExists(root); + +const App = () => { + use(pluginRegisterPromise); + const plugins = useAtomValue(registeredPluginAtom); + _pluginNestedImportsMap.forEach(value => { + const exports = value.get('index.js'); + assertExists(exports); + assertExists(exports?.get('entry')); + }); + return ( +
+
Successfully loaded plugins:
+ {plugins.map(plugin => { + return
{plugin}
; + })} +
+ ); +}; + +createRoot(root).render( + + + + + +); diff --git a/apps/core/src/bootstrap/plugins/setup.ts b/apps/core/src/bootstrap/plugins/setup.ts index 26df4123a0..b2b6cdb6cf 100644 --- a/apps/core/src/bootstrap/plugins/setup.ts +++ b/apps/core/src/bootstrap/plugins/setup.ts @@ -37,43 +37,46 @@ const permissionLogger = new DebugLogger('plugins:permission'); const importLogger = new DebugLogger('plugins:import'); const setupRootImportsMap = () => { - rootImportsMap.set('react', new Map(Object.entries(React))); - rootImportsMap.set( + _rootImportsMap.set('react', new Map(Object.entries(React))); + _rootImportsMap.set( 'react/jsx-runtime', new Map(Object.entries(ReactJSXRuntime)) ); - rootImportsMap.set('react-dom', new Map(Object.entries(ReactDom))); - rootImportsMap.set( + _rootImportsMap.set('react-dom', new Map(Object.entries(ReactDom))); + _rootImportsMap.set( 'react-dom/client', new Map(Object.entries(ReactDomClient)) ); - rootImportsMap.set('@blocksuite/icons', new Map(Object.entries(Icons))); - rootImportsMap.set( + _rootImportsMap.set('@blocksuite/icons', new Map(Object.entries(Icons))); + _rootImportsMap.set( '@affine/component', new Map(Object.entries(AFFiNEComponent)) ); - rootImportsMap.set( + _rootImportsMap.set( '@blocksuite/blocks/std', new Map(Object.entries(BlockSuiteBlocksStd)) ); - rootImportsMap.set( + _rootImportsMap.set( '@blocksuite/global/utils', new Map(Object.entries(BlockSuiteGlobalUtils)) ); - rootImportsMap.set('jotai', new Map(Object.entries(Jotai))); - rootImportsMap.set('jotai/utils', new Map(Object.entries(JotaiUtils))); - rootImportsMap.set( + _rootImportsMap.set('jotai', new Map(Object.entries(Jotai))); + _rootImportsMap.set('jotai/utils', new Map(Object.entries(JotaiUtils))); + _rootImportsMap.set( '@toeverything/plugin-infra/atom', new Map(Object.entries(Atom)) ); - rootImportsMap.set('swr', new Map(Object.entries(SWR))); + _rootImportsMap.set('swr', new Map(Object.entries(SWR))); }; // module -> importName -> updater[] -const rootImportsMap = new Map>(); +export const _rootImportsMap = new Map>(); setupRootImportsMap(); // pluginName -> module -> importName -> updater[] -const pluginNestedImportsMap = new Map>>(); +export const _pluginNestedImportsMap = new Map< + string, + Map> +>(); const pluginImportsFunctionMap = new Map void>(); export const createImports = (pluginName: string) => { @@ -85,12 +88,12 @@ export const createImports = (pluginName: string) => { newUpdaters: [string, [string, ((val: any) => void)[]][]][] ) => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const currentImportMap = pluginNestedImportsMap.get(pluginName)!; - console.log('currentImportMap', pluginName, currentImportMap); + const currentImportMap = _pluginNestedImportsMap.get(pluginName)!; + importLogger.debug('currentImportMap', pluginName, currentImportMap); for (const [module, moduleUpdaters] of newUpdaters) { - console.log('imports module', module, moduleUpdaters); - let moduleImports = rootImportsMap.get(module); + importLogger.debug('imports module', module, moduleUpdaters); + let moduleImports = _rootImportsMap.get(module); if (!moduleImports) { moduleImports = currentImportMap.get(module); } @@ -107,11 +110,11 @@ export const createImports = (pluginName: string) => { } } } else { - console.log( + console.error( 'cannot find module in plugin import map', module, currentImportMap, - pluginNestedImportsMap + _pluginNestedImportsMap ); } } @@ -299,13 +302,13 @@ export const setupPluginCode = async ( pluginName: string, filename: string ) => { - if (!pluginNestedImportsMap.has(pluginName)) { - pluginNestedImportsMap.set(pluginName, new Map()); + if (!_pluginNestedImportsMap.has(pluginName)) { + _pluginNestedImportsMap.set(pluginName, new Map()); } // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const currentImportMap = pluginNestedImportsMap.get(pluginName)!; + const currentImportMap = _pluginNestedImportsMap.get(pluginName)!; const isMissingPackage = (name: string) => - rootImportsMap.has(name) && !currentImportMap.has(name); + _rootImportsMap.has(name) && !currentImportMap.has(name); const bundleAnalysis = await fetch(`${baseUrl}/${filename}.json`).then(res => res.json() @@ -321,7 +324,7 @@ export const setupPluginCode = async ( if (isMissingPackage(name)) { return Promise.resolve(); } else { - console.log('missing package', name); + importLogger.debug('missing package', name); return setupPluginCode(baseUrl, pluginName, name); } }) @@ -329,7 +332,7 @@ export const setupPluginCode = async ( const code = await fetch(`${baseUrl}/${filename.replace(/^\.\//, '')}`).then( res => res.text() ); - console.log('evaluating', filename); + importLogger.debug('evaluating', filename); const moduleCompartment = new Compartment( createOrGetGlobalThis( pluginName, @@ -358,7 +361,6 @@ export const setupPluginCode = async ( onceVar: setVarProxy, }); - console.log('module exports alias', moduleExports); for (const [newExport, [originalExport]] of Object.entries(moduleExports)) { if (newExport === originalExport) continue; const value = moduleExportsMap.get(originalExport); @@ -366,7 +368,6 @@ export const setupPluginCode = async ( moduleExportsMap.delete(originalExport); } - console.log('module re-exports', moduleReexports); for (const [name, reexports] of Object.entries(moduleReexports)) { const targetExports = currentImportMap.get(filename); const moduleExports = currentImportMap.get(name); @@ -374,7 +375,6 @@ export const setupPluginCode = async ( assertExists(moduleExports); for (const [exportedName, localName] of reexports) { const exportedValue: any = moduleExports.get(exportedName); - console.log('re-export', name, localName, exportedName, exportedValue); assertExists(exportedValue); targetExports.set(localName, exportedValue); } @@ -395,7 +395,7 @@ const entryLogger = new DebugLogger('plugin:entry'); export const evaluatePluginEntry = (pluginName: string) => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const currentImportMap = pluginNestedImportsMap.get(pluginName)!; + const currentImportMap = _pluginNestedImportsMap.get(pluginName)!; const pluginExports = currentImportMap.get('index.js'); assertExists(pluginExports); const entryFunction = pluginExports.get('entry'); diff --git a/apps/core/src/bootstrap/register-plugins.ts b/apps/core/src/bootstrap/register-plugins.ts index 2679c0e9e1..afd62506b9 100644 --- a/apps/core/src/bootstrap/register-plugins.ts +++ b/apps/core/src/bootstrap/register-plugins.ts @@ -22,7 +22,7 @@ declare global { globalThis.__pluginPackageJson__ = []; -Promise.all( +export const pluginRegisterPromise = Promise.all( [...builtinPluginUrl].map(url => { return fetch(`${url}/package.json`) .then(async res => { diff --git a/tests/affine-plugin/e2e/basic.spec.ts b/tests/affine-plugin/e2e/basic.spec.ts index d299b50d9c..1597bd5184 100644 --- a/tests/affine-plugin/e2e/basic.spec.ts +++ b/tests/affine-plugin/e2e/basic.spec.ts @@ -1,8 +1,13 @@ import { test } from '@affine-test/kit/playwright'; -import { openHomePage } from '@affine-test/kit/utils/load-page'; +import { openHomePage, openPluginPage } from '@affine-test/kit/utils/load-page'; import { waitEditorLoad } from '@affine-test/kit/utils/page-logic'; import { expect } from '@playwright/test'; +test('plugin map should valid', async ({ page }) => { + await openPluginPage(page); + await page.waitForSelector('[data-plugins-load-status="success"]'); +}); + test('plugin should exist', async ({ page }) => { await openHomePage(page); await waitEditorLoad(page); diff --git a/tests/kit/utils/load-page.ts b/tests/kit/utils/load-page.ts index 87e8c7730c..f50c192c99 100644 --- a/tests/kit/utils/load-page.ts +++ b/tests/kit/utils/load-page.ts @@ -5,3 +5,7 @@ export const webUrl = 'http://localhost:8080'; export async function openHomePage(page: Page) { await page.goto(webUrl); } + +export async function openPluginPage(page: Page) { + await page.goto(`${webUrl}/_plugin/index.html`); +} diff --git a/vitest.config.ts b/vitest.config.ts index f96aed4cc1..6974d34f28 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -26,8 +26,8 @@ export default defineConfig({ include: [ resolve(rootDir, 'packages/**/*.spec.ts'), resolve(rootDir, 'packages/**/*.spec.tsx'), - resolve(rootDir, 'apps/web/**/*.spec.ts'), - resolve(rootDir, 'apps/web/**/*.spec.tsx'), + resolve(rootDir, 'apps/core/**/*.spec.ts'), + resolve(rootDir, 'apps/core/**/*.spec.tsx'), resolve(rootDir, 'tests/unit/**/*.spec.ts'), resolve(rootDir, 'tests/unit/**/*.spec.tsx'), ],