Skip to content

Commit 46b3071

Browse files
committed
Refactor dynamic module import transform code
1 parent 703563f commit 46b3071

5 files changed

Lines changed: 228 additions & 96 deletions

File tree

frontend/packages/console-dynamic-plugin-sdk/CHANGELOG-webpack.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ For current development version of Console, use `4.x.0-prerelease.n` packages.
1010
For older 1.x plugin SDK packages, refer to "OpenShift Console Versions vs SDK Versions" compatibility
1111
table in [Console dynamic plugins README](./README.md).
1212

13+
## 4.22.0-prerelease.3 - TBD
14+
15+
- **Breaking**: `ConsoleRemotePlugin` shared dynamic module option `transformImports` renamed to `moduleFilter` ([CONSOLE-5065], [#16224])
16+
- Update the default list of PatternFly packages that support dynamic modules ([CONSOLE-5065], [#16182])
17+
- Add support for `dist/dynamic-modules.json` when resolving dynamic module maps ([CONSOLE-5065], [#16224])
18+
1319
## 4.22.0-prerelease.2 - 2026-03-26
1420

1521
- **Deprecated**: `loadPluginEntry` callback is deprecated in favor of `__load_plugin_entry__`. Migrate by
@@ -110,6 +116,7 @@ table in [Console dynamic plugins README](./README.md).
110116
[CONSOLE-4400]: https://issues.redhat.com/browse/CONSOLE-4400
111117
[CONSOLE-4623]: https://issues.redhat.com/browse/CONSOLE-4623
112118
[CONSOLE-5050]: https://issues.redhat.com/browse/CONSOLE-5050
119+
[CONSOLE-5065]: https://issues.redhat.com/browse/CONSOLE-5065
113120
[CONSOLE-5135]: https://issues.redhat.com/browse/CONSOLE-5135
114121
[OCPBUGS-30762]: https://issues.redhat.com/browse/OCPBUGS-30762
115122
[OCPBUGS-30824]: https://issues.redhat.com/browse/OCPBUGS-30824
@@ -145,3 +152,5 @@ table in [Console dynamic plugins README](./README.md).
145152
[#15934]: https://github.com/openshift/console/pull/15934
146153
[#15945]: https://github.com/openshift/console/pull/15945
147154
[#16178]: https://github.com/openshift/console/pull/16178
155+
[#16182]: https://github.com/openshift/console/pull/16182
156+
[#16224]: https://github.com/openshift/console/pull/16224

frontend/packages/console-dynamic-plugin-sdk/src/shared-modules/shared-modules-meta.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ type SharedModuleMetadata = Partial<{
1717
*/
1818
allowFallback: boolean;
1919

20-
/** A message describing the deprecation, if the module is deprecated. */
20+
/**
21+
* A message describing the deprecation, if the module has been deprecated.
22+
*
23+
* @default false
24+
*/
2125
deprecated: string | false;
2226
}>;
2327

frontend/packages/console-dynamic-plugin-sdk/src/webpack/ConsoleRemotePlugin.ts

Lines changed: 51 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import * as fs from 'fs';
21
import * as path from 'path';
32
import type {
43
EncodedExtension,
@@ -18,16 +17,13 @@ import {
1817
getSharedModuleMetadata,
1918
} from '../shared-modules/shared-modules-meta';
2019
import type { DynamicModuleMap } from '../utils/dynamic-module-parser';
21-
import { getDynamicModuleMap } from '../utils/dynamic-module-parser';
2220
import { parseJSONC } from '../utils/jsonc';
2321
import { loadSchema } from '../utils/schema';
2422
import { ExtensionValidator } from '../validation/ExtensionValidator';
2523
import { SchemaValidator } from '../validation/SchemaValidator';
2624
import { ValidationResult } from '../validation/ValidationResult';
27-
import type { DynamicModuleImportLoaderOptions } from './loaders/dynamic-module-import-loader';
28-
29-
const dynamicModuleImportLoader =
30-
'@openshift-console/dynamic-plugin-sdk-webpack/lib/webpack/loaders/dynamic-module-import-loader';
25+
import type { DynamicModulePackageSpecs } from './DynamicModuleImportPlugin';
26+
import { DynamicModuleImportPlugin, resolveDynamicModuleMaps } from './DynamicModuleImportPlugin';
3127

3228
const loadPluginPackageJSON = () => readPkg.sync({ normalize: false }) as ConsolePluginPackageJSON;
3329

@@ -130,7 +126,7 @@ const getDeprecatedSharedModuleWarnings = (pkg: ConsolePluginPackageJSON): strin
130126

131127
if (deprecated && pluginDeps[moduleName]) {
132128
warnings.push(
133-
`shared modules: [DEPRECATION ALERT] '${moduleName}' is deprecated. ${deprecated}`,
129+
`[DEPRECATION WARNING] Console provided shared module ${moduleName} has been deprecated: ${deprecated}`,
134130
);
135131
}
136132
});
@@ -169,7 +165,7 @@ const validateConsoleProvidedSharedModules = (pkg: ConsolePluginPackageJSON) =>
169165
/**
170166
* PatternFly packages that support dynamic modules to be used with webpack module federation.
171167
*
172-
* Console provided {@link sharedPluginModules} should NOT be listed here.
168+
* Console provided {@link sharedPluginModules} should _NOT_ be listed here.
173169
*/
174170
const dynamicModulePatternFlyPackages = [
175171
'@patternfly/react-charts',
@@ -180,13 +176,16 @@ const dynamicModulePatternFlyPackages = [
180176
'@patternfly/react-templates',
181177
];
182178

183-
type DynamicModulePackageSpec = Partial<{
184-
/** @default 'dist/esm/index.js' */
185-
indexModule: string;
179+
export const dynamicModulePackageSpecs = dynamicModulePatternFlyPackages.reduce<
180+
DynamicModulePackageSpecs
181+
>((acc, moduleName) => ({ ...acc, [moduleName]: {} }), {});
186182

187-
/** @default 'module' */
188-
resolutionField: string;
189-
}>;
183+
export const dynamicModuleImportTransformFilter = (moduleRequest: string) => {
184+
const isCode = /\.(jsx?|tsx?)$/.test(moduleRequest);
185+
const isVendor = moduleRequest.includes('/node_modules/');
186+
187+
return isCode && (!isVendor || moduleRequest.includes('/node_modules/@openshift-console/'));
188+
};
190189

191190
export type ConsoleRemotePluginOptions = Partial<{
192191
/**
@@ -250,9 +249,9 @@ export type ConsoleRemotePluginOptions = Partial<{
250249
/**
251250
* Some vendor packages may support dynamic modules to be used with webpack module federation.
252251
*
253-
* If a module request matches the `transformImports` filter, that module will have its imports
254-
* transformed so that any _index_ imports for given vendor packages become imports for specific
255-
* dynamic modules of these vendor packages.
252+
* If a module request matches the `moduleFilter`, code of that module will be modified so that
253+
* any _index_ imports for given vendor packages become imports for specific dynamic modules of
254+
* these vendor packages.
256255
*
257256
* For example, the following import:
258257
* ```ts
@@ -268,11 +267,23 @@ export type ConsoleRemotePluginOptions = Partial<{
268267
* Each dynamic module (such as `@patternfly/react-core/dist/dynamic/components/Alert`) will
269268
* be treated as a separate shared module at runtime. This approach allows for more efficient
270269
* federation of vendor package code, as opposed to sharing the whole vendor package index
271-
* (such as `@patternfly/react-core`) that pulls in all of its code.
270+
* (such as `@patternfly/react-core`) that would cause all of its code to be pulled into the
271+
* Console compilation and inflate the vendor bundle size.
272272
*/
273273
sharedDynamicModuleSettings: Partial<{
274274
/**
275-
* Paths to `node_modules` directories to search when parsing dynamic modules.
275+
* Packages that support dynamic modules for use with webpack module federation.
276+
*
277+
* Each vendor package listed here should include a `dist/dynamic` directory containing
278+
* `package.json` files representing parts of that package to be shared separately between
279+
* the Console application and its plugins at runtime.
280+
*
281+
* If not specified, use a default list of PatternFly packages that support dynamic modules.
282+
*/
283+
packageSpecs: DynamicModulePackageSpecs;
284+
285+
/**
286+
* Paths to `node_modules` directories to search when resolving dynamic modules.
276287
*
277288
* Paths listed here _must_ be absolute.
278289
*
@@ -284,33 +295,22 @@ export type ConsoleRemotePluginOptions = Partial<{
284295
modulePaths: string[];
285296

286297
/**
287-
* Attempt to parse dynamic modules for these packages.
288-
*
289-
* Each package listed here should include a `dist/dynamic` directory containing `package.json`
290-
* files that refer to specific modules of that package.
291-
*
292-
* If not specified, use packages listed in {@link dynamicModulePatternFlyPackages} with default
293-
* settings.
294-
*/
295-
packageSpecs: Record<string, DynamicModulePackageSpec>;
296-
297-
/**
298-
* Import transformations will be applied to modules that match this filter.
298+
* Modules that match this filter will have their imports transformed.
299299
*
300300
* If not specified, the following conditions must be all true for a module to be matched:
301-
* - request ends with one of `.js`, `.jsx`, `.ts`, `.tsx`
301+
* - request ends with `.js`, `.jsx`, `.ts` or `.tsx`
302302
* - request does not contain `node_modules` path elements (i.e. not a vendor module request),
303303
* _except_ for `@openshift-console/*` packages
304304
*/
305-
transformImports: (moduleRequest: string) => boolean;
305+
moduleFilter: (moduleRequest: string) => boolean;
306306
}>;
307307
}>;
308308

309309
/**
310310
* Generates Console dynamic plugin remote container and related assets.
311311
*
312-
* Refer to `frontend/packages/console-dynamic-plugin-sdk/src/shared-modules.ts` for details on
313-
* Console application vs. dynamic plugins shared module configuration.
312+
* Refer to `console-dynamic-plugin-sdk/src/shared-modules.ts` for details on Console provided
313+
* shared modules and their configuration.
314314
*
315315
* @see {@link sharedPluginModules}
316316
* @see {@link getSharedModuleMetadata}
@@ -342,6 +342,8 @@ export class ConsoleRemotePlugin implements WebpackPluginInstance {
342342
validateConsoleProvidedSharedModules(this.pkg).report();
343343
}
344344

345+
validateConsoleBuildMetadata(this.adaptedOptions.pluginMetadata).report();
346+
345347
const overlapDependencyNames = _.intersection(
346348
Object.keys(this.adaptedOptions.pluginMetadata.dependencies ?? {}),
347349
Object.keys(this.adaptedOptions.pluginMetadata.optionalDependencies ?? {}),
@@ -359,24 +361,10 @@ export class ConsoleRemotePlugin implements WebpackPluginInstance {
359361
path.resolve(process.cwd(), 'node_modules'),
360362
];
361363

362-
const sharedDynamicModulePackageSpecs =
363-
this.adaptedOptions.sharedDynamicModuleSettings.packageSpecs ??
364-
dynamicModulePatternFlyPackages.reduce<Record<string, DynamicModulePackageSpec>>(
365-
(acc, moduleName) => ({ ...acc, [moduleName]: {} }),
366-
{},
367-
);
368-
369-
this.sharedDynamicModuleMaps = Object.entries(sharedDynamicModulePackageSpecs).reduce<
370-
Record<string, DynamicModuleMap>
371-
>((acc, [pkgName, { indexModule = 'dist/esm/index.js', resolutionField = 'module' }]) => {
372-
const basePath = resolvedModulePaths
373-
.map((p) => path.resolve(p, pkgName))
374-
.find((p) => fs.existsSync(p) && fs.statSync(p).isDirectory());
375-
376-
return basePath
377-
? { ...acc, [pkgName]: getDynamicModuleMap(basePath, indexModule, resolutionField) }
378-
: acc;
379-
}, {});
364+
this.sharedDynamicModuleMaps = resolveDynamicModuleMaps(
365+
this.adaptedOptions.sharedDynamicModuleSettings.packageSpecs ?? dynamicModulePackageSpecs,
366+
resolvedModulePaths,
367+
);
380368
}
381369

382370
apply(compiler: Compiler) {
@@ -401,6 +389,8 @@ export class ConsoleRemotePlugin implements WebpackPluginInstance {
401389

402390
const logger = compiler.getInfrastructureLogger(ConsoleRemotePlugin.name);
403391

392+
// Dynamic plugin assets should be loaded from /api/plugins/<plugin-name> endpoint.
393+
// Console Bridge server will fetch the asset from the appropriate plugin web server.
404394
const publicPath = `/api/plugins/${name}/`;
405395

406396
if (compiler.options.output.publicPath !== undefined) {
@@ -454,10 +444,14 @@ export class ConsoleRemotePlugin implements WebpackPluginInstance {
454444
: 'plugin-entry.js',
455445
}).apply(compiler);
456446

457-
validateConsoleBuildMetadata(pluginMetadata).report();
447+
new DynamicModuleImportPlugin({
448+
dynamicModuleMaps: this.sharedDynamicModuleMaps,
449+
moduleFilter: sharedDynamicModuleSettings.moduleFilter ?? dynamicModuleImportTransformFilter,
450+
}).apply(compiler);
458451

459-
if (validateExtensionIntegrity) {
460-
compiler.hooks.emit.tap(ConsoleRemotePlugin.name, (compilation) => {
452+
// Post-build validations performed before emitting assets
453+
compiler.hooks.emit.tap(ConsoleRemotePlugin.name, (compilation) => {
454+
if (validateExtensionIntegrity) {
461455
const result = new ExtensionValidator('Console plugin extensions').validate(
462456
compilation,
463457
extensions,
@@ -471,49 +465,11 @@ export class ConsoleRemotePlugin implements WebpackPluginInstance {
471465
error.file = extensionsFile;
472466
compilation.errors.push(error);
473467
}
474-
});
475-
}
476-
477-
const transformImports =
478-
sharedDynamicModuleSettings.transformImports ??
479-
((moduleRequest) => {
480-
const isCode = /\.(jsx?|tsx?)$/.test(moduleRequest);
481-
const isVendor = moduleRequest.includes('/node_modules/');
482-
483-
return isCode && (!isVendor || moduleRequest.includes('/node_modules/@openshift-console/'));
484-
});
468+
}
485469

486-
compiler.hooks.thisCompilation.tap(ConsoleRemotePlugin.name, (compilation) => {
487470
getDeprecatedSharedModuleWarnings(this.pkg).forEach((message) => {
488471
compilation.warnings.push(new compiler.webpack.WebpackError(message));
489472
});
490-
491-
const modifiedModules: string[] = [];
492-
493-
compiler.webpack.NormalModule.getCompilationHooks(compilation).beforeLoaders.tap(
494-
ConsoleRemotePlugin.name,
495-
(loaders, normalModule) => {
496-
const { userRequest } = normalModule;
497-
498-
const moduleRequest = userRequest.substring(
499-
userRequest.lastIndexOf('!') === -1 ? 0 : userRequest.lastIndexOf('!') + 1,
500-
);
501-
502-
if (!modifiedModules.includes(moduleRequest) && transformImports(moduleRequest)) {
503-
const loaderOptions: DynamicModuleImportLoaderOptions = {
504-
dynamicModuleMaps: this.sharedDynamicModuleMaps,
505-
resourceMetadata: { jsx: /\.(jsx|tsx)$/.test(moduleRequest) },
506-
};
507-
508-
normalModule.loaders.push({
509-
loader: dynamicModuleImportLoader,
510-
options: loaderOptions,
511-
} as any);
512-
513-
modifiedModules.push(moduleRequest);
514-
}
515-
},
516-
);
517473
});
518474
}
519475
}

0 commit comments

Comments
 (0)