Skip to content

Commit bfa17a9

Browse files
committed
Refactor dynamic module import transform code
1 parent 2e67298 commit bfa17a9

File tree

7 files changed

+253
-99
lines changed

7 files changed

+253
-99
lines changed

frontend/packages/console-app/src/components/data-view/ConsoleDataView.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ import {
99
import { Bullseye, Pagination, Tooltip } from '@patternfly/react-core';
1010
import {
1111
DataView,
12+
DataViewFilters,
1213
DataViewState,
1314
DataViewTable,
1415
DataViewToolbar,
1516
} from '@patternfly/react-data-view';
16-
import DataViewFilters from '@patternfly/react-data-view/dist/esm/DataViewFilters';
1717
import { ColumnsIcon, UndoIcon } from '@patternfly/react-icons';
1818
import { css } from '@patternfly/react-styles';
1919
import { InnerScrollContainer, Tbody, Td, Tr } from '@patternfly/react-table';

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 & 96 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,24 +165,26 @@ 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 = [
175-
'@patternfly/react-charts',
176171
'@patternfly/react-core',
177172
'@patternfly/react-data-view',
178173
'@patternfly/react-icons',
179174
'@patternfly/react-table',
180175
'@patternfly/react-templates',
181176
];
182177

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

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

191189
export type ConsoleRemotePluginOptions = Partial<{
192190
/**
@@ -250,9 +248,9 @@ export type ConsoleRemotePluginOptions = Partial<{
250248
/**
251249
* Some vendor packages may support dynamic modules to be used with webpack module federation.
252250
*
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.
251+
* If a module request matches the `moduleFilter`, code of that module will be modified so that
252+
* any _index_ imports for given vendor packages become imports for specific dynamic modules of
253+
* these vendor packages.
256254
*
257255
* For example, the following import:
258256
* ```ts
@@ -268,11 +266,23 @@ export type ConsoleRemotePluginOptions = Partial<{
268266
* Each dynamic module (such as `@patternfly/react-core/dist/dynamic/components/Alert`) will
269267
* be treated as a separate shared module at runtime. This approach allows for more efficient
270268
* 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.
269+
* (such as `@patternfly/react-core`) that would cause all of its code to be pulled into the
270+
* Console compilation and inflate the vendor bundle size.
272271
*/
273272
sharedDynamicModuleSettings: Partial<{
274273
/**
275-
* Paths to `node_modules` directories to search when parsing dynamic modules.
274+
* Packages that support dynamic modules for use with webpack module federation.
275+
*
276+
* Each vendor package listed here should include a `dist/dynamic` directory containing
277+
* `package.json` files representing parts of that package to be shared separately between
278+
* the Console application and its plugins at runtime.
279+
*
280+
* If not specified, use a default list of PatternFly packages that support dynamic modules.
281+
*/
282+
packageSpecs: DynamicModulePackageSpecs;
283+
284+
/**
285+
* Paths to `node_modules` directories to search when resolving dynamic modules.
276286
*
277287
* Paths listed here _must_ be absolute.
278288
*
@@ -284,33 +294,22 @@ export type ConsoleRemotePluginOptions = Partial<{
284294
modulePaths: string[];
285295

286296
/**
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.
297+
* Modules that match this filter will have their imports transformed.
299298
*
300299
* 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`
300+
* - request ends with `.js`, `.jsx`, `.ts` or `.tsx`
302301
* - request does not contain `node_modules` path elements (i.e. not a vendor module request),
303302
* _except_ for `@openshift-console/*` packages
304303
*/
305-
transformImports: (moduleRequest: string) => boolean;
304+
moduleFilter: (moduleRequest: string) => boolean;
306305
}>;
307306
}>;
308307

309308
/**
310309
* Generates Console dynamic plugin remote container and related assets.
311310
*
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.
311+
* Refer to `console-dynamic-plugin-sdk/src/shared-modules.ts` for details on Console provided
312+
* shared modules and their configuration.
314313
*
315314
* @see {@link sharedPluginModules}
316315
* @see {@link getSharedModuleMetadata}
@@ -342,6 +341,8 @@ export class ConsoleRemotePlugin implements WebpackPluginInstance {
342341
validateConsoleProvidedSharedModules(this.pkg).report();
343342
}
344343

344+
validateConsoleBuildMetadata(this.adaptedOptions.pluginMetadata).report();
345+
345346
const overlapDependencyNames = _.intersection(
346347
Object.keys(this.adaptedOptions.pluginMetadata.dependencies ?? {}),
347348
Object.keys(this.adaptedOptions.pluginMetadata.optionalDependencies ?? {}),
@@ -359,24 +360,10 @@ export class ConsoleRemotePlugin implements WebpackPluginInstance {
359360
path.resolve(process.cwd(), 'node_modules'),
360361
];
361362

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-
}, {});
363+
this.sharedDynamicModuleMaps = resolveDynamicModuleMaps(
364+
this.adaptedOptions.sharedDynamicModuleSettings.packageSpecs ?? dynamicModulePackageSpecs,
365+
resolvedModulePaths,
366+
);
380367
}
381368

382369
apply(compiler: Compiler) {
@@ -401,6 +388,8 @@ export class ConsoleRemotePlugin implements WebpackPluginInstance {
401388

402389
const logger = compiler.getInfrastructureLogger(ConsoleRemotePlugin.name);
403390

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

406395
if (compiler.options.output.publicPath !== undefined) {
@@ -454,10 +443,14 @@ export class ConsoleRemotePlugin implements WebpackPluginInstance {
454443
: 'plugin-entry.js',
455444
}).apply(compiler);
456445

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

459-
if (validateExtensionIntegrity) {
460-
compiler.hooks.emit.tap(ConsoleRemotePlugin.name, (compilation) => {
451+
// Post-build validations performed before emitting assets
452+
compiler.hooks.emit.tap(ConsoleRemotePlugin.name, (compilation) => {
453+
if (validateExtensionIntegrity) {
461454
const result = new ExtensionValidator('Console plugin extensions').validate(
462455
compilation,
463456
extensions,
@@ -471,49 +464,11 @@ export class ConsoleRemotePlugin implements WebpackPluginInstance {
471464
error.file = extensionsFile;
472465
compilation.errors.push(error);
473466
}
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-
});
467+
}
485468

486-
compiler.hooks.thisCompilation.tap(ConsoleRemotePlugin.name, (compilation) => {
487469
getDeprecatedSharedModuleWarnings(this.pkg).forEach((message) => {
488470
compilation.warnings.push(new compiler.webpack.WebpackError(message));
489471
});
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-
);
517472
});
518473
}
519474
}

0 commit comments

Comments
 (0)