-
Notifications
You must be signed in to change notification settings - Fork 3
STRIPES-861: Setup module federation #105
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
mkuklis
wants to merge
22
commits into
main
Choose a base branch
from
STRIPES-861
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
bc644ce
STRIPES-861: Setup module federation
mkuklis b9431f1
Cleanup
mkuklis 467f390
Cleanup
mkuklis 7c3576a
cleanup
mkuklis 2deed9e
Use shutdown hook
mkuklis 49189bd
Cleanup
mkuklis 12cc324
Cleanup
mkuklis 60b5dae
Cleanup
mkuklis 97ba155
Add required version to shared singletons
mkuklis 8c47f56
Start remotes automatically
mkuklis 84c0199
Expose icons via public endpoint
mkuklis 54db9c9
react-query provides a context, so must be a singleton
zburke ecaa7a6
STCOR-726 map sounds directory for remote applications
zburke 42cac16
current versions
zburke 12613ed
separate handling of stripes-components and application icons
zburke 0c73e79
resolve conflicts
JohnC-80 626a7c8
provide registry url in stripes-config
JohnC-80 b399047
resolve conflict
JohnC-80 08068ed
collect translations from 'StripesDeps' modules in built translations
JohnC-80 8889e08
get the app's main entry from package.json
JohnC-80 a8b5fb0
ensure there are aliases before member access
JohnC-80 fddfec2
expand the singletons for the platform
JohnC-80 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| // 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', | ||
| 'react-query': '^3.39.3', | ||
| 'react-redux': '^8.1', | ||
| 'react-router': '^5.2.0', | ||
| 'react-router-dom': '^5.2.0', | ||
| 'redux-observable': '^1.2.0', | ||
| 'rxjs': '^6.6.3' | ||
| }; | ||
|
|
||
| const defaultRegistryUrl = 'http://localhost:3001/registry'; | ||
|
|
||
| module.exports = { | ||
| defaultRegistryUrl, | ||
| singletons, | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,145 @@ | ||
| 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'); | ||
| const esbuildLoaderRule = require('./webpack/esbuild-loader-rule'); | ||
| const { singletons } = require('./consts'); | ||
|
|
||
| const buildConfig = (metadata) => { | ||
| 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'); | ||
|
|
||
| // 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 = { | ||
| 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' | ||
| }, | ||
| { | ||
| directory: iconsPath, | ||
| publicPath: '/icons' | ||
| }, | ||
| { | ||
| directory: soundsPath, | ||
| publicPath: '/sounds' | ||
| }, | ||
| ] | ||
| }, | ||
| 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: /\.svg$/, | ||
| 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$/, | ||
| enforce: "pre", | ||
| use: ['source-map-loader'], | ||
| } | ||
| ] | ||
| }, | ||
| // 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 }, | ||
| name, | ||
| filename: 'remoteEntry.js', | ||
| exposes: { | ||
| './MainEntry': mainEntry, | ||
| }, | ||
| shared | ||
| }), | ||
| ] | ||
| }; | ||
|
|
||
| return config; | ||
| } | ||
|
|
||
| module.exports = buildConfig; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| 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 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 = options.port ?? await portfinder.getPortPromise(); | ||
| const host = `http://localhost`; | ||
| const url = `${host}:${port}/remoteEntry.js`; | ||
|
|
||
| const { name: packageName, version, description, stripes, main } = require(packageJsonPath); | ||
| const { permissionSets: _, ...stripesRest } = stripes; | ||
| const name = snakeCase(packageName); | ||
| const metadata = { | ||
| module: packageName, | ||
| version, | ||
| description, | ||
| host, | ||
| port, | ||
| url, | ||
| name, | ||
| main, | ||
| ...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(); | ||
|
|
||
| compiler.hooks.shutdown.tapPromise('AsyncShutdownHook', async (stats) => { | ||
| try { | ||
| await axios.delete(registryUrl, { data: metadata }); | ||
| } catch (error) { | ||
| console.error(`registry not found. Please check ${registryUrl}`); | ||
| } | ||
| }); | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| 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 for machines | ||
| app.get('/registry', (_, res) => res.json(registry)); | ||
|
|
||
| // return entire registry for humans | ||
| app.get('/code', (_, res) => res.send(`<pre>${JSON.stringify(registry, null, 2)}</pre>`)); | ||
Check failureCode scanning / SonarCloud Endpoints should not be vulnerable to reflected cross-site scripting (XSS) attacks
<!--SONAR_ISSUE_KEY:AZOTRIftzi_v0-Uo8dk1-->Change this code to not reflect user-controlled data. <p>See more on <a href="https://sonarcloud.io/project/issues?id=org.folio%3Astripes-webpack&issues=AZOTRIftzi_v0-Uo8dk1&open=AZOTRIftzi_v0-Uo8dk1&branch=STRIPES-861">SonarQube Cloud</a></p>
|
||
|
|
||
| app.delete('/registry', (req, res) => { | ||
| const metadata = req.body; | ||
| const { name } = metadata; | ||
|
|
||
| delete registry.remotes[name]; | ||
|
|
||
| res.status(200).send(`Remote ${name} removed`); | ||
Check failureCode scanning / SonarCloud Endpoints should not be vulnerable to reflected cross-site scripting (XSS) attacks
<!--SONAR_ISSUE_KEY:AZOTRIftzi_v0-Uo8dkz-->Change this code to not reflect user-controlled data. <p>See more on <a href="https://sonarcloud.io/project/issues?id=org.folio%3Astripes-webpack&issues=AZOTRIftzi_v0-Uo8dkz&open=AZOTRIftzi_v0-Uo8dkz&branch=STRIPES-861">SonarQube Cloud</a></p>
|
||
| }); | ||
|
|
||
| app.listen(3001, () => { | ||
| console.log('Starting registry server at http://localhost:3001'); | ||
| }); | ||
| } | ||
| }; | ||
|
|
||
| module.exports = registryServer; | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Check failure
Code scanning / SonarCloud
Endpoints should not be vulnerable to reflected cross-site scripting (XSS) attacks