diff --git a/packages/core/app/client/.umirc.ts b/packages/core/app/client/.umirc.ts index 6447221c6..013345e53 100644 --- a/packages/core/app/client/.umirc.ts +++ b/packages/core/app/client/.umirc.ts @@ -10,7 +10,7 @@ process.env.DID_YOU_KNOW = 'none'; const pluginPrefix = (process.env.PLUGIN_PACKAGE_PREFIX || '').split(',').filter((item) => !item.includes('preset')); // 因为现在 preset 是直接引入的,所以不能忽略,如果以后 preset 也是动态插件的形式引入,那么这里可以去掉 const pluginDirs = 'packages/plugins/,packages/samples/'.split(',').map((item) => path.join(process.cwd(), item)); -const outputPluginPath = path.join(__dirname, 'src', '.plugins', 'index.ts'); +const outputPluginPath = path.join(__dirname, 'src', '.plugins'); const indexGenerator = new IndexGenerator(outputPluginPath, pluginDirs); indexGenerator.generate(); diff --git a/packages/core/app/client/src/pages/index.tsx b/packages/core/app/client/src/pages/index.tsx index 514f9e1f9..d098cfbec 100644 --- a/packages/core/app/client/src/pages/index.tsx +++ b/packages/core/app/client/src/pages/index.tsx @@ -1,13 +1,13 @@ import { Application } from '@nocobase/client'; import { NocoBaseClientPresetPlugin } from '@nocobase/preset-nocobase/client'; -import devPlugins from '../.plugins/index'; +import devDynamicImport from '../.plugins/index'; export const app = new Application({ apiClient: { baseURL: process.env.API_BASE_URL, }, plugins: [NocoBaseClientPresetPlugin], - devPlugins, + devDynamicImport, }); export default app.getRootComponent(); diff --git a/packages/core/client/src/application/Application.tsx b/packages/core/client/src/application/Application.tsx index dbfec6725..3ce0ac745 100644 --- a/packages/core/client/src/application/Application.tsx +++ b/packages/core/client/src/application/Application.tsx @@ -24,6 +24,7 @@ declare global { } } +export type DevDynamicImport = (packageName: string) => Promise<{ default: typeof Plugin }>; export type ComponentAndProps = [ComponentType, T]; export interface ApplicationOptions { apiClient?: APIClientOptions; @@ -33,7 +34,7 @@ export interface ApplicationOptions { components?: Record; scopes?: Record; router?: RouterOptions; - devPlugins?: Record>; + devDynamicImport?: DevDynamicImport; } export class Application { @@ -44,12 +45,12 @@ export class Application { public apiClient: APIClient; public components: Record = { ...defaultAppComponents }; public pm: PluginManager; - public devPlugins: Record> = {}; + public devDynamicImport: DevDynamicImport; public requirejs: RequireJS; constructor(protected options: ApplicationOptions = {}) { this.initRequireJs(); - this.devPlugins = options.devPlugins || {}; + this.devDynamicImport = options.devDynamicImport; this.scopes = merge(this.scopes, options.scopes); this.components = merge(this.components, options.components); this.apiClient = new APIClient(options.apiClient); diff --git a/packages/core/client/src/application/PluginManager.ts b/packages/core/client/src/application/PluginManager.ts index e91e4c452..6285e29ea 100644 --- a/packages/core/client/src/application/PluginManager.ts +++ b/packages/core/client/src/application/PluginManager.ts @@ -17,7 +17,10 @@ export class PluginManager { protected pluginsAliases: Record = {}; private initPlugins: Promise; - constructor(protected _plugins: PluginType[], protected app: Application) { + constructor( + protected _plugins: PluginType[], + protected app: Application, + ) { this.app = app; this.initPlugins = this.init(_plugins); } @@ -43,7 +46,7 @@ export class PluginManager { requirejs: this.app.requirejs, pluginData: pluginList, baseURL: this.app.apiClient.axios?.defaults?.baseURL, - devPlugins: this.app.devPlugins, + devDynamicImport: this.app.devDynamicImport, }); for await (const plugin of plugins) { await this.add(plugin); diff --git a/packages/core/client/src/application/utils/remotePlugins.ts b/packages/core/client/src/application/utils/remotePlugins.ts index 2168ae3d3..e76709b75 100644 --- a/packages/core/client/src/application/utils/remotePlugins.ts +++ b/packages/core/client/src/application/utils/remotePlugins.ts @@ -1,6 +1,7 @@ import type { Plugin } from '../Plugin'; import type { PluginData } from '../PluginManager'; import type { RequireJS } from './requirejs'; +import type { DevDynamicImport } from '../Application'; export function defineDevPlugins(plugins: Record) { Object.entries(plugins).forEach(([name, plugin]) => { @@ -44,11 +45,11 @@ interface GetPluginsOption { requirejs: RequireJS; pluginData: PluginData[]; baseURL?: string; - devPlugins?: Record>; + devDynamicImport?: DevDynamicImport; } export async function getPlugins(options: GetPluginsOption): Promise> { - const { requirejs, pluginData, baseURL, devPlugins = {} } = options; + const { requirejs, pluginData, baseURL, devDynamicImport } = options; if (pluginData.length === 0) return []; @@ -57,16 +58,18 @@ export async function getPlugins(options: GetPluginsOption): Promise = {}; const pluginPackageNames = pluginData.map((item) => item.packageName); - for await (const packageName of pluginPackageNames) { - if (devPlugins[packageName]) { - const plugin = await devPlugins[packageName]; - plugins.push(plugin.default); - resolveDevPlugins[packageName] = plugin.default; + if (devDynamicImport) { + for await (const packageName of pluginPackageNames) { + const plugin = await devDynamicImport(packageName); + if (plugin) { + plugins.push(plugin.default); + resolveDevPlugins[packageName] = plugin.default; + } } + defineDevPlugins(resolveDevPlugins); } - defineDevPlugins(resolveDevPlugins); - const remotePlugins = pluginData.filter((item) => !devPlugins[item.packageName]); + const remotePlugins = pluginData.filter((item) => !resolveDevPlugins[item.packageName]); if (remotePlugins.length === 0) { return plugins; diff --git a/packages/core/devtools/umiConfig.js b/packages/core/devtools/umiConfig.js index 43a2be8ff..59c5868fc 100644 --- a/packages/core/devtools/umiConfig.js +++ b/packages/core/devtools/umiConfig.js @@ -105,70 +105,101 @@ class IndexGenerator { this.pluginsPath = pluginsPath; } + get indexPath() { + return path.join(this.outputPath, 'index.ts'); + } + + get packageMapPath() { + return path.join(this.outputPath, 'packageMap.json'); + } + + get packagesPath() { + return path.join(this.outputPath, 'packages'); + } + generate() { - this.generatePluginIndex(); + this.generatePluginContent(); if (process.env.NODE_ENV === 'production') return; this.pluginsPath.forEach((pluginPath) => { if (!fs.existsSync(pluginPath)) { return; } fs.watch(pluginPath, { recursive: false }, () => { - this.generatePluginIndex(); + this.generatePluginContent(); }); }); } - generatePluginIndex() { - if (!fs.existsSync(this.outputPath)) { - fs.mkdirSync(path.dirname(this.outputPath), { recursive: true }); - fs.writeFileSync(this.outputPath, 'export default {}'); + get indexContent() { + return `// @ts-nocheck +import packageMap from './packageMap.json'; + +function devDynamicImport(packageName: string): Promise { + const fileName = packageMap[packageName]; + if (!fileName) { + return Promise.resolve(null); + } + return import(\`./packages/\${fileName}\`) +} +export default devDynamicImport;`; + } + + get emptyIndexContent() { + return ` +export default function devDynamicImport(packageName: string): Promise { + return Promise.resolve(null); +}`; + } + + generatePluginContent() { + if (fs.existsSync(this.outputPath)) { + fs.rmdirSync(this.outputPath, { recursive: true, force: true }); } + fs.mkdirSync(this.outputPath); const validPluginPaths = this.pluginsPath.filter((pluginPath) => fs.existsSync(pluginPath)); - if (!validPluginPaths.length) { + if (!validPluginPaths.length || process.env.NODE_ENV === 'production') { + fs.writeFileSync(this.indexPath, this.emptyIndexContent); return; } - if (process.env.NODE_ENV === 'production') { - fs.writeFileSync(this.outputPath, 'export default {}'); - return; - } - const pluginInfo = validPluginPaths.map((pluginPath) => this.getContent(pluginPath)); - const importContent = pluginInfo.map(({ indexContent }) => indexContent).join('\n'); - const exportContent = pluginInfo.map(({ exportContent }) => exportContent).join('\n'); - const fileContent = `${importContent}\n\nexport default {\n${exportContent}\n}`; + const pluginInfos = validPluginPaths.map((pluginPath) => this.getContent(pluginPath)).flat(); - fs.writeFileSync(this.outputPath, fileContent); + // index.ts + fs.writeFileSync(this.indexPath, this.indexContent); + // packageMap.json + const packageMapContent = pluginInfos.reduce((memo, item) => { + memo[item.packageJsonName] = item.pluginFileName + '.ts'; + return memo; + }, {}); + fs.writeFileSync(this.packageMapPath, JSON.stringify(packageMapContent, null, 2)); + // packages + fs.mkdirSync(this.packagesPath, { recursive: true }); + pluginInfos.forEach((item) => { + const pluginPackagePath = path.join(this.packagesPath, item.pluginFileName + '.ts'); + fs.writeFileSync(pluginPackagePath, item.exportStatement); + }); } getContent(pluginPath) { const pluginFolders = fs.readdirSync(pluginPath); - const pluginImports = pluginFolders + const pluginInfos = pluginFolders .filter((folder) => { const pluginPackageJsonPath = path.join(pluginPath, folder, 'package.json'); const pluginSrcClientPath = path.join(pluginPath, folder, 'src', 'client'); return fs.existsSync(pluginPackageJsonPath) && fs.existsSync(pluginSrcClientPath); }) - .map((folder, index) => { + .map((folder) => { const pluginPackageJsonPath = path.join(pluginPath, folder, 'package.json'); const pluginPackageJson = require(pluginPackageJsonPath); const pluginSrcClientPath = path - .relative(path.dirname(this.outputPath), path.join(pluginPath, folder, 'src', 'client')) + .relative(this.packagesPath, path.join(pluginPath, folder, 'src', 'client')) .replaceAll('\\', '/'); - const pluginName = `${folder.replaceAll('-', '_')}${index}`; - const importStatement = `const ${pluginName} = import('${pluginSrcClientPath}');`; - return { importStatement, pluginName, packageJsonName: pluginPackageJson.name }; + const pluginFileName = `${path.basename(pluginPath)}_${folder.replaceAll('-', '_')}`; + const exportStatement = `export { default } from '${pluginSrcClientPath}';`; + return { exportStatement, pluginFileName, packageJsonName: pluginPackageJson.name }; }); - const indexContent = pluginImports.map(({ importStatement }) => importStatement).join('\n'); - - const exportContent = pluginImports - .map(({ pluginName, packageJsonName }) => ` "${packageJsonName}": ${pluginName},`) - .join('\n'); - - return { - indexContent, - exportContent, - }; + return pluginInfos; } }