apps in a shell
The portal is a shell. It takes care of signing in, navigation, search, notifications and settings, and the apps run inside it as federated modules, as part of the shell's own page.
The shell also owns the design system. It loads one copy, and every app uses that copy, so a page never carries two versions of it.
one copy, from the shell
- The shell loads the design system once: the package and its stylesheet, near its root, and one toaster, the place where toasts appear.
- An app shares it instead of bundling it. The app lists the design system as a peer dependency, together with React, and its federation settings share all three packages as single copies. When it runs, the app gets the shell's copy.
- An app doesn't import the stylesheet in the module the shell loads. It imports it only in its own standalone entry, the one used to run the app on its own.
The federation settings look like this, in the shell and in every app:
// in the shell's federation config and in every app's
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
'@nalet/design-system': { singleton: true, requiredVersion: '^0.3.0' },
},
why one copy
Two copies of the design system on one page:
- Look different. Both stylesheets define the same class names, so whichever loads last wins, for every app on the page, and a component from the older copy is drawn with the newer one's styles.
- Lose toasts. The toasts belong to one copy of the library. A toast sent from an app's own copy only shows up in a toaster of that same copy, not in the shell's.
- Cost twice. Each copy is downloaded, read and kept in memory.
inside the shell
The components are made to live inside another app's page:
- React is a peer dependency, so the shell's single copy of React is the one used.
- The modal and the action menu are drawn at the top level of the page, so no panel or table of the shell can cut them off. Toasts are fixed to the window.
- Nothing is global beyond the tokens and the page's base styles, and there is no provider to set up.
- When the shell owns the page's main heading, give a worklist or a form a second-level heading.
versions
- The shell pins one version of the design system, and every app runs on it.
- An app says which versions it was built and tested with, in its federation settings and in its peer dependency. While the system is below 1.0, a new middle number may change how components look, so when the shell moves to one, apps check its changelog.
- An app that needs a newer version waits for the shell to move, rather than bundling the version it wants.
on its own
Outside the shell, when the app runs on its own or in tests, it loads the design system itself: the install, the stylesheet and the fonts, as described on React.
Today:
- The package ships no federation settings; the shell and the apps declare the shared modules in their own builds.
- Nothing warns when two copies end up on one page. Look in the browser's network panel for a second stylesheet, or for a second copy of the package in the app's bundle.
specs
- The shell: imports
@nalet/design-systemand itsstyles.cssnear its root, and renders one<Toaster />. - An app: lists
@nalet/design-system,reactandreact-domas peer dependencies, and shares all three as singletons, with the version range it was tested with inrequiredVersion('^0.3.0'above). - The stylesheet:
styles.cssis imported only in the app's standalone entry, never in the module the shell loads. - Toasts:
toast()reaches only a<Toaster />of the same copy of the library. - Overlays: the modal and the action menu render into
document.bodythrough a React portal; toasts areposition: fixedto the viewport. - Headings:
headingLevel={2}on a worklist or a form when the shell owns theh1. - Two copies: look for a second
styles.cssin the network panel, or a second copy of the package in the app's bundle.