Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
bc644ce
STRIPES-861: Setup module federation
mkuklis Apr 25, 2023
b9431f1
Cleanup
mkuklis Apr 25, 2023
467f390
Cleanup
mkuklis Apr 25, 2023
7c3576a
cleanup
mkuklis Apr 25, 2023
2deed9e
Use shutdown hook
mkuklis Apr 25, 2023
49189bd
Cleanup
mkuklis Apr 26, 2023
12cc324
Cleanup
mkuklis Apr 26, 2023
60b5dae
Cleanup
mkuklis Apr 26, 2023
97ba155
Add required version to shared singletons
mkuklis Apr 27, 2023
8c47f56
Start remotes automatically
mkuklis May 1, 2023
84c0199
Expose icons via public endpoint
mkuklis May 10, 2023
54db9c9
react-query provides a context, so must be a singleton
zburke May 16, 2023
ecaa7a6
STCOR-726 map sounds directory for remote applications
zburke Jun 8, 2023
42cac16
current versions
zburke Dec 4, 2024
12613ed
separate handling of stripes-components and application icons
zburke Dec 4, 2024
0c73e79
resolve conflicts
JohnC-80 Oct 31, 2025
626a7c8
provide registry url in stripes-config
JohnC-80 Nov 5, 2025
b399047
resolve conflict
JohnC-80 Nov 5, 2025
08068ed
collect translations from 'StripesDeps' modules in built translations
JohnC-80 Nov 18, 2025
8889e08
get the app's main entry from package.json
JohnC-80 Nov 18, 2025
a8b5fb0
ensure there are aliases before member access
JohnC-80 Nov 18, 2025
fddfec2
expand the singletons for the platform
JohnC-80 Nov 18, 2025
52d545f
make module-federation opt-in via stripes-config
JohnC-80 Dec 5, 2025
2c8dbae
sync up stripes-config-plugin
JohnC-80 Dec 16, 2025
dbd8bde
Merge branch 'main' into STRIPES-861-int
JohnC-80 Dec 16, 2025
c915218
tweak local federation commands
JohnC-80 Dec 16, 2025
0b23f66
Merge branch 'STRIPES-861-int' of github.com:folio-org/stripes-webpac…
JohnC-80 Dec 16, 2025
233244e
switch back to spawn vs spawnsync
JohnC-80 Dec 16, 2025
c7739b0
update to current vm expected exports
JohnC-80 Dec 17, 2025
89c80cb
put back inclusion of stripes-deps in the main stripes-translation pl…
JohnC-80 Dec 17, 2025
a3b01e5
add tests for StripesTranslationPlugin federate mode
JohnC-80 Dec 17, 2025
9639baa
use base webpack config from main branch
JohnC-80 Dec 17, 2025
2018a55
add comment to top of federate remote webpack config
JohnC-80 Dec 17, 2025
0ef9438
log changes
JohnC-80 Dec 17, 2025
17ff134
log more changes
JohnC-80 Dec 17, 2025
eea2bb9
add loader entry for sound file
JohnC-80 Dec 18, 2025
7cf2de0
remove StripesLocalFederationPlugin
JohnC-80 Dec 22, 2025
c9182a9
remove commented svg rules in federate config
JohnC-80 Dec 22, 2025
6d4e80a
remove axios
JohnC-80 Dec 22, 2025
55c989a
high level comments and mod-fed vs monolithic translation differences
JohnC-80 Dec 22, 2025
cef8363
dynamically fetch versions for platform singletons
JohnC-80 Dec 23, 2025
84dd206
configure for module-federation with --federate flag on build and ser…
JohnC-80 Jan 14, 2026
7d47eb9
clean and comment edit
JohnC-80 Jan 14, 2026
dbbeef8
Merge branch 'main' into STRIPES-861-int
JohnC-80 Jan 14, 2026
093724d
buildConfig is now async
JohnC-80 Jan 16, 2026
08f7281
Merge branch 'STRIPES-861-int' of github.com:folio-org/stripes-webpac…
JohnC-80 Jan 16, 2026
b9c3344
remove async fallback for platform singletons
JohnC-80 Jan 22, 2026
4e83e7b
shape dev registry response closer to actual entitlement response
JohnC-80 Jan 22, 2026
4e58dc6
remove the await for buildConfig
JohnC-80 Jan 22, 2026
bded4e7
add tenant name to registry server for spoofing the metadata endpoint
JohnC-80 Jan 22, 2026
f2099e8
look in same folder for module-level builds
JohnC-80 Jan 23, 2026
7fc5d00
const/let in stripes-translations-plugin, ts loader rules in federate…
JohnC-80 Jan 27, 2026
a799aa6
add extensions to resolve in federate webpack config
JohnC-80 Jan 27, 2026
43b1d09
add more 'include' paths for module-level builds
JohnC-80 Jan 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
* Unlock `esbuild-loader` from `~3.0.0`, bumping to `^4.2.2`. Refs STRWEB-95.
* Prune dead code, `stripes.js` and its dep `commander`. Refs STRWEB-134.
* Provide `getDynamicModule`, returning a module via `import()`. Refs STRWEB-137.
* Add `subscribesTo` field to module metadata. Refs STRWEB-143.
* * Add `subscribesTo` field to module metadata. Refs STRWEB-143.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extremely very petty: just * not * *

* Add `StripesLocalFederation` plugin, inject module federation plugin for federated platforms and modules. Refs STRIPES-861.
* Adjust `StripesTranslationsPlugin` for working at the module level and including translations from `stripesDeps`. Refs STRIPES-861.

## [6.0.0](https://github.com/folio-org/stripes-webpack/tree/v6.0.0) (2025-02-24)
[Full Changelog](https://github.com/folio-org/stripes-webpack/compare/v5.2.0...v6.0.0)
Expand Down
59 changes: 59 additions & 0 deletions consts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Anythign that we want *the platform to provide to modules should be here.
// If an item is not in this list, modules will each load their own version of it.
// This can be problematic for React Context if mutliple copies of the same context are loaded.

const singletons = {
'@folio/stripes-components': '^13.1.0',
'@folio/stripes-connect': '^10.0.1',
'@folio/stripes-core': '^11.1.0',
"moment": "^2.29.0",
Comment on lines +1 to +9
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: Anything

'moment' not "moment"

These are all just fallback/default versions, right? Can we say that in the comments, or just initialize everything to null/empty string and then die later on if any value is still falsey? These hard-coded versions just make me cringe.

'react': '~18.3',
'react-dom': '~18.3',
'react-intl': '^7.1.14',
'react-query': '^3.39.3',
'react-redux': '^8.1',
'react-router': '^5.2.0',
'react-router-dom': '^5.2.0',
'redux-observable': '^1.2.0',
'rxjs': '^6.6.3',
};

/** getHostAppSingletons
* get singletons from stripes-core package.json on Github.
*/
const getHostAppSingletons = () => {
let platformSingletons = {};

const handlePkgData = (corePkg) => {
const pkgObject = corePkg.data ? JSON.parse(corePkg.data) : corePkg;
const stripesCoreVersion = pkgObject.version;
platformSingletons['@folio/stripes-core'] = `~${stripesCoreVersion}`;
Object.keys(singletons).forEach(dep => {
const depVersion = pkgObject.peerDependencies[dep];
if (depVersion) {
platformSingletons[dep] = depVersion;
}
});
platformSingletons = { ...platformSingletons, ...singletons };
}

let corePkg;
// try to get the locally installed stripes-core
try {
corePkg = require('@folio/stripes-core/package.json');
} catch (e) {
corePkg = singletons;
throw new Error('Error retrieving singletons list from platform. Falling back to static list');
}

handlePkgData(corePkg);
return platformSingletons;
}

const defaultentitlementUrl = 'http://localhost:3001/registry';

module.exports = {
defaultentitlementUrl,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

petty: defaultEntitlementUrl?

singletons,
getHostAppSingletons
};
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,17 @@
"@cerner/duplicate-package-checker-webpack-plugin": "~2.1.0",
"@csstools/postcss-global-data": "^3.0.0",
"@csstools/postcss-relative-color-syntax": "^3.0.7",
"@octokit/rest": "^19.0.7",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.4",
"@svgr/webpack": "^8.1.0",
"add-asset-html-webpack-plugin": "^6.0.0",
"autoprefixer": "^10.4.13",
"babel-loader": "^9.1.3",
"buffer": "^6.0.3",
"connect-history-api-fallback": "^1.3.0",
"copy-webpack-plugin": "^13.0.1",
"core-js": "^3.6.1",
"cors": "^2.8.5",
"css-loader": "^6.4.0",
"csv-loader": "^3.0.3",
"debug": "^4.0.1",
Expand All @@ -52,6 +55,7 @@
"lodash": "^4.17.21",
"mini-css-extract-plugin": "^2.7.6",
"node-object-hash": "^1.2.0",
"portfinder": "^1.0.32",
"postcss": "^8.4.2",
"postcss-custom-media": "^9.0.1",
"postcss-import": "^15.0.1",
Expand All @@ -70,6 +74,7 @@
"util-ex": "^0.3.15",
"validate-npm-package-name": "^6.0.2",
"webpack-dev-middleware": "^5.2.1",
"webpack-dev-server": "^4.13.1",
"webpack-hot-middleware": "^2.25.1",
"webpack-remove-empty-scripts": "^1.0.1",
"webpack-virtual-modules": "^0.4.3"
Expand Down
2 changes: 1 addition & 1 deletion test/webpack/stripes-config-plugin.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ describe('The stripes-config-plugin', function () {
expect(writeModuleArgs[0]).to.be.a('string').that.equals('node_modules/stripes-config.js');

// TODO: More thorough analysis of the generated virtual module
expect(writeModuleArgs[1]).to.be.a('string').with.match(/export { okapi, config, modules, branding, errorLogging, translations, metadata, icons }/);
expect(writeModuleArgs[1]).to.be.a('string').with.match(/export { branding, config, errorLogging, icons, metadata, modules, okapi, translations }/);
});
});

Expand Down
55 changes: 45 additions & 10 deletions test/webpack/stripes-translations-plugin.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ const StripesTranslationsPlugin = require('../../webpack/stripes-translations-pl

// Stub the parts of the webpack compiler that the StripesTranslationsPlugin interacts with
const compilerStub = {
apply: () => {},
plugin: () => {},
apply: () => { },
plugin: () => { },
options: {
output: {
publicPath: '/',
Expand All @@ -21,22 +21,22 @@ const compilerStub = {
},
hooks: {
beforeWrite: {
tap: () => {},
tap: () => { },
},
emit: {
tapAsync: () => {},
tapAsync: () => { },
},
processAssets: {
tap: () => {},
tap: () => { },
},
thisCompilation: {
tap: () => {},
tap: () => { },
},
contextModuleFactory: {
tap: () => {},
tap: () => { },
},
afterResolve: {
tap: () => {},
tap: () => { },
}
}
};
Expand All @@ -52,6 +52,8 @@ describe('The stripes-translations-plugin', function () {
'@folio/checkout': {},
},
};

this.stripesFederateConfig = { ...this.stripesConfig, federate: true };
});

describe('constructor', function () {
Expand Down Expand Up @@ -89,12 +91,12 @@ describe('The stripes-translations-plugin', function () {
this.sandbox.spy(webpack.ContextReplacementPlugin.prototype, 'apply');
this.sandbox.spy(compilerStub.hooks.emit, 'tapAsync');
this.sandbox.spy(compilerStub.hooks.thisCompilation, 'tap');
this.sandbox.stub(StripesTranslationsPlugin, 'loadFile').returns({ key1: 'Value 1', key2: 'Value 2' });
this.sandbox.stub(StripesTranslationsPlugin, 'loadFile').returns({ key1: 'Value 1', key2: 'Value 2', name: 'testPackage', stripes: { stripesDeps: ['stripes-federate-dependency'] } });
this.compilationStub = {
assets: {},
hooks: {
processAssets: {
tap: () => {}
tap: () => { }
},
},
};
Expand Down Expand Up @@ -131,6 +133,15 @@ describe('The stripes-translations-plugin', function () {
expect(this.sut.modules).to.be.an('object').with.property('stripes-dependency');
});

it('includes certain modules and stripes-deps in "federate" mpode', function () {
// federate mode is per-module, so the plugin executes outside of StripesConfigPlugin, with its own hook.
this.sut = new StripesTranslationsPlugin(this.stripesFederateConfig);
this.sut.apply({ ...compilerStub, context: __dirname });

expect(this.sut.modules).to.be.an('object').with.property('testPackage');
expect(this.sut.modules).to.be.an('object').with.property('stripes-federate-dependency');
});

it('generates an emit function with all translations', function () {
this.sut = new StripesTranslationsPlugin(this.stripesConfig);
this.sut.apply(compilerStub);
Expand All @@ -156,6 +167,30 @@ describe('The stripes-translations-plugin', function () {
expect(emitFiles).to.match(/translations\/fr-\d+\.json/);
});

it('generates an emit function with all translations (federate mode)', function () {
this.sut = new StripesTranslationsPlugin(this.stripesFederateConfig);
this.sut.apply({ ...compilerStub, context: __dirname });

// Get the callback passed to 'thisCompilation' hook
const pluginArgs = compilerStub.hooks.thisCompilation.tap.getCall(0).args;
const compilerCallback = pluginArgs[1];

compilerCallback(this.compilationStub);

const compilationArgs = this.compilationStub.hooks.processAssets.tap.getCall(0).args;
const compilationCallback = compilationArgs[1];

// Call it and observe the modification to compilation.asset
compilationCallback();

const emitFiles = Object.keys(this.compilationStub.assets);

expect(emitFiles).to.have.length(3);
expect(emitFiles).to.match(/translations\/en-\d+\.json/);
expect(emitFiles).to.match(/translations\/es-\d+\.json/);
expect(emitFiles).to.match(/translations\/fr-\d+\.json/);
});

it('applies ContextReplacementPlugins when language filters are set', function () {
this.sut = new StripesTranslationsPlugin(this.stripesConfig);
this.sut.languageFilter = ['en'];
Expand Down
3 changes: 2 additions & 1 deletion webpack.config.base.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ const baseConfig = {
}),
new webpack.EnvironmentPlugin(['NODE_ENV']),
new RemoveEmptyScriptsPlugin(),
new webpack.ManifestPlugin({ entrypoints: true }),
],
module: {
rules: [
Expand Down Expand Up @@ -162,7 +163,7 @@ const buildConfig = (modulePaths) => {
test: /\.css$/,
exclude: [cssDistPathRegex],
use: [
{ loader: isProduction ? MiniCssExtractPlugin.loader : 'style-loader' },
{ loader: isProduction ? MiniCssExtractPlugin.loader : 'style-loader' },
{
loader: 'css-loader',
options: {
Expand Down
13 changes: 11 additions & 2 deletions webpack.config.cli.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ const esbuildLoaderRule = require('./webpack/esbuild-loader-rule');
const utils = require('./webpack/utils');
const buildBaseConfig = require('./webpack.config.base');
const cli = require('./webpack.config.cli');

const { getHostAppSingletons } = require('./consts');
const { ModuleFederationPlugin } = require('webpack').container;
const { processShared } = require('./webpack/utils');

const useBrowserMocha = () => {
return tryResolve('mocha/mocha-es2018.js') ? 'mocha/mocha-es2018.js' : 'mocha';
Expand Down Expand Up @@ -56,10 +58,17 @@ const buildConfig = (stripesConfig) => {
if (utils.isDevelopment) {
devConfig.plugins = devConfig.plugins.concat([
new webpack.HotModuleReplacementPlugin(),
new ReactRefreshWebpackPlugin()
new ReactRefreshWebpackPlugin(),
]);
}

// Enable module federation, setting up the host platform to share singletons (react, stripes-core, etc) with remote modules.
if (stripesConfig.okapi.entitlementUrl) {
const hostAppSingletons = getHostAppSingletons();
const shared = processShared(hostAppSingletons, { singleton: true, eager: true });
devConfig.plugins.push(new ModuleFederationPlugin({ name: 'host', shared }));
}

// This alias avoids a console warning for react-dom patch
devConfig.resolve.alias.process = 'process/browser.js';
devConfig.resolve.alias['mocha'] = useBrowserMocha();
Expand Down
13 changes: 13 additions & 0 deletions webpack.config.cli.prod.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ const buildBaseConfig = require('./webpack.config.base');
const cli = require('./webpack.config.cli');
const esbuildLoaderRule = require('./webpack/esbuild-loader-rule');
const { getModulesPaths, getStripesModulesPaths, getTranspiledModules } = require('./webpack/module-paths');
const { processShared } = require('./webpack/utils');
const { ModuleFederationPlugin } = require('webpack').container;
const { getHostAppSingletons } = require('./consts');


const buildConfig = (stripesConfig, options = {}) => {
const modulePaths = getModulesPaths(stripesConfig.modules);
Expand Down Expand Up @@ -54,6 +58,15 @@ const buildConfig = (stripesConfig, options = {}) => {
}),
]);

// build platform with Module Federation if --federate flag is passed
if (options.federate) {
const singletons = getHostAppSingletons();
const shared = processShared(singletons, { singleton: true, eager: true });
prodConfig.plugins.push(
new ModuleFederationPlugin({ name: 'host', shared })
);
}

prodConfig.optimization = {
mangleWasmImports: false,
minimizer: [
Expand Down
Loading