From bc644ceb35d405c1f19a30fa1cdb5d8f9cca8857 Mon Sep 17 00:00:00 2001 From: Michal Kuklis Date: Tue, 25 Apr 2023 09:41:17 -0400 Subject: [PATCH 01/38] STRIPES-861: Setup module federation --- consts.js | 18 +++++ package.json | 4 ++ webpack.config.base.js | 9 ++- webpack.config.cli.dev.js | 1 - webpack.config.federate.remote.js | 115 ++++++++++++++++++++++++++++++ webpack/federate.js | 64 +++++++++++++++++ webpack/module-paths.js | 1 + webpack/registryServer.js | 40 +++++++++++ webpack/serve.js | 4 ++ webpack/stripes-config-plugin.js | 16 +++-- webpack/stripes-module-parser.js | 7 +- webpack/stripes-node-api.js | 2 + webpack/utils.js | 8 +++ 13 files changed, 281 insertions(+), 8 deletions(-) create mode 100644 consts.js create mode 100644 webpack.config.federate.remote.js create mode 100644 webpack/federate.js create mode 100644 webpack/registryServer.js diff --git a/consts.js b/consts.js new file mode 100644 index 0000000..041f7a9 --- /dev/null +++ b/consts.js @@ -0,0 +1,18 @@ +// TODO: should these come from https://github.com/folio-org/stripes-core/blob/1d5d4f00a3756702e828856d4ef9349ceb9f1c08/package.json#L116-L129 +const singletons = { + '@folio/stripes': '^9.0.0', + '@folio/stripes-shared-context': '^1.0.0', + 'react': '^18.2', + 'react-dom': '^18.2', + 'react-intl': '^6.4.4', + 'react-query': '^3.39.3', + 'react-redux': '^8.0.5', + 'react-router': '^5.2.0', + 'react-router-dom': '^5.2.0', + 'redux-observable': '^1.2.0', + 'rxjs': '^6.6.3' +}; + +module.exports = { + singletons, +}; diff --git a/package.json b/package.json index 6721220..ba20aa8 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "@pmmmwh/react-refresh-webpack-plugin": "^0.5.4", "@svgr/webpack": "^8.1.0", "add-asset-html-webpack-plugin": "^6.0.0", + "axios": "^1.3.6", "autoprefixer": "^10.4.13", "babel-loader": "^9.1.3", "babel-plugin-remove-jsx-attributes": "^0.0.2", @@ -41,6 +42,7 @@ "commander": "^2.9.0", "connect-history-api-fallback": "^1.3.0", "core-js": "^3.6.1", + "cors": "^2.8.5", "crypto-browserify": "^3.12.0", "css-loader": "^6.4.0", "csv-loader": "^3.0.3", @@ -55,6 +57,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", @@ -72,6 +75,7 @@ "typescript": "^5.3.3", "util-ex": "^0.3.15", "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" diff --git a/webpack.config.base.js b/webpack.config.base.js index 0be1037..3889973 100644 --- a/webpack.config.base.js +++ b/webpack.config.base.js @@ -5,11 +5,16 @@ const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const RemoveEmptyScriptsPlugin = require('webpack-remove-empty-scripts'); +const { ModuleFederationPlugin } = require('webpack').container; -const { generateStripesAlias } = require('./webpack/module-paths'); +const { generateStripesAlias, locatePackageJsonPath } = require('./webpack/module-paths'); +const { processShared } = require('./webpack/utils'); const typescriptLoaderRule = require('./webpack/typescript-loader-rule'); const { isProduction } = require('./webpack/utils'); const { getTranspiledCssPaths } = require('./webpack/module-paths'); +const { singletons } = require('./consts'); + +const shared = processShared(singletons, { singleton: true, eager: true }); // React doesn't like being included multiple times as can happen when using // yarn link. Here we find a more specific path to it by first looking in @@ -65,6 +70,7 @@ const baseConfig = { }), new webpack.EnvironmentPlugin(['NODE_ENV']), new RemoveEmptyScriptsPlugin(), + new ModuleFederationPlugin({ name: 'host', shared }), ], module: { rules: [ @@ -131,7 +137,6 @@ const baseConfig = { }, }; - const buildConfig = (modulePaths) => { const transpiledCssPaths = getTranspiledCssPaths(modulePaths); const cssDistPathRegex = /dist[\/\\]style\.css/; diff --git a/webpack.config.cli.dev.js b/webpack.config.cli.dev.js index baed059..912ddb6 100644 --- a/webpack.config.cli.dev.js +++ b/webpack.config.cli.dev.js @@ -10,7 +10,6 @@ const utils = require('./webpack/utils'); const buildBaseConfig = require('./webpack.config.base'); const cli = require('./webpack.config.cli'); - const useBrowserMocha = () => { return tryResolve('mocha/mocha-es2018.js') ? 'mocha/mocha-es2018.js' : 'mocha'; }; diff --git a/webpack.config.federate.remote.js b/webpack.config.federate.remote.js new file mode 100644 index 0000000..286081c --- /dev/null +++ b/webpack.config.federate.remote.js @@ -0,0 +1,115 @@ +const path = require('path'); +const webpack = require('webpack'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const { container } = webpack; +const { processExternals, processShared } = require('./webpack/utils'); +const { getStripesModulesPaths } = require('./webpack/module-paths'); +const esbuildLoaderRule = require('./webpack/esbuild-loader-rule'); +const { singletons } = require('./consts'); + +const buildConfig = (metadata) => { + const { host, port, name, displayName } = metadata; + const mainEntry = path.join(process.cwd(), 'src', 'index.js'); + const stripesModulePaths = getStripesModulesPaths(); + const translationsPath = path.join(process.cwd(), 'translations', displayName.split('.').shift()); + const shared = processShared(singletons, { singleton: true }); + console.log(shared); + + const config = { + name, + devtool: 'inline-source-map', + mode: 'development', + entry: mainEntry, + output: { + publicPath: `${host}:${port}/`, + }, + devServer: { + port: port, + open: false, + headers: { + 'Access-Control-Allow-Origin': '*', + }, + static: { + directory: translationsPath, + publicPath: '/translations' + } + }, + module: { + rules: [ + esbuildLoaderRule(stripesModulePaths), + { + test: /\.(woff2?)$/, + type: 'asset/resource', + generator: { + filename: './fonts/[name].[contenthash].[ext]', + }, + }, + { + test: /\.css$/, + use: [ + { + loader: MiniCssExtractPlugin.loader, + }, + { + loader: 'css-loader', + options: { + modules: { + localIdentName: '[local]---[hash:base64:5]', + }, + sourceMap: true, + importLoaders: 1, + }, + }, + { + loader: 'postcss-loader', + options: { + postcssOptions: { + config: path.resolve(__dirname, 'postcss.config.js'), + }, + sourceMap: true, + }, + }, + ], + }, + { + test: /\.(jpg|jpeg|gif|png|ico)$/, + type: 'asset/resource', + generator: { + filename: './img/[name].[contenthash].[ext]', + }, + }, + { + test: /\.svg$/, + use: [{ + loader: 'url-loader', + options: { + esModule: false, + }, + }] + }, + { + test: /\.js.map$/, + enforce: "pre", + use: ['source-map-loader'], + } + ] + }, + externals: processExternals(['@folio/stripes', 'stripes-config']), + plugins: [ + new MiniCssExtractPlugin({ filename: 'style.css', ignoreOrder: false }), + new container.ModuleFederationPlugin({ + library: { type: 'var', name }, + name, + filename: 'remoteEntry.js', + exposes: { + './MainEntry': mainEntry, + }, + shared + }), + ] + }; + + return config; +} + +module.exports = buildConfig; diff --git a/webpack/federate.js b/webpack/federate.js new file mode 100644 index 0000000..1d6702e --- /dev/null +++ b/webpack/federate.js @@ -0,0 +1,64 @@ +const path = require('path'); +const webpack = require('webpack'); +const WebpackDevServer = require('webpack-dev-server'); +const axios = require('axios'); +const { snakeCase } = require('lodash'); +const portfinder = require('portfinder'); + +const applyWebpackOverrides = require('./apply-webpack-overrides'); +const buildConfig = require('../webpack.config.federate.remote'); +const { tryResolve } = require('./module-paths'); +const logger = require('./logger')(); + +// Remotes will be serve starting from port 3002 +portfinder.setBasePort(3002); + +module.exports = async function federate(options = {}) { + logger.log('starting federation...'); + + const packageJsonPath = tryResolve(path.join(process.cwd(), 'package.json')); + + if (!packageJsonPath) { + console.error('package.json not found'); + process.exit(); + } + + const port = await portfinder.getPortPromise(); + const host = `http://localhost`; + const url = `${host}:${port}/remoteEntry.js`; + + const { name: packageName, version, description, stripes } = require(packageJsonPath); + const { permissionSets: _, ...stripesRest } = stripes; + const name = snakeCase(packageName); + const metadata = { + module: packageName, + version, + description, + host, + port, + url, + name, + ...stripesRest, + }; + + const config = buildConfig(metadata); + + // TODO: allow for configuring registryUrl via env var or stripes config + const registryUrl = 'http://localhost:3001/registry'; + + // update registry + axios.post(registryUrl, metadata).catch(error => { + console.error(`Registry not found. Please check ${registryUrl}`); + process.exit(); + }); + + const compiler = webpack(config); + const server = new WebpackDevServer(config.devServer, compiler); + console.log(`Starting remote server on port ${port}`); + server.start(); + + process.on('SIGINT', async () => { + await axios.delete(registryUrl, { data: metadata }); + process.exit(0); + }); +}; diff --git a/webpack/module-paths.js b/webpack/module-paths.js index 00ea92b..9989090 100644 --- a/webpack/module-paths.js +++ b/webpack/module-paths.js @@ -264,4 +264,5 @@ module.exports = { getNonTranspiledModules, getTranspiledModules, getTranspiledCssPaths, + locatePackageJsonPath, }; diff --git a/webpack/registryServer.js b/webpack/registryServer.js new file mode 100644 index 0000000..6f17a04 --- /dev/null +++ b/webpack/registryServer.js @@ -0,0 +1,40 @@ +const express = require('express'); +const cors = require('cors'); + +// Registry data +const registry = { remotes: {} }; + +const registryServer = { + start: () => { + const app = express(); + + app.use(express.json()); + app.use(cors()); + + // add/update remote to registry + app.post('/registry', (req, res) => { + const metadata = req.body; + const { name } = metadata; + + registry.remotes[name] = metadata; + res.status(200).send(`Remote ${name} metadata updated`); + }); + + // return entire registry + app.get('/registry', (_, res) => res.json(registry)); + + app.delete('/registry', (req, res) => { + const metadata = req.body; + const { name } = metadata; + delete registry.remotes[name]; + + res.status(200).send(`Remote ${name} removed`); + }); + + app.listen(3001, () => { + console.log('Starting registry server at http://localhost:3001'); + }); + } +}; + +module.exports = registryServer; diff --git a/webpack/serve.js b/webpack/serve.js index c0e3b51..29c0291 100644 --- a/webpack/serve.js +++ b/webpack/serve.js @@ -10,6 +10,7 @@ const logger = require('./logger')(); const buildConfig = require('../webpack.config.cli.dev'); const sharedStylesConfig = require('../webpack.config.cli.shared.styles'); const buildServiceWorkerConfig = require('../webpack.config.service.worker'); +const registryServer = require('./registryServer'); const cwd = path.resolve(); const platformModulePath = path.join(cwd, 'node_modules'); @@ -32,6 +33,9 @@ module.exports = function serve(stripesConfig, options) { serviceWorkerConfig.resolve = { modules: ['node_modules', platformModulePath, coreModulePath] }; serviceWorkerConfig.resolveLoader = { modules: ['node_modules', platformModulePath, coreModulePath] }; + // stripes module registry + registryServer.start(); + let config = buildConfig(stripesConfig); config = sharedStylesConfig(config, {}); diff --git a/webpack/stripes-config-plugin.js b/webpack/stripes-config-plugin.js index 3ef8996..d9fdffa 100644 --- a/webpack/stripes-config-plugin.js +++ b/webpack/stripes-config-plugin.js @@ -44,8 +44,14 @@ module.exports = class StripesConfigPlugin { apply(compiler) { const enabledModules = this.options.modules; logger.log('enabled modules:', enabledModules); - const { config, metadata, icons, stripesDeps, warnings } = stripesModuleParser.parseAllModules(enabledModules, compiler.context, compiler.options.resolve.alias); - this.mergedConfig = Object.assign({}, this.options, { modules: config }); + //const { config, metadata, icons, stripesDeps, warnings } = stripesModuleParser.parseAllModules(enabledModules, compiler.context, compiler.options.resolve.alias); + const stripesDeps = {}; + const config = this.options; + const warnings = {}; + const metadata = {}; + const icons = {}; + this.mergedConfig = config; + // Object.assign({}, this.options); this.metadata = metadata; this.icons = icons; this.warnings = warnings; @@ -78,11 +84,13 @@ module.exports = class StripesConfigPlugin { const branding = ${stripesSerialize.serializeWithRequire(pluginData.branding)}; const errorLogging = ${stripesSerialize.serializeWithRequire(pluginData.errorLogging)}; const translations = ${serialize(pluginData.translations, { space: 2 })}; - const metadata = ${stripesSerialize.serializeWithRequire(this.metadata)}; - const icons = ${stripesSerialize.serializeWithRequire(this.icons)}; + // const metadata = ${stripesSerialize.serializeWithRequire(this.metadata)}; + // const icons = ${stripesSerialize.serializeWithRequire(this.icons)}; export { okapi, config, modules, branding, errorLogging, translations, metadata, icons }; `; + console.log(stripesVirtualModule); + logger.log('writing virtual module...', stripesVirtualModule); this.virtualModule.writeModule('node_modules/stripes-config.js', stripesVirtualModule); } diff --git a/webpack/stripes-module-parser.js b/webpack/stripes-module-parser.js index 93b06f8..a7e482f 100644 --- a/webpack/stripes-module-parser.js +++ b/webpack/stripes-module-parser.js @@ -109,7 +109,8 @@ class StripesModuleParser { // Validates and parses a module's stripes data parseStripesConfig(moduleName, packageJson) { const { stripes, description, version } = packageJson; - const getModule = new Function([], `return require('${moduleName}').default;`); + //const getModule = new Function([], `return require('${moduleName}').default;`); + const getModule = new Function([], ``); const stripesConfig = _.omit(Object.assign({}, stripes, this.overrideConfig, { module: moduleName, getModule, @@ -207,14 +208,17 @@ function parseAllModules(enabledModules, context, aliases) { // stripesDeps const config = parsedModule.config; + if (Array.isArray(config.stripesDeps)) { config.stripesDeps.forEach(dep => { // locate dep relative to the module that depends on it const depContext = modulePaths.locateStripesModule(context, config.module, aliases, 'package.json'); const packageJsonPath = modulePaths.locateStripesModule(depContext, dep, aliases, 'package.json'); + if (!packageJsonPath) { throw new StripesBuildError(`StripesModuleParser: Unable to locate ${dep}'s package.json (dependency of ${config.module})`); } + const packageJson = require(packageJsonPath); const resolvedPath = packageJsonPath.replace('/package.json', ''); unsortedStripesDeps[dep] = appendOrSingleton(unsortedStripesDeps[dep], { @@ -255,6 +259,7 @@ function parseAllModules(enabledModules, context, aliases) { } return depIcons; }, {}); + for (const [key, value] of Object.entries(stripesDeps)) { if (anyHasIcon(value)) { icons[key] = mergeIcons(value); diff --git a/webpack/stripes-node-api.js b/webpack/stripes-node-api.js index afffb6d..2b0d9d7 100644 --- a/webpack/stripes-node-api.js +++ b/webpack/stripes-node-api.js @@ -1,9 +1,11 @@ const build = require('./build'); const serve = require('./serve'); const transpile = require('./transpile'); +const federate = require('./federate'); module.exports = { build, serve, transpile, + federate, }; diff --git a/webpack/utils.js b/webpack/utils.js index b29bfda..b274568 100644 --- a/webpack/utils.js +++ b/webpack/utils.js @@ -14,8 +14,16 @@ const processExternals = (peerDeps) => { }, {}); }; +const processShared = (shared, options = {}) => { + return shared.reduce((acc, name) => { + acc[name] = options; + return acc; + }, {}); +}; + module.exports = { processExternals, isDevelopment, isProduction, + processShared, }; From b9431f10ba0977dddf9cedc9a57c0e9c8e05bc9c Mon Sep 17 00:00:00 2001 From: Michal Kuklis Date: Tue, 25 Apr 2023 09:42:49 -0400 Subject: [PATCH 02/38] Cleanup --- webpack.config.federate.remote.js | 1 - webpack/stripes-config-plugin.js | 7 ++----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/webpack.config.federate.remote.js b/webpack.config.federate.remote.js index 286081c..3fc1af2 100644 --- a/webpack.config.federate.remote.js +++ b/webpack.config.federate.remote.js @@ -13,7 +13,6 @@ const buildConfig = (metadata) => { const stripesModulePaths = getStripesModulesPaths(); const translationsPath = path.join(process.cwd(), 'translations', displayName.split('.').shift()); const shared = processShared(singletons, { singleton: true }); - console.log(shared); const config = { name, diff --git a/webpack/stripes-config-plugin.js b/webpack/stripes-config-plugin.js index d9fdffa..a9f3672 100644 --- a/webpack/stripes-config-plugin.js +++ b/webpack/stripes-config-plugin.js @@ -51,7 +51,6 @@ module.exports = class StripesConfigPlugin { const metadata = {}; const icons = {}; this.mergedConfig = config; - // Object.assign({}, this.options); this.metadata = metadata; this.icons = icons; this.warnings = warnings; @@ -84,13 +83,11 @@ module.exports = class StripesConfigPlugin { const branding = ${stripesSerialize.serializeWithRequire(pluginData.branding)}; const errorLogging = ${stripesSerialize.serializeWithRequire(pluginData.errorLogging)}; const translations = ${serialize(pluginData.translations, { space: 2 })}; - // const metadata = ${stripesSerialize.serializeWithRequire(this.metadata)}; - // const icons = ${stripesSerialize.serializeWithRequire(this.icons)}; + const metadata = ${stripesSerialize.serializeWithRequire(this.metadata)}; + const icons = ${stripesSerialize.serializeWithRequire(this.icons)}; export { okapi, config, modules, branding, errorLogging, translations, metadata, icons }; `; - console.log(stripesVirtualModule); - logger.log('writing virtual module...', stripesVirtualModule); this.virtualModule.writeModule('node_modules/stripes-config.js', stripesVirtualModule); } From 467f39049a9203f4a759a8652b9f281ff629b388 Mon Sep 17 00:00:00 2001 From: Michal Kuklis Date: Tue, 25 Apr 2023 10:20:33 -0400 Subject: [PATCH 03/38] Cleanup --- consts.js | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/consts.js b/consts.js index 041f7a9..6a81051 100644 --- a/consts.js +++ b/consts.js @@ -1,17 +1,14 @@ -// TODO: should these come from https://github.com/folio-org/stripes-core/blob/1d5d4f00a3756702e828856d4ef9349ceb9f1c08/package.json#L116-L129 -const singletons = { - '@folio/stripes': '^9.0.0', - '@folio/stripes-shared-context': '^1.0.0', - 'react': '^18.2', - 'react-dom': '^18.2', - 'react-intl': '^6.4.4', - 'react-query': '^3.39.3', - 'react-redux': '^8.0.5', - 'react-router': '^5.2.0', - 'react-router-dom': '^5.2.0', - 'redux-observable': '^1.2.0', - 'rxjs': '^6.6.3' -}; +const singletons = [ + '@folio/stripes', + '@folio/stripes-shared-context', + 'react', + 'react-dom', + 'react-router', + 'react-router-dom', + 'react-redux', + 'react-intl', + 'react-query', +]; module.exports = { singletons, From 7c3576afe2d21413f22ec3de0dce34cc1a11a033 Mon Sep 17 00:00:00 2001 From: Michal Kuklis Date: Tue, 25 Apr 2023 12:38:29 -0400 Subject: [PATCH 04/38] cleanup --- webpack.config.federate.remote.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/webpack.config.federate.remote.js b/webpack.config.federate.remote.js index 3fc1af2..e82338a 100644 --- a/webpack.config.federate.remote.js +++ b/webpack.config.federate.remote.js @@ -93,7 +93,8 @@ const buildConfig = (metadata) => { } ] }, - externals: processExternals(['@folio/stripes', 'stripes-config']), + // TODO: remove this after stripes-config is gone. + externals: processExternals({ 'stripes-config': true }), plugins: [ new MiniCssExtractPlugin({ filename: 'style.css', ignoreOrder: false }), new container.ModuleFederationPlugin({ From 2deed9e6e5843e031a67901acc02bb5e8ab6dece Mon Sep 17 00:00:00 2001 From: Michal Kuklis Date: Tue, 25 Apr 2023 16:01:46 -0400 Subject: [PATCH 05/38] Use shutdown hook --- webpack/federate.js | 9 ++++++--- webpack/registryServer.js | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/webpack/federate.js b/webpack/federate.js index 1d6702e..d9637ab 100644 --- a/webpack/federate.js +++ b/webpack/federate.js @@ -57,8 +57,11 @@ module.exports = async function federate(options = {}) { console.log(`Starting remote server on port ${port}`); server.start(); - process.on('SIGINT', async () => { - await axios.delete(registryUrl, { data: metadata }); - process.exit(0); + compiler.hooks.shutdown.tapPromise('AsyncShutdownHook', async (stats) => { + try { + await axios.delete(registryUrl, { data: metadata }); + } catch (error) { + console.error('AsyncShutdownHook error:', error); + } }); }; diff --git a/webpack/registryServer.js b/webpack/registryServer.js index 6f17a04..edf2e69 100644 --- a/webpack/registryServer.js +++ b/webpack/registryServer.js @@ -26,6 +26,7 @@ const registryServer = { app.delete('/registry', (req, res) => { const metadata = req.body; const { name } = metadata; + delete registry.remotes[name]; res.status(200).send(`Remote ${name} removed`); From 49189bd718a3651a448b26e96b1c394ab38e624c Mon Sep 17 00:00:00 2001 From: Michal Kuklis Date: Tue, 25 Apr 2023 21:16:42 -0400 Subject: [PATCH 06/38] Cleanup --- webpack/stripes-config-plugin.js | 35 ++++---------------------- webpack/stripes-translations-plugin.js | 6 ----- 2 files changed, 5 insertions(+), 36 deletions(-) diff --git a/webpack/stripes-config-plugin.js b/webpack/stripes-config-plugin.js index a9f3672..9cdc0fa 100644 --- a/webpack/stripes-config-plugin.js +++ b/webpack/stripes-config-plugin.js @@ -10,7 +10,6 @@ const _ = require('lodash'); const VirtualModulesPlugin = require('webpack-virtual-modules'); const serialize = require('serialize-javascript'); const { SyncHook } = require('tapable'); -const stripesModuleParser = require('./stripes-module-parser'); const StripesBuildError = require('./stripes-build-error'); const stripesSerialize = require('./stripes-serialize'); const logger = require('./logger')('stripesConfigPlugin'); @@ -20,9 +19,7 @@ const stripesConfigPluginHooksMap = new WeakMap(); module.exports = class StripesConfigPlugin { constructor(options) { logger.log('initializing...'); - if (!_.isObject(options.modules)) { - throw new StripesBuildError('stripes-config-plugin was not provided a "modules" object for enabling stripes modules'); - } + this.options = _.omit(options, 'branding', 'errorLogging'); } @@ -42,36 +39,24 @@ module.exports = class StripesConfigPlugin { } apply(compiler) { - const enabledModules = this.options.modules; - logger.log('enabled modules:', enabledModules); - //const { config, metadata, icons, stripesDeps, warnings } = stripesModuleParser.parseAllModules(enabledModules, compiler.context, compiler.options.resolve.alias); - const stripesDeps = {}; const config = this.options; - const warnings = {}; - const metadata = {}; - const icons = {}; - this.mergedConfig = config; - this.metadata = metadata; - this.icons = icons; - this.warnings = warnings; + this.config = config; // Prep the virtual module now, we will write to it when ready this.virtualModule = new VirtualModulesPlugin(); this.virtualModule.apply(compiler); StripesConfigPlugin.getPluginHooks(compiler).beforeWrite.tap( { name: 'StripesConfigPlugin', context: true }, - context => Object.assign(context, { config, metadata, icons, stripesDeps, warnings })); + context => Object.assign(context, { config })); // Wait until after other plugins to generate virtual stripes-config compiler.hooks.afterPlugins.tap('StripesConfigPlugin', (theCompiler) => this.afterPlugins(theCompiler)); - compiler.hooks.emit.tapAsync('StripesConfigPlugin', (compilation, callback) => this.processWarnings(compilation, callback)); } afterPlugins(compiler) { // Data provided by other stripes plugins via hooks const pluginData = { branding: {}, - errorLogging: {}, translations: {}, }; @@ -79,23 +64,13 @@ module.exports = class StripesConfigPlugin { // Create a virtual module for Webpack to include in the build const stripesVirtualModule = ` - const { okapi, config, modules } = ${serialize(this.mergedConfig, { space: 2 })}; + const { okapi, config } = ${serialize(this.config, { space: 2 })}; const branding = ${stripesSerialize.serializeWithRequire(pluginData.branding)}; - const errorLogging = ${stripesSerialize.serializeWithRequire(pluginData.errorLogging)}; const translations = ${serialize(pluginData.translations, { space: 2 })}; - const metadata = ${stripesSerialize.serializeWithRequire(this.metadata)}; - const icons = ${stripesSerialize.serializeWithRequire(this.icons)}; - export { okapi, config, modules, branding, errorLogging, translations, metadata, icons }; + export { okapi, config, branding, translations }; `; logger.log('writing virtual module...', stripesVirtualModule); this.virtualModule.writeModule('node_modules/stripes-config.js', stripesVirtualModule); } - - processWarnings(compilation, callback) { - if (this.warnings.length) { - compilation.warnings.push(new StripesBuildError(`stripes-config-plugin:\n ${this.warnings.join('\n ')}`)); - } - callback(); - } }; diff --git a/webpack/stripes-translations-plugin.js b/webpack/stripes-translations-plugin.js index c2dcbb2..97fd973 100644 --- a/webpack/stripes-translations-plugin.js +++ b/webpack/stripes-translations-plugin.js @@ -47,12 +47,6 @@ module.exports = class StripesTranslationPlugin { // Hook into stripesConfigPlugin to supply paths to translation files // and gather additional modules from stripes.stripesDeps StripesConfigPlugin.getPluginHooks(compiler).beforeWrite.tap({ name: 'StripesTranslationsPlugin', context: true }, (context, config) => { - // Add stripesDeps - for (const [key, value] of Object.entries(context.stripesDeps)) { - // TODO: merge translations from all versions of stripesDeps - this.modules[key] = value[value.length - 1]; - } - // Gather all translations available in each module const allTranslations = this.gatherAllTranslations(); From 12cc324f4357dca6173a2ba243344570e56b7959 Mon Sep 17 00:00:00 2001 From: Michal Kuklis Date: Tue, 25 Apr 2023 22:20:48 -0400 Subject: [PATCH 07/38] Cleanup --- webpack/federate.js | 2 +- webpack/stripes-module-parser.js | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/webpack/federate.js b/webpack/federate.js index d9637ab..9aa62a2 100644 --- a/webpack/federate.js +++ b/webpack/federate.js @@ -61,7 +61,7 @@ module.exports = async function federate(options = {}) { try { await axios.delete(registryUrl, { data: metadata }); } catch (error) { - console.error('AsyncShutdownHook error:', error); + console.error(`registry not found. Please check ${registryUrl}`); } }); }; diff --git a/webpack/stripes-module-parser.js b/webpack/stripes-module-parser.js index a7e482f..a5fd649 100644 --- a/webpack/stripes-module-parser.js +++ b/webpack/stripes-module-parser.js @@ -109,8 +109,7 @@ class StripesModuleParser { // Validates and parses a module's stripes data parseStripesConfig(moduleName, packageJson) { const { stripes, description, version } = packageJson; - //const getModule = new Function([], `return require('${moduleName}').default;`); - const getModule = new Function([], ``); + const getModule = new Function([], `return require('${moduleName}').default;`); const stripesConfig = _.omit(Object.assign({}, stripes, this.overrideConfig, { module: moduleName, getModule, @@ -208,13 +207,11 @@ function parseAllModules(enabledModules, context, aliases) { // stripesDeps const config = parsedModule.config; - if (Array.isArray(config.stripesDeps)) { config.stripesDeps.forEach(dep => { // locate dep relative to the module that depends on it const depContext = modulePaths.locateStripesModule(context, config.module, aliases, 'package.json'); const packageJsonPath = modulePaths.locateStripesModule(depContext, dep, aliases, 'package.json'); - if (!packageJsonPath) { throw new StripesBuildError(`StripesModuleParser: Unable to locate ${dep}'s package.json (dependency of ${config.module})`); } @@ -259,7 +256,6 @@ function parseAllModules(enabledModules, context, aliases) { } return depIcons; }, {}); - for (const [key, value] of Object.entries(stripesDeps)) { if (anyHasIcon(value)) { icons[key] = mergeIcons(value); From 60b5daed25f00132527f49f12b87210f76b1da3d Mon Sep 17 00:00:00 2001 From: Michal Kuklis Date: Tue, 25 Apr 2023 22:22:08 -0400 Subject: [PATCH 08/38] Cleanup --- webpack/stripes-module-parser.js | 1 - 1 file changed, 1 deletion(-) diff --git a/webpack/stripes-module-parser.js b/webpack/stripes-module-parser.js index a5fd649..93b06f8 100644 --- a/webpack/stripes-module-parser.js +++ b/webpack/stripes-module-parser.js @@ -215,7 +215,6 @@ function parseAllModules(enabledModules, context, aliases) { if (!packageJsonPath) { throw new StripesBuildError(`StripesModuleParser: Unable to locate ${dep}'s package.json (dependency of ${config.module})`); } - const packageJson = require(packageJsonPath); const resolvedPath = packageJsonPath.replace('/package.json', ''); unsortedStripesDeps[dep] = appendOrSingleton(unsortedStripesDeps[dep], { From 97ba1557e4597b3c355823315397426b31fdbb93 Mon Sep 17 00:00:00 2001 From: Michal Kuklis Date: Thu, 27 Apr 2023 14:43:49 -0400 Subject: [PATCH 09/38] Add required version to shared singletons --- consts.js | 24 +++++++++++++----------- webpack/utils.js | 8 ++++++-- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/consts.js b/consts.js index 6a81051..d4bef6a 100644 --- a/consts.js +++ b/consts.js @@ -1,14 +1,16 @@ -const singletons = [ - '@folio/stripes', - '@folio/stripes-shared-context', - 'react', - 'react-dom', - 'react-router', - 'react-router-dom', - 'react-redux', - 'react-intl', - 'react-query', -]; +// TODO: should these come from https://github.com/folio-org/stripes-core/blob/1d5d4f00a3756702e828856d4ef9349ceb9f1c08/package.json#L116-L129 +const singletons = { + '@folio/stripes': '^8.1.0', + '@folio/stripes-shared-context': '^1.0.0', + 'react': '^17.0.2', + 'react-dom': '^17.0.2', + 'react-intl': '^5.7.0', + 'react-redux': '^8.0.5', + 'react-router': '^5.2.0', + 'react-router-dom': '^5.2.0', + 'redux-observable': '^1.2.0', + 'rxjs': '^6.6.3' +}; module.exports = { singletons, diff --git a/webpack/utils.js b/webpack/utils.js index b274568..d106d65 100644 --- a/webpack/utils.js +++ b/webpack/utils.js @@ -15,8 +15,12 @@ const processExternals = (peerDeps) => { }; const processShared = (shared, options = {}) => { - return shared.reduce((acc, name) => { - acc[name] = options; + return Object.keys(shared).reduce((acc, name) => { + acc[name] = { + requiredVersion: shared[name], + ...options + }; + return acc; }, {}); }; From 8c47f565dadb3caec59511c52010854b34789e6c Mon Sep 17 00:00:00 2001 From: Michal Kuklis Date: Mon, 1 May 2023 13:39:22 -0400 Subject: [PATCH 10/38] Start remotes automatically --- webpack.config.base.js | 2 +- webpack.config.cli.dev.js | 4 ++- webpack/federate.js | 3 +-- webpack/stripes-federation-plugin.js | 38 ++++++++++++++++++++++++++++ 4 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 webpack/stripes-federation-plugin.js diff --git a/webpack.config.base.js b/webpack.config.base.js index 3889973..14d76f3 100644 --- a/webpack.config.base.js +++ b/webpack.config.base.js @@ -7,7 +7,7 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const RemoveEmptyScriptsPlugin = require('webpack-remove-empty-scripts'); const { ModuleFederationPlugin } = require('webpack').container; -const { generateStripesAlias, locatePackageJsonPath } = require('./webpack/module-paths'); +const { generateStripesAlias, } = require('./webpack/module-paths'); const { processShared } = require('./webpack/utils'); const typescriptLoaderRule = require('./webpack/typescript-loader-rule'); const { isProduction } = require('./webpack/utils'); diff --git a/webpack.config.cli.dev.js b/webpack.config.cli.dev.js index 912ddb6..fe4259b 100644 --- a/webpack.config.cli.dev.js +++ b/webpack.config.cli.dev.js @@ -9,6 +9,7 @@ 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 StripesFederationPlugin = require('./webpack/stripes-federation-plugin'); const useBrowserMocha = () => { return tryResolve('mocha/mocha-es2018.js') ? 'mocha/mocha-es2018.js' : 'mocha'; @@ -55,7 +56,8 @@ const buildConfig = (stripesConfig) => { if (utils.isDevelopment) { devConfig.plugins = devConfig.plugins.concat([ new webpack.HotModuleReplacementPlugin(), - new ReactRefreshWebpackPlugin() + new ReactRefreshWebpackPlugin(), + new StripesFederationPlugin(stripesConfig) ]); } diff --git a/webpack/federate.js b/webpack/federate.js index 9aa62a2..f72bef1 100644 --- a/webpack/federate.js +++ b/webpack/federate.js @@ -5,7 +5,6 @@ const axios = require('axios'); const { snakeCase } = require('lodash'); const portfinder = require('portfinder'); -const applyWebpackOverrides = require('./apply-webpack-overrides'); const buildConfig = require('../webpack.config.federate.remote'); const { tryResolve } = require('./module-paths'); const logger = require('./logger')(); @@ -23,7 +22,7 @@ module.exports = async function federate(options = {}) { process.exit(); } - const port = await portfinder.getPortPromise(); + const port = options.port ?? await portfinder.getPortPromise(); const host = `http://localhost`; const url = `${host}:${port}/remoteEntry.js`; diff --git a/webpack/stripes-federation-plugin.js b/webpack/stripes-federation-plugin.js new file mode 100644 index 0000000..50fcf21 --- /dev/null +++ b/webpack/stripes-federation-plugin.js @@ -0,0 +1,38 @@ +// This webpack plugin wraps all other stripes webpack plugins to simplify inclusion within the webpack config +const spawn = require('child_process').spawn; +const path = require('path'); +const portfinder = require('portfinder'); + +const { locateStripesModule } = require('./module-paths'); + +portfinder.setBasePort(3002); + +module.exports = class StripesFederationPlugin { + constructor(stripesConfig) { + this.stripesConfig = stripesConfig; + } + + async startRemotes(modules) { + const ctx = process.cwd(); + + for (const moduleName in modules) { + const packageJsonPath = locateStripesModule(ctx, moduleName, {}, 'package.json'); + const basePath = path.dirname(packageJsonPath); + + portfinder.getPort((err, port) => { + const child = spawn(`yarn stripes federate --port ${port}`, { + cwd: basePath, + shell: true, + }); + + child.stdout.pipe(process.stdout); + }); + } + } + + apply() { + const { modules } = this.stripesConfig; + + this.startRemotes(modules); + } +}; From 84c01996da5c08e501f73bccfdad44002f5f940a Mon Sep 17 00:00:00 2001 From: Michal Kuklis Date: Tue, 9 May 2023 20:13:19 -0400 Subject: [PATCH 11/38] Expose icons via public endpoint --- webpack.config.federate.remote.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/webpack.config.federate.remote.js b/webpack.config.federate.remote.js index e82338a..f10a799 100644 --- a/webpack.config.federate.remote.js +++ b/webpack.config.federate.remote.js @@ -12,6 +12,7 @@ const buildConfig = (metadata) => { const mainEntry = path.join(process.cwd(), 'src', 'index.js'); const stripesModulePaths = getStripesModulesPaths(); const translationsPath = path.join(process.cwd(), 'translations', displayName.split('.').shift()); + const iconsPath = path.join(process.cwd(), 'icons'); const shared = processShared(singletons, { singleton: true }); const config = { @@ -28,10 +29,16 @@ const buildConfig = (metadata) => { headers: { 'Access-Control-Allow-Origin': '*', }, - static: { - directory: translationsPath, - publicPath: '/translations' - } + static: [ + { + directory: translationsPath, + publicPath: '/translations' + }, + { + directory: iconsPath, + publicPath: '/icons' + }, + ] }, module: { rules: [ From 54db9c937c00bbb66c5a31c831fb2385f588be97 Mon Sep 17 00:00:00 2001 From: Zak Burke Date: Tue, 16 May 2023 19:34:36 -0400 Subject: [PATCH 12/38] react-query provides a context, so must be a singleton --- consts.js | 1 + 1 file changed, 1 insertion(+) diff --git a/consts.js b/consts.js index d4bef6a..8566160 100644 --- a/consts.js +++ b/consts.js @@ -5,6 +5,7 @@ const singletons = { 'react': '^17.0.2', 'react-dom': '^17.0.2', 'react-intl': '^5.7.0', + 'react-query': '^3.39.3', 'react-redux': '^8.0.5', 'react-router': '^5.2.0', 'react-router-dom': '^5.2.0', From ecaa7a63c6b2ee263bf0ca748ffa02adb981679d Mon Sep 17 00:00:00 2001 From: Zak Burke Date: Thu, 8 Jun 2023 16:50:18 -0400 Subject: [PATCH 13/38] STCOR-726 map sounds directory for remote applications * map the `sounds` directory for remote applications, analogous to how translations and icons are served * provide `/code` to make the registry human-readable * catch and display startup errors in case humans make stupid coding mistakes and need help finding them --- webpack.config.federate.remote.js | 8 ++++++++ webpack/registryServer.js | 5 ++++- webpack/serve.js | 7 ++++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/webpack.config.federate.remote.js b/webpack.config.federate.remote.js index f10a799..afb8932 100644 --- a/webpack.config.federate.remote.js +++ b/webpack.config.federate.remote.js @@ -13,6 +13,10 @@ const buildConfig = (metadata) => { const stripesModulePaths = getStripesModulesPaths(); const translationsPath = path.join(process.cwd(), 'translations', displayName.split('.').shift()); const iconsPath = path.join(process.cwd(), 'icons'); + + // yeah, yeah, soundsPath vs sound. sorry. `sound` is a legacy name. + // other paths are plural and I'm sticking with that convention. + const soundsPath = path.join(process.cwd(), 'sound'); const shared = processShared(singletons, { singleton: true }); const config = { @@ -38,6 +42,10 @@ const buildConfig = (metadata) => { directory: iconsPath, publicPath: '/icons' }, + { + directory: soundsPath, + publicPath: '/sounds' + }, ] }, module: { diff --git a/webpack/registryServer.js b/webpack/registryServer.js index edf2e69..b4e3fb4 100644 --- a/webpack/registryServer.js +++ b/webpack/registryServer.js @@ -20,9 +20,12 @@ const registryServer = { res.status(200).send(`Remote ${name} metadata updated`); }); - // return entire registry + // return entire registry for machines app.get('/registry', (_, res) => res.json(registry)); + // return entire registry for humans + app.get('/code', (_, res) => res.send(`
${JSON.stringify(registry, null, 2)}
`)); + app.delete('/registry', (req, res) => { const metadata = req.body; const { name } = metadata; diff --git a/webpack/serve.js b/webpack/serve.js index 29c0291..2afddf2 100644 --- a/webpack/serve.js +++ b/webpack/serve.js @@ -34,7 +34,12 @@ module.exports = function serve(stripesConfig, options) { serviceWorkerConfig.resolveLoader = { modules: ['node_modules', platformModulePath, coreModulePath] }; // stripes module registry - registryServer.start(); + try { + registryServer.start(); + } + catch (e) { + console.error(e) + } let config = buildConfig(stripesConfig); From 42cac160705fc322434105e233918953f755f396 Mon Sep 17 00:00:00 2001 From: Zak Burke Date: Wed, 4 Dec 2024 14:54:20 -0500 Subject: [PATCH 14/38] current versions --- consts.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/consts.js b/consts.js index 8566160..cd76b30 100644 --- a/consts.js +++ b/consts.js @@ -1,12 +1,12 @@ // TODO: should these come from https://github.com/folio-org/stripes-core/blob/1d5d4f00a3756702e828856d4ef9349ceb9f1c08/package.json#L116-L129 const singletons = { - '@folio/stripes': '^8.1.0', + '@folio/stripes': '^9.3.0', '@folio/stripes-shared-context': '^1.0.0', - 'react': '^17.0.2', - 'react-dom': '^17.0.2', - 'react-intl': '^5.7.0', + 'react': '~18.2', + 'react-dom': '~18.2', + 'react-intl': '^6.8.0', 'react-query': '^3.39.3', - 'react-redux': '^8.0.5', + 'react-redux': '^8.1', 'react-router': '^5.2.0', 'react-router-dom': '^5.2.0', 'redux-observable': '^1.2.0', From 12613edc917b79e2acabd69a564df1ceaf65f666 Mon Sep 17 00:00:00 2001 From: Zak Burke Date: Wed, 4 Dec 2024 14:54:26 -0500 Subject: [PATCH 15/38] separate handling of stripes-components and application icons Icons in stripes-components are imported as components whereas those in applications are just resources, so we need to load them differently. --- webpack.config.federate.remote.js | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/webpack.config.federate.remote.js b/webpack.config.federate.remote.js index afb8932..5ccab6a 100644 --- a/webpack.config.federate.remote.js +++ b/webpack.config.federate.remote.js @@ -92,14 +92,24 @@ const buildConfig = (metadata) => { filename: './img/[name].[contenthash].[ext]', }, }, + // { + // test: /\.svg$/, + // use: [{ + // loader: 'url-loader', + // options: { + // esModule: false, + // }, + // }] + // }, { test: /\.svg$/, - use: [{ - loader: 'url-loader', - options: { - esModule: false, - }, - }] + type: 'asset/inline', + resourceQuery: { not: /icon/ } // exclude built-in icons from stripes-components which are loaded as react components. + }, + { + test: /\.svg$/, + resourceQuery: /icon/, // stcom icons use this query on the resource. + use: ['@svgr/webpack'] }, { test: /\.js.map$/, From 626a7c86815bc1e83845c21c01a08736038a0f13 Mon Sep 17 00:00:00 2001 From: John Coburn Date: Wed, 5 Nov 2025 14:41:30 -0600 Subject: [PATCH 16/38] provide registry url in stripes-config --- consts.js | 9 ++++++--- webpack/serve.js | 9 +++++++++ webpack/stripes-config-plugin.js | 22 ++++++++++++++++++---- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/consts.js b/consts.js index cd76b30..2b72fb7 100644 --- a/consts.js +++ b/consts.js @@ -2,9 +2,9 @@ const singletons = { '@folio/stripes': '^9.3.0', '@folio/stripes-shared-context': '^1.0.0', - 'react': '~18.2', - 'react-dom': '~18.2', - 'react-intl': '^6.8.0', + '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', @@ -13,6 +13,9 @@ const singletons = { 'rxjs': '^6.6.3' }; +const defaultRegistryUrl = 'http://localhost:3001/registry'; + module.exports = { + defaultRegistryUrl, singletons, }; diff --git a/webpack/serve.js b/webpack/serve.js index 87ec589..2d64af8 100644 --- a/webpack/serve.js +++ b/webpack/serve.js @@ -25,6 +25,15 @@ module.exports = function serve(stripesConfig, options) { logger.log('starting serve...'); const app = express(); + + // stripes module registry + try { + registryServer.start(); + } + catch (e) { + console.error(e) + } + let config = buildConfig(stripesConfig); config = sharedStylesConfig(config, {}); diff --git a/webpack/stripes-config-plugin.js b/webpack/stripes-config-plugin.js index d1ef5a0..1bc9f94 100644 --- a/webpack/stripes-config-plugin.js +++ b/webpack/stripes-config-plugin.js @@ -10,8 +10,10 @@ const _ = require('lodash'); const VirtualModulesPlugin = require('webpack-virtual-modules'); const serialize = require('serialize-javascript'); const { SyncHook } = require('tapable'); +const stripesModuleParser = require('./stripes-module-parser'); const StripesBuildError = require('./stripes-build-error'); const stripesSerialize = require('./stripes-serialize'); +const { defaultRegistryUrl } = require('../consts'); const logger = require('./logger')('stripesConfigPlugin'); const stripesConfigPluginHooksMap = new WeakMap(); @@ -20,7 +22,9 @@ module.exports = class StripesConfigPlugin { // options is actually stripes.config.js constructor(options, lazy) { logger.log('initializing...'); - + if (!_.isObject(options.modules)) { + throw new StripesBuildError('stripes-config-plugin was not provided a "modules" object for enabling stripes modules'); + } this.options = _.omit(options, 'branding', 'errorLogging'); this.lazy = lazy; } @@ -44,7 +48,14 @@ module.exports = class StripesConfigPlugin { const enabledModules = this.options.modules; logger.log('enabled modules:', enabledModules); const { config, metadata, icons, stripesDeps, warnings } = stripesModuleParser.parseAllModules(enabledModules, compiler.context, compiler.options.resolve.alias, this.lazy); - this.mergedConfig = Object.assign({}, this.options, { modules: config }); + const modulesInitialState = { + app: [], + handler: [], + plugin: [], + settings: [], + }; + this.mergedOkapi = Object.assign({ registryUrl: defaultRegistryUrl }, this.options.okapi); + this.mergedConfig = Object.assign({ modules: modulesInitialState }, this.options, { modules: config, okapi: this.mergedOkapi }); this.metadata = metadata; this.icons = icons; this.warnings = warnings; @@ -71,10 +82,13 @@ module.exports = class StripesConfigPlugin { // Create a virtual module for Webpack to include in the build const stripesVirtualModule = ` - const { okapi, config } = ${serialize(this.config, { space: 2 })}; + const { okapi, config, modules } = ${serialize(this.mergedConfig, { space: 2 })}; + const errorLogging = ${stripesSerialize.serializeWithRequire(pluginData.errorLogging)}; const branding = ${stripesSerialize.serializeWithRequire(pluginData.branding)}; const translations = ${serialize(pluginData.translations, { space: 2 })}; - export { okapi, config, branding, translations }; + const metadata = ${stripesSerialize.serializeWithRequire(this.metadata)}; + const icons = ${stripesSerialize.serializeWithRequire(this.icons)}; + export { branding, config, errorLogging, icons, metadata, modules, okapi, translations }; `; logger.log('writing virtual module...', stripesVirtualModule); From 08068edc1c9b922183b8544de410c4f88ee17079 Mon Sep 17 00:00:00 2001 From: John Coburn Date: Tue, 18 Nov 2025 13:59:25 -0600 Subject: [PATCH 17/38] collect translations from 'StripesDeps' modules in built translations --- webpack.config.federate.remote.js | 9 ++- webpack/stripes-translations-plugin.js | 83 +++++++++++++++++++------- 2 files changed, 70 insertions(+), 22 deletions(-) diff --git a/webpack.config.federate.remote.js b/webpack.config.federate.remote.js index 5ccab6a..db97085 100644 --- a/webpack.config.federate.remote.js +++ b/webpack.config.federate.remote.js @@ -1,6 +1,7 @@ const path = require('path'); const webpack = require('webpack'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const StripesTranslationsPlugin = require('./webpack/stripes-translations-plugin'); const { container } = webpack; const { processExternals, processShared } = require('./webpack/utils'); const { getStripesModulesPaths } = require('./webpack/module-paths'); @@ -8,8 +9,10 @@ const esbuildLoaderRule = require('./webpack/esbuild-loader-rule'); const { singletons } = require('./consts'); const buildConfig = (metadata) => { - const { host, port, name, displayName } = metadata; - const mainEntry = path.join(process.cwd(), 'src', 'index.js'); + const { host, port, name, displayName, main } = metadata; + + // using main from metadata since the location of main could vary between modules. + let mainEntry = path.join(process.cwd(), main || 'index.js'); const stripesModulePaths = getStripesModulesPaths(); const translationsPath = path.join(process.cwd(), 'translations', displayName.split('.').shift()); const iconsPath = path.join(process.cwd(), 'icons'); @@ -17,6 +20,7 @@ const buildConfig = (metadata) => { // yeah, yeah, soundsPath vs sound. sorry. `sound` is a legacy name. // other paths are plural and I'm sticking with that convention. const soundsPath = path.join(process.cwd(), 'sound'); + const shared = processShared(singletons, { singleton: true }); const config = { @@ -121,6 +125,7 @@ const buildConfig = (metadata) => { // TODO: remove this after stripes-config is gone. externals: processExternals({ 'stripes-config': true }), plugins: [ + new StripesTranslationsPlugin({ federate: true }), new MiniCssExtractPlugin({ filename: 'style.css', ignoreOrder: false }), new container.ModuleFederationPlugin({ library: { type: 'var', name }, diff --git a/webpack/stripes-translations-plugin.js b/webpack/stripes-translations-plugin.js index 97fd973..2d04d21 100644 --- a/webpack/stripes-translations-plugin.js +++ b/webpack/stripes-translations-plugin.js @@ -16,23 +16,30 @@ function prefixKeys(obj, prefix) { module.exports = class StripesTranslationPlugin { constructor(options) { + // in module federation mode, we emit translations for the module being built and + // for any stripesDeps it has. + this.federate = options?.federate || false; + // Include stripes-core et al because they have translations - this.modules = { + // translations should come from the host application for stripes + // rather than being overwritten by consuming apps. + this.modules = this.federate ? {} : { '@folio/stripes-core': {}, '@folio/stripes-components': {}, '@folio/stripes-smart-components': {}, '@folio/stripes-form': {}, '@folio/stripes-ui': {}, }; - Object.assign(this.modules, options.modules); - this.languageFilter = options.config.languages || []; + + Object.assign(this.modules, options?.modules); + this.languageFilter = options?.config?.languages || []; logger.log('language filter', this.languageFilter); } apply(compiler) { // Used to help locate modules this.context = compiler.context; - // 'publicPath' is not present when running tests via karma-webpack + // 'publicPath' is not present when running tests via karma-webpack // so when running in test mode use absolute 'path'. this.publicPath = process.env.NODE_ENV !== 'test' ? compiler.options.output.publicPath : `./absolute${compiler.options.output.path}`; this.aliases = compiler.options.resolve.alias; @@ -44,21 +51,30 @@ module.exports = class StripesTranslationPlugin { new webpack.ContextReplacementPlugin(/moment[/\\]locale/, filterRegex).apply(compiler); } - // Hook into stripesConfigPlugin to supply paths to translation files - // and gather additional modules from stripes.stripesDeps - StripesConfigPlugin.getPluginHooks(compiler).beforeWrite.tap({ name: 'StripesTranslationsPlugin', context: true }, (context, config) => { - // Gather all translations available in each module - const allTranslations = this.gatherAllTranslations(); + if (this.federate) { + const packageJsonPath = path.join(this.context, 'package.json'); + const packageJson = StripesTranslationPlugin.loadFile(packageJsonPath); + + this.modules[packageJson.name] = {}; + if (packageJson) { + const stripesDeps = packageJson?.stripes?.stripesDeps; + if (stripesDeps) { + stripesDeps.forEach((dep) => { + // TODO: merge translations from all versions of stripesDeps + this.modules[dep] = {}; + }); + } + } - const fileData = this.generateFileNames(allTranslations); - const allFiles = _.mapValues(fileData, data => data.browserPath); + compiler.hooks.thisCompilation.tap('StripesTranslationsPlugin', (compilation) => { + compilation.hooks.processAssets.tap({ + name: 'StripesTranslationsPlugin', + stage: compilation.PROCESS_ASSETS_STAGE_PRE_PROCESS + }, () => { - config.translations = allFiles; - logger.log('stripesConfigPluginBeforeWrite', config.translations); + const allTranslations = this.gatherAllTranslations(); + const fileData = this.generateFileNames(allTranslations, false); - compiler.hooks.thisCompilation.tap('StripesTranslationsPlugin', (compilation) => { - // Emit merged translations to the output directory - compilation.hooks.processAssets.tap('StripesTranslationsPlugin', () => { Object.keys(allTranslations).forEach((language) => { logger.log(`emitting translations for ${language} --> ${fileData[language].emitPath}`); const content = JSON.stringify(allTranslations[language]); @@ -69,7 +85,34 @@ module.exports = class StripesTranslationPlugin { }); }); }); - }); + } else { + // Hook into stripesConfigPlugin to supply paths to translation files + // and gather additional modules from stripes.stripesDeps + StripesConfigPlugin.getPluginHooks(compiler).beforeWrite.tap({ name: 'StripesTranslationsPlugin', context: true }, (context, config) => { + // Gather all translations available in each module + const allTranslations = this.gatherAllTranslations(); + + const fileData = this.generateFileNames(allTranslations); + const allFiles = _.mapValues(fileData, data => data.browserPath); + + config.translations = allFiles; + logger.log('stripesConfigPluginBeforeWrite', config.translations); + + compiler.hooks.thisCompilation.tap('StripesTranslationsPlugin', (compilation) => { + // Emit merged translations to the output directory + compilation.hooks.processAssets.tap('StripesTranslationsPlugin', () => { + Object.keys(allTranslations).forEach((language) => { + logger.log(`emitting translations for ${language} --> ${fileData[language].emitPath}`); + const content = JSON.stringify(allTranslations[language]); + compilation.assets[fileData[language].emitPath] = { + source: () => content, + size: () => content.length, + }; + }); + }); + }); + }); + } } // Locate each module's translations directory (current) or package.json data (fallback) @@ -170,14 +213,14 @@ module.exports = class StripesTranslationPlugin { } // Assign output path names for each to be accessed later by stripes-config-plugin - generateFileNames(allTranslations) { + generateFileNames(allTranslations, useSuffix = true) { const files = {}; - const timestamp = Date.now(); // To facilitate cache busting, could also generate a hash + const timestamp = useSuffix ? Date.now() : ''; // To facilitate cache busting, could also generate a hash Object.keys(allTranslations).forEach((language) => { files[language] = { // Fetching from the browser must take into account public path. The replace regex removes double slashes browserPath: `${this.publicPath}/translations/${language}-${timestamp}.json`.replace(/\/\//, '/'), - emitPath: `translations/${language}-${timestamp}.json`, + emitPath: `translations/${language}${timestamp ? `-${timestamp}` : ''}.json`, }; }); return files; From 8889e08874276557edefb7127149c246e1bc74cc Mon Sep 17 00:00:00 2001 From: John Coburn Date: Tue, 18 Nov 2025 14:08:02 -0600 Subject: [PATCH 18/38] get the app's main entry from package.json --- webpack/federate.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/webpack/federate.js b/webpack/federate.js index f72bef1..7380f8f 100644 --- a/webpack/federate.js +++ b/webpack/federate.js @@ -26,7 +26,7 @@ module.exports = async function federate(options = {}) { const host = `http://localhost`; const url = `${host}:${port}/remoteEntry.js`; - const { name: packageName, version, description, stripes } = require(packageJsonPath); + const { name: packageName, version, description, stripes, main } = require(packageJsonPath); const { permissionSets: _, ...stripesRest } = stripes; const name = snakeCase(packageName); const metadata = { @@ -37,6 +37,7 @@ module.exports = async function federate(options = {}) { port, url, name, + main, ...stripesRest, }; From a8b5fb0214d5e29ce426fca15ebaf80becf2a123 Mon Sep 17 00:00:00 2001 From: John Coburn Date: Tue, 18 Nov 2025 14:09:19 -0600 Subject: [PATCH 19/38] ensure there are aliases before member access --- webpack/module-paths.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webpack/module-paths.js b/webpack/module-paths.js index 9989090..de1ce03 100644 --- a/webpack/module-paths.js +++ b/webpack/module-paths.js @@ -63,7 +63,7 @@ function locateStripesModule(context, moduleName, alias, ...segments) { } // When available, try for the alias first - if (alias[moduleName]) { + if (alias && alias[moduleName]) { tryPaths.unshift({ request: path.join(alias[moduleName], ...segments), }); From fddfec2c37fd8c37081292730107d12d948fde3c Mon Sep 17 00:00:00 2001 From: John Coburn Date: Tue, 18 Nov 2025 14:10:08 -0600 Subject: [PATCH 20/38] expand the singletons for the platform --- consts.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/consts.js b/consts.js index 2b72fb7..1db7962 100644 --- a/consts.js +++ b/consts.js @@ -1,7 +1,15 @@ // TODO: should these come from https://github.com/folio-org/stripes-core/blob/1d5d4f00a3756702e828856d4ef9349ceb9f1c08/package.json#L116-L129 +// 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': '^9.3.0', + '@folio/stripes-components': '^13.1.0', + '@folio/stripes-connect': '^10.0.1', + '@folio/stripes-core': '^11.1.0', '@folio/stripes-shared-context': '^1.0.0', + "moment": "^2.29.0", 'react': '~18.3', 'react-dom': '~18.3', 'react-intl': '^7.1.14', From 915fa56b25649a7a72675d38b0be0b44fea7c236 Mon Sep 17 00:00:00 2001 From: John Coburn Date: Mon, 1 Dec 2025 08:56:37 -0600 Subject: [PATCH 21/38] migrate mf plugin/libraries to @module-federation/enhanced --- consts.js | 4 ++-- package.json | 1 + webpack.config.base.js | 8 +++---- webpack.config.cli.shared.styles.js | 5 +++-- webpack.config.cli.transpile.js | 18 ++++++++++++++-- webpack.config.federate.remote.js | 6 ++++-- webpack/esbuild-loader-rule.js | 5 ++++- webpack/federate.js | 5 +++++ webpack/transpile.js | 7 +++--- webpack/utils.js | 33 ++++++++++++++++++++--------- 10 files changed, 66 insertions(+), 26 deletions(-) diff --git a/consts.js b/consts.js index 1db7962..0be7ace 100644 --- a/consts.js +++ b/consts.js @@ -5,9 +5,9 @@ const singletons = { '@folio/stripes': '^9.3.0', - '@folio/stripes-components': '^13.1.0', '@folio/stripes-connect': '^10.0.1', - '@folio/stripes-core': '^11.1.0', + '@folio/stripes-core/index': '^11.1.0', + '@folio/stripes-components': '^13.1.0', '@folio/stripes-shared-context': '^1.0.0', "moment": "^2.29.0", 'react': '~18.3', diff --git a/package.json b/package.json index 945ca4b..e5c93b0 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "@cerner/duplicate-package-checker-webpack-plugin": "~2.1.0", "@csstools/postcss-global-data": "^3.0.0", "@csstools/postcss-relative-color-syntax": "^3.0.7", + "@module-federation/enhanced": "^0.21.6", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.4", "@svgr/webpack": "^8.1.0", "add-asset-html-webpack-plugin": "^6.0.0", diff --git a/webpack.config.base.js b/webpack.config.base.js index 14d76f3..62bc389 100644 --- a/webpack.config.base.js +++ b/webpack.config.base.js @@ -5,9 +5,9 @@ const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const RemoveEmptyScriptsPlugin = require('webpack-remove-empty-scripts'); -const { ModuleFederationPlugin } = require('webpack').container; +const { ModuleFederationPlugin } = require('@module-federation/enhanced/webpack'); -const { generateStripesAlias, } = require('./webpack/module-paths'); +const { generateStripesAlias, } = require('./webpack/module-paths'); const { processShared } = require('./webpack/utils'); const typescriptLoaderRule = require('./webpack/typescript-loader-rule'); const { isProduction } = require('./webpack/utils'); @@ -70,7 +70,7 @@ const baseConfig = { }), new webpack.EnvironmentPlugin(['NODE_ENV']), new RemoveEmptyScriptsPlugin(), - new ModuleFederationPlugin({ name: 'host', shared }), + new ModuleFederationPlugin({ name: 'host', exposes: {}, shared }), ], module: { rules: [ @@ -167,7 +167,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: { diff --git a/webpack.config.cli.shared.styles.js b/webpack.config.cli.shared.styles.js index 66c5170..0169dbc 100644 --- a/webpack.config.cli.shared.styles.js +++ b/webpack.config.cli.shared.styles.js @@ -5,9 +5,10 @@ module.exports = (config, context) => { // stripes components doesn't need these aliases since to it, the references are internal. if (context.moduleName !== '@folio/stripes-components') { // aliasing the interactionStyles.css and variables.css as resolving those can be problematic in a workspace. + config.resolve = config.resolve ? config.resolve : {}; config.resolve.alias = { - ...config.resolve.alias, - "./@folio/stripes-components/lib/sharedStyles/interactionStyles.css" : getSharedStyles("lib/sharedStyles/interactionStyles"), + ...config.resolve?.alias, + "./@folio/stripes-components/lib/sharedStyles/interactionStyles.css": getSharedStyles("lib/sharedStyles/interactionStyles"), "./@folio/stripes-components/lib/variables.css": getSharedStyles("lib/variables"), "stcom-interactionStyles": getSharedStyles("lib/sharedStyles/interactionStyles"), "stcom-variables": getSharedStyles("lib/variables"), diff --git a/webpack.config.cli.transpile.js b/webpack.config.cli.transpile.js index 969ca2c..bfa520c 100644 --- a/webpack.config.cli.transpile.js +++ b/webpack.config.cli.transpile.js @@ -4,14 +4,15 @@ const path = require('path'); const webpack = require('webpack'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const { EsbuildPlugin } = require('esbuild-loader'); +const sharedStylesConfig = require('./webpack.config.cli.shared.styles'); -const config = { +let config = { mode: 'production', devtool: 'source-map', entry: path.resolve('./index.js'), output: { library: { - type: 'umd', + type: 'module', }, path: path.resolve('./dist'), filename: 'index.js', @@ -27,6 +28,9 @@ const config = { loader: 'tsx', jsx: 'automatic', }, + resolve: { + fullySpecified: false, + } }, { test: /\.(woff2?)$/, @@ -69,6 +73,11 @@ const config = { filename: './img/[name].[contenthash].[ext]', }, }, + { + test: /\.svg$/, + type: 'asset/inline', + resourceQuery: { not: /icon/ } // exclude built-in icons from stripes-components which are loaded as react components. + }, { test: /\.js.map$/, enforce: "pre", @@ -77,6 +86,9 @@ const config = { ] }, externals: {}, + experiments: { + outputModule: true, + } }; config.optimization = { @@ -96,4 +108,6 @@ config.plugins = [ new webpack.EnvironmentPlugin(['NODE_ENV']), ]; +config = sharedStylesConfig(config, { moduleName: '@folio/stripes-core' }); + module.exports = config; diff --git a/webpack.config.federate.remote.js b/webpack.config.federate.remote.js index db97085..e15348f 100644 --- a/webpack.config.federate.remote.js +++ b/webpack.config.federate.remote.js @@ -2,7 +2,7 @@ const path = require('path'); const webpack = require('webpack'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const StripesTranslationsPlugin = require('./webpack/stripes-translations-plugin'); -const { container } = webpack; +const { ModuleFederationPlugin } = require('@module-federation/enhanced/webpack'); const { processExternals, processShared } = require('./webpack/utils'); const { getStripesModulesPaths } = require('./webpack/module-paths'); const esbuildLoaderRule = require('./webpack/esbuild-loader-rule'); @@ -27,6 +27,7 @@ const buildConfig = (metadata) => { name, devtool: 'inline-source-map', mode: 'development', + target: 'web', entry: mainEntry, output: { publicPath: `${host}:${port}/`, @@ -127,12 +128,13 @@ const buildConfig = (metadata) => { plugins: [ new StripesTranslationsPlugin({ federate: true }), new MiniCssExtractPlugin({ filename: 'style.css', ignoreOrder: false }), - new container.ModuleFederationPlugin({ + new ModuleFederationPlugin({ library: { type: 'var', name }, name, filename: 'remoteEntry.js', exposes: { './MainEntry': mainEntry, + '.': mainEntry, }, shared }), diff --git a/webpack/esbuild-loader-rule.js b/webpack/esbuild-loader-rule.js index ec3f533..7720e17 100644 --- a/webpack/esbuild-loader-rule.js +++ b/webpack/esbuild-loader-rule.js @@ -80,6 +80,9 @@ module.exports = (modulePaths) => { return { test: /\.js$/, include: shouldModuleBeIncluded, + resolve: { + fullySpecified: false, + }, oneOf: [ { // handle all bigtest files and interactor files via babel @@ -107,6 +110,6 @@ module.exports = (modulePaths) => { }, ], }, - ], + ], }; }; diff --git a/webpack/federate.js b/webpack/federate.js index 7380f8f..5aa155c 100644 --- a/webpack/federate.js +++ b/webpack/federate.js @@ -1,3 +1,6 @@ +// This file is responsible for building a federated bundle AND +// starting the dev server for the module to serve the bundle. + const path = require('path'); const webpack = require('webpack'); const WebpackDevServer = require('webpack-dev-server'); @@ -25,6 +28,7 @@ module.exports = async function federate(options = {}) { const port = options.port ?? await portfinder.getPortPromise(); const host = `http://localhost`; const url = `${host}:${port}/remoteEntry.js`; + const entry = `${host}:${port}/mf-manifest.json`; const { name: packageName, version, description, stripes, main } = require(packageJsonPath); const { permissionSets: _, ...stripesRest } = stripes; @@ -38,6 +42,7 @@ module.exports = async function federate(options = {}) { url, name, main, + entry, ...stripesRest, }; diff --git a/webpack/transpile.js b/webpack/transpile.js index 970c4d4..3ea9e0c 100644 --- a/webpack/transpile.js +++ b/webpack/transpile.js @@ -19,12 +19,13 @@ module.exports = function transpile(options = {}) { const { name, peerDependencies } = packageJson; config.output.library = { - type: 'umd', - name, + type: 'module', }; + const externs = { 'stripes-config': 'stripes-config', ...peerDependencies }; if (peerDependencies) { - config.externals = processExternals(peerDependencies); + config.externals = processExternals(externs); + config.externalsType = 'module-import'; } } diff --git a/webpack/utils.js b/webpack/utils.js index d106d65..b1ebc18 100644 --- a/webpack/utils.js +++ b/webpack/utils.js @@ -1,17 +1,30 @@ const isDevelopment = process.env.NODE_ENV === 'development'; const isProduction = process.env.NODE_ENV === 'production'; +const externMapping = { + 'stripes-config': 'stripes-config', +}; + const processExternals = (peerDeps) => { - return Object.keys(peerDeps).reduce((acc, name) => { - acc[name] = { - root: name, - commonjs2: name, - commonjs: name, - amd: name, - umd: name - }; + // return Object.keys(peerDeps).reduce((acc, name) => { + // acc[name] = { + // root: name, + // commonjs2: name, + // commonjs: name, + // amd: name, + // umd: name + // }; - return acc; - }, {}); + // return acc; + // }, {}); + const externs = {}; + Object.keys(peerDeps).forEach((dep) => { + if (externMapping[dep]) { + externs[dep] = externMapping[dep]; + } else { + externs[dep] = dep; + } + }); + return externs; }; const processShared = (shared, options = {}) => { From e72914945dd83b99955208df2c37d282c66c8217 Mon Sep 17 00:00:00 2001 From: John Coburn Date: Thu, 5 Feb 2026 15:26:51 -0600 Subject: [PATCH 22/38] bump webpack-dev-server version --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index f07752b..5f2ff84 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,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-dev-server": "^5.2.3", "webpack-hot-middleware": "^2.25.1", "webpack-remove-empty-scripts": "^1.0.1", "webpack-virtual-modules": "^0.4.3" @@ -92,4 +92,4 @@ "react-dom": "^18.2.0", "webpack": "^5.58.1" } -} +} \ No newline at end of file From 3646be8cdf5fbf3c9279702acfeeebad071031ec Mon Sep 17 00:00:00 2001 From: John Coburn Date: Thu, 5 Feb 2026 16:49:07 -0600 Subject: [PATCH 23/38] restore stripes-config-plugin from main --- webpack/stripes-config-plugin.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/webpack/stripes-config-plugin.js b/webpack/stripes-config-plugin.js index 4c60288..74fb359 100644 --- a/webpack/stripes-config-plugin.js +++ b/webpack/stripes-config-plugin.js @@ -13,7 +13,6 @@ const { SyncHook } = require('tapable'); const stripesModuleParser = require('./stripes-module-parser'); const StripesBuildError = require('./stripes-build-error'); const stripesSerialize = require('./stripes-serialize'); -const { defaultRegistryUrl } = require('../consts'); const logger = require('./logger')('stripesConfigPlugin'); const stripesConfigPluginHooksMap = new WeakMap(); @@ -54,12 +53,7 @@ module.exports = class StripesConfigPlugin { plugin: [], settings: [], }; -<<<<<<< HEAD this.mergedConfig = Object.assign({ modules: modulesInitialState }, this.options, { modules: config }); -======= - this.mergedOkapi = Object.assign({ registryUrl: defaultRegistryUrl }, this.options.okapi); - this.mergedConfig = Object.assign({ modules: modulesInitialState }, this.options, { modules: config, okapi: this.mergedOkapi }); ->>>>>>> STRIPES-861-new-mf-plugin this.metadata = metadata; this.icons = icons; this.warnings = warnings; @@ -69,16 +63,18 @@ module.exports = class StripesConfigPlugin { StripesConfigPlugin.getPluginHooks(compiler).beforeWrite.tap( { name: 'StripesConfigPlugin', context: true }, - context => Object.assign(context, { config })); + context => Object.assign(context, { config, metadata, icons, stripesDeps, warnings })); // Wait until after other plugins to generate virtual stripes-config compiler.hooks.afterPlugins.tap('StripesConfigPlugin', (theCompiler) => this.afterPlugins(theCompiler)); + compiler.hooks.emit.tapAsync('StripesConfigPlugin', (compilation, callback) => this.processWarnings(compilation, callback)); } afterPlugins(compiler) { // Data provided by other stripes plugins via hooks const pluginData = { branding: {}, + errorLogging: {}, translations: {}, }; @@ -90,8 +86,8 @@ module.exports = class StripesConfigPlugin { // Create a virtual module for Webpack to include in the build const stripesVirtualModule = ` const { okapi, config, modules } = ${serialize(this.mergedConfig, { space: 2 })}; - const errorLogging = ${stripesSerialize.serializeWithRequire(pluginData.errorLogging)}; const branding = ${stripesSerialize.serializeWithRequire(pluginData.branding)}; + const errorLogging = ${stripesSerialize.serializeWithRequire(pluginData.errorLogging)}; const translations = ${serialize(pluginData.translations, { space: 2 })}; const metadata = ${stripesSerialize.serializeWithRequire(this.metadata)}; const icons = ${stripesSerialize.serializeWithRequire(this.icons)}; @@ -101,4 +97,11 @@ module.exports = class StripesConfigPlugin { logger.log('writing virtual module...', stripesVirtualModule); this.virtualModule.writeModule('node_modules/stripes-config.js', stripesVirtualModule); } + + processWarnings(compilation, callback) { + if (this.warnings.length) { + compilation.warnings.push(new StripesBuildError(`stripes-config-plugin:\n ${this.warnings.join('\n ')}`)); + } + callback(); + } }; From 019d1851c32f66ca256b4346b87f3d5b485a50ba Mon Sep 17 00:00:00 2001 From: John Coburn Date: Tue, 10 Feb 2026 10:51:51 -0600 Subject: [PATCH 24/38] turn off hot reloading in module-level dev server --- webpack.config.federate.remote.js | 1 + 1 file changed, 1 insertion(+) diff --git a/webpack.config.federate.remote.js b/webpack.config.federate.remote.js index 15cca77..451a1fc 100644 --- a/webpack.config.federate.remote.js +++ b/webpack.config.federate.remote.js @@ -165,6 +165,7 @@ const buildConfig = (metadata, options) => { // in development mode, setup the devserver... config.devtool = 'inline-source-map'; config.devServer = { + hot: false, port: port, open: false, headers: { From 5a580c79ff4d6b17bfd6f8253fd25a46975ebc58 Mon Sep 17 00:00:00 2001 From: John Coburn Date: Tue, 10 Feb 2026 13:15:33 -0600 Subject: [PATCH 25/38] turn off hot reloading in devtools for now --- webpack.config.federate.remote.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/webpack.config.federate.remote.js b/webpack.config.federate.remote.js index 451a1fc..badeba5 100644 --- a/webpack.config.federate.remote.js +++ b/webpack.config.federate.remote.js @@ -164,10 +164,14 @@ const buildConfig = (metadata, options) => { } else { // in development mode, setup the devserver... config.devtool = 'inline-source-map'; + // turning off hot reloading and overlay since we're using the dev sever for hosting static files rather than actual dev work. config.devServer = { hot: false, port: port, open: false, + client: { + overlay: false, + }, headers: { 'Access-Control-Allow-Origin': '*', }, From b44d8a5d4f717a335235e4168752fd5dc70059cd Mon Sep 17 00:00:00 2001 From: John Coburn Date: Wed, 11 Feb 2026 16:08:39 -0600 Subject: [PATCH 26/38] use default discoveryUrl for local --- consts.js | 4 ++-- webpack/federate.js | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/consts.js b/consts.js index f0978a7..ffc4519 100644 --- a/consts.js +++ b/consts.js @@ -59,10 +59,10 @@ const getHostAppSingletons = () => { return platformSingletons; } -const defaultEntitlementUrl = 'http://localhost:3001/registry'; +const defaultDiscoveryUrl = 'http://localhost:3001/registry'; module.exports = { - defaultEntitlementUrl, + defaultDiscoveryUrl, singletons, getHostAppSingletons }; diff --git a/webpack/federate.js b/webpack/federate.js index b188f32..fe7a2a1 100644 --- a/webpack/federate.js +++ b/webpack/federate.js @@ -7,6 +7,7 @@ const { snakeCase } = require('lodash'); const buildConfig = require('../webpack.config.federate.remote'); const { tryResolve } = require('./module-paths'); const logger = require('./logger')(); +const { defaultDiscoveryUrl } = require('../consts'); // Function to check if a port is free function isPortFree(port) { @@ -33,7 +34,9 @@ async function findFreePort(startPort) { module.exports = async function federate(stripesConfig, options = {}, callback = () => { }) { logger.log('starting federation...'); - const { discoveryUrl } = stripesConfig.okapi; + const { discoveryUrl: configDiscoveryUrl } = stripesConfig.okapi; + const discoveryUrl = configDiscoveryUrl || defaultDiscoveryUrl; + const packageJsonPath = tryResolve(path.join(process.cwd(), 'package.json')); if (!packageJsonPath) { @@ -81,7 +84,8 @@ module.exports = async function federate(stripesConfig, options = {}, callback = method: 'POST', headers: requestHeader, body: JSON.stringify(metadata), - }) + }); + console.log(`Module registered with local discovery at ${discoveryUrl}`); } catch (err) { console.error(`Local discovery not found for module registration. Please check ${discoveryUrl}:${err}`); process.exit(); From 18f969ba6ef5345df0c138e1c895dd7abd48e101 Mon Sep 17 00:00:00 2001 From: John Coburn Date: Thu, 12 Feb 2026 23:15:01 -0600 Subject: [PATCH 27/38] fussing with attempting to override shares with host dependencies --- package.json | 2 +- .../node_modules/@folio/app1/icons/app.png | 0 .../node_modules/@folio/app1/icons/app.svg | 0 .../node_modules/@folio/app1/package.json | 29 ------------------- .../node_modules/@folio/app2/icons/app.svg | 0 .../@folio/stripes-dep1/icons/thing.png | 0 .../@folio/stripes-dep1/icons/thing.svg | 0 .../@folio/stripes-dep1/package.json | 14 --------- .../node_modules/@folio/app2/package.json | 25 ---------------- .../@folio/stripes-dep1/icons/otherthing.png | 0 .../@folio/stripes-dep1/icons/otherthing.svg | 0 .../@folio/stripes-dep1/icons/thing.png | 0 .../@folio/stripes-dep1/icons/thing.svg | 0 .../@folio/stripes-dep1/package.json | 19 ------------ .../@notfolio/stripes-dep2/package.json | 5 ---- webpack.config.base.js | 1 - webpack.config.cli.dev.js | 16 ++++++++-- webpack.config.cli.prod.js | 23 ++++++++------- webpack.config.federate.remote.js | 9 ++++-- webpack/federate.js | 2 +- webpack/utils.js | 4 +-- 21 files changed, 37 insertions(+), 112 deletions(-) delete mode 100644 test/webpack/node_modules/@folio/app1/icons/app.png delete mode 100644 test/webpack/node_modules/@folio/app1/icons/app.svg delete mode 100644 test/webpack/node_modules/@folio/app1/package.json delete mode 100644 test/webpack/node_modules/@folio/app2/icons/app.svg delete mode 100644 test/webpack/node_modules/@folio/app2/node_modules/@folio/stripes-dep1/icons/thing.png delete mode 100644 test/webpack/node_modules/@folio/app2/node_modules/@folio/stripes-dep1/icons/thing.svg delete mode 100644 test/webpack/node_modules/@folio/app2/node_modules/@folio/stripes-dep1/package.json delete mode 100644 test/webpack/node_modules/@folio/app2/package.json delete mode 100644 test/webpack/node_modules/@folio/stripes-dep1/icons/otherthing.png delete mode 100644 test/webpack/node_modules/@folio/stripes-dep1/icons/otherthing.svg delete mode 100644 test/webpack/node_modules/@folio/stripes-dep1/icons/thing.png delete mode 100644 test/webpack/node_modules/@folio/stripes-dep1/icons/thing.svg delete mode 100644 test/webpack/node_modules/@folio/stripes-dep1/package.json delete mode 100644 test/webpack/node_modules/@notfolio/stripes-dep2/package.json diff --git a/package.json b/package.json index 5018ceb..26ffb08 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "@cerner/duplicate-package-checker-webpack-plugin": "~2.1.0", "@csstools/postcss-global-data": "^3.0.0", "@csstools/postcss-relative-color-syntax": "^3.0.7", - "@module-federation/enhanced": "^0.21.6", + "@module-federation/enhanced": "^2.0.0", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.4", "@svgr/webpack": "^8.1.0", "add-asset-html-webpack-plugin": "^6.0.0", diff --git a/test/webpack/node_modules/@folio/app1/icons/app.png b/test/webpack/node_modules/@folio/app1/icons/app.png deleted file mode 100644 index e69de29..0000000 diff --git a/test/webpack/node_modules/@folio/app1/icons/app.svg b/test/webpack/node_modules/@folio/app1/icons/app.svg deleted file mode 100644 index e69de29..0000000 diff --git a/test/webpack/node_modules/@folio/app1/package.json b/test/webpack/node_modules/@folio/app1/package.json deleted file mode 100644 index 3f0601f..0000000 --- a/test/webpack/node_modules/@folio/app1/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "@folio/app1", - "version": "1.2.3", - "main": "src/index.js", - "stripes": { - "actsAs": [ - "app", - "settings" - ], - "displayName": "ui-app1.meta.title", - "route": "/app1", - "actionNames": [ - "stripesHome", - "app1SortByName" - ], - "icons": [ - { - "name": "app", - "alt": "Create, view and manage app1", - "title": "Application 1" - } - ], - "okapiInterfaces": { - "users": "15.0", - "configuration": "2.0" - }, - "stripesDeps": ["@folio/stripes-dep1", "@notfolio/stripes-dep2"] - } -} diff --git a/test/webpack/node_modules/@folio/app2/icons/app.svg b/test/webpack/node_modules/@folio/app2/icons/app.svg deleted file mode 100644 index e69de29..0000000 diff --git a/test/webpack/node_modules/@folio/app2/node_modules/@folio/stripes-dep1/icons/thing.png b/test/webpack/node_modules/@folio/app2/node_modules/@folio/stripes-dep1/icons/thing.png deleted file mode 100644 index e69de29..0000000 diff --git a/test/webpack/node_modules/@folio/app2/node_modules/@folio/stripes-dep1/icons/thing.svg b/test/webpack/node_modules/@folio/app2/node_modules/@folio/stripes-dep1/icons/thing.svg deleted file mode 100644 index e69de29..0000000 diff --git a/test/webpack/node_modules/@folio/app2/node_modules/@folio/stripes-dep1/package.json b/test/webpack/node_modules/@folio/app2/node_modules/@folio/stripes-dep1/package.json deleted file mode 100644 index 8b3b7d1..0000000 --- a/test/webpack/node_modules/@folio/app2/node_modules/@folio/stripes-dep1/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "@folio/stripes-dep1", - "version": "1.2.3", - "main": "src/index.js", - "stripes": { - "icons": [ - { - "name": "thing", - "alt": "Do thing", - "title": "Thing" - } - ] - } -} diff --git a/test/webpack/node_modules/@folio/app2/package.json b/test/webpack/node_modules/@folio/app2/package.json deleted file mode 100644 index 6f00a55..0000000 --- a/test/webpack/node_modules/@folio/app2/package.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "@folio/app2", - "version": "1.2.3", - "main": "src/index.js", - "stripes": { - "actsAs": [ - "app", - "settings" - ], - "displayName": "ui-app2.meta.title", - "route": "/app2", - "icons": [ - { - "name": "app", - "alt": "Create, view and manage app2", - "title": "Application 2" - } - ], - "okapiInterfaces": { - "users": "15.0", - "configuration": "2.0" - }, - "stripesDeps": ["@folio/stripes-dep1"] - } -} diff --git a/test/webpack/node_modules/@folio/stripes-dep1/icons/otherthing.png b/test/webpack/node_modules/@folio/stripes-dep1/icons/otherthing.png deleted file mode 100644 index e69de29..0000000 diff --git a/test/webpack/node_modules/@folio/stripes-dep1/icons/otherthing.svg b/test/webpack/node_modules/@folio/stripes-dep1/icons/otherthing.svg deleted file mode 100644 index e69de29..0000000 diff --git a/test/webpack/node_modules/@folio/stripes-dep1/icons/thing.png b/test/webpack/node_modules/@folio/stripes-dep1/icons/thing.png deleted file mode 100644 index e69de29..0000000 diff --git a/test/webpack/node_modules/@folio/stripes-dep1/icons/thing.svg b/test/webpack/node_modules/@folio/stripes-dep1/icons/thing.svg deleted file mode 100644 index e69de29..0000000 diff --git a/test/webpack/node_modules/@folio/stripes-dep1/package.json b/test/webpack/node_modules/@folio/stripes-dep1/package.json deleted file mode 100644 index 0bc5dda..0000000 --- a/test/webpack/node_modules/@folio/stripes-dep1/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "@folio/stripes-dep1", - "version": "3.4.5", - "main": "src/index.js", - "stripes": { - "icons": [ - { - "name": "thing", - "alt": "Do thing", - "title": "Thingy" - }, - { - "name": "otherthing", - "alt": "Do other thing", - "title": "Other Thing" - } - ] - } -} diff --git a/test/webpack/node_modules/@notfolio/stripes-dep2/package.json b/test/webpack/node_modules/@notfolio/stripes-dep2/package.json deleted file mode 100644 index 8db0fd5..0000000 --- a/test/webpack/node_modules/@notfolio/stripes-dep2/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "@notfolio/stripes-dep2", - "version": "1.2.3", - "main": "src/index.js" -} diff --git a/webpack.config.base.js b/webpack.config.base.js index 62bc389..c587736 100644 --- a/webpack.config.base.js +++ b/webpack.config.base.js @@ -70,7 +70,6 @@ const baseConfig = { }), new webpack.EnvironmentPlugin(['NODE_ENV']), new RemoveEmptyScriptsPlugin(), - new ModuleFederationPlugin({ name: 'host', exposes: {}, shared }), ], module: { rules: [ diff --git a/webpack.config.cli.dev.js b/webpack.config.cli.dev.js index a1927fd..50ae325 100644 --- a/webpack.config.cli.dev.js +++ b/webpack.config.cli.dev.js @@ -64,9 +64,21 @@ const buildConfig = (stripesConfig) => { // Enable module federation, setting up the host platform to share singletons (react, stripes-core, etc) with remote modules. if (stripesConfig.okapi.discoveryUrl) { + devConfig.cache = false; const hostAppSingletons = getHostAppSingletons(); - const shared = processShared(hostAppSingletons, { singleton: true, eager: true }); - devConfig.plugins.push(new ModuleFederationPlugin({ name: 'host', shared })); + const shared = processShared(hostAppSingletons, { requiredVersion: 'auto', singleton: true, eager: true }); + devConfig.plugins.push(new ModuleFederationPlugin({ + experiments: { + provideExternalRuntime: true, + optimization: { + target: 'web', + } + }, + name: 'host', + shared, + runtimePlugins: [require.resolve('./webpack/host-override-share-plugin')], + shareStrategy: 'loaded-first', + })); } // This alias avoids a console warning for react-dom patch diff --git a/webpack.config.cli.prod.js b/webpack.config.cli.prod.js index 6675680..e122dce 100644 --- a/webpack.config.cli.prod.js +++ b/webpack.config.cli.prod.js @@ -10,7 +10,7 @@ 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 { ModuleFederationPlugin } = require('@module-federation/enhanced/webpack'); const { getHostAppSingletons } = require('./consts'); @@ -61,21 +61,22 @@ 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 }); + const shared = processShared(singletons, { singleton: true, eager: false }); prodConfig.plugins.push( new ModuleFederationPlugin({ name: 'host', shared }) ); + } else { + prodConfig.optimization = { + mangleWasmImports: false, + minimizer: [ + new EsbuildPlugin({ + css: true, + }), + ], + splitChunks, + } } - prodConfig.optimization = { - mangleWasmImports: false, - minimizer: [ - new EsbuildPlugin({ - css: true, - }), - ], - splitChunks, - } prodConfig.module.rules.push(esbuildLoaderRule(allModulePaths)); diff --git a/webpack.config.federate.remote.js b/webpack.config.federate.remote.js index badeba5..88386cb 100644 --- a/webpack.config.federate.remote.js +++ b/webpack.config.federate.remote.js @@ -36,7 +36,7 @@ const buildConfig = (metadata, options) => { // For dependencies that are configured as singletons, only a single version will be loaded from the host app. // If a version is semver incompatible, a console warning will be emitted. const configSingletons = getHostAppSingletons(); - const shared = processShared(configSingletons, { singleton: true }); + const shared = processShared(configSingletons, { requiredVersion: 'auto', singleton: true, eager: false }, true); // general webpack config. // Some noteworthy settings: @@ -55,6 +55,7 @@ const buildConfig = (metadata, options) => { publicPath: 'auto', // webpack will determine publicPath of script at runtime. path: options.outputPath ? path.resolve(options.outputPath) : undefined }, + cache: false, stats: { errorDetails: true }, @@ -140,13 +141,17 @@ const buildConfig = (metadata, options) => { // 3. The above are stored in a 'container' (webpack/mod-fed term) - a global variable by the 'name' field. // The host app 'imports' the app via container.get('MainEntry') from the loaded code. new ModuleFederationPlugin({ + experiments: { + externalRuntime: true, + }, library: { type: 'var', name }, name, filename: 'remoteEntry.js', exposes: { '.': mainEntry, }, - shared + shared, + shareStrategy: 'loaded-first', }), ] }; diff --git a/webpack/federate.js b/webpack/federate.js index fe7a2a1..6d4ea23 100644 --- a/webpack/federate.js +++ b/webpack/federate.js @@ -49,7 +49,7 @@ module.exports = async function federate(stripesConfig, options = {}, callback = // and the port is passed through to webpack-dev-server to host the module in dev mode. const port = options.port ?? await findFreePort(3002); const host = options.host ?? 'http://localhost'; - const url = `${host}:${port}/remoteEntry.js`; + const url = `${host}:${port}/mf-manifest.json`; const { name: packageName, version, description, stripes, main } = require(packageJsonPath); const { permissionSets: _, ...stripesRest } = stripes; diff --git a/webpack/utils.js b/webpack/utils.js index 7133f8d..5727e3e 100644 --- a/webpack/utils.js +++ b/webpack/utils.js @@ -29,9 +29,9 @@ const processExternals = (peerDeps) => { // and applies additional options for the module federation configuration, // like setting the shared items as singletons, or using the 'eager' consumption // setting (chunks are included in the initial bundle whether than split out/lazy loaded) -const processShared = (shared, options = {}) => { +const processShared = (shared, options = {}, remote) => { return Object.keys(shared).reduce((acc, name) => { - acc[name] = { + acc[name] = remote ? options : { requiredVersion: shared[name], ...options }; From 70e0922206f277d6ad62f779c272b41bb1a6d569 Mon Sep 17 00:00:00 2001 From: John Coburn Date: Thu, 12 Feb 2026 23:15:34 -0600 Subject: [PATCH 28/38] re-add test folder --- .../node_modules/@folio/app1/icons/app.png | 0 .../node_modules/@folio/app1/icons/app.svg | 0 .../node_modules/@folio/app1/package.json | 29 +++++++++++++++++++ .../node_modules/@folio/app2/icons/app.svg | 0 .../@folio/stripes-dep1/icons/thing.png | 0 .../@folio/stripes-dep1/icons/thing.svg | 0 .../@folio/stripes-dep1/package.json | 14 +++++++++ .../node_modules/@folio/app2/package.json | 25 ++++++++++++++++ .../@folio/stripes-dep1/icons/otherthing.png | 0 .../@folio/stripes-dep1/icons/otherthing.svg | 0 .../@folio/stripes-dep1/icons/thing.png | 0 .../@folio/stripes-dep1/icons/thing.svg | 0 .../@folio/stripes-dep1/package.json | 19 ++++++++++++ .../@notfolio/stripes-dep2/package.json | 5 ++++ 14 files changed, 92 insertions(+) create mode 100644 test/webpack/node_modules/@folio/app1/icons/app.png create mode 100644 test/webpack/node_modules/@folio/app1/icons/app.svg create mode 100644 test/webpack/node_modules/@folio/app1/package.json create mode 100644 test/webpack/node_modules/@folio/app2/icons/app.svg create mode 100644 test/webpack/node_modules/@folio/app2/node_modules/@folio/stripes-dep1/icons/thing.png create mode 100644 test/webpack/node_modules/@folio/app2/node_modules/@folio/stripes-dep1/icons/thing.svg create mode 100644 test/webpack/node_modules/@folio/app2/node_modules/@folio/stripes-dep1/package.json create mode 100644 test/webpack/node_modules/@folio/app2/package.json create mode 100644 test/webpack/node_modules/@folio/stripes-dep1/icons/otherthing.png create mode 100644 test/webpack/node_modules/@folio/stripes-dep1/icons/otherthing.svg create mode 100644 test/webpack/node_modules/@folio/stripes-dep1/icons/thing.png create mode 100644 test/webpack/node_modules/@folio/stripes-dep1/icons/thing.svg create mode 100644 test/webpack/node_modules/@folio/stripes-dep1/package.json create mode 100644 test/webpack/node_modules/@notfolio/stripes-dep2/package.json diff --git a/test/webpack/node_modules/@folio/app1/icons/app.png b/test/webpack/node_modules/@folio/app1/icons/app.png new file mode 100644 index 0000000..e69de29 diff --git a/test/webpack/node_modules/@folio/app1/icons/app.svg b/test/webpack/node_modules/@folio/app1/icons/app.svg new file mode 100644 index 0000000..e69de29 diff --git a/test/webpack/node_modules/@folio/app1/package.json b/test/webpack/node_modules/@folio/app1/package.json new file mode 100644 index 0000000..3f0601f --- /dev/null +++ b/test/webpack/node_modules/@folio/app1/package.json @@ -0,0 +1,29 @@ +{ + "name": "@folio/app1", + "version": "1.2.3", + "main": "src/index.js", + "stripes": { + "actsAs": [ + "app", + "settings" + ], + "displayName": "ui-app1.meta.title", + "route": "/app1", + "actionNames": [ + "stripesHome", + "app1SortByName" + ], + "icons": [ + { + "name": "app", + "alt": "Create, view and manage app1", + "title": "Application 1" + } + ], + "okapiInterfaces": { + "users": "15.0", + "configuration": "2.0" + }, + "stripesDeps": ["@folio/stripes-dep1", "@notfolio/stripes-dep2"] + } +} diff --git a/test/webpack/node_modules/@folio/app2/icons/app.svg b/test/webpack/node_modules/@folio/app2/icons/app.svg new file mode 100644 index 0000000..e69de29 diff --git a/test/webpack/node_modules/@folio/app2/node_modules/@folio/stripes-dep1/icons/thing.png b/test/webpack/node_modules/@folio/app2/node_modules/@folio/stripes-dep1/icons/thing.png new file mode 100644 index 0000000..e69de29 diff --git a/test/webpack/node_modules/@folio/app2/node_modules/@folio/stripes-dep1/icons/thing.svg b/test/webpack/node_modules/@folio/app2/node_modules/@folio/stripes-dep1/icons/thing.svg new file mode 100644 index 0000000..e69de29 diff --git a/test/webpack/node_modules/@folio/app2/node_modules/@folio/stripes-dep1/package.json b/test/webpack/node_modules/@folio/app2/node_modules/@folio/stripes-dep1/package.json new file mode 100644 index 0000000..8b3b7d1 --- /dev/null +++ b/test/webpack/node_modules/@folio/app2/node_modules/@folio/stripes-dep1/package.json @@ -0,0 +1,14 @@ +{ + "name": "@folio/stripes-dep1", + "version": "1.2.3", + "main": "src/index.js", + "stripes": { + "icons": [ + { + "name": "thing", + "alt": "Do thing", + "title": "Thing" + } + ] + } +} diff --git a/test/webpack/node_modules/@folio/app2/package.json b/test/webpack/node_modules/@folio/app2/package.json new file mode 100644 index 0000000..6f00a55 --- /dev/null +++ b/test/webpack/node_modules/@folio/app2/package.json @@ -0,0 +1,25 @@ +{ + "name": "@folio/app2", + "version": "1.2.3", + "main": "src/index.js", + "stripes": { + "actsAs": [ + "app", + "settings" + ], + "displayName": "ui-app2.meta.title", + "route": "/app2", + "icons": [ + { + "name": "app", + "alt": "Create, view and manage app2", + "title": "Application 2" + } + ], + "okapiInterfaces": { + "users": "15.0", + "configuration": "2.0" + }, + "stripesDeps": ["@folio/stripes-dep1"] + } +} diff --git a/test/webpack/node_modules/@folio/stripes-dep1/icons/otherthing.png b/test/webpack/node_modules/@folio/stripes-dep1/icons/otherthing.png new file mode 100644 index 0000000..e69de29 diff --git a/test/webpack/node_modules/@folio/stripes-dep1/icons/otherthing.svg b/test/webpack/node_modules/@folio/stripes-dep1/icons/otherthing.svg new file mode 100644 index 0000000..e69de29 diff --git a/test/webpack/node_modules/@folio/stripes-dep1/icons/thing.png b/test/webpack/node_modules/@folio/stripes-dep1/icons/thing.png new file mode 100644 index 0000000..e69de29 diff --git a/test/webpack/node_modules/@folio/stripes-dep1/icons/thing.svg b/test/webpack/node_modules/@folio/stripes-dep1/icons/thing.svg new file mode 100644 index 0000000..e69de29 diff --git a/test/webpack/node_modules/@folio/stripes-dep1/package.json b/test/webpack/node_modules/@folio/stripes-dep1/package.json new file mode 100644 index 0000000..0bc5dda --- /dev/null +++ b/test/webpack/node_modules/@folio/stripes-dep1/package.json @@ -0,0 +1,19 @@ +{ + "name": "@folio/stripes-dep1", + "version": "3.4.5", + "main": "src/index.js", + "stripes": { + "icons": [ + { + "name": "thing", + "alt": "Do thing", + "title": "Thingy" + }, + { + "name": "otherthing", + "alt": "Do other thing", + "title": "Other Thing" + } + ] + } +} diff --git a/test/webpack/node_modules/@notfolio/stripes-dep2/package.json b/test/webpack/node_modules/@notfolio/stripes-dep2/package.json new file mode 100644 index 0000000..8db0fd5 --- /dev/null +++ b/test/webpack/node_modules/@notfolio/stripes-dep2/package.json @@ -0,0 +1,5 @@ +{ + "name": "@notfolio/stripes-dep2", + "version": "1.2.3", + "main": "src/index.js" +} From 0e3218cc92f43dd80fcaec4e4723fe624d0dbf97 Mon Sep 17 00:00:00 2001 From: John Coburn Date: Thu, 19 Feb 2026 07:56:50 -0600 Subject: [PATCH 29/38] sync with STCOR STRWEB-139 changes --- webpack.config.cli.dev.js | 5 ++--- webpack.config.cli.prod.js | 2 +- webpack.config.federate.remote.js | 6 ++---- webpack/federate.js | 2 +- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/webpack.config.cli.dev.js b/webpack.config.cli.dev.js index 50ae325..5419c79 100644 --- a/webpack.config.cli.dev.js +++ b/webpack.config.cli.dev.js @@ -66,18 +66,17 @@ const buildConfig = (stripesConfig) => { if (stripesConfig.okapi.discoveryUrl) { devConfig.cache = false; const hostAppSingletons = getHostAppSingletons(); - const shared = processShared(hostAppSingletons, { requiredVersion: 'auto', singleton: true, eager: true }); + const shared = processShared(hostAppSingletons, { singleton: true, eager: true }); devConfig.plugins.push(new ModuleFederationPlugin({ experiments: { - provideExternalRuntime: true, optimization: { target: 'web', } }, name: 'host', shared, - runtimePlugins: [require.resolve('./webpack/host-override-share-plugin')], shareStrategy: 'loaded-first', + runtimePlugins: [require.resolve('./webpack/host-override-share-plugin')], })); } diff --git a/webpack.config.cli.prod.js b/webpack.config.cli.prod.js index e122dce..b4d8fc6 100644 --- a/webpack.config.cli.prod.js +++ b/webpack.config.cli.prod.js @@ -61,7 +61,7 @@ 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: false }); + const shared = processShared(singletons, { singleton: true, eager: true }); prodConfig.plugins.push( new ModuleFederationPlugin({ name: 'host', shared }) ); diff --git a/webpack.config.federate.remote.js b/webpack.config.federate.remote.js index 88386cb..840f4f8 100644 --- a/webpack.config.federate.remote.js +++ b/webpack.config.federate.remote.js @@ -141,16 +141,14 @@ const buildConfig = (metadata, options) => { // 3. The above are stored in a 'container' (webpack/mod-fed term) - a global variable by the 'name' field. // The host app 'imports' the app via container.get('MainEntry') from the loaded code. new ModuleFederationPlugin({ - experiments: { - externalRuntime: true, - }, library: { type: 'var', name }, name, filename: 'remoteEntry.js', exposes: { - '.': mainEntry, + './MainEntry': mainEntry, }, shared, + runtimePlugins: [require.resolve('./webpack/host-override-share-plugin')], shareStrategy: 'loaded-first', }), ] diff --git a/webpack/federate.js b/webpack/federate.js index 6d4ea23..fe7a2a1 100644 --- a/webpack/federate.js +++ b/webpack/federate.js @@ -49,7 +49,7 @@ module.exports = async function federate(stripesConfig, options = {}, callback = // and the port is passed through to webpack-dev-server to host the module in dev mode. const port = options.port ?? await findFreePort(3002); const host = options.host ?? 'http://localhost'; - const url = `${host}:${port}/mf-manifest.json`; + const url = `${host}:${port}/remoteEntry.js`; const { name: packageName, version, description, stripes, main } = require(packageJsonPath); const { permissionSets: _, ...stripesRest } = stripes; From 319461f9589d999de54faf57462f90bd4955c2d3 Mon Sep 17 00:00:00 2001 From: John Coburn Date: Thu, 19 Feb 2026 07:57:58 -0600 Subject: [PATCH 30/38] add runtimePlugin for host override --- webpack/host-override-share-plugin.js | 75 +++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 webpack/host-override-share-plugin.js diff --git a/webpack/host-override-share-plugin.js b/webpack/host-override-share-plugin.js new file mode 100644 index 0000000..733747a --- /dev/null +++ b/webpack/host-override-share-plugin.js @@ -0,0 +1,75 @@ +const HostOverrideSharePlugin = () => { + return { + name: 'host-override-share-plugin', + // beforeInit(args) { + + // const { origin } = args; + // if (origin.name !== 'host') { + // console.log(`replacing shareInfo for ${origin.name} with host app shareScopeMap.`); + // const hostInstance = __FEDERATION__.__INSTANCES__[0]; + // args.origin.shareScopeMap = hostInstance.shareScopeMap; + // args.shareInfo = hostInstance.options.shared; + // args.userOptions.shared = hostInstance.options.shared; + // } + // return args; + // }, + // onload(args) { + // console.log('HostOverrideSharePlugin loaded with args:', args); + // return args; + // }, + // resolveShare: args => { + // const { shareScopeMap, scope, pkgName, version, GlobalFederation } = args; + + // console.log('resolveShare:', args); + + // const host = GlobalFederation['__INSTANCES__'][0]; + // if (!host) { + // return args; + // } + + // if (!host.options.shared[pkgName]) { + // return args; + // } + + // args.resolver = function () { + // args.shareScopeMap[scope][pkgName][version] = + // host.options.shared[pkgName][0]; + + // console.log('Replaced share resolution for', pkgName, 'with version', version, 'to host app shared version', host.options.shared[pkgName][0]); + // return { + // shared: args.shareScopeMap[scope][pkgName][version], + // useTreesShaking: false, + // }; + // }; + + // return args; + // }, + // async beforeLoadShare(args) { + // console.log('beforeLoadShare', args); + // return args; + // }, + // beforeRegisterShare(args) { + // console.log('beforeRegisterShare', args); + // return args; + // }, + // async afterResolve(args) { + // console.log('afterResolve', args); + // return args; + // }, + // async loadShare(args) { + // console.log('loadShare:', args); + // return args; + // }, + // async beforeLoadShare(args) { + // console.log('beforeloadShare:', args); + // return args; + // }, + // initContainerShareScopeMap(args) { + // console.log('initContainerShareScopeMap:', args); + // return args; + // } + + }; +}; + +export default HostOverrideSharePlugin; \ No newline at end of file From 027d41ed2f5c29ef8bef40d6862370982656316b Mon Sep 17 00:00:00 2001 From: John Coburn Date: Thu, 19 Feb 2026 11:13:23 -0600 Subject: [PATCH 31/38] remove auto version from modules --- webpack.config.federate.remote.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webpack.config.federate.remote.js b/webpack.config.federate.remote.js index 840f4f8..3d0d24a 100644 --- a/webpack.config.federate.remote.js +++ b/webpack.config.federate.remote.js @@ -36,7 +36,7 @@ const buildConfig = (metadata, options) => { // For dependencies that are configured as singletons, only a single version will be loaded from the host app. // If a version is semver incompatible, a console warning will be emitted. const configSingletons = getHostAppSingletons(); - const shared = processShared(configSingletons, { requiredVersion: 'auto', singleton: true, eager: false }, true); + const shared = processShared(configSingletons, { singleton: true, eager: false }, true); // general webpack config. // Some noteworthy settings: From 09a1b0efeca9c9f4531e1e3f07ea599ea939a4c8 Mon Sep 17 00:00:00 2001 From: John Coburn Date: Fri, 20 Feb 2026 07:56:47 -0600 Subject: [PATCH 32/38] single runtime from host --- webpack.config.cli.dev.js | 1 + webpack.config.federate.remote.js | 3 + webpack/host-override-share-plugin.js | 80 ++++++++++++++++++--------- 3 files changed, 59 insertions(+), 25 deletions(-) diff --git a/webpack.config.cli.dev.js b/webpack.config.cli.dev.js index 5419c79..6136a41 100644 --- a/webpack.config.cli.dev.js +++ b/webpack.config.cli.dev.js @@ -69,6 +69,7 @@ const buildConfig = (stripesConfig) => { const shared = processShared(hostAppSingletons, { singleton: true, eager: true }); devConfig.plugins.push(new ModuleFederationPlugin({ experiments: { + provideExternalRuntime: true, optimization: { target: 'web', } diff --git a/webpack.config.federate.remote.js b/webpack.config.federate.remote.js index 840f4f8..9c09f17 100644 --- a/webpack.config.federate.remote.js +++ b/webpack.config.federate.remote.js @@ -148,6 +148,9 @@ const buildConfig = (metadata, options) => { './MainEntry': mainEntry, }, shared, + experiments: { + externalRuntime: false, + }, runtimePlugins: [require.resolve('./webpack/host-override-share-plugin')], shareStrategy: 'loaded-first', }), diff --git a/webpack/host-override-share-plugin.js b/webpack/host-override-share-plugin.js index 733747a..26959d1 100644 --- a/webpack/host-override-share-plugin.js +++ b/webpack/host-override-share-plugin.js @@ -17,33 +17,40 @@ const HostOverrideSharePlugin = () => { // console.log('HostOverrideSharePlugin loaded with args:', args); // return args; // }, - // resolveShare: args => { - // const { shareScopeMap, scope, pkgName, version, GlobalFederation } = args; + resolveShare: args => { + const { shareScopeMap, scope, pkgName, version, GlobalFederation } = args; - // console.log('resolveShare:', args); + // console.log('resolveShare:', args); - // const host = GlobalFederation['__INSTANCES__'][0]; - // if (!host) { - // return args; - // } + const host = GlobalFederation['__INSTANCES__'][0]; + if (!host) { + return args; + } - // if (!host.options.shared[pkgName]) { - // return args; - // } + if (!host.options.shared[pkgName]) { + return args; + } - // args.resolver = function () { - // args.shareScopeMap[scope][pkgName][version] = - // host.options.shared[pkgName][0]; + args.resolver = function () { + const hostVersion = host.options.shared[pkgName][0].version; - // console.log('Replaced share resolution for', pkgName, 'with version', version, 'to host app shared version', host.options.shared[pkgName][0]); - // return { - // shared: args.shareScopeMap[scope][pkgName][version], - // useTreesShaking: false, - // }; - // }; + if (hostVersion !== version) { + console.warn(`Version mismatch for "${pkgName}":" host app: ${hostVersion} remote: ${version}. Attempting to use host app version...`); + } - // return args; - // }, + args.shareScopeMap[scope][pkgName][version] = + host.options.shared[pkgName][0]; + + + console.log('Replaced share resolution for', pkgName, 'with version', version, 'to host app shared version', host.options.shared[pkgName][0]); + return { + shared: args.shareScopeMap[scope][pkgName][version], + useTreesShaking: false, + }; + }; + + return args; + }, // async beforeLoadShare(args) { // console.log('beforeLoadShare', args); // return args; @@ -60,10 +67,33 @@ const HostOverrideSharePlugin = () => { // console.log('loadShare:', args); // return args; // }, - // async beforeLoadShare(args) { - // console.log('beforeloadShare:', args); - // return args; - // }, + async beforeLoadShare(args) { + console.log('beforeloadShare:', args); + + if (!globalThis.__NON_HOST_REMOTE_SHARE__) { + globalThis.__NON_HOST_REMOTE_SHARE__ = []; + } + + const hostInstance = __FEDERATION__.__INSTANCES__[0]; + if (!hostInstance) { + return args; + } + const hostShared = hostInstance.options.shared[args.pkgName][0]; + + if (!hostShared) { + return args; + } + + let hostVersion = hostShared.version; + + if (args.shareInfo.version !== hostVersion) { + + + globalThis.__NON_HOST_REMOTE_SHARE__.push(args.shareInfo); + } + + return args; + }, // initContainerShareScopeMap(args) { // console.log('initContainerShareScopeMap:', args); // return args; From fec245e58c7420f4c3e6527bdbb6f587b371b37a Mon Sep 17 00:00:00 2001 From: John Coburn Date: Fri, 20 Feb 2026 08:08:20 -0600 Subject: [PATCH 33/38] set external runtime to true for modules --- webpack.config.federate.remote.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webpack.config.federate.remote.js b/webpack.config.federate.remote.js index 6b39b04..5fc79aa 100644 --- a/webpack.config.federate.remote.js +++ b/webpack.config.federate.remote.js @@ -149,7 +149,7 @@ const buildConfig = (metadata, options) => { }, shared, experiments: { - externalRuntime: false, + externalRuntime: true, }, runtimePlugins: [require.resolve('./webpack/host-override-share-plugin')], shareStrategy: 'loaded-first', From 15874db5b695b4774abe78290c15f76ffcd2e91c Mon Sep 17 00:00:00 2001 From: John Coburn Date: Fri, 20 Feb 2026 09:36:48 -0600 Subject: [PATCH 34/38] exclude host-shares from module bundle --- webpack.config.federate.remote.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webpack.config.federate.remote.js b/webpack.config.federate.remote.js index 5fc79aa..cfc0097 100644 --- a/webpack.config.federate.remote.js +++ b/webpack.config.federate.remote.js @@ -36,7 +36,7 @@ const buildConfig = (metadata, options) => { // For dependencies that are configured as singletons, only a single version will be loaded from the host app. // If a version is semver incompatible, a console warning will be emitted. const configSingletons = getHostAppSingletons(); - const shared = processShared(configSingletons, { singleton: true, eager: false }, true); + const shared = processShared(configSingletons, { singleton: true, eager: false, import: false }, true); // general webpack config. // Some noteworthy settings: From 0fc851e2a4f86feed7b4e3c6555d416b25e110a5 Mon Sep 17 00:00:00 2001 From: John Coburn Date: Fri, 20 Feb 2026 10:17:29 -0600 Subject: [PATCH 35/38] add further optimization to module bundle, remove runtime plugins --- webpack.config.federate.remote.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/webpack.config.federate.remote.js b/webpack.config.federate.remote.js index cfc0097..704c334 100644 --- a/webpack.config.federate.remote.js +++ b/webpack.config.federate.remote.js @@ -150,8 +150,10 @@ const buildConfig = (metadata, options) => { shared, experiments: { externalRuntime: true, + optimization: { + target: 'web', + } }, - runtimePlugins: [require.resolve('./webpack/host-override-share-plugin')], shareStrategy: 'loaded-first', }), ] From 3b6f2c0e1103365528afea601cfbb6aabd479610 Mon Sep 17 00:00:00 2001 From: John Coburn Date: Fri, 20 Feb 2026 15:07:39 -0600 Subject: [PATCH 36/38] add remote runtime plugin to absorb plugins of host at runtime --- webpack.config.federate.remote.js | 1 + webpack/remote-runtime-plugin.js | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 webpack/remote-runtime-plugin.js diff --git a/webpack.config.federate.remote.js b/webpack.config.federate.remote.js index 704c334..7bcfa3f 100644 --- a/webpack.config.federate.remote.js +++ b/webpack.config.federate.remote.js @@ -155,6 +155,7 @@ const buildConfig = (metadata, options) => { } }, shareStrategy: 'loaded-first', + runtimePlugins: [require.resolve('./webpack/remote-runtime-plugin')], }), ] }; diff --git a/webpack/remote-runtime-plugin.js b/webpack/remote-runtime-plugin.js new file mode 100644 index 0000000..7a04553 --- /dev/null +++ b/webpack/remote-runtime-plugin.js @@ -0,0 +1,22 @@ +// Remote containers include runtime plugins from their own build configuration, but not those of the host. +// In order for the remote's runtime plugins to be updated without a rebuild, +// we need to apply the host's plugins to the remote's container. +// This plugin sets a lifecycle method Before initialization of the remote's ModuleFederation instance +// to pass the host's runtime plugins to the remote container. + +const RemoteRuntimePlugin = () => ({ + name: 'remote-runtime-plugin', + beforeInit(args) { + console.log('BeforeInit Remote', args); + + // const { origin } = args; + // if (origin.name !== 'host') { + // console.log(`replacing shareInfo for ${origin.name} with host app shareScopeMap.`); + // const hostInstance = __FEDERATION__.__INSTANCES__[0]; + // args.origin.shareScopeMap = hostInstance.shareScopeMap; + // args.shareInfo = hostInstance.options.shared; + // args.userOptions.shared = hostInstance.options.shared; + // } + return args; + }, +}); From e66662c244ab837a58dc0b2dc0928726bfb1c166 Mon Sep 17 00:00:00 2001 From: John Coburn Date: Fri, 20 Feb 2026 21:56:01 -0600 Subject: [PATCH 37/38] push host plugins to remote runtime --- webpack/remote-runtime-plugin.js | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/webpack/remote-runtime-plugin.js b/webpack/remote-runtime-plugin.js index 7a04553..d449bba 100644 --- a/webpack/remote-runtime-plugin.js +++ b/webpack/remote-runtime-plugin.js @@ -9,14 +9,23 @@ const RemoteRuntimePlugin = () => ({ beforeInit(args) { console.log('BeforeInit Remote', args); - // const { origin } = args; - // if (origin.name !== 'host') { - // console.log(`replacing shareInfo for ${origin.name} with host app shareScopeMap.`); - // const hostInstance = __FEDERATION__.__INSTANCES__[0]; - // args.origin.shareScopeMap = hostInstance.shareScopeMap; - // args.shareInfo = hostInstance.options.shared; - // args.userOptions.shared = hostInstance.options.shared; - // } + // get override plugin from host instance... + const hostInstance = __FEDERATION__.__INSTANCES__[0]; + if (!hostInstance) { + return args; + } + const hostOverridePlugin = hostInstance.options.plugins.find(plugin => plugin.name === 'host-override-share-plugin'); + if (!hostOverridePlugin) { + return args; + } + + // injects it into new instance at runtime. + if (!args.userOptions.plugins) { + args.userOptions.plugins = []; + }; + args.userOptions.plugins.unshift(hostOverridePlugin); return args; }, }); + +export default RemoteRuntimePlugin; \ No newline at end of file From 6f9812ad01bc36341e950ae59b4466b2053909e4 Mon Sep 17 00:00:00 2001 From: John Coburn Date: Fri, 20 Feb 2026 23:05:08 -0600 Subject: [PATCH 38/38] collect debug info, cleanup logging in runtime plugins --- webpack/host-override-share-plugin.js | 94 +++++---------------------- webpack/remote-runtime-plugin.js | 8 +-- 2 files changed, 18 insertions(+), 84 deletions(-) diff --git a/webpack/host-override-share-plugin.js b/webpack/host-override-share-plugin.js index 26959d1..5d0ee0e 100644 --- a/webpack/host-override-share-plugin.js +++ b/webpack/host-override-share-plugin.js @@ -1,84 +1,24 @@ +// This file can be used to attach lifecycle hooks to the module federation runtime logic. +// See https://module-federation.io/guide/runtime/runtime-hooks.html for possible entries. + const HostOverrideSharePlugin = () => { return { name: 'host-override-share-plugin', - // beforeInit(args) { - - // const { origin } = args; - // if (origin.name !== 'host') { - // console.log(`replacing shareInfo for ${origin.name} with host app shareScopeMap.`); - // const hostInstance = __FEDERATION__.__INSTANCES__[0]; - // args.origin.shareScopeMap = hostInstance.shareScopeMap; - // args.shareInfo = hostInstance.options.shared; - // args.userOptions.shared = hostInstance.options.shared; - // } - // return args; - // }, - // onload(args) { - // console.log('HostOverrideSharePlugin loaded with args:', args); - // return args; - // }, - resolveShare: args => { - const { shareScopeMap, scope, pkgName, version, GlobalFederation } = args; - - // console.log('resolveShare:', args); - - const host = GlobalFederation['__INSTANCES__'][0]; - if (!host) { - return args; - } - - if (!host.options.shared[pkgName]) { - return args; - } - - args.resolver = function () { - const hostVersion = host.options.shared[pkgName][0].version; - - if (hostVersion !== version) { - console.warn(`Version mismatch for "${pkgName}":" host app: ${hostVersion} remote: ${version}. Attempting to use host app version...`); - } - - args.shareScopeMap[scope][pkgName][version] = - host.options.shared[pkgName][0]; - - - console.log('Replaced share resolution for', pkgName, 'with version', version, 'to host app shared version', host.options.shared[pkgName][0]); - return { - shared: args.shareScopeMap[scope][pkgName][version], - useTreesShaking: false, - }; - }; - - return args; - }, - // async beforeLoadShare(args) { - // console.log('beforeLoadShare', args); - // return args; - // }, - // beforeRegisterShare(args) { - // console.log('beforeRegisterShare', args); - // return args; - // }, - // async afterResolve(args) { - // console.log('afterResolve', args); - // return args; - // }, - // async loadShare(args) { - // console.log('loadShare:', args); - // return args; - // }, + // collect requests for shares that are different versions from the host app's provided versions for debugging purposes. + // accessible in the console via the __DEBUG_MISSED_DEPS__ global variable. async beforeLoadShare(args) { - console.log('beforeloadShare:', args); - - if (!globalThis.__NON_HOST_REMOTE_SHARE__) { - globalThis.__NON_HOST_REMOTE_SHARE__ = []; + if (!globalThis.__DEBUG_MISSED_DEPS__) { + globalThis.__DEBUG_MISSED_DEPS__ = []; } const hostInstance = __FEDERATION__.__INSTANCES__[0]; if (!hostInstance) { return args; } - const hostShared = hostInstance.options.shared[args.pkgName][0]; + + const { origin, shareInfo, pkgName } = args; + + const hostShared = hostInstance.options.shared[pkgName][0]; if (!hostShared) { return args; @@ -86,19 +26,15 @@ const HostOverrideSharePlugin = () => { let hostVersion = hostShared.version; - if (args.shareInfo.version !== hostVersion) { - - globalThis.__NON_HOST_REMOTE_SHARE__.push(args.shareInfo); + if (shareInfo.shareConfig.requiredVersion !== hostVersion) { + if (globalThis.__DEBUG_MISSED_DEPS__.findIndex(s => s.pkgName === pkgName && s.remoteApp === origin.name) === -1) { + globalThis.__DEBUG_MISSED_DEPS__.push({ pkgName, hostVersion: hostVersion, remoteApp: origin.name, ...shareInfo }); + } } return args; }, - // initContainerShareScopeMap(args) { - // console.log('initContainerShareScopeMap:', args); - // return args; - // } - }; }; diff --git a/webpack/remote-runtime-plugin.js b/webpack/remote-runtime-plugin.js index d449bba..f02832e 100644 --- a/webpack/remote-runtime-plugin.js +++ b/webpack/remote-runtime-plugin.js @@ -7,7 +7,6 @@ const RemoteRuntimePlugin = () => ({ name: 'remote-runtime-plugin', beforeInit(args) { - console.log('BeforeInit Remote', args); // get override plugin from host instance... const hostInstance = __FEDERATION__.__INSTANCES__[0]; @@ -20,10 +19,9 @@ const RemoteRuntimePlugin = () => ({ } // injects it into new instance at runtime. - if (!args.userOptions.plugins) { - args.userOptions.plugins = []; - }; - args.userOptions.plugins.unshift(hostOverridePlugin); + const { origin } = args; + origin.registerPlugin(hostOverridePlugin); + return args; }, });