Module Federation Server Side Rendering example using React Suspense.
This example demonstrates a basic shell application loading remote components and rendering them server side. With different types of components styling
It combines three different approaches of styling
- with plain CSS/(CSS Modules)/Scss/Less/(Tailwind)
- React-Jss
- Styled-Components
- There First One
CSS/(CSS Modules)/Scss/Less/(Tailwind)
requires to useisomorphic-style-loader
and exposeStyleContext
from it from each remote that use it. So basically each remote application can expose any amount of components styled withwithStyles
and also need to expose singleStyleContext
which is used in this app.
exposes: {
'./Content': './src/Content',
'./LoaderContext': './src/LoaderContext'
},
- For the React-Jss you can expose any amount of components the only required thing is to add
react-jss
into shared and make it singletone
exposes: {
'./Content': './src/Content',
},
shared: {
...YOUR_SHARED_DEPS,
"react-jss": {
singleton: true,
},
},
- For the Styled-Components you can expose any amount of components the only required thing is to add
styled-components
into shared and make it singleton
exposes: {
'./Content': './src/Content',
'./LoaderContext': './src/LoaderContext'
},
shared: {
...YOUR_SHARED_DEPS,
"styled-components": {
singleton: true,
},
},
Configs:
isomorphic-style-loader
styling type only require to consume the remotereact-jss
- require to addreact-jss
as a shared singleton to your Shell MF configstyled-components
- require to addstyled-components
as a shared singleton to your Shell MF config
Rendering: Basically there is two main parts of this
- Server Side Rendering
- Application Hydration in runtime
The main SSR related staff of each shell is a /shell/server/render.js
;
Overall understanding: before you send the response with your SSR html you need to collect all CSS
that is used inside your application.
For isomorphic-style-loader
and react-jss
this could be done through corresponding ContextProviders during the DOM Rendering function call
and for styled-components
after DOM Rendering;
- for the
isomorphic-style-loader
you need to provideinsertCss
(which will collect styles into some variable and then those could be added to the response.) to each of your consumedStyleContext
- for the
react-jss
you need to provideSheetsRegistry
instance to theJssProvider
and then you will be able to extractCSS
from this instance; - for
styled-components
you need to createServerStyleSheet
instance use its methodcollectStyles
and after that you will be able to extract style tags withgetStyleTags
method of it.
The main Application Hydration related things could be found in corresponding library official doc:
- if you are going to consume components styled with
isomorphic-style-loader
you will need to import eachStyleContext
and compose them for your App. There is basic example of React Context Provider compositionComposeProviders.js
(of course you can use your own) For this approach the basic scenario inrender.js
will look something like thisimport React from 'react'; import { renderToString } from 'react-dom/server'; import App from '../src/components/App'; // Your Compose Component import Compose from '../src/ComposeProviders'; // Your Array of `isomorphic-style-loader` StyleContext providers import providers from '../src/StyleProviders'; export default async function(req, res) { const css = new Set(); // required function that will collect css const insertCss = (...styles) => { styles.forEach(style => css.add(style._getCss())); }; const combinedProviders = providers.map(p => [p, { value: { insertCss } }]); const component = renderToString( <Compose providers={combinedProviders}> <App /> </Compose> ); const html = `<!doctype html> <html> <head> <!--Here we add our collected styles--> <style>${[...css].join('')}</style> </head> <body> <div id="root">${component}</div> <script async data-chunk="main" src="http://localhost:4001/static/main.js"></script> </body> </html>` res.status(200).send(html); };
- if you are going to consume components styled with
react-jss
you will need to useJssProvider
and compose it for your App. There is basic example of React Context Provider compositionComposeProviders.js
(of course you can use your own) For this approach the basic scenario inrender.js
will look something like thisimport React from 'react'; import { renderToString } from 'react-dom/server'; import App from '../src/components/App'; // Your Compose Component import Compose from '../src/ComposeProviders'; // Jss related import {JssProvider, SheetsRegistry} from 'react-jss'; export default async function(req, res) { const sheets = new SheetsRegistry(); const combinedProviders = [[JssProvider, { registry: sheets }]]; const component = renderToString( <Compose providers={combinedProviders}> <App /> </Compose> ); const html = `<!doctype html> <html> <head> <!--Here we add our collected styles--> <style>${sheets.toString()}</style> </head> <body> <div id="root">${component}</div> <script async data-chunk="main" src="http://localhost:4001/static/main.js"></script> </body> </html>` res.status(200).send(html); };
- if you are going to consume components styled with
styled-components
you will need to useServerStyleSheet
and use it for App Rendering. There is basic example of React Context Provider compositionComposeProviders.js
(of course you can use your own) For this approach the basic scenario inrender.js
will look something like thisimport React from 'react'; import { renderToString } from 'react-dom/server'; import App from '../src/components/App'; // Your Compose Component import Compose from '../src/ComposeProviders'; // Styled-components related import {ServerStyleSheet} from "styled-components"; export default async function(req, res) { const sheet = new ServerStyleSheet(); const combinedProviders = []; const component = renderToString(sheet.collectStyles( <Compose providers={combinedProviders}> <App /> </Compose> )); const styleTags = sheet.getStyleTags(); const html = `<!doctype html> <html> <head> <!--Here we add our collected styles--> ${styleTags} </head> <body> <div id="root">${component}</div> <script async data-chunk="main" src="http://localhost:4001/static/main.js"></script> </body> </html>` res.status(200).send(html); };
Exposed Styling | React |
---|---|
Css | ✅ |
Scss | ✅ |
Less | ✅ |
Css Module | ✅ |
react-jss | ✅ |
styled-components | ✅ |
tailwind css (as module) | ✅ |
Run yarn
to install the dependencies.
Run yarn build
to build the packages.
Run yarn serve
in the shell and related expose remotes folders to start the servers.
This will build the packages and serve them.
Expose Remotes
- localhost:3001 (STANDALONE CSS-EXPOSE)
- localhost:3002 (STANDALONE JSS-EXPOSE)
- localhost:3003 (STANDALONE TAILWIND EXPOSE)
- localhost:3004 (STANDALONE SCSS-EXPOSE)
- localhost:3005 (STANDALONE STYLED-COMPONENT-EXPOSE)
- localhost:3006 (STANDALONE CSS-MODULE-EXPOSE)
- localhost:3007 (STANDALONE LESS-EXPOSE)
Shells
- localhost:4000 (SHELL CSS-JSS)
- localhost:4000 (SHELL CSS-SCSS)
- localhost:4000 (SHELL JSS-STYLED-COMPONENTS)
- localhost:4000 (SHELL JSS-STYLED-COMPONENTS-CSS-MODULE)
- localhost:4000 (SHELL LESS-SCSS)
- localhost:4000 (SHELL SCSS-TAILWIND)