{"id":14976518,"url":"https://github.com/patricklafrance/wmfnext-shell","last_synced_at":"2026-03-06T01:01:46.266Z","repository":{"id":65255704,"uuid":"578374991","full_name":"patricklafrance/wmfnext-shell","owner":"patricklafrance","description":"Candidate for an application shell specialized for federated application using Webpack Module Federation and React Router","archived":false,"fork":false,"pushed_at":"2023-03-01T17:38:41.000Z","size":709,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-08T10:48:45.416Z","etag":null,"topics":["federated-application","federation","microfrontend","microfrontends","module-federation","react","react-router","spa","webpack","webpack-module-federation"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/patricklafrance.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-12-14T22:50:50.000Z","updated_at":"2023-08-30T11:40:33.000Z","dependencies_parsed_at":"2024-10-24T19:04:44.976Z","dependency_job_id":"b0efd0b5-50e3-49a4-85c1-6ef5ad261025","html_url":"https://github.com/patricklafrance/wmfnext-shell","commit_stats":{"total_commits":133,"total_committers":2,"mean_commits":66.5,"dds":0.09022556390977443,"last_synced_commit":"d04f05d2acb09ba4306b7d8f583e6a029a64f58b"},"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/patricklafrance/wmfnext-shell","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patricklafrance%2Fwmfnext-shell","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patricklafrance%2Fwmfnext-shell/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patricklafrance%2Fwmfnext-shell/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patricklafrance%2Fwmfnext-shell/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/patricklafrance","download_url":"https://codeload.github.com/patricklafrance/wmfnext-shell/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patricklafrance%2Fwmfnext-shell/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30156845,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-05T22:39:40.138Z","status":"ssl_error","status_checked_at":"2026-03-05T22:39:24.771Z","response_time":93,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["federated-application","federation","microfrontend","microfrontends","module-federation","react","react-router","spa","webpack","webpack-module-federation"],"created_at":"2024-09-24T13:54:00.914Z","updated_at":"2026-03-06T01:01:46.231Z","avatar_url":"https://github.com/patricklafrance.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# wmfnext-shell\n\n\u003e **Warning**\n\u003e\n\u003e This repository will not be maintained as it's purpose is to inspire teams by showcasing how a federated SPA could be build on top of [Webpack Module Federation](https://webpack.js.org/concepts/module-federation) and [React Router](https://reactrouter.com/).\n\nWebpack Module Federation is a powerful tool for sharing code and dependencies across independent codebases. However, as is, it's pretty raw as it's a low level mecanism.\n\nThis shell adds a thin layer on top of Webpack Module Federation by complementing the sharing mechanism with additional functionalities. These functionalities aim to ease the adoption of a federated application architecture by providing an opinionated direction on how it should be implemented.\n\n### How does it work?\n\n1. At bootstrap, the host application will try to load predefined modules and call a registration function matching a specific name and signature for each module that is successfully loaded.\n\n2. During it's registration, a module will receive the shared services of the federation application and use them to dynamically register its routes and navigation items.\n\n3. Once all the remote modules are registered, the host application will create a React Router instance with the registered routes and will also render a navigation menu with the registered navigation items.\n\nThat's it in a nutshell. Of course, there's more to it, but those are the main ideas.\n\nOne more thing, a module is always a set of pages for a unique subdomain of the application. There is no such thing as loading a standalone remote component with this shell.\n\n### Remote modules vs Static modules\n\nLoading remote modules at runtime with Webpack Module Federation is the reason why this shell exists and it is what we recommend products to aim for. It enables teams to be fully autonomous by deploying their modules independently from the other parts of the application.\n\nHowever, we understand that _teams working on mature products_ will most likely prefer to _gradually migrate towards a distributed architecture_ by first extracting subdomains into independent modules in their current monolithic setup before fully committing to remote modules loaded at runtime.\n\nTo facilitate the transition, this shell also supports static modules registered at build time.\n\nA static module is a local code bundle exposing a registration function. A registration function could be imported from a standalone package, a sibling project in a monorepo setup, or even a local folder of the host application.\n\nBoth remote and static modules can be used in the same application as this shell supports dual bootstrapping. For example, an application could be configured to load a few remote modules at runtime and also register a few static modules at build time.\"\n\n## 📌 Table of contents\n\n- [Features](#-features)\n- [Examples](#-examples)\n- [Installation](#-installation)\n- [Basic usage](#-basic-usage)\n- [Guides](#-guides)\n    - [Setup an host application](#setup-an-host-application)\n    - [Setup a remote module](#setup-a-remote-module)\n    - [Register a module routes](#register-a-module-routes)\n    - [Re-render the host application after the remote modules are ready](#re-render-the-host-application-after-the-remote-modules-are-ready)\n    - [Setup a static module](#setup-a-static-module)\n    - [Register a module navigation items](#register-a-module-navigation-items)\n    - [Isolate module failures](#isolate-module-failures)\n    - [Override the host layout](#override-the-host-layout)\n    - [Share a user session](#share-a-user-session)\n    - [Use the event bus](#use-the-event-bus)\n    - [Share a custom service](#share-a-custom-service)\n    - [Use a custom logger](#use-a-custom-logger)\n    - [Data and state](#data-and-state)\n    - [Develop a module in isolation](#develop-a-module-in-isolation)\n- [API](#-api)\n    - [wmfnext-shell package](#wmfnext-shell-package)\n        - [Runtime](#runtime)\n        - [Static modules registration](#static-modules-registration)\n        - [Routing](#routing)\n        - [Navigation items](#navigation-items)\n        - [Logging](#logging)\n        - [Messaging](#messaging)\n        - [Utils](#utils)\n    - [wmfnext-remote-loader package](#wmfnext-remote-loader-package)\n        - [Remote modules registration](#remote-modules-registration)\n        - [Webpack config utils](#webpack-config-utils)\n    - [wmfnext-fakes package](#wmfnext-fakes-package)\n- [Contributors guide](./CONTRIBUTING.md)\n\n## 🙌 Features\n\n- Loading of hosted remote modules at runtime\n- Loading of static modules at build time\n- Routing\n- Navigation\n- Shared user session\n- Cross application messaging\n- Logging\n- Failures isolation\n- Development of modules in isolation\n\n## 🎉 Examples\n\n- [Live example](https://wmfnext-host.netlify.app) hosted on Netlify.\n- [wmfnext-host](https://github.com/patricklafrance/wmfnext-host) is an example of an host application. The repository also includes a static module example.\n- [wmfnext-remote-1](https://github.com/patricklafrance/wmfnext-remote-1) is an example of a remote module.\n\n## 🤘 Installation\n\nTo install the packages, [open a terminal in VSCode](https://code.visualstudio.com/docs/editor/integrated-terminal#_managing-multiple-terminals) and execute the following command at the root of the projects workspaces (host and modules):\n\n```bash\nyarn add wmfnext-shell\n```\n\n❗ If you wish to include remote modules also execute the following command at the root of the projects workspaces (host and every remote module):\n\n```bash\nyarn add wmfnext-remote-loader\n```\n\nOnce, installed, we recommend that you configure your projects to use [ESM](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) by default. To do so, open the `package.json` file and add the `type` property:\n\n```json\n{\n    \"type\": \"module\"\n}\n```\n\n## 📄 Basic usage\n\nIf you don't want to go through our guides, here's a minimal example of how to create a federated SPA using this shell. The example focuses solely on a remote modules and omits static modules.\n\nTo learn more about the built-in features/options of this shell and static modules, refer to the [guides](#-guides) and the [API](#-api) section.\n\n### Host application\n\n👉 First, create a new application with the following files:\n\n```\nhost-app\n├── src\n├──── App.tsx\n├──── RootLayout.tsx\n├──── HomePage.tsx\n├──── bootstrap.tsx\n├──── index.ts\n├── webpack.config.js\n```\n\n👉 Then, in the `boostrap.tsx` file, instanciate the shell `Runtime` and load the remote module:\n\n```tsx\n// host - boostrap.tsx\n\nimport { ConsoleLogger, RuntimeContext, Runtime } from \"wmfnext-shell\";\nimport type { RemoteDefinition } from \"wmfnext-remote-loader\";\nimport { App } from \"./App\";\nimport { createRoot } from \"react-dom/client\";\nimport { registerRemoteModules } from \"wmfnext-remote-loader\";\n\nconst Remotes: RemoteDefinition[] = [\n    {\n        url: \"http://localhost:8081\",\n        name: \"remote1\"\n    }\n];\n\n// Create the shell runtime.\nconst runtime = new Runtime({\n    loggers: [new ConsoleLogger()]\n});\n\n// Register the remote module.\nregisterRemoteModules(Remotes, runtime);\n\nconst root = createRoot(document.getElementById(\"root\"));\n\n// Render the React app.\nroot.render(\n    \u003cRuntimeContext.Provider value={runtime}\u003e\n        \u003cApp /\u003e\n    \u003c/RuntimeContext.Provider\u003e\n);\n```\n\n\u003e To learn more about the `bootstrap.tsx` file, read the following [article](https://dev.to/infoxicator/module-federation-shared-api-ach#using-an-async-boundary).\n\n👉 Next, in the `App.tsx` file, retrieve the routes registered by the module and create the router:\n\n```tsx\n// host - App.tsx\n\nimport { RouterProvider, createBrowserRouter } from \"react-router-dom\";\nimport { lazy, useMemo } from \"react\";\nimport { useRoutes } from \"wmfnext-shell\";\nimport { RootLayout } from \"./RootLayout\";\nimport { useAreRemotesReady } from \"wmfnext-remote-loader\";\n\nconst HomePage = lazy(() =\u003e import(\"./HomePage\"));\n\nexport function App() {\n    // Re-render the application once the remote module is registered.\n    const isReady = useAreRemotesReady();\n\n    // Retrieve the routes registered by the module.\n    const routes = useRoutes(runtime);\n\n    // Create the router with an home page and the module routes.\n    const router = useMemo(() =\u003e {\n        return createBrowserRouter([\n            {\n                path: \"/\",\n                element: \u003cRootLayout /\u003e,\n                children: [\n                    {\n                        index: true,\n                        element: \u003cHomePage /\u003e\n                    },\n                    ...routes\n                ]\n            }\n        ]);\n    }, [routes]);\n\n    // Display a loading until the remote module is registered.\n    if (!isReady) {\n        return \u003cLoading /\u003e;\n    }\n\n    // Render the router.\n    return (\n        \u003cRouterProvider\n            router={router}\n            fallbackElement={\u003cLoading /\u003e}\n        /\u003e\n    );\n}\n```\n\n👉 Then, create the `RootLayout` component to render the navigation items:\n\n```tsx\n// host - RootLayout.tsx\n\nimport { Link, Outlet } from \"react-router-dom\";\nimport { Suspense } from \"react\";\nimport { useNavigationItems } from \"wmfnext-shell\";\n\nexport function RootLayout() {\n    // Retrieve the navigation items registered by the module.\n    const navigationItems = useNavigationItems();\n\n    return (\n        \u003c\u003e\n            \u003cnav\u003e\n                \u003cul\u003e\n                    {navigationItems.map((x, index) =\u003e {\n                        \u003cli key={index}\u003e\n                            \u003cLink to={x.to}\u003e\n                                {x.content}\n                            \u003c/Link\u003e\n                        \u003c/li\u003e\n                    })}\n                \u003c/ul\u003e\n            \u003c/nav\u003e\n            \u003cSuspense fallback={\u003cLoading /\u003e}\u003e\n                \u003cOutlet /\u003e\n            \u003c/Suspense\u003e\n        \u003c/\u003e\n    );\n}\n```\n\n👉 Finally, add `ModuleFederationPlugin` to the `webpack.config.js` file by using the `createHostPlugin()` function:\n\n```js\n// host webpack.config.js\n\nimport { createHostPlugin } from \"wmfnext-remote-loader/webpack.js\";\nimport packageJson from \"../package.json\" assert { type: \"json\" };\n\nexport default {\n    plugins: [\n        createHostPlugin(\"host\", packageJson)\n    ]\n}\n```\n\n👉 Start the host application, you should see the home page. Even if the remote module application doesn't exist yet, the host application will render what is currently available, e.g. only the host application at the moment.\n\n### Remote module\n\n👉 Start by creating a new application with a `register.tsx` file and a page:\n\n```\nremote-1\n├── src\n├──── register.tsx\n├──── Page1.tsx\n├── webpack.config.js\n```\n\n👉 Then, use the `register.tsx` file to register the module pages and navigation items:\n\n```tsx\n// remote-1 - register.tsx\n\nimport { ModuleRegisterFunction, Runtime, registerRoutes, registerNavigationItems } from \"wmfnext-shell\";\nimport { lazy } from \"react\";\n\nconst Page1 = lazy(() =\u003e import(\"./Page1\"));\n\nexport const register: ModuleRegisterFunction = (runtime: Runtime) =\u003e {\n    // This route will then be returned by \"useRoutes()\" in the host application.\n    runtime.registerRoutes([\n        {\n            {\n                path: \"/remote1/page-1\",\n                element: \u003cPage1 /\u003e\n            },\n        }\n    ]);\n\n    // This navigation item will then be returned by \"useNavigationItems()\" in the host application.\n    runtime.registerNavigationItems([\n        {\n            to: \"/remote1/page-1\",\n            content: \"Remote1/Page 1\"\n        }\n    ]);\n}\n```\n\n👉 And add the `ModuleFederationPlugin` to the `webpack.config.js` file by using the `createModulePlugin()` function. Make sure the `entry` property value is set to `./src/register.tsx` rather than the default index file:\n\n```js\nimport { createModulePlugin } from \"wmfnext-remote-loader/webpack.js\";\nimport packageJson from \"../package.json\" assert { type: \"json\" };\n\nexport default {\n    entry: \"./src/register.tsx\",\n    plugins: [\n        createModulePlugin(\"remote1\", packageJson)\n    ]\n}\n```\n\n👉 Start the remote module application, then the host application. You should see a navigation item named _\"Remote1/Page 1\"_. Click on the link to navigate to the federated page.\n\n\u003e If you are having issues, make sure that both applications `package.json` files have `react`, `react-dom`, `react-router-dom`, `wmfnext-shell` and `wmfnext-remote-loader` listed in their dependencies. The dependency versions should be the same for both applications.\n\n## 📚 Guides\n\nIn the following guides, we'll go through the process of creating a federated SPA leveraging this shell. As we progress, we'll gradually add more parts to the application, ultimately resulting in an application that matches the following diagram:\n\n\u003cbr /\u003e\n\u003cp align=\"center\"\u003e\n    \u003cimg alt=\"Target application\" src=\"./diagram-dark.svg#gh-dark-mode-only\" /\u003e\n    \u003cimg alt=\"Target application\" src=\"./diagram-light.svg#gh-light-mode-only\" /\u003e\n\u003c/p\u003e\n\n### Setup an host application\n\n👉 First, create an host application. According to [Webpack Module Federation](https://webpack.js.org/concepts/module-federation/) best practices we'll create 3 files:\n\n```\nhost-app\n├── src\n├──── App.tsx\n├──── bootstrap.tsx\n└──── index.ts\n```\n\n👉 Then, add an `App` component to the `App.tsx` file. The `App` component will be the entry point of the React application:\n\n```tsx\n// host - App.tsx\n\nexport function App() {\n    return (\n        \u003cdiv\u003eHello world!\u003c/div\u003e\n    );\n}\n```\n\n👉 Then, add a dynamic import to the `bootstrap.tsx` file:\n\n\u003e This indirection is called an \"async boundary\". It is necessary so that Webpack can load all the remote modules and their dependencies before rendering the host\n\u003e application. More information can be found [here](https://dev.to/infoxicator/module-federation-shared-api-ach#using-an-async-boundary).\n\u003e\n\u003e If you're not using any remote modules you don't need a `bootstrap.tsx` file.\n\n```ts\n// host - index.ts\n\nimport(\"./bootstrap\");\n```\n\n👉 Next, add the following code to the `boostrap.tsx` file to render the React application:\n\n```tsx\n// host - bootstrap.tsx\n\nimport { App } from \"./App\";\nimport { createRoot } from \"react-dom/client\";\n\nconst root = createRoot(document.getElementById(\"root\"));\n\nroot.render(\n    \u003cApp /\u003e\n);\n```\n\n\u003e **Note**\n\u003e\n\u003e If your application is not loading any remote modules, you can skip the `bootstrap.tsx` file and move the previous code to the `index.ts` file.\n\nNow, let's say that you want to load a remote module at runtime using [Webpack Module Federation](https://webpack.js.org/concepts/module-federation/) (make sure you installed `wmfnext-remote-loader` dependency).\n\n👉 First, add the [ModuleFederationPlugin](https://webpack.js.org/plugins/module-federation-plugin) plugin to the Webpack configuration with the `createHostPlugin(moduleName, packageJson, options)` function:\n\n```js\n// host - webpack.dev.js\n\nimport { createHostPlugin } from \"wmfnext-remote-loader/webpack.js\";\nimport packageJson from \"./package.json\" assert { type: \"json\" };\n\nexport default {\n    plugins: [\n        // Only use the ModuleFederationPlugin plugin if you\n        // are loading remote modules at runtime.\n        createHostPlugin(\"host\", packageJson)\n    ]\n}\n```\n\n\u003cdetails\u003e\n    \u003csummary\u003eView the full Webpack config\u003c/summary\u003e\n    \u003cbr /\u003e\n\n```js\n// host - webpack.dev.js\n\nimport { createHostPlugin, getFileDirectory } from \"wmfnext-remote-loader/webpack.js\";\nimport HtmlWebpackPlugin from \"html-webpack-plugin\";\nimport path from \"path\";\nimport packageJson from \"./package.json\" assert { type: \"json\" };\n\nconst __dirname = getFileDirectory(import.meta);\n\n/** @type {import(\"webpack\").Configuration} */\nexport default {\n    mode: \"development\",\n    target: \"web\",\n    devtool: \"inline-source-map\",\n    devServer: {\n        port: 8080,\n        historyApiFallback: true\n    },\n    entry: \"./src/index.ts\",\n    output: {\n        // The trailing / is important otherwise hot reload doesn't work.\n        publicPath: \"http://localhost:8080/\"\n    },\n    module: {\n        rules: [\n            {\n                test: /\\.(ts|tsx)$/,\n                exclude: /node_modules/,\n                use: {\n                    loader: \"ts-loader\",\n                    options: {\n                        transpileOnly: true,\n                        configFile: path.resolve(__dirname, \"tsconfig.json\")\n                    }\n                }\n            },\n            {\n                // https://stackoverflow.com/questions/69427025/programmatic-webpack\n                // -jest-esm-cant-resolve-module-without-js-file-exten\n                test: /\\.js/,\n                resolve: {\n                    fullySpecified: false\n                }\n            },\n            {\n                test: /\\.(css)$/,\n                use: [\"style-loader\", \"css-loader\"]\n            },\n            {\n                test: /\\.(png|jpe?g|gif)$/i,\n                type: \"asset/resource\"\n            }\n        ]\n    },\n    resolve: {\n        // Must add \".js\" for files imported from node_modules.\n        extensions: [\".js\", \".ts\", \".tsx\", \".css\"]\n    },\n    plugins: [\n        // Only use the ModuleFederationPlugin plugin if you\n        // are loading remote modules at runtime.\n        createHostPlugin(\"host\", packageJson),\n        new HtmlWebpackPlugin({\n            template: \"./public/index.html\"\n        })\n    ]\n};\n```\n\n```html\n\u003c!-- host - public/index.html --\u003e\n\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n  \u003chead\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n    \u003cdiv id=\"root\"\u003e\u003c/div\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\u003c/details\u003e\n\n\u003e **Note**\n\u003e\n\u003e If you are using a [CommonJS](https://en.wikipedia.org/wiki/CommonJS) Webpack configuration file, import the `createHostPlugin()` function from `wmfnext-remote-loader/webpack.cjs` instead.\n\nYou may have noticed that the `ModuleFederationPlugin` instance is created by the `createHostPlugin()` function.\n\nThe `createHostPlugin()` function is a built-in utility function that helps configure the federation plugin by enforcing the shell conventions and adding the mandatory shared dependencies.\n\nThe function takes as it's first parameter the name of the application and as its second parameter a `package.json` configuration. At build time, the function parses the package configuration to search for the version of the mandatory shared dependencies of the shell.\n\n\u003e Mandatory shared dependencies are libraries like [react](https://reactjs.org/), react-dom, [react-router-dom](https://reactrouter.com/) and the shell itself.\n\nThe `createHostPlugin()` function also accepts a third parameter, an object literal used to specify options. One of those option is a `sharedDependencies` object. With this object, you can specify additional shared dependencies that are specific to your application, such as a design system library.\n\nIf the `requiredVersion` of a shared dependency is not specified, the `createHostPlugin()` function will try to resolve the dependency version from the provided package configuration.\n\nThe `sharedDependencies` object support the same syntax as the `ModuleFederationPlugin` [shared object](https://webpack.js.org/plugins/module-federation-plugin/#sharing-hints).\n\n```js\nexport default {\n    plugins: [\n        createHostPlugin(\n            \"host\",\n            packageJson,\n            {\n                sharedDependencies: {\n                    \"@sharegate/orbit-ui\": {\n                        singleton: true,\n                        requiredVersion: \"10.0.0\"\n                    }\n                }\n            }\n        )\n    ]\n};\n```\n\n👉 Then, add your [TypeScript configuration](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html) to the root of the project and include a command in the `package.json` file to start Webpack in development mode:\n\n```json\n{\n    \"scripts\": {\n        \"dev\": \"webpack serve --config webpack.dev.js\"\n    }\n}\n```\n\n\u003e **Note**\n\u003e\n\u003e You can find a TS configuration sample in the [host application example repository](https://github.com/patricklafrance/wmfnext-host/tree/master/packages/app).\n\n👉 Now that the basics are covered, let's dive into the more exciting stuff and start using built-in features of the shell. Open the `bootstrap.tsx` file and instanciate a `Runtime` object:\n\n```tsx\n// host - bootstrap.tsx\n\nimport { ConsoleLogger, RuntimeContext, Runtime } from \"wmfnext-shell\";\nimport { App } from \"./App\";\nimport { createRoot } from \"react-dom/client\";\n\n// Instanciate a runtime instance to share among the host and the modules.\n// The runtime instance will give modules access to functionalities such as\n// routing and navigation.\nconst runtime = new Runtime({\n    // The shell comes with a basic console logger.\n    loggers: [new ConsoleLogger()]\n});\n\nconst root = createRoot(document.getElementById(\"root\"));\n\nroot.render(\n    \u003cRuntimeContext.Provider value={runtime}\u003e\n        \u003cApp /\u003e\n    \u003c/RuntimeContext.Provider\u003e\n);\n```\n\n👉 Then, register the remote module:\n\n```tsx\n// host - bootstrap.tsx\n\nimport { ConsoleLogger, RuntimeContext, Runtime } from \"wmfnext-shell\";\nimport type { RemoteDefinition } from \"wmfnext-remote-loader\";\nimport { App } from \"./App\";\nimport { createRoot } from \"react-dom/client\";\nimport { registerRemoteModules } from \"wmfnext-remote-loader\";\n\nconst Remotes: RemoteDefinition[] = [\n    {\n        url: \"http://localhost:8081\",\n        // The remote name must match the name defined in the remote application\n        // Webpack configuration that we'll define later.\n        name: \"remote1\"\n    }\n];\n\n// Instanciate a runtime instance to share among the host and the modules.\n// The runtime instance will give modules access to functionalities such as\n// routing and navigation.\nconst runtime = new Runtime({\n    // The shell comes with a basic console logger.\n    loggers: [new ConsoleLogger()]\n});\n\nregisterRemoteModules(Remotes, runtime);\n\nconst root = createRoot(document.getElementById(\"root\"));\n\nroot.render(\n    \u003cRuntimeContext.Provider value={runtime}\u003e\n        \u003cApp /\u003e\n    \u003c/RuntimeContext.Provider\u003e\n);\n```\n\n\u003e The remote modules are registered in the `bootstrap.ts` file rather than the `App.tsx` file as they must be loaded inside the \"async boundary\".\n\nAt bootstrap, the `registerRemoteModules(modules, runtime)` function will try to load the provided modules asynchronously, and then register every module that succesfully loads.\n\nIf an error occurs during the process, a message will automatically be logged with the runtime logger.\n\nIf you prefer to handle errors manually, you can chain an handler to the returned [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) object:\n\n```js\nimport { RegistrationError } from \"wmfnext-remote-loader\";\n\nregisterRemoteModules(Remotes, runtime)\n    .then((errors: RegistrationError[]) =\u003e {\n        if (errors.length \u003e 0) {\n            runtime.logger.error(\"Errors occured while registering remotes: \", errors);\n        }\n    });\n```\n\n\u003e **Note**\n\u003e\n\u003e The `registerRemoteModules()` function can only be called once. Attempting to call the function twice will result in an error.\n\n👉 Start the host application with the `dev` command. You should see a page displaying _\"Hello world!\"_. Even if the remote module application has not been created yet, the host application will render what is currently available. In this case, it's the default page of the host application.\n\n### Setup a remote module\n\nNow that we have a working host application, it's time to create our first module.\n\n👉 Let's start by creating a new application with the following files:\n\n```\nremote-1\n├── src\n├──── App.tsx\n└──── index.tsx\n```\n\n👉 Then, add an `App` component to the `App.tsx` file. It will be the entry point of the React application:\n\n```tsx\n// remote-1 - App.tsx\n\nexport function App() {\n    return (\n        \u003cdiv\u003eHello from remote!\u003c/div\u003e\n    );\n}\n```\n\n👉 And, add the following code to the `index.tsx` file to render the React application:\n\n```tsx\n// remote-1 - index.tsx\n\nimport { App } from \"./App\";\nimport { createRoot } from \"react-dom/client\";\n\nconst root = createRoot(document.getElementById(\"root\"));\n\nroot.render(\n    \u003cApp /\u003e\n);\n```\n\n👉 Next, add the [ModuleFederationPlugin](https://webpack.js.org/plugins/module-federation-plugin) plugin to the Webpack config file with the `createModulePlugin(moduleName, packageJson, options)` function:\n\n```js\n// remote-1 - webpack.dev.js\n\nimport { createModulePlugin } from \"wmfnext-remote-loader/webpack.js\";\nimport packageJson from \"./package.json\" assert { type: \"json\" };\n\nexport default {\n    plugins: [\n        createModuleConfiguration(\"remote1\", packageJson)\n    ]\n}\n```\n\n\u003cdetails\u003e\n    \u003csummary\u003eView the full Webpack configuration\u003c/summary\u003e\n    \u003cbr /\u003e\n\n```js\n// remote-1 - webpack.dev.js\n\nimport HtmlWebpackPlugin from \"html-webpack-plugin\";\nimport { createModulePlugin, getFileDirectory } from \"wmfnext-remote-loader/webpack.js\";\nimport path from \"path\";\nimport packageJson from \"./package.json\" assert { type: \"json\" };\n\nconst __dirname = getFileDirectory(import.meta);\n\n/** @type {import(\"webpack\").Configuration} */\nexport default {\n    mode: \"development\",\n    target: \"web\",\n    devtool: \"inline-source-map\",\n    devServer: {\n        port: 8081,\n        historyApiFallback: true,\n        // Otherwise hot reload in the host failed with a CORS error.\n        headers: {\n            \"Access-Control-Allow-Origin\": \"*\"\n        }\n    },\n    entry: \"./src/register.tsx\"\n    output: {\n        // The trailing / is important otherwise hot reload doesn't work.\n        publicPath: \"http://localhost:8081/\"\n    },\n    module: {\n        rules: [\n            {\n                test: /\\.(ts|tsx)$/,\n                exclude: /node_modules/,\n                use: {\n                    loader: \"ts-loader\",\n                    options: {\n                        transpileOnly: true,\n                        configFile: path.resolve(__dirname, \"tsconfig.json\")\n                    }\n                }\n            },\n            {\n                // https://stackoverflow.com/questions/69427025/programmatic-webpack\n                // -jest-esm-cant-resolve-module-without-js-file-exten\n                test: /\\.js/,\n                resolve: {\n                    fullySpecified: false\n                }\n            },\n            {\n                test: /\\.(css)$/,\n                use: [\"style-loader\", \"css-loader\"]\n            },\n            {\n                test: /\\.(png|jpe?g|gif)$/i,\n                type: \"asset/resource\"\n            }\n        ]\n    },\n    resolve: {\n        // Must add \".js\" for files imported from node_modules.\n        extensions: [\".js\", \".ts\", \".tsx\", \".css\"]\n    },\n    plugins: [\n        createModuleConfiguration(\"remote1\", packageJson),\n        new HtmlWebpackPlugin({\n            template: \"./public/index.html\"\n        })\n    ]\n};\n```\n\n```html\n\u003c!-- remote-1 - public/index.html --\u003e\n\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n  \u003chead\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n    \u003cdiv id=\"root\"\u003e\u003c/div\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\u003c/details\u003e\n\n\u003e **Note**\n\u003e\n\u003e If you are using a [CommonJS](https://en.wikipedia.org/wiki/CommonJS) Webpack configuration file, import the `createModulePlugin()` function from `wmfnext-remote-loader/webpack.cjs` instead.\n\nThe `createModulePlugin()` function serve the same purpose as the `createHostPlugin()` but return a `ModuleFederationPlugin` instance configured for a remote module application instead.\n\n👉 Then, add your [TypeScript configuration](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html) to the root of the project and include a command in the `package.json` file to start Webpack in development mode:\n\n```json\n{\n    \"scripts\": {\n        \"dev\": \"webpack serve --config webpack.dev.js\"\n    }\n}\n```\n\n\u003e **Note**\n\u003e\n\u003e You can find a TS configuration sample in the [remote-1 application example repository](https://github.com/patricklafrance/wmfnext-remote-1).\n\n👉 Start the remote module application with the `dev` command. You should see a page displaying __Hello from remote!__.\n\nNow, as stated in the introduction of this document, the purpose of the shell is to provide an _opinionated direction_ on how to _implement a federation application_.\n\n💡 Our first take is that a module should _always match a subdomain_ of the application business domain and should _only export pages_.\n\nTo do so, by convention, a remote module must share a single file named `remoteEntry.js` and that file must expose a single module named `./register` exporting a `register(runtime, context)` function responsible of registering the pages and navigation items of the remote module.\n\n👉 So, let's create a `register.tsx` file at the root of the remote module project with the following files:\n\n```\nremote-1\n├── src\n├──── App.tsx\n└──── index.ts\n└──── register.tsx\n```\n\n```tsx\n// remote-1 - register.tsx\n\nimport { ModuleRegisterFunction } from \"wmfnext-shell\";\n\nexport const register: ModuleRegisterFunction = (runtime, context) =\u003e {\n    runtime.logger.log(\"Remote 1 registered\", context);\n};\n```\n\nFor now we won't register any routes or navigation items. Instead, we'll use the `Runtime` instance to log something in the console.\n\n👉 Update the Webpack config to use the `/src/register.tsx` file as the entry point of the application rather than the default index file:\n\n```js\nexport default {\n    entry: \"./src/register.tsx\"\n};\n```\n\n👉 In separate terminals, start the remote module application with the `dev` command, then the host application with the `dev` command. Refresh the host application, you should see similar logs if you open the dev tools:\n\n```bash\n[shell] Found 1 remote modules to register\n[shell] 1/1 Loading module \"./register\" from container \"remote1\" of remote \"http://localhost:8081/remoteEntry.js\"\n[shell] 1/1 Registering module \"./register\" from container \"remote1\" of remote \"http://localhost:8081/remoteEntry.js\"\nRemote 1 registered\n[shell] 1/1 container \"remote1\" of remote \"http://localhost:8081/remoteEntry.js\" registration completed\"\n```\n\n### Register a module routes\n\nIf you successfully completed the previous steps, you should have a federated application that.... doesn't do much.\n\nTo start rendering federated routes, we'll have to make a few changes to both applications.\n\n👉 Let's start by adding [React Router](https://reactrouter.com/) to the `App` component. Any version greater than `6.4` will work as long as the new [createBrowserRouter()](https://reactrouter.com/en/main/routers/create-browser-router) function is available:\n\n```tsx\n// host - App.tsx\n\nimport { RouterProvider, createBrowserRouter } from \"react-router-dom\";\nimport { RootLayout } from \"./layouts\";\nimport { Loading } from \"./components\";\nimport { lazy } from \"react\";\n\nconst HomePage = lazy(() =\u003e import(\"./pages/Home\"));\nconst NotFoundPage = lazy(() =\u003e import(\"./pages/NotFound\"));\n\nexport function App() {\n    const router = createBrowserRouter([\n        {\n            path: \"/\",\n            element: \u003cRootLayout /\u003e,\n            children: [\n                {\n                    index: true,\n                    element: \u003cHomePage /\u003e\n                }\n            ]\n        },\n        {\n            path: \"*\",\n            element: \u003cNotFoundPage /\u003e\n        }\n    ]);\n\n   return (\n        \u003cRouterProvider\n            router={router}\n            fallbackElement={\u003cLoading /\u003e}\n        /\u003e\n    );\n}\n```\n\n👉 Start the application to validate that the home page successfully renders.\n\nThat's great progress but the home page is a local page of the host application; there's nothing fancy there! To render federated routes, there are a few other additions to make.\n\n👉 First, retrieve the module routes with the `useRoutes()` hook and add those routes to the router:\n\n```tsx\n// host - App.tsx\n\nimport { RouterProvider, createBrowserRouter } from \"react-router-dom\";\nimport { RootLayout } from \"./layouts\";\nimport { Loading } from \"./components\";\nimport { useRoutes } from \"wmfnext-shell\";\nimport { lazy } from \"react\";\n\nconst HomePage = lazy(() =\u003e import(\"./pages/Home\"));\nconst NotFoundPage = lazy(() =\u003e import(\"./pages/NotFound\"));\n\nexport function App() {\n    // Retrieve the routes registered by the modules.\n    const routes = useRoutes();\n\n    const router = useMemo(() =\u003e {\n        return createBrowserRouter([\n            {\n                path: \"/\",\n                element: \u003cRootLayout /\u003e,\n                children: [\n                    {\n                        index: true,\n                        element: \u003cHomePage /\u003e\n                    },\n                    // Add the modules routes to the router.\n                    ...routes\n                ]\n            },\n            {\n                path: \"*\",\n                element: \u003cNotFoundPage /\u003e\n            }\n        ]);\n    }, [routes]);\n\n   return (\n        \u003cRouterProvider\n            router={router}\n            fallbackElement={\u003cLoading /\u003e}\n        /\u003e\n    );\n}\n```\n\n👉 Then update the remote module `register.tsx` file to register a few routes with the `runtime.registerRoutes(routes)` function:\n\n```tsx\n// remote-1 - register.tsx\n\nimport type { ModuleRegisterFunction, Runtime } from \"wmfnext-shell\";\nimport { lazy } from \"react\";\n\nconst Page1 = lazy(() =\u003e import(\"./pages/Page1\"));\nconst Page2 = lazy(() =\u003e import(\"./pages/Page2\"));\n\nexport const register: ModuleRegisterFunction = (runtime: Runtime) =\u003e {\n    runtime.registerRoutes([\n        {\n            path: \"remote1/page-1\",\n            element: \u003cPage1 /\u003e\n        },\n        {\n            path: \"remote1/page-2\",\n            element: \u003cPage2 /\u003e\n        }\n    ]);\n};\n```\n\nThe `runtime.registerRoutes()` function accepts an array of route objects. These route objects accept the same properties as the React Router [`RouteObject`](https://reactrouter.com/en/main/route/route#type-declaration) with a few additional properties (which will be revealed in upcoming guides).\n\n👉 Next update the host application `RootLayout` component to add links to those newly registered federated routes:\n\n```tsx\n// host - RootLayout.tsx\n\nimport { Link, Outlet } from \"react-router-dom\";\nimport { Loading } from \"../components\";\nimport { Suspense } from \"react\";\n\nexport function RootLayout() {\n    return (\n        \u003cdiv\u003e\n            \u003cnav\u003e\n                \u003cul\u003e\n                    \u003cli\u003e\u003cLink to=\"/\"\u003eHome\u003c/Link\u003e\u003c/li\u003e\n                    \u003cli\u003e\u003cLink to=\"remote1/page-1\"\u003eRemote1/Page1\u003c/Link\u003e\u003c/li\u003e\n                    \u003cli\u003e\u003cLink to=\"remote1/page-2\"\u003eRemote1/Page2\u003c/Link\u003e\u003c/li\u003e\n                \u003c/ul\u003e\n            \u003c/nav\u003e\n            \u003cSuspense fallback={\u003cLoading /\u003e}\u003e\n                \u003cOutlet /\u003e\n            \u003c/Suspense\u003e\n        \u003c/div\u003e\n    );\n}\n```\n\n👉 Start both applications and try navigating to _\"/remote1/page-1\"_ and _\"remote1/page-2\"_. You should be redirected to a 404 page, what's going on?\n\n### Re-render the host application after the remote modules are ready\n\n\u003e **Warning**\n\u003e\n\u003e You can skip this section if your host application is strictly importing static modules.\n\nYou are redirected to a 404 page because the host application rendered **before** the remote module is registered. Therefore, only the host application routes were added to the router at the time the application rendered.\n\nTo fix this, the host application must re-render once the remote module is registered.\n\nTo help with that, the shell comes with a build-in `useAreRemotesReady()` hook.\n\nThe `useAreRemotesReady()` hook takes care of re-rendering the application once all the remote modules are ready (registered) and return a `boolean` value indicating if the remote applications are ready. This is useful as you'll probably want to show a loading indicator while the remote modules are registering.\n\n\u003e If you are not using the `useAreRemotesReady()` hook and you need access to the remote modules registration status you can import a `registrationStatus` variable from the `wmfnext-remote-loader` package.\n\n👉 To fix this, first update the host application `App` component to use the `useAreRemotesReady()` hook. Then, use the `boolean` value returned by the hook to conditionally render a loading message:\n\n```tsx\n// host - App.tsx\n\nimport { RouterProvider, createBrowserRouter } from \"react-router-dom\";\nimport { RootLayout } from \"./layouts\";\nimport { Loading } from \"./components\";\nimport { useAreRemotesReady } from \"wmfnext-remote-loader\";\nimport { useRoutes } from \"wmfnext-shell\";\nimport { lazy } from \"react\";\n\nconst HomePage = lazy(() =\u003e import(\"./pages/Home\"));\nconst NotFoundPage = lazy(() =\u003e import(\"./pages/NotFound\"));\n\nexport function App() {\n    const isReady = useAreRemotesReady();\n    const routes = useRoutes(runtime);\n\n    const router = useMemo(() =\u003e {\n        return createBrowserRouter([\n            {\n                path: \"/\",\n                element: \u003cRootLayout /\u003e,\n                children: [\n                    {\n                        index: true,\n                        element: \u003cHomePage /\u003e\n                    },\n                    ...routes\n                ]\n            },\n            {\n                path: \"*\",\n                element: \u003cNotFoundPage /\u003e\n            }\n        ]);\n    }, [routes]);\n\n    if (!isReady) {\n        return \u003cLoading /\u003e;\n    }\n\n   return (\n        \u003cRouterProvider\n            router={router}\n            fallbackElement={\u003cLoading /\u003e}\n        /\u003e\n    );\n}\n```\n\n👉 Finally, move the home page to the remote module by replacing the page registration object `path` property with an `index` property and deleting the `HomePage` component from the host application:\n\n```tsx\n// host - App.tsx\n\nexport function App() {\n    const router = useMemo(() =\u003e {\n        // Remove the home page from the host application routes.\n        return createBrowserRouter([\n            {\n                path: \"/\",\n                element: \u003cRootLayout /\u003e,\n                children: [\n                    ...routes\n                ]\n            },\n            {\n                path: \"*\",\n                element: \u003cNotFoundPage /\u003e\n            }\n        ]);\n    }, [routes]);\n}\n```\n\n```tsx\n// host - RootLayout.tsx\n\nimport { Link, Outlet } from \"react-router-dom\";\nimport { Loading } from \"../components\";\nimport { Suspense } from \"react\";\n\nexport function RootLayout() {\n    return (\n        \u003cdiv\u003e\n            \u003cnav\u003e\n                \u003cul\u003e\n                    {/* Remove the home page from the links and update the \"Remote1/Page1\"\n                        page link \"to\" property to \"/\" as it's now the index route.\n                    */}\n                    \u003cli\u003e\u003cLink to=\"/\"\u003eRemote1/Page1 - Home\u003c/Link\u003e\u003c/li\u003e\n                    \u003cli\u003e\u003cLink to=\"remote1/page-2\"\u003eRemote1/Page2\u003c/Link\u003e\u003c/li\u003e\n                \u003c/ul\u003e\n            \u003c/nav\u003e\n            \u003cSuspense fallback={\u003cLoading /\u003e}\u003e\n                \u003cOutlet /\u003e\n            \u003c/Suspense\u003e\n        \u003c/div\u003e\n    );\n}\n```\n\n```tsx\n// remote-1 - register.tsx\n\nconst Page1 = lazy(() =\u003e import(\"./pages/Page1\"));\n\nexport const register: ModuleRegisterFunction = (runtime: Runtime) =\u003e {\n    runtime.registerRoutes([\n        {\n            // Remove the \"path\" property and add an \"index\" property.\n            index: true,\n            element: \u003cPage1 /\u003e\n        }\n    ]);\n};\n```\n\n👉 Start both applications again and try navigating between pages. Everything should be working fine now.\n\n\u003e If you are still having issues, make sure that both applications `package.json` files have `react`, `react-dom`, `react-router-dom`, `wmfnext-shell` and `wmfnext-remote-loader` listed in their dependencies. The dependency versions should be the same for the host and the module application.\n\n### Setup a static module\n\n\u003e **Warning**\n\u003e\n\u003e Before reading this section, make sure you already when trough the following sections:\n\u003e - [Setup an host application](#setup-an-host-application)\n\u003e - [Register a module routes](#register-a-module-routes)\n\nThis shell also supports static modules loaded at build time to accomodate different migration scenarios.\n\nA static module can be a sibling project of the host application in a monorepo setup, a standalone package developed in its own repository or even a folder of the host application.\n\nFor this example, our static module will be a sibling project in the host application monorepo:\n\n```\npackages\n├── app (the host application)\n├── static-1\n├─────src\n├───────register.tsx\n├─────package.json\n```\n\n👉 First, create the project with the following `package.json` fields:\n\n```json\n{\n    \"name\": \"wmfnext-static-1\",\n    \"version\": \"0.0.1\",\n    \"main\": \"dist/register.js\"\n}\n```\n\n👉 Then, register a few pages in the `register.tsx` file using the `runtime.registerRoutes()` function:\n\n```tsx\n// static-1 - register.tsx\n\nimport type { ModuleRegisterFunction, Runtime } from \"wmfnext-shell\";\nimport { lazy } from \"react\";\n\nconst Page1 = lazy(() =\u003e import(\"./pages/Page1\"));\nconst Page2 = lazy(() =\u003e import(\"./pages/Page2\"));\n\nexport const register: ModuleRegisterFunction = (runtime: Runtime) =\u003e {\n    runtime.registerRoutes([\n        {\n            path: \"static1/page-1\",\n            element: \u003cPage1 /\u003e\n        },\n        {\n            path: \"static1/page-2\",\n            element: \u003cPage2 /\u003e\n        }\n    ]);\n};\n```\n\n👉 Now, open the host application's `package.json` file and add a dependency to the newly created project:\n\n```json\n{\n    \"dependency\": {\n        \"wmfnext-static-1\": \"0.0.1\"\n    }\n}\n```\n\n👉 Next, update the host application's `bootstrap.tsx` file to import the `register` function of the static module package and use it to register the module at build time using the `registerStaticModules(registerFunctions, runtime, options)` function:\n\n```tsx\n// host - bootstrap.tsx\n\nimport { ConsoleLogger, RuntimeContext, Runtime, registerStaticModules } from \"wmfnext-shell\";\nimport type { RemoteDefinition } from \"wmfnext-remote-loader\";\nimport { App } from \"./App\";\nimport { createRoot } from \"react-dom/client\";\nimport { registerRemoteModules } from \"wmfnext-remote-loader\";\nimport { register as registerModule } from \"wmfnext-static-1\";\n\nconst StaticModules = [\n    registerModule\n];\n\nconst Remotes: RemoteDefinition[] = [\n    {\n        url: \"http://localhost:8081\",\n        name: \"remote1\"\n    }\n];\n\nconst runtime = new Runtime({\n    loggers: [new ConsoleLogger()]\n});\n\n// Register the static modules at build time.\nregisterStaticModules(StaticModules, runtime);\n\nregisterRemoteModules(Remotes, runtime);\n\nconst root = createRoot(document.getElementById(\"root\"));\n\nroot.render(\n    \u003cRuntimeContext.Provider value={runtime}\u003e\n        \u003cApp /\u003e\n    \u003c/RuntimeContext.Provider\u003e\n);\n```\n\nBy calling the `registerStaticModules()` function with the static module's `register` function, the module's routes will be added to the host application router at build time.\n\n👉 Then, update the host application's `RootLayout` component to add links to the static module's pages:\n\n```tsx\n// host - RootLayout.tsx\n\nimport { Link, Outlet } from \"react-router-dom\";\nimport { Loading } from \"../components\";\nimport { Suspense } from \"react\";\n\nexport function RootLayout() {\n    return (\n        \u003cdiv\u003e\n            \u003cnav\u003e\n                \u003cul\u003e\n                    \u003cli\u003e\u003cLink to=\"/\"\u003eStatic1/Page1 - Home\u003c/Link\u003e\u003c/li\u003e\n                    \u003cli\u003e\u003cLink to=\"static1/page-2\"\u003eStatic1/Page2\u003c/Link\u003e\u003c/li\u003e\n                    \u003cli\u003e\u003cLink to=\"remote1/page-1\"\u003eRemote1/Page1\u003c/Link\u003e\u003c/li\u003e\n                    \u003cli\u003e\u003cLink to=\"remote1/page-2\"\u003eRemote1/Page2\u003c/Link\u003e\u003c/li\u003e\n                \u003c/ul\u003e\n            \u003c/nav\u003e\n            \u003cSuspense fallback={\u003cLoading /\u003e}\u003e\n                \u003cOutlet /\u003e\n            \u003c/Suspense\u003e\n        \u003c/div\u003e\n    );\n}\n```\n\n👉 And, add your [TypeScript configuration](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html) to the root of the project and include a command in the `package.json` file to transpile the code using the `tsc` CLI.\n\n```json\n{\n    \"scripts\": {\n        \"dev\": \"tsc --watch --project ./tsconfig.json\"\n    }\n}\n```\n\n\u003e **Note**\n\u003e\n\u003e You can find a TS configuration sample in the [static-1 application example repository](https://github.com/patricklafrance/wmfnext-host/tree/master/packages/static-module-1).\n\n👉 Finally, update the host application's file `package.json` file to add a reference to the newly created `wmfnext-static-module-1` package.\n\n👉 Start the applications with the `dev` command and navigate to _\"static1/page-1\"_ and _\"static1/page-2\"_, the pages should render without any errors.\n\n### Register a module navigation items\n\nWe now have a federated SPA displaying pages from a remote module loaded at runtime and a static module registered at build time.\n\nStill, _teams are not fully autonomous yet_ as links to pages are hardcoded in the host application layout. To change those links, teams have to coordinate with each others.\n\n💡 Our second take is that a module should be fully autonomous. It shouldn't have to coordinate with other parts of the application for things as trivial as navigation links.\n\nTo _enable fully autonomous teams_, the shell has built-in support for dynamic navigation items. With this feature, a module can dynamically register its navigation items at registration.\n\n👉 To use the feature, first update every module's `register.tsx` file to add navigation items with the `runtime.registerNavigationItems(navigationItems)` function.\n\n```tsx\n// remote-1 - register.tsx\n\nexport const register: ModuleRegisterFunction = (runtime: Runtime) =\u003e {\n    runtime.registerNavigationItems([\n        {\n            to: \"remote1/page-1\",\n            content: \"Remote1/Page 1 - Home\"\n        },\n        {\n            to: \"remote1/page-2\",\n            content: \"Remote1/Page 2\"\n        }\n    ]);\n};\n```\n\n```tsx\n// static-1 - register.tsx\n\nimport { ArchiveIcon } from \"./ArchiveIcon\";\n\nexport const register: ModuleRegisterFunction = (runtime: Runtime) =\u003e {\n    runtime.registerNavigationItems([\n        {\n            to: \"static1/page-1\",\n            content: (\n                \u003c\u003e\n                    \u003cArchiveIcon /\u003e\n                    \u003cspan\u003e\n                        Static1/Page 1 - Item with a React element as content + additional Link props\n                    \u003c/span\u003e\n                \u003c/\u003e\n            ),\n            style: {\n                display: \"flex\",\n                alignItems: \"center\"\n            },\n            target: \"_blank\"\n        },\n        {\n            to: \"static1/page-2\",\n            content: \"Static1/Page 2 - Item with children\",\n            children: [\n                {\n                    to: \"static1/page-4\",\n                    content: \"Static1/Page 4 - Child item\"\n                },\n                {\n                    to: \"static1/page-5\",\n                    content: \"Static1/Page 5 - Child item\"\n                }\n            ]\n        },\n        {\n            to: \"static1/page-3\",\n            content: \"Static1/Page 3 - Item with a top priority and custom additional props\",\n            priority: 99,\n            additionalProps: {\n                highlight: true\n            }\n        }\n    ]);\n};\n```\n\nA navigation item object accepts the same properties as a React Router [Link](https://reactrouter.com/en/main/components/link) component with the addition of the `content`, `priority`, `children` and `additionalProps` properties.\n\nThere are a couple things worth mentionning in the previous code sample:\n\n1. The navigation item labelled  _\"Static1/Page 1\"_ has rich content value. The `content` property accepts a `string` value or a `React element` value.\n\n2. The navigation item labelled  _\"Static1/Page 1\"_ has a `style` and `target` properties. Those properties are valid because they are supported by the React Router [Link](https://reactrouter.com/en/main/components/link) component.\n\n3. The navigation item labelled _\"Static1/Page 3\"_ has a `priority` property. The `priority` property allows a navigation item to render higher in the navigation items hierarchy. The higher the `priority`, the higher the navigation item will be rendered.\n\n4. The navigation item labelled _\"Static1/Page 3\"_ also has an an `additionalProps` property. It's an untyped bucket property to provide contextual value to the render function of the application.\n\n5. The navigation item labelled _\"Static1/Page 2\"_ has a `children` property with nested navigation items. The navigation registry is a tree structure with an infinite numbers of levels.\n\n👉 Now, update the host application's `RootLayout` component to render the module navigation items with the `useNavigationItems()` hook:\n\n```tsx\n// host - RootLayout.tsx\n\nimport { Link, Outlet } from \"react-router-dom\";\nimport type { RenderItemFunction, RenderSectionFunction } from \"wmfnext-shell\";\nimport { Suspense, useCallback } from \"react\";\nimport { useNavigationItems, useRenderedNavigationItems } from \"wmfnext-shell\";\nimport { Loading } from \"../components\";\nimport type { ReactNode } from \"react\";\nimport type { RenderNavigationItem } from \"wmfnext-shell\";\n\nexport function RootLayout() {\n    const navigationItems = useNavigationItems();\n\n    const renderItem: RenderItemFunction = useCallback(\n        ({ content, linkProps, additionalProps: { highlight, ...additionalProps } }, index, level) =\u003e {\n        return (\n            \u003cli key={`${level}-${index}`} className={highlight \u0026\u0026 \"highlight\"}\u003e\n                \u003cLink {...linkProps} {...additionalProps}\u003e\n                    {content}\n                \u003c/Link\u003e\n            \u003c/li\u003e\n        );\n    }, []);\n\n    const renderSection: RenderSectionFunction = useCallback((itemElements, index, level) =\u003e {\n        return (\n            \u003cul key={`${level}-${index}`}\u003e\n                {itemElements}\n            \u003c/ul\u003e\n        );\n    }, []);\n\n    const renderedNavigationItems = useRenderedNavigationItems(navigationItems, renderItem, renderSection);\n\n    return (\n        \u003cdiv\u003e\n            \u003cnav className=\"nav\"\u003e\n                {renderedNavigationItems}\n            \u003c/nav\u003e\n            \u003cSuspense fallback={\u003cLoading /\u003e}\u003e\n                \u003cOutlet /\u003e\n            \u003c/Suspense\u003e\n        \u003c/div\u003e\n    );\n}\n```\n\nThe `useNavigationItems()` hook returns the navigation items tree structure as is, meaning you'll still have to recursively parse the structure to transform the items into actual React components.\n\nAs it's a non-trivial process, the shell provides a utility hook called `useRenderedNavigationItems(navigationItems, renderItem, renderSection)` to help with that.\n\nThe `useRenderedNavigationItems()` hook accepts two render functions as its second and third parameters. The *second parameter* function renders a single link from a navigation item and the *third parameter* function renders a section from a collection of items.\n\nIn the previous example, there are two sections. A root section containing all the navigation items, and a nested section containing only _\"Static1/Page 4\"_ and _\"Static1/Page 5\"_ navigation items.\n\n👉 Start the applications and try navigating between pages. Everything should work fine.\n\n\u003e **Note**\n\u003e\n\u003e It's important to provide memoized render functions to the `useRenderedNavigationItems()` hook, as otherwise, the navigation items will be parsed over and over on re-renders rather than being returned from the cache.\n\n### Isolate module failures\n\nOne of the key characteristics of micro-frontends implementations like [iframes](https://martinfowler.com/articles/micro-frontends.html#Run-timeIntegrationViaIframes) and subdomains is that a single module failure can't break the whole application.\n\nWith our implementation, this is not the case, as all the modules live in the same domain and share the same DOM.\n\nStill, we can get very close to iframes failure isolation by leveraging React Router [Outlet](https://reactrouter.com/en/main/components/outlet) component and routes' [errorElement](https://reactrouter.com/en/main/route/error-element) property.\n\nOur host application `RootLayout` component is already rendering an `Outlet` component. Therefore, to support failure isolation, we only need to add a nested pathless route with an `errorElement` property under the root layout to catch unmanaged errors from modules.\n\n👉 First, let's create a `RootErrorBoundary` component in the host application to handle errors:\n\n```tsx\n// host - RootErrorBoundary.tsx\n\nimport { isRouteErrorResponse, useLocation, useRouteError } from \"react-router-dom\";\nimport { useLogger } from \"wmfnext-shell\";\n\nfunction getErrorMessage(error: unknown) {\n    if (isRouteErrorResponse(error)) {\n        return `${error.status} ${error.statusText}`;\n    }\n\n    return error instanceof Error\n        ? error.message\n        : JSON.stringify(error);\n}\n\nexport function RootErrorBoundary() {\n    const error = useRouteError();\n    const location = useLocation();\n    const logger = useLogger();\n\n    logger.error(`[shell] An unmanaged error occured while rendering the\n        route with path ${location.pathname}`, error);\n\n    return (\n        \u003cp className=\"error-message\"\u003e\n            An unmanaged error occured insisde a module and other parts of the application\n            are still fully functional!\n            \u003cbr /\u003e\n            \u003cspan role=\"img\" aria-label=\"pointer\"\u003e👉\u003c/span\u003e {getErrorMessage(error)}\n        \u003c/p\u003e\n    );\n}\n```\n\n👉 Then, update the host application's router code to add the nested pathless route under the root layout route:\n\n```tsx\n// host - App.tsx\n\nimport { RootErrorBoundary } from \"./layouts\";\n\nexport function App() {\n    const router = useMemo(() =\u003e {\n        return createBrowserRouter([\n            {\n                path: \"/\",\n                element: \u003cRootLayout /\u003e,\n                children: [\n                    {\n                        // Pathless route to set an error boundary inside the layout instead of outside.\n                        // It's quite useful to not lose the layout when an unmanaged error occurs.\n                        errorElement: \u003cRootErrorBoundary /\u003e,\n                        children: [\n                            ...routes,\n                        ]\n                    }\n                ]\n            },\n            {\n                path: \"*\",\n                element: \u003cNotFoundPage /\u003e\n            }\n        ]);\n    }, [routes]);\n}\n```\n\nAs the pathless route with the error boundary has been declared under the root layout route, when an unmanaged error bubbles up, the `Outlet` component output is replaced by the `RootErrorBoundary` component's output. Hence, other parts of the `RootLayout` component, like the navigation section are still rendered.\n\n👉 Next, add to the remote module a new route component called `Page3` that will throw an error on render:\n\n```tsx\n// remote-1 - Page3.tsx\n\nexport default function Page3(): JSX.Element {\n    throw new Error(\"Page3 from \\\"remote-1\\\" failed to render\");\n}\n```\n\n```tsx\n// remote-1 - register.tsx\n\nconst Page3 = lazy(() =\u003e import(\"./pages/Page3\"));\n\nexport const register: ModuleRegisterFunction = (runtime: Runtime) =\u003e {\n    runtime.registerRoutes([\n        {\n            path: \"remote1/page-3\",\n            element: \u003cPage3 /\u003e\n        }\n    ]);\n\n    runtime.registerNavigationItems([\n        {\n            to: \"remote1/page-3\",\n            content: \"Remote1/Page 3 - Failing page\"\n        }\n    ]);\n};\n```\n\n👉 Start the applications and navigate to the _\"remote1/page-3\"_ page. The page will throw an error but other parts of the application should still be functional.\n\n### Override the host layout\n\nMost applications usually have a default layout with at least a navigation section and a user profile menu, as a majority of the application's pages tend to use the same layout. However, there are usually a few pages for which the default layout will not work, often because they are not bound to a user session, such as a login page.\n\nTo accommodate for these pages, the shell has a built-in mechanism called \"page hoisting\". Unlike a regular page, a hoisted page is added at the root of the router, outside of the boundaries of the host application's root layout. This means that a hoisted page is not affected by the default layout and has full control over its rendering.\n\n\u003e **Warning**\n\u003e\n\u003e By declaring a page as hoisted, other parts of the application will not be isolated anymore from this page's failures as the page will be rendered outside of the host application's root error boundary. To avoid breaking the entire application when an hoisted page cause unmanaged errors, it is highly recommended to set a React Router [errorElement](https://reactrouter.com/en/main/route/error-element) property for every hoisted pages.\n\n👉 Now, let's hoist a few pages of the remote module by adding the `hoist` property to the route definition:\n\n```tsx\n// remote-1 - register.tsx\n\nconst FullLayout = lazy(() =\u003e import(\"./layouts/FullPageLayout\"));\n\nconst Page2 = lazy(() =\u003e import(\"./pages/Page2\"));\nconst Page4 = lazy(() =\u003e import(\"./pages/Page4\"));\n\nexport const register: ModuleRegisterFunction = (runtime: Runtime) =\u003e {\n    runtime.registerRoutes([\n        {\n            hoist: true,\n            path: \"remote1/page-2\",\n            element: \u003cFullLayout /\u003e,\n            errorElement: \u003cErrorBoundary /\u003e,\n            children: [\n                {\n                    element: \u003cPage2 /\u003e\n                }\n            ]\n        },\n        {\n            hoist: true,\n            path: \"remote1/page-4\",\n            element: \u003cPage4 /\u003e,\n            errorElement: \u003cErrorBoundary /\u003e\n        }\n    ]);\n\n    runtime.registerNavigationItems([\n        {\n            to: \"remote1/page-2\",\n            content: \"Remote1/Page 2 - Overrided layout\"\n        },\n        {\n            to: \"remote1/page-4\",\n            content: \"Remote1/Page 4 - Hoisted route\"\n        }\n    ]);\n};\n```\n\nBy setting the `hoist` property to `true` for the _\"Remote1/Page 2\"_ route, we are telling the shell to add this page at the root of the router rather than under the root layout of the host application.\n\nSince the page is not rendered within the boundaries of the host application root layout, we can now set a custom layout to the _\"Remote1/Page 2\"_ page by nesting the route under a new layout. For this example, we'll use the `FullLayout` component.\n\nThe _\"Remote1/Page 4\"_ page is also hoisted. An hoisted page doesn't have to be assigned a custom layout, it can be rendered on its own.\n\n👉 To test the changes, start the applications and navigate to _\"remote1/page-2\"_ and _\"remote1/page-4\"_ pages. The root layout should not be rendered for both pages, as they are hoisted and rendered outside of the boundaries of the host application's root layout. What's going on?\n\nBy default, the shell doesn't support page hoisting. To support page hoisting, the host application must use the `useHoistedRoutes(routes, options)` hook.\n\n👉 Update the host application to support page hoisting by adding the `useHoistedRoutes(routes, options)` hook:\n\n```tsx\n// host - App.tsx\n\nimport { useHoistedRoutes, useRoutes } from \"wmfnext-shell\";;\n\nconst NotFoundPage = lazy(() =\u003e import(\"./pages/NotFound\"));\n\nexport function App() {\n    const routes = useRoutes();\n\n    const wrapManagedRoutes = useCallback((managedRoutes: Readonly\u003cRoute[]\u003e) =\u003e {\n        return {\n            path: \"/\",\n            element: \u003cRootLayout /\u003e,\n            children: [\n                {\n                    errorElement: \u003cRootErrorBoundary /\u003e,\n                    children: [\n                        ...managedRoutes\n                    ]\n                }\n            ]\n        };\n    }, []);\n\n    // The \"useHoistedRoutes\" hook move hoisted pages at the root.\n    const hoistedRoutes = useHoistedRoutes(routes, {\n        wrapManagedRoutes\n    });\n\n    const router = useMemo(() =\u003e {\n        return createBrowserRouter([\n            ...hoistedRoutes,\n            {\n                path: \"*\",\n                element: \u003cNotFoundPage /\u003e\n            }\n        ]);\n    }, [hoistedRoutes]);\n}\n```\n\nYou may have noticed that a `wrapManagedRoutes` option is passed to the `useHoistedRoutes()` hook. This is an optional function, which allows the host application to nest the *\"non hoisted routes\"* under a specific route.\n\nIn this example, the `wrapManagedRoutes` option is used to wrap all the _\"non hoisted routes\"_ under the `RootLayout` component and the `RootErrorBoundary` to isolate module failures.\n\n\u003e It's important to memoize the `wrapManagedRoutes` function, otherwise, the hoisting code will be executed on every re-render rather than returning from the cache.\n\nAn host application can choose to disallow page hoisting by not using the `useHoistedRoutes()` hook or using the `allowedPaths` option of the `useHoistedRoutes()` hook to specify a subset of module routes that are eligible for hoisting.\n\n```tsx\nconst hoistedRoutes = useHoistedRoutes(routes, {\n    wrapManagedRoutes,\n    allowedPaths: [\n        \"remote1/page-2\",\n        \"remote1/page-4\"\n    ]\n});\n```\n\n👉 Now, let's start the applications again and navigate to _\"remote1/page-2\"_ and _\"remote1/page-4\"_ pages. You should not see the host application root layout anymore.\n\n### Share a user session\n\nThe shell facilitates the sharing of a user session object between the host application and the module applications through its runtime instance.\n\nTo keep things simple, in this example, we'll use the fake `SessionManager` implementation of the `wmfnext-fakes` package. For a real application, you should implement your own session provider.\n\n\u003e The `wmfnext-fakes` package is a collection of fake implementations offered to accelerate the setup of an environment to develop a module in isolation.\n\n👉 To share a user session, first, create an instance of the `SessionManager` to store the session object and define a `sessionAccessor()` function to access the session:\n\n```ts\n// host -session.ts\n\nimport type { Session } from \"wmfnext-shared\";\nimport type { SessionAccessorFunction } from \"wmfnext-shell\";\nimport { SessionManager } from \"wmfnext-fakes\";\n\nexport const sessionManager = new SessionManager\u003cSession\u003e();\n\nexport const sessionAccessor: SessionAccessorFunction = () =\u003e {\n    return sessionManager.getSession();\n};\n```\n\n\u003e The `wmfnext-shared` package will be created later in this section.\n\n👉 Then, add a login page to the host application and use the newly created `sessionManager` instance to store the session object once a user is authenticated:\n\n```tsx\n// host - Login.tsx\n\nimport { sessionManager } from \"../session\";\nimport type { ChangeEvent, MouseEvent } from \"react\";\nimport { Navigate, useNavigate } from \"react-router-dom\";\nimport { useCallback, useState } from \"react\";\n\nexport default function Login() {\n    const [username, setUserName] = useState(\"\");\n    const [password, setPassword] = useState(\"\");\n\n    const navigate = useNavigate();\n\n    const handleClick = useCallback((event: MouseEvent\u003cHTMLButtonElement\u003e) =\u003e {\n        event.preventDefault();\n\n        if (username === \"temp\" \u0026\u0026 password === \"temp\") {\n            sessionManager.setSession({\n                user: {\n                    name: temp\n                }\n            });\n\n            navigate(\"/\");\n        }\n    }, [username, password, navigate]);\n\n    const handleUserNameChange = useCallback((event: ChangeEvent\u003cHTMLInputElement\u003e) =\u003e {\n        setUserName(event.target.value);\n    }, []);\n\n    const handlePasswordChange = useCallback((event: ChangeEvent\u003cHTMLInputElement\u003e) =\u003e {\n        setPassword(event.target.value);\n    }, []);\n\n    return (\n        \u003cmain\u003e\n            \u003cform\u003e\n                \u003cdiv\u003e\n                    \u003clabel htmlFor=\"username\"\u003eUsername\u003c/label\u003e\n                    \u003cinput id=\"username\" type=\"text\" onChange={handleUserNameChange} /\u003e\n                \u003c/div\u003e\n                \u003cdiv\u003e\n                    \u003clabel htmlFor=\"password\"\u003ePassword\u003c/label\u003e\n                    \u003cinput id=\"password\" type=\"password\" onChange={handlePasswordChange} /\u003e\n                \u003c/div\u003e\n                \u003cdiv\u003e\n                    \u003cbutton type=\"submit\" onClick={handleClick}\u003e\n                        Login\n                    \u003c/button\u003e\n                \u003c/div\u003e\n            \u003c/form\u003e\n        \u003c/main\u003e\n    );\n}\n```\n\n```tsx\n// host - App.tsx\n\nconst LoginPage = lazy(() =\u003e import(\"./pages/Login\"));\n\nexport function App() {\n    const router = useMemo(() =\u003e {\n        return createBrowserRouter([\n            ...hoistedRoutes,\n            {\n                // Newly added login page.\n                path: \"login\",\n                element: \u003cLoginPage /\u003e\n            },\n            {\n                path: \"*\",\n                element: \u003cNotFoundPage /\u003e\n            }\n        ]);\n    }, [hoistedRoutes]);\n}\n```\n\n👉 Then, associate the `sessionAccessor()` function to the `runtime` instance created in the host application and wrap the `App` component within a `Suspense` boundary:\n\n```tsx\n// host - bootstrap.tsx\n\nimport { sessionAccessor } from \"./session\";\n\nconst runtime = new Runtime({\n    loggers: [new ConsoleLogger()],\n    sessionAccessor\n});\n\nroot.render(\n    \u003cRuntimeContext.Provider value={runtime}\u003e\n        {/* New required suspense boundary */}\n        \u003cSuspense fallback={\u003cLoading /\u003e}\u003e\n            \u003cApp /\u003e\n        \u003c/Suspense\u003e\n    \u003c/RuntimeContext.Provider\u003e\n);\n```\n\nSince the `sessionManager` instance has access to the user session and the `sessionAccessor()` function is bound to the `sessionManager` instance, modules that receive the `runtime` instance will now have access to the user session.\n\nThere's 2 ways for a module to access the user session:\n\n1. By using the `useSession()` hook\n\n2. By using `runtime.getSession()`\n\n👉 Now, for the host application and the remote module to share the same `Session` type, a shared package must be created. In this example, we'll add a `shared` project to the host application monorepo:\n\n```\npackages\n├── app (the host application)\n├── shared (the new shared package)\n├─────src\n├────── components\n├────── services\n├────── events\n├────── utils\n├────── types\n├───────── session.ts\n├────── index.ts\n├─────package.json\n```\n\n\u003e **Note**\n\u003e\n\u003e For the sake of this demo, all shared assets will be added to a single shared package. However, when developing a real application, it is recommend to split shared assets into multiple standalone packages to maximise dependency segregation, improve cohesion and minimize the scope of an update.\n\n👉 First, add a `Session` type to the `wmfnext-shared` package:\n\n```ts\n// shared - session.ts\n\nexport interface Session {\n    user: {\n        name: string;\n    };\n}\n```\n\n👉 Next, create a new page in the remote module using the current user session to render the user name:\n\n```tsx\n// remote-1 - Page5.tsx\n\nimport { useLogger, useSession } from \"wmfnext-shell\";\nimport { Session } from \"wmfnext-shared\";\n\nexport default function Page5() {\n    const logger = useLogger();\n    const session = useSession() as Session;\n\n    logger.debug(\"Rendering \\\"page5\\\" from module \\\"remote1\\\"\");\n\n    return (\n        \u003cmain\u003e\n            \u003ch1\u003ePage 5\u003c/h1\u003e\n            \u003cp\u003eFrom remote-1\u003c/p\u003e\n            {/* Retrieving the user name from the shared session */}\n            \u003cp\u003eAuthenticated user: {session.user.name}\u003c/p\u003e\n        \u003c/main\u003e\n    );\n}\n```\n\n👉 Then, add references to the newly created `wmfnext-shared` package and start everything.\n\nTo test that the session is shared:\n\n1. Navigate to the login page\n2. Authenticate with \"temp\" / \"temp\"\n3. Navigate to _\"remote1/page-6\"_\n4. The user name should be rendered in the page content.\n\n👉 Next, let's use React Router [nested routes](https://reactrouter.com/en/main/start/overview#nested-routes) to add an authentication boundary and redirect unauthenticated users to the login page:\n\n```tsx\n// host - App.jsx\n\nimport { Navigate, Outlet } from \"react-router-dom\";\nimport { useIsAuthenticated } from \"wmfnext-shell\";\n\n// Will redirect to the login page if the user is not authenticated.\nfunction AuthenticationBoundary() {\n    return useIsAuthenticated() ? \u003cOutlet /\u003e : \u003cNavigate to=\"/login\" /\u003e;\n}\n\nexport function App() {\n    const wrapManagedRoutes = useCallback((managedRoutes: Readonly\u003cRoute[]\u003e) =\u003e {\n        return {\n            // New pathless route to set an authentication boundary around the\n            // managed routes of the application.\n            element: \u003cAuthenticationBoundary /\u003e,\n            children: [\n                {\n                    path: \"/\",\n                    element: \u003cRootLayout /\u003e,\n                    children: [\n                        {\n                            errorElement: \u003cRootErrorBoundary /\u003e,\n                            children: [\n                                ...managedRoutes\n                            ]\n                        }\n                    ]\n                }\n            ]\n        };\n    }, []);\n}\n```\n\nThe `AuthenticationBoundary` component uses the `useIsAuthenticated()` hook to determine if a user is authenticated or not. Alternatively, the `useSession()` hook could also be used. \n\nBy wrapping the root layout within the `AuthenticationBoundary` component, only authenticated users have access to the managed module routes.\n\n👉 To test the changes, clear your session storage and navigate to any route protected by the authentication boundary. You should be redirected to the login page.\n\nBefore jumping to the next section, since we added a new package called `wmfnext-shared` which is shared by every part of the federated application, we need to make sure that it is added as a [shared singleton dependency](https://dev.to/infoxicator/module-federation-shared-api-ach).\n\n👉 Let's add the shared dependency to the application's Webpack configuration file:\n\n```js\n// host - webpack.dev.js\n\nimport { createHostPlugin } from \"wmnext-remote-loader/webpack.js\";\n\nexport default {\n    plugins: [\n        createHostPlugin(\"host\", packageJson, {\n            sharedDependencies: {\n                \"wmfnext-shared\": {\n                    singleton: true,\n                    requiredVersion: \"0.0.1\"\n                }\n            }\n        })\n    ]\n}\n```\n\n```js\n// remote-1 - webpack.dev.js\n\nimport { createModulePlugin } from \"wmnext-remote-loader/webpack.js\";\n\nexport default {\n    plugins: [\n        createModulePlugin(\"remote1\", packageJson, {\n            sharedDependencies: {\n                \"wmfnext-shared\": {\n                    singleton: true,\n                    requiredVersion: \"0.0.1\"\n                }\n            }\n        })\n    ]\n}\n```\n\n### Use the event bus\n\n💡 Our third take is that a federated application should feel homogenous. Different parts of a federation application should have the ability to communicate with each others and react to changes happening outside of their boundaries.\n\nTo enable a loosely coupled communication between the parts of the application, the shell offer a basic implementation of a pub/sub mecanism called the event bus.\n\n👉 To showcase how it works, we'll start by adding a counter functionality to the host application and an event listener to increment the value when a specific event is dispatched:\n\n```tsx\n// host - RootLayout.tsx\n\nimport { useCallback } from \"react\";\nimport { useEventBusListener } from \"wmfnext-shell\";\nimport { IncrementCountEvent } from \"wmfnext-shared\";\n\nexport function RootLayout() {\n    // The counter is basically only a useState.\n    const [count, setCount] = useState(0);\n\n    const handleIncrementCountEvent = useCallback(() =\u003e {\n        setCount(x =\u003e x + 1);\n    }, [setCount]);\n\n    // Add an event listener to react to increment request from independent modules.\n    useEventBusListener(IncrementCountEvent, handleIncrementCountEvent);\n\n    return (\n        \u003cdiv className=\"wrapper\"\u003e\n            {session \u0026\u0026 (\n                \u003cdiv className=\"top-bar\"\u003e\n                    \u003cdiv className=\"counter\"\u003e\n                        \u003cspan\u003eCount: {count}\u003c/span\u003e\n                    \u003c/div\u003e\n                    \u003cdiv\u003e\n                        \u003cspan\u003eCurrent user: \u003c/span\u003e{session.user.name}\n                    \u003c/div\u003e\n                \u003c/div\u003e\n            )}\n            \u003cnav className=\"nav\"\u003e\n                {renderedNavigationItems}\n            \u003c/nav\u003e\n            \u003cSuspense fallback={\u003cLoading /\u003e}\u003e\n                \u003cOutlet /\u003e\n            \u003c/Suspense\u003e\n        \u003c/div\u003e\n    );\n}\n```\n\nIn this example, the `RootLayout` component is using the `useEventBusListener(eventName, callback, options)` hook to listen for increment events.\n\nThere are 2 ways to liste to events:\n\n1. Use the `useEventBusListener()` hook as we did in the previous example. It's convenient for components as the listener will be disposed automatically when the components is disposed.\n\n2. Access the event bus directly from the `runtime` instance with `runtime.eventBus`.\n\n\u003e **Note**\n\u003e\n\u003e To prevent the event listener from being removed through re-renders, it's important to provide a memoized function.\n\n👉 Next, add a new page to the remote module application to dispatch increment events:\n\n```tsx\n// remote-1 - Page6.tsx\n\nimport { useEventBusDispatcher, useLogger } from \"wmfnext-shell\";\nimport { IncrementCountEvent } from \"wmfnext-shared\";\nimport { useCallback } from \"react\";\n\nexport default function Page6() {\n    const logger = useLogger();\n    const dispatch = useEventBusDispatcher();\n\n    logger.debug(\"Rendering \\\"page6\\\" from module \\\"remote1\\\"\");\n\n    const handleIncrementCount = useCallback(() =\u003e {\n        // When the button is clicked, an increment event is dispatched.\n        dispatch(IncrementCountEvent);\n    }, [dispatch]);\n\n    return (\n        \u003cmain\u003e\n            \u003ch1\u003ePage 6\u003c/h1\u003e\n            \u003cp\u003eFrom remote-1\u003c/p\u003e\n            \u003cbutton type=\"button\" onClick={handleIncrementCount}\u003e\n                Increment count\n            \u003c/button\u003e\n        \u003c/main\u003e\n    );\n}\n```\n\n👉 Start the applications and navigate to _*remote1/page-6*_. Click on the button *\"Increment count\"*. Everytime the button is clicked, the top left counter should increment by 1.\n\n### Share a custom service\n\nThe shell runtime offers a few built-in services. However, by no mean these services alone can support the needs of a mature application. That's why custom services can be added to the shell runtime.\n\n👉 First, add a `TrackingService` class to the host application:\n\n```tsx\n// host - trackingService.ts\n\nexport class TrackingService {\n    track(data: unknown) {\n        console.log(\"[tracking] Tracking the following data: \", data);\n    }\n}\n```\n\n👉 Then, make the service available to all the registered modules by passing a `TrackingService` instance to the runtime with the `services` option:\n\n```tsx\n// host - bootstrap.tsx\n\nimport { TrackingService } from \"./trackingService\";\n\nconst runtime = new Runtime({\n    loggers: [new ConsoleLogger()],\n    // Register the tracking service with \"tracking\" as key.\n    services: {\n        \"tracking\": new TrackingService()\n    },\n    sessionAccessor\n});\n```\n\n👉 Before a module can use the shared instance of `TrackingService`, it's type must be shared. To do this, we'll use move the `TrackingService` class to the `wmfnext-shared` package created earlier:\n\n```tsx\n// shared - trackingService.ts\n\nexport interface TrackingService {\n    track: (data: unknown) =\u003e void;\n}\n```\n\n👉 The service instance can now be retrieved by any modules by using the runtime `runtime.getService(serviceName)` function:\n\n```ts\n// remote-1 - register.tsx\n\nexport const register: ModuleRegisterFunction = (runtime: Runtime) =\u003e {\n    const trackingService = runtime.getService(\"tracking\");\n});\n```\n\n👉 For convenience we'll also add a `useTrackingService()` hook to retrieve the service instance. This way, modules can easily access the service without hardcoding it's key and manually force a cast:\n\n```tsx\n// shared - trackingService.ts\n\nimport { useRuntime } from \"wmfnext-shell\";\n\nexport interface TrackingService {\n    track: (data: unknown) =\u003e void;\n}\n\nexport function useTrackingService() {\n    const runtime = useRuntime();\n\n    return runtime.getService(\"tracking\") as TrackingService;\n}\n```\n\n👉 Finally, create a new page in the remote module and use the tracking service:\n\n```tsx\n// remote-1 - Page7.tsx\n\nimport { useLogger } from \"wmfnext-shell\";\nimport { useTrackingService } from \"wmfnext-shared\";\n\nexport default function Page7() {\n    const logger = useLogger();\n    const trackingService = useTrackingService();\n\n    logger.debug(\"Rendering \\\"page7\\\" from module \\\"remote1\\\"\");\n\n    trackingService.track({\n        page: \"page7\",\n        module: \"remote-1\"\n    });\n\n    return (\n        \u003cmain\u003e\n            \u003ch1\u003ePage 7\u003c/h1\u003e\n            \u003cp\u003eFrom remote-1\u003c/p\u003e\n        \u003c/main\u003e\n    );\n}\n```\n\n👉 Start the applications and navigate to the _\"remote1/page-7\"_ page. Open the console and you should see the following log:\n\n```\n[tracking] Tracking the following data: {page: 'page7', module: 'remote-1'}\n```\n\n### Use a custom logger\n\nMany applications must integrates with specific remote logging solutions like [Datadog](https://www.datadoghq.com/) and [Azure Application Insights](https://learn.microsoft.com/en-us/azure/azure-monitor/app/app-insights-overview).\n\nTo help with that, the shell runtime accept any custom loggers that implements the `Logger` interface.\n\n👉 Let's add a `CustomLogger` class to the host application:\n\n```ts\n// host - customLogger.ts\n\nimport { LogLevel } from \"wmfnext-shell\";\nimport type { Logger } from \"wmfnext-shell\";\n\nexport class CustomLogger implements Logger {\n    private _logLevel: LogLevel;\n\n    constructor(logLevel: LogLevel = LogLevel.critical) {\n        this._logLevel = logLevel;\n    }\n\n    debug(log: string, ...rest: unknown[]): Promise\u003cunknown\u003e {\n        if (this._logLevel \u003e= LogLevel.debug) {\n            console.log(`[custom-logger] ${log}`, ...rest);\n        }\n\n        return Promise.resolve();\n    }\n\n    information(log: string, ...rest: unknown[]): Promise\u003cunknown\u003e {\n        if (this._logLevel \u003e= LogLevel.information) {\n            console.info(`[custom-logger] ${log}`, ...rest);\n        }\n\n        return Promise.resolve();\n    }\n\n    warning(log: string, ...rest: unknown[]): Promise\u003cunknown\u003e {\n        if (this._logLevel \u003e= LogLevel.warning) {\n            console.warn(`[custom-logger] ${log}`, ...rest);\n        }\n\n        return Promise.resolve();\n    }\n\n    error(log: string, ...rest: unknown[]): Promise\u003cunknown\u003e {\n        if (this._logLevel \u003e= LogLevel.error) {\n            console.error(`[custom-logger] ${log}`, ...rest);\n        }\n\n        return Promise.resolve();\n    }\n\n    critical(log: string, ...rest: unknown[]): Promise\u003cunknown\u003e {\n        if (this._logLevel \u003e= LogLevel.critical) {\n            console.error(`[custom-logger] ${log}`, ...rest);\n        }\n\n        return Promise.resolve();\n    }\n}\n```\n\n👉 Then, update the host application to register an instance of the `CustomLogger`:\n\n```tsx\n// host - bootstrap.tsx\n\nimport { CustomLogger } from \"./customLogger\";\n\nconst runtime = new Runtime({\n    loggers: [\n        new ConsoleLogger(),\n        new CustomLogger()\n    ],\n    services: {\n        [TrackingServiceKey]: new TrackingService()\n    },\n    sessionAccessor\n});\n```\n\n👉 Start the applications and open the dev tools. Refresh the page. The console logs should be displayed twice:\n\n```\n[shell] Found 1 static modules to register\n[custom-logger] [shell] Found 1 static modules to register\n```\n\n### Data and state\n\nThis shell doesn't offer any build-in feature to handle data and state management. Why?\n\n💡 It's our 4th take! Data and state should never be shared between parts of a federated application. Even if two parts needs the same data or the same state values, they should load, store and manage those independently.\n\n### Develop a module in isolation\n\nTo develop their own module, an independent team should not be required to install the host application or any other modules they do not own. However, they should still have a way to integrate with the federated application shell (root layout, root error boundary, etc..) while developing their module in isolation.\n\nTo achieve this, the first step is to move the federated application shell to the `wmfnext-shared` package.\n\n👉 Let's move the host application's `RootLayout`, `RootErrorBoundary` and `AuthenticationBoundary` components to the `app-shell` folder of the `wmfnext-shared` package:\n\n```\nshared\n├── src\n├──── app-shell\n├────── AuthenticationBoundary.tsx\n├────── RootErrorBoundary.tsx\n├────── RootLayout.tsx\n```\n\n\u003e For a real application, it is recommend to move the federated application shell into its own package to prevent the code from being bundled with the actual module code. If its not possible, at least declare the package as a shared singleton dependency.\n\n👉 Then, refactor the host application's `App` component router initialization code into a reusable `useAppRouter(options)`:\n\n```tsx\n// shared - useAppRouter.tsx\n\nimport { Route, useHoistedRoutes, useRoutes } from \"wmfnext-shell\";\nimport { useCallback, useMemo, useState } from \"react\";\nimport { AuthenticationBoundary } from \"./AuthenticationBoundary\";\nimport { RootErrorBoundary } from \"./RootErrorBoundary\";\nimport { RootLayout } from \"./RootLayout\";\nimport { createBrowserRouter } from \"react-router-dom\";\n\nexport interface UseAppRouterOptions {\n    rootRoutes?: Route[];\n}\n\nexport function useAppRouter({ rootRoutes = [] }: UseAppRouterOptions = {}) {\n    // Hack to reuse the same array reference through re-renders.\n    const [memoizedRootRoutes] = useState(rootRoutes);\n\n    const routes = useRoutes();\n\n    const wrapManagedRoutes = useCallback((managedRoutes: Readonly\u003cRoute[]\u003e) =\u003e {\n        return {\n            element: \u003cAuthenticationBoundary /\u003e,\n            children: [\n                {\n                    path: \"/\",\n                    element: \u003cRootLayout /\u003e,\n                    children: [\n                        {\n                            errorElement: \u003cRootErrorBoundary /\u003e,\n                            children: [\n                                ...managedRoutes\n                            ]\n                        }\n                    ]\n                }\n            ]\n        };\n    }, []);\n\n    const hoistedRoutes = useHoistedRoutes(routes, {\n        wrapManagedRoutes,\n        allowedPaths: [\n            \"remote1/page-2\",\n            \"remote1/page-4\"\n        ]\n    });\n\n    const router = useMemo(() =\u003e {\n        return createBrowserRouter([...hoistedRoutes, ...memoizedRootRoutes]);\n    }, [hoistedRoutes, memoizedRootRoutes]);\n\n    return router;\n}\n```\n\n👉 Finally, update the host application's `App.tsx` file to use the newly created `useAppRouter()` hook:\n\n```tsx\n// host - App.tsx\n\nimport { Loading, useAppRouter } from \"wmfnext-shared\";\nimport { RouterProvider } from \"react-router-dom\";\nimport { lazy } from \"react\";\nimport { useAreRemotesReady } from \"wmfnext-remote-loader\";\n\nconst LoginPage = lazy(() =\u003e import(\"./pages/Login\"));\nconst LogoutPage = lazy(() =\u003e import(\"./pages/Logout\"));\nconst NotFoundPage = lazy(() =\u003e import(\"./pages/NotFound\"));\n\nexport function App() {\n    const isReady = useAreRemotesReady();\n\n    const router = useAppRouter({\n        rootRoutes: [\n            {\n                path: \"login\",\n                element: \u003cLoginPage /\u003e\n            },\n            {\n                path: \"logout\",\n                element: \u003cLogoutPage /\u003e\n            },\n            {\n                path: \"*\",\n                element: \u003cNotFoundPage /\u003e\n            }\n        ]\n    });\n\n    if (!isReady) {\n        return \u003cLoading /\u003e;\n    }\n\n    return (\n        \u003cRouterProvider\n            router={router}\n            fallbackElement={\u003cLoading /\u003e}\n        /\u003e\n    );\n}\n```\n\nWith this setup in place, we can now configure our module applications to be developed in isolation.\n\n#### Remote modules\n\nFor a remote module application to be developed in isolation, there are a few steps to take:\n\n1. Create a new `index.tsx` file that will instanciate a `Runtime` instance and register the remote module as a static module.\n2. Create a new `App.tsx` component to render the federated application shell.\n3. Add Webpack `public` folder at the root of the project.\n4. Add a new command to serve the app as a local application rather than a federated module.\n5. Update the Webpack config `entry` file to `.src/index.tsx` instead of `.src/register.tsx`.\n6. Replace the `ModuleFederationPlugin` with the `HtmlWebpackPlugin`.\n\n👉 Let's starts by creating the new `index.tsx` and `App.tsx` files:\n\n```tsx\n// remote-1 - index.tsx\n\nimport { ConsoleLogger, Runtime, RuntimeContext, deepFreeze, registerStaticModules } from \"wmfnext-shell\";\nimport { Loading, TrackingService, TrackingServiceKey } from \"wmfnext-shared\";\nimport { App } from \"./App\";\nimport type { Session } from \"wmfnext-shared\";\nimport { Suspense } from \"react\";\nimport { createRoot } from \"react-dom/client\";\nimport { register } from \"./register\";\n\n// Creating a runtime instance with a fake user session.\n// To use an in-memory session the \"wmfnext-fakes\"\n// SessionManager implementation can be used instead.\nconst runtime = new Runtime({\n    loggers: [new ConsoleLogger()],\n    services: {\n        [TrackingServiceKey]: new TrackingService()\n    },\n    sessionAccessor: () =\u003e {\n        const session = deepFreeze({\n            user: {\n                name: \"temp\"\n            }\n        });\n\n        return session as Readonly\u003cSession\u003e;\n    }\n});\n\n// Registering the remote module as a static module because the\n// \"register\" is local when developing in isolation.\nregisterStaticModules([register], runtime);\n\nconst root = createRoot(document.getElementById(\"root\"));\n\nroot.render(\n    \u003cRuntimeContext.Provider value={runtime}\u003e\n        \u003cSuspense fallback={\u003cLoading /\u003e}\u003e\n            \u003cApp /\u003e\n        \u003c/Suspense\u003e\n    \u003c/RuntimeContext.Provider\u003e\n);\n```\n\n```tsx\n// remote-1 - App.tsx\n\nimport { Loading, useAppRouter } from \"wmfnext-shared\";\nimport { RouterProvider } from \"react-router-dom\";\n\nexport function App() {\n    // We are using again the hook that we created at the beginning\n    // of this section. useAppRouter takes care of creating the router\n    // instance and setuping the federated application shell.\n    const router = useAppRouter();\n\n    return (\n        \u003cRouterProvider\n            router={router}\n            fallbackElement={\u003cLoading /\u003e}\n        /\u003e\n    );\n}\n```\n\n👉 Next, add a new `dev-local` command to the `package.json` file to start the local development server:\n\n```json\n{\n    \"dev\": \"webpack serve --config webpack.dev.js\",\n    \"dev-local\": \"cross-env LOCAL=true webpack serve --config webpack.dev.js\",\n}\n```\n\nThe `dev-local` command is very similar to the `dev` command but it defines a `LOCAL` environment variable. This new environment variable will be read by the `webpack.dev.js` file to adapt the Webpack configuration accordingly.\n\n👉 Next, update the `webpack.dev.js` file to leverage the `LOCAL` environment variable:\n\n```js\n// remote-1 - webpack.dev.js\n\nimport { createModulePlugin, isLocal } from \"wmfnext-shared/webpack.js\";\nimport HtmlWebpackPlugin from \"html-webpack-plugin\";\nimport packageJson from \"./package.json\" assert { type: \"json\" };\n\n/** @type {import(\"webpack\").Configuration} */\nexport default {\n    entry: isLocal ? \"./src/index.tsx\" : \"./src/register.tsx\",\n    plugins: [\n        isLocal\n            ? new HtmlWebpackPlugin({ template: \"./public/index.html\" })\n            : createModulePlugin(\"remote1\", packageJson)\n    ]\n};\n```\n\n👉 Start the local application by running the `dev-local` command. The federated application shell should wrap the content of the index route of the module.\n\n#### Static modules\n\nPutting in place a development setup for a static module application is very similar to what we've done previously for a remote module application. The key difference is that since a static module is never served as a remote bundle, we start with a blank Webpack configuration file.\n\nHere's what we'll do:\n\n1. Create a new `index.tsx` file that will instanciate a `Runtime` instance and register the static module.\n2. Create a new `App.tsx` component to render the federated application shell.\n3. Add Webpack `public` folder at the root of the project.\n4. Add a new command to serve the local application.\n5. Create a `webpack.config.js` file.\n\n👉 First, create the new `index.tsx` and `App.tsx` files:\n\n```tsx\n// static-1 - index.tsx\n\nimport { ConsoleLogger, Runtime, RuntimeContext, deepFreeze, registerStaticModules } from \"wmfnext-shell\";\nimport { Loading, TrackingService, TrackingServiceKey } from \"wmfnext-shared\";\n\nimport { App } from \"./App\";\nimport type { Session } from \"wmfnext-shared\";\nimport { Suspense } from \"react\";\nimport { createRoot } from \"react-dom/client\";\nimport { register } from \"./register\";\n\n// Creating a runtime instance with a fake user session.\n// To use an in-memory session the \"wmfnext-fakes\"\n// SessionManager implementation can be used instead.\nconst runtime = new Runtime({\n    loggers: [new ConsoleLogger()],\n    services: {\n        [TrackingServiceKey]: new TrackingService()\n    },\n    sessionAccessor: () =\u003e {\n        const session = deepFreeze({\n            user: {\n                name: \"temp\"\n            }\n        });\n\n        return session as Readonly\u003cSession\u003e;\n    }\n});\n\nregisterStaticModules([register], runtime);\n\nconst root = createRoot(document.getElementById(\"root\"));\n\nroot.render(\n    \u003cRuntimeContext.Provider value={runtime}\u003e\n        \u003cSuspense fallback={\u003cLoading /\u003e}\u003e\n            \u003cApp /\u003e\n        \u003c/Suspense\u003e\n    \u003c/RuntimeContext.Provider\u003e\n);\n```\n\n```tsx\n// static-1 - App.tsx\n\nimport { Loading, useAppRouter } from \"wmfnext-shared\";\nimport { RouterProvider } from \"react-router-dom\";\n\nexport function App() {\n    // We are using again the hook that we created at the beginning\n    // of this section. useAppRouter takes care of creating the router\n    // instance and setuping the federated application shell.\n    const router = useAppRouter();\n\n    return (\n        \u003cRouterProvider\n            router={router}\n            fallbackElement={\u003cLoading /\u003e}\n        /\u003e\n    );\n}\n```\n\n👉 Next, add a new `dev-local` command to the `package.json` file to start the local development server:\n\n```json\n{\n    \"dev\": \"tsc --watch --project ./tsconfig.json\",\n    \"dev-local\": \"webpack serve --config webpack.config.js\"\n}\n```\n\n👉 Next, create a `webpack.config.js` and add the following configuration:\n\n\u003cdetails\u003e\n    \u003csummary\u003eView configuration\u003c/summary\u003e\n    \u003cbr /\u003e\n\n```js\n// static-1 - webpack.config\n\nimport HtmlWebpackPlugin from \"html-webpack-plugin\";\nimport { getFileDirectory } from \"wmfnext-remote-loader/webpack.js\";\nimport path from \"path\";\n\nconst __dirname = getFileDirectory(import.meta);\n\n/** @type {import(\"webpack\").Configuration} */\nexport default {\n    mode: \"development\",\n    target: \"web\",\n    devtool: \"inline-source-map\",\n    devServer: {\n        port: 8082,\n        historyApiFallback: true\n    },\n    entry: \"./src/index.tsx\",\n    output: {\n        // The trailing / is very important, otherwise paths will ne be resolved correctly.\n        publicPath: \"http://localhost:8082/\"\n    },\n    module: {\n        rules: [\n            {\n                test: /\\.(ts|tsx)$/,\n                exclude: /node_modules/,\n                use: {\n                    loader: \"ts-loader\",\n                    options: {\n                        transpileOnly: true,\n                        configFile: path.resolve(__dirname, \"tsconfig.json\")\n                    }\n                }\n            },\n            {\n                // https://stackoverflow.com/questions/69427025/programmatic-webpack-jest-esm-cant-resolve-module-without-js-file-exten\n                test: /\\.js/,\n                resolve: {\n                    fullySpecified: false\n                }\n            },\n            {\n                test: /\\.(css)$/,\n                use: [\"style-loader\", \"css-loader\"]\n            },\n            {\n                test: /\\.(png|jpe?g|gif)$/i,\n                type: \"asset/resource\"\n            }\n        ]\n    },\n    resolve: {\n        // Must add \".js\" for files imported from node_modules.\n        extensions: [\".js\", \".ts\", \".tsx\", \".css\"]\n    },\n    plugins: [\n        new HtmlWebpackPlugin({\n            template: \"./public/index.html\"\n        })\n    ]\n};\n```\n\n```html\n\u003c!-- static-1 - public/index.html --\u003e\n\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n  \u003chead\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n    \u003cdiv id=\"root\"\u003e\u003c/div\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\u003c/details\u003e\n\n👉 Run the local application with the `dev-local` command. The federated application shell should wrap the content of the index route of the module.\n\n## 🔧 API\n\n### wmfnext-shell package\n\n#### Runtime\n\n##### class Runtime({ loggers, services, sessionAccessor })\n\n```ts\nimport { Runtime } from \"wmfnext-shell\";\n\nconst runtime = new Runtime({\n    loggers: [],\n    services: {},\n    sessionAccessor: () =\u003e {}\n});\n\nruntime.registerRoutes([\n    {\n        path: \"/page-1\",\n        element: \u003cPage /\u003e\n    }\n]);\n\nconst routes = runtime.routes;\n\nruntime.registerNavigationItems([\n    {\n        to: \"/page-1\",\n        content: \"Page 1\"\n    }\n]);\n\nconst navigationItems = runtime.navigationItems;\n\nconst logger = runtime.logger;\n\nconst eventBus = runtime.eventBus;\n\nconst service = runtime.getService(\"serviceName\") as TService;\n\nconst session = runtime.getSession() as TSession;\n```\n\n##### RuntimeContext\n\n```tsx\nimport { RuntimeContext } from \"wmfnext-shell\";\n\nconst runtime = new Runtime();\n\nroot.render(\n    \u003cRuntimeContext.Provider value={runtime}\u003e\n        \u003cApp /\u003e\n    \u003c/RuntimeContext.Provider\u003e\n);\n```\n\n##### useRuntime()\n\n```ts\nimport { useRuntime } from \"wmfnext-shell\";\n\nconst runtime = useRuntime();\n```\n\n##### useRoutes()\n\n```ts\nimport { useRoutes } from \"wmfnext-shell\";\n\nconst routes = useRoutes();\n```\n\n##### useNavigationItems()\n\n```ts\nimport { useNavigationItems } from \"wmfnext-shell\";\n\nconst items = useNavigationItems();\n```\n\n##### useLogger()\n\n```ts\nimport { useLogger } from \"wmfnext-shell\";\n\nconst logger = useLogger();\n```\n\n##### useSession()\n\n```ts\nimport { useSession } from \"wmfnext-shell\";\n\nconst session = useSession() as T;\n```\n\n##### useService(serviceName)\n\n```ts\nimport { useService } from \"wmfnext-shell\";\n\nconst service = useService(\"serviceName\") as T;\n```\n\n#### Static modules registration\n\n##### registerStaticModule(registerFunctions, runtime, { context })\n\n```ts\nimport { registerStaticModule, Runtime, ModuleRegisterFunction } from \"wmfnext-shell\";\n\nconst register: ModuleRegisterFunction = (runtime, context) =\u003e {\n    runtime.logger.debug(context.foo);\n};\n\nconst runtime = new Runtime();\n\nregisterStaticModules([register], runtime, {\n    context: {\n        foo: \"bar\"\n    }\n});\n```\n\n#### Routing\n\n##### useHoistedRoutes(routes, { wrapManagedRoutes, allowedPaths })\n\n```tsx\nimport { useRoutes, useHoistedRoutes, Route } from \"wmfnext-shell\";\nimport { useCallback } from \"react\";\n\nconst routes = useRoutes();\n\nconst wrapManagedRoutes = useCallback((managedRoutes: Readonly\u003cRoute[]\u003e) =\u003e {\n    return {\n        path: \"/\",\n        element: \u003cRootLayout /\u003e,\n        children: [\n            ...managedRoutes\n        ]\n    };\n}, []);\n\nconst hoistedRoutes = useHoistedRoutes(routes, {\n    wrapManagedRoutes,\n    allowedPaths: [\n        \"page-1\"\n    ]\n});\n```\n\n#### Navigation items\n\n##### useRenderedNavigationItems(navigationItems, renderItem, renderSection)\n\n```tsx\nimport { useNavigationItems, useRenderedNavigationItems } from \"wmfnext-shell\";\nimport { useCallback } from \"react\";\nimport { Link } from \"react-router-dom\";\n\nconst navigationItems = useNavigationItems();\n\nconst renderItem: RenderItemFunction = useCallback(({ content, linkProps, index, level }) =\u003e {\n    return (\n        \u003cli key={`${level}-${index}`}\u003e\n            \u003cLink {...linkProps}\u003e\n                {content}\n            \u003c/Link\u003e\n        \u003c/li\u003e\n    );\n}, []);\n\nconst renderSection: RenderSectionFunction = useCallback((itemElements, index, level) =\u003e {\n    return (\n        \u003cul key={`${level}-${index}`}\u003e\n            {itemElements}\n        \u003c/ul\u003e\n    );\n}, []);\n\nconst renderedNavigationItems = useRenderedNavigationItems(navigationItems, renderItem, renderSection);\n```\n\n#### Logging\n\n##### interface Logger\n\n```ts\nimport { Logger } from \"wmfnext-shell\";\n\nclass CustomLogger: Logger {\n    debug(log) { ... }\n    information(log) { ... }\n    warning(log) { ... }\n    error(log) { ... }\n    critical(log) { ... }\n}\n```\n\n##### class ConsoleLogger\n\n```ts\nimport { ConsoleLogger, LogLevel } from \"wmfnext-shell\";\n\nconst logger = new ConsoleLogger(Loglevel.debug);\n\nlogger.debug(\"Debug log\", { foo: \"bar\" });\nlogger.information(\"Info log\");\nlogger.warning(\"Warning log\");\nlogger.error(\"Error log\");\nlogger.critical(\"Critical log\");\n```\n\n#### Messaging\n\n##### class EventBus({ logger })\n\n```ts\nimport { EventBus, ConsoleLogger } from \"wmfnext-shell\";\n\nconst eventBus = new EventBus({\n    logger: new ConsoleLogger()\n});\n\nconst handleFoo = (data, context) =\u003e {\n    // do something...\n}\n\neventBus.addListener(\"foo\", handleFoo);\neventBus.removeListener(\"foo\", handleFoo);\n\neventBus.addListener(\"foo-once\", handleFoo, { once: true });\neventBus.removeListener(\"foo-once\", handleFoo, { once: true });\n\neventBus.dispatch(\"foo\", \"bar\");\n```\n\n##### useEventBusListener(eventName, callback, { once })\n\n```ts\nimport { useEventBusListener } from \"wmfnext-shell\";\nimport { useCallback } from \"react\";\n\nconst handleFoo = useCallback(() =\u003e {\n    // do something...\n}, []);\n\nuseEventBusListener(\"foo\", handleFoo);\n```\n\n##### useEventBusDispatcher(eventName, data)\n\n```ts\nimport { useEventBusDispatcher } from \"wmfnext-shell\";\n\nconst dispatch = useEventBusDispatcher();\n\ndispatch(\"foo\", \"bar\");\n```\n\n#### Utils\n\n##### deepFreeze(obj)\n\n```ts\nimport { deepFreeze } from \"wmfnext-shell\";\n\ndeepFreeze({\n    foo: {\n        bar: {\n            to: \"to\"\n        }\n    }\n});\n```\n\n### wmfnext-remote-loader package\n\n#### Remote modules registration\n\n##### registerRemoteModule(remotes, runtime, { context })\n\n```ts\nimport { registerRemoteModules, RemoteDefinition } from \"wmfnext-remote-loader\";\nimport { Runtime } from \"wmfnext-shell\";\n\nconst runtime = new Runtime();\n\nconst remotes: RemoteDefinition[] = [\n    { name: \"remote-1\", url: \"http://localhost:8081\" }\n];\n\nregisterRemoteModules(remotes, runtime, {\n    context: {\n        foo: \"bar\"\n    }\n});\n```\n\n##### useAreRemotesReady()\n\n```ts\nimport { useAreRemotesReady } from \"wmfnext-remote-loader\";\n\nconst isReady = useAreRemotesReady();\n\nif (!isReady) {\n    // do something...\n}\n```\n\n##### registrationStatus\n\n```ts\nimport { registrationStatus } from \"wmnext-remote-loader\";\n\nif (registrationStatus !== \"ready\") {\n    // dom something...\n}\n```\n\n#### Webpack config utils\n\n##### getFileDirectory(meta)\n\n```ts\nimport { getFileDirectory } from \"wmfnext-remote-loader/webpack.js\";\n\nconst __dirname = getFileDirectory(import.meta);\n```\n\n##### createHostConfiguration(moduleName, packageJson, { sharedDependencies })\n\n```ts\nimport { createHostConfiguration } from \"wmfnext-remote-loader/webpack.js\";\nimport packageJson from \"./package.json\" assert { type: \"json\" };\nimport ModuleFederationPlugin from \"webpack/lib/container/ModuleFederationPlugin.js\";\n\nconst config = createHostConfiguration(\"host\", packageJson);\nconst plugin = new ModuleFederationPlugin(config);\n```\n\n##### createModuleConfiguration(moduleName, packageJson, { sharedDependencies })\n\n```ts\nimport { createModuleConfiguration } from \"wmfnext-remote-loader/webpack.js\";\nimport packageJson from \"./package.json\" assert { type: \"json\" };\nimport ModuleFederationPlugin from \"webpack/lib/container/ModuleFederationPlugin.js\";\n\nconst config = createModuleConfiguration(\"remote1\", packageJson);\nconst plugin = new ModuleFederationPlugin(config);\n```\n\n##### createHostPlugin(moduleName, packageJson, { sharedDependencies })\n\n```ts\nimport { createHostPlugin } from \"wmfnext-remote-loader/webpack.js\";\nimport packageJson from \"./package.json\" assert { type: \"json\" };\n\nconst plugin = createHostPlugin(\"host\", packageJson);\n```\n\n##### createModulePlugin(moduleName, packageJson, { sharedDependencies })\n\n```ts\nimport { createModulePlugin } from \"wmfnext-remote-loader/webpack.js\";\nimport packageJson from \"./package.json\" assert { type: \"json\" };\n\nconst plugin = createModulePlugin(\"remote1\", packageJson);\n```\n\n### wmfnext-fakes package\n\n##### class SessionManager({ key })\n\n```ts\nimport { SessionManager } from \"wmfnext-fakes\";\n\nconst sessionManager = new SessionManager();\n\nsessi","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpatricklafrance%2Fwmfnext-shell","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpatricklafrance%2Fwmfnext-shell","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpatricklafrance%2Fwmfnext-shell/lists"}