mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-12-22 18:11:32 +03:00
test: add test case for plugin bootstrap (#3529)
This commit is contained in:
parent
dcd070b3e7
commit
d3c719d89a
@ -1,7 +1,6 @@
|
|||||||
import { join, resolve } from 'node:path';
|
import { join, resolve } from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { createRequire } from 'node:module';
|
import { createRequire } from 'node:module';
|
||||||
import HTMLPlugin from 'html-webpack-plugin';
|
|
||||||
import type { Configuration as DevServerConfiguration } from 'webpack-dev-server';
|
import type { Configuration as DevServerConfiguration } from 'webpack-dev-server';
|
||||||
import { PerfseePlugin } from '@perfsee/webpack';
|
import { PerfseePlugin } from '@perfsee/webpack';
|
||||||
import { sentryWebpackPlugin } from '@sentry/webpack-plugin';
|
import { sentryWebpackPlugin } from '@sentry/webpack-plugin';
|
||||||
@ -253,14 +252,6 @@ export const createConfiguration: (
|
|||||||
ignoreOrder: true,
|
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 VanillaExtractPlugin(),
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
'process.env': JSON.stringify({}),
|
'process.env': JSON.stringify({}),
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import { createConfiguration, rootPath } from './config.js';
|
import { createConfiguration, rootPath } from './config.js';
|
||||||
import { merge } from 'webpack-merge';
|
import { merge } from 'webpack-merge';
|
||||||
import { resolve } from 'node:path';
|
import { join, resolve } from 'node:path';
|
||||||
import type { BuildFlags } from '@affine/cli/config';
|
import type { BuildFlags } from '@affine/cli/config';
|
||||||
import { getRuntimeConfig } from './runtime-config.js';
|
import { getRuntimeConfig } from './runtime-config.js';
|
||||||
|
import HTMLPlugin from 'html-webpack-plugin';
|
||||||
|
|
||||||
export default async function (cli_env: any, _: any) {
|
export default async function (cli_env: any, _: any) {
|
||||||
const flags: BuildFlags = JSON.parse(
|
const flags: BuildFlags = JSON.parse(
|
||||||
@ -28,6 +29,29 @@ export default async function (cli_env: any, _: any) {
|
|||||||
dependOn: ['polyfill-ses', 'plugin'],
|
dependOn: ['polyfill-ses', 'plugin'],
|
||||||
import: resolve(rootPath, 'src/index.tsx'),
|
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',
|
||||||
|
}),
|
||||||
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
42
apps/core/src/_plugin/index.test.tsx
Normal file
42
apps/core/src/_plugin/index.test.tsx
Normal file
@ -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 (
|
||||||
|
<div>
|
||||||
|
<div data-plugins-load-status="success">Successfully loaded plugins:</div>
|
||||||
|
{plugins.map(plugin => {
|
||||||
|
return <div key={plugin}>{plugin}</div>;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
createRoot(root).render(
|
||||||
|
<StrictMode>
|
||||||
|
<Provider store={rootStore}>
|
||||||
|
<App />
|
||||||
|
</Provider>
|
||||||
|
</StrictMode>
|
||||||
|
);
|
@ -37,43 +37,46 @@ const permissionLogger = new DebugLogger('plugins:permission');
|
|||||||
const importLogger = new DebugLogger('plugins:import');
|
const importLogger = new DebugLogger('plugins:import');
|
||||||
|
|
||||||
const setupRootImportsMap = () => {
|
const setupRootImportsMap = () => {
|
||||||
rootImportsMap.set('react', new Map(Object.entries(React)));
|
_rootImportsMap.set('react', new Map(Object.entries(React)));
|
||||||
rootImportsMap.set(
|
_rootImportsMap.set(
|
||||||
'react/jsx-runtime',
|
'react/jsx-runtime',
|
||||||
new Map(Object.entries(ReactJSXRuntime))
|
new Map(Object.entries(ReactJSXRuntime))
|
||||||
);
|
);
|
||||||
rootImportsMap.set('react-dom', new Map(Object.entries(ReactDom)));
|
_rootImportsMap.set('react-dom', new Map(Object.entries(ReactDom)));
|
||||||
rootImportsMap.set(
|
_rootImportsMap.set(
|
||||||
'react-dom/client',
|
'react-dom/client',
|
||||||
new Map(Object.entries(ReactDomClient))
|
new Map(Object.entries(ReactDomClient))
|
||||||
);
|
);
|
||||||
rootImportsMap.set('@blocksuite/icons', new Map(Object.entries(Icons)));
|
_rootImportsMap.set('@blocksuite/icons', new Map(Object.entries(Icons)));
|
||||||
rootImportsMap.set(
|
_rootImportsMap.set(
|
||||||
'@affine/component',
|
'@affine/component',
|
||||||
new Map(Object.entries(AFFiNEComponent))
|
new Map(Object.entries(AFFiNEComponent))
|
||||||
);
|
);
|
||||||
rootImportsMap.set(
|
_rootImportsMap.set(
|
||||||
'@blocksuite/blocks/std',
|
'@blocksuite/blocks/std',
|
||||||
new Map(Object.entries(BlockSuiteBlocksStd))
|
new Map(Object.entries(BlockSuiteBlocksStd))
|
||||||
);
|
);
|
||||||
rootImportsMap.set(
|
_rootImportsMap.set(
|
||||||
'@blocksuite/global/utils',
|
'@blocksuite/global/utils',
|
||||||
new Map(Object.entries(BlockSuiteGlobalUtils))
|
new Map(Object.entries(BlockSuiteGlobalUtils))
|
||||||
);
|
);
|
||||||
rootImportsMap.set('jotai', new Map(Object.entries(Jotai)));
|
_rootImportsMap.set('jotai', new Map(Object.entries(Jotai)));
|
||||||
rootImportsMap.set('jotai/utils', new Map(Object.entries(JotaiUtils)));
|
_rootImportsMap.set('jotai/utils', new Map(Object.entries(JotaiUtils)));
|
||||||
rootImportsMap.set(
|
_rootImportsMap.set(
|
||||||
'@toeverything/plugin-infra/atom',
|
'@toeverything/plugin-infra/atom',
|
||||||
new Map(Object.entries(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[]
|
// module -> importName -> updater[]
|
||||||
const rootImportsMap = new Map<string, Map<string, any>>();
|
export const _rootImportsMap = new Map<string, Map<string, any>>();
|
||||||
setupRootImportsMap();
|
setupRootImportsMap();
|
||||||
// pluginName -> module -> importName -> updater[]
|
// pluginName -> module -> importName -> updater[]
|
||||||
const pluginNestedImportsMap = new Map<string, Map<string, Map<string, any>>>();
|
export const _pluginNestedImportsMap = new Map<
|
||||||
|
string,
|
||||||
|
Map<string, Map<string, any>>
|
||||||
|
>();
|
||||||
|
|
||||||
const pluginImportsFunctionMap = new Map<string, (imports: any) => void>();
|
const pluginImportsFunctionMap = new Map<string, (imports: any) => void>();
|
||||||
export const createImports = (pluginName: string) => {
|
export const createImports = (pluginName: string) => {
|
||||||
@ -85,12 +88,12 @@ export const createImports = (pluginName: string) => {
|
|||||||
newUpdaters: [string, [string, ((val: any) => void)[]][]][]
|
newUpdaters: [string, [string, ((val: any) => void)[]][]][]
|
||||||
) => {
|
) => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
const currentImportMap = pluginNestedImportsMap.get(pluginName)!;
|
const currentImportMap = _pluginNestedImportsMap.get(pluginName)!;
|
||||||
console.log('currentImportMap', pluginName, currentImportMap);
|
importLogger.debug('currentImportMap', pluginName, currentImportMap);
|
||||||
|
|
||||||
for (const [module, moduleUpdaters] of newUpdaters) {
|
for (const [module, moduleUpdaters] of newUpdaters) {
|
||||||
console.log('imports module', module, moduleUpdaters);
|
importLogger.debug('imports module', module, moduleUpdaters);
|
||||||
let moduleImports = rootImportsMap.get(module);
|
let moduleImports = _rootImportsMap.get(module);
|
||||||
if (!moduleImports) {
|
if (!moduleImports) {
|
||||||
moduleImports = currentImportMap.get(module);
|
moduleImports = currentImportMap.get(module);
|
||||||
}
|
}
|
||||||
@ -107,11 +110,11 @@ export const createImports = (pluginName: string) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log(
|
console.error(
|
||||||
'cannot find module in plugin import map',
|
'cannot find module in plugin import map',
|
||||||
module,
|
module,
|
||||||
currentImportMap,
|
currentImportMap,
|
||||||
pluginNestedImportsMap
|
_pluginNestedImportsMap
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -299,13 +302,13 @@ export const setupPluginCode = async (
|
|||||||
pluginName: string,
|
pluginName: string,
|
||||||
filename: string
|
filename: string
|
||||||
) => {
|
) => {
|
||||||
if (!pluginNestedImportsMap.has(pluginName)) {
|
if (!_pluginNestedImportsMap.has(pluginName)) {
|
||||||
pluginNestedImportsMap.set(pluginName, new Map());
|
_pluginNestedImportsMap.set(pluginName, new Map());
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
const currentImportMap = pluginNestedImportsMap.get(pluginName)!;
|
const currentImportMap = _pluginNestedImportsMap.get(pluginName)!;
|
||||||
const isMissingPackage = (name: string) =>
|
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 =>
|
const bundleAnalysis = await fetch(`${baseUrl}/${filename}.json`).then(res =>
|
||||||
res.json()
|
res.json()
|
||||||
@ -321,7 +324,7 @@ export const setupPluginCode = async (
|
|||||||
if (isMissingPackage(name)) {
|
if (isMissingPackage(name)) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
} else {
|
} else {
|
||||||
console.log('missing package', name);
|
importLogger.debug('missing package', name);
|
||||||
return setupPluginCode(baseUrl, pluginName, name);
|
return setupPluginCode(baseUrl, pluginName, name);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -329,7 +332,7 @@ export const setupPluginCode = async (
|
|||||||
const code = await fetch(`${baseUrl}/${filename.replace(/^\.\//, '')}`).then(
|
const code = await fetch(`${baseUrl}/${filename.replace(/^\.\//, '')}`).then(
|
||||||
res => res.text()
|
res => res.text()
|
||||||
);
|
);
|
||||||
console.log('evaluating', filename);
|
importLogger.debug('evaluating', filename);
|
||||||
const moduleCompartment = new Compartment(
|
const moduleCompartment = new Compartment(
|
||||||
createOrGetGlobalThis(
|
createOrGetGlobalThis(
|
||||||
pluginName,
|
pluginName,
|
||||||
@ -358,7 +361,6 @@ export const setupPluginCode = async (
|
|||||||
onceVar: setVarProxy,
|
onceVar: setVarProxy,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('module exports alias', moduleExports);
|
|
||||||
for (const [newExport, [originalExport]] of Object.entries(moduleExports)) {
|
for (const [newExport, [originalExport]] of Object.entries(moduleExports)) {
|
||||||
if (newExport === originalExport) continue;
|
if (newExport === originalExport) continue;
|
||||||
const value = moduleExportsMap.get(originalExport);
|
const value = moduleExportsMap.get(originalExport);
|
||||||
@ -366,7 +368,6 @@ export const setupPluginCode = async (
|
|||||||
moduleExportsMap.delete(originalExport);
|
moduleExportsMap.delete(originalExport);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('module re-exports', moduleReexports);
|
|
||||||
for (const [name, reexports] of Object.entries(moduleReexports)) {
|
for (const [name, reexports] of Object.entries(moduleReexports)) {
|
||||||
const targetExports = currentImportMap.get(filename);
|
const targetExports = currentImportMap.get(filename);
|
||||||
const moduleExports = currentImportMap.get(name);
|
const moduleExports = currentImportMap.get(name);
|
||||||
@ -374,7 +375,6 @@ export const setupPluginCode = async (
|
|||||||
assertExists(moduleExports);
|
assertExists(moduleExports);
|
||||||
for (const [exportedName, localName] of reexports) {
|
for (const [exportedName, localName] of reexports) {
|
||||||
const exportedValue: any = moduleExports.get(exportedName);
|
const exportedValue: any = moduleExports.get(exportedName);
|
||||||
console.log('re-export', name, localName, exportedName, exportedValue);
|
|
||||||
assertExists(exportedValue);
|
assertExists(exportedValue);
|
||||||
targetExports.set(localName, exportedValue);
|
targetExports.set(localName, exportedValue);
|
||||||
}
|
}
|
||||||
@ -395,7 +395,7 @@ const entryLogger = new DebugLogger('plugin:entry');
|
|||||||
|
|
||||||
export const evaluatePluginEntry = (pluginName: string) => {
|
export const evaluatePluginEntry = (pluginName: string) => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// 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');
|
const pluginExports = currentImportMap.get('index.js');
|
||||||
assertExists(pluginExports);
|
assertExists(pluginExports);
|
||||||
const entryFunction = pluginExports.get('entry');
|
const entryFunction = pluginExports.get('entry');
|
||||||
|
@ -22,7 +22,7 @@ declare global {
|
|||||||
|
|
||||||
globalThis.__pluginPackageJson__ = [];
|
globalThis.__pluginPackageJson__ = [];
|
||||||
|
|
||||||
Promise.all(
|
export const pluginRegisterPromise = Promise.all(
|
||||||
[...builtinPluginUrl].map(url => {
|
[...builtinPluginUrl].map(url => {
|
||||||
return fetch(`${url}/package.json`)
|
return fetch(`${url}/package.json`)
|
||||||
.then(async res => {
|
.then(async res => {
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
import { test } from '@affine-test/kit/playwright';
|
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 { waitEditorLoad } from '@affine-test/kit/utils/page-logic';
|
||||||
import { expect } from '@playwright/test';
|
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 }) => {
|
test('plugin should exist', async ({ page }) => {
|
||||||
await openHomePage(page);
|
await openHomePage(page);
|
||||||
await waitEditorLoad(page);
|
await waitEditorLoad(page);
|
||||||
|
@ -5,3 +5,7 @@ export const webUrl = 'http://localhost:8080';
|
|||||||
export async function openHomePage(page: Page) {
|
export async function openHomePage(page: Page) {
|
||||||
await page.goto(webUrl);
|
await page.goto(webUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function openPluginPage(page: Page) {
|
||||||
|
await page.goto(`${webUrl}/_plugin/index.html`);
|
||||||
|
}
|
||||||
|
@ -26,8 +26,8 @@ export default defineConfig({
|
|||||||
include: [
|
include: [
|
||||||
resolve(rootDir, 'packages/**/*.spec.ts'),
|
resolve(rootDir, 'packages/**/*.spec.ts'),
|
||||||
resolve(rootDir, 'packages/**/*.spec.tsx'),
|
resolve(rootDir, 'packages/**/*.spec.tsx'),
|
||||||
resolve(rootDir, 'apps/web/**/*.spec.ts'),
|
resolve(rootDir, 'apps/core/**/*.spec.ts'),
|
||||||
resolve(rootDir, 'apps/web/**/*.spec.tsx'),
|
resolve(rootDir, 'apps/core/**/*.spec.tsx'),
|
||||||
resolve(rootDir, 'tests/unit/**/*.spec.ts'),
|
resolve(rootDir, 'tests/unit/**/*.spec.ts'),
|
||||||
resolve(rootDir, 'tests/unit/**/*.spec.tsx'),
|
resolve(rootDir, 'tests/unit/**/*.spec.tsx'),
|
||||||
],
|
],
|
||||||
|
Loading…
Reference in New Issue
Block a user