├── LICENSE ├── README.md ├── buildtime └── .gitkeep └── runtime ├── cssBoundary ├── .babelrc ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── rollup.config.js ├── src │ ├── CssBoundary.tsx │ ├── global.d.ts │ ├── index.ts │ └── styleLoader.ts └── tsconfig.json └── importRemote ├── .babelrc ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── rollup.config.js ├── src ├── global.d.ts └── index.ts └── tsconfig.json /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Module Federation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Module Federation Utilities 2 | 3 | This repository contains various runtime and buildtime utilities for Module Federation 4 | 5 | > Legend: 6 | > 7 | > - ⚠️: In Progress/Incomplete 8 | 9 | ## Runtime 10 | - [x] [Import Remote](./runtime/importRemote/README.md) — Dynamically import a remotely exposed module 11 | - [x] [CSS Boundary](./runtime/cssBoundary/README.md) — Block outside CSS from affecting your microfrontend application 12 | 13 | ## Buildtime ⚠️ 14 | -------------------------------------------------------------------------------- /buildtime/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/module-federation/utilities/46097d387f1ec2af426406a9461e2a0fc5319e15/buildtime/.gitkeep -------------------------------------------------------------------------------- /runtime/cssBoundary/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-typescript", 4 | [ 5 | "@babel/preset-env", 6 | { 7 | "corejs": 3, 8 | "useBuiltIns": "entry", 9 | "targets": "> 0.25%, not dead" 10 | } 11 | ], 12 | "@babel/preset-react" 13 | ], 14 | "plugins": [["@babel/plugin-transform-runtime", { "useESModules": false }]] 15 | } 16 | -------------------------------------------------------------------------------- /runtime/cssBoundary/.gitignore: -------------------------------------------------------------------------------- 1 | # These are some examples of commonly ignored file patterns. 2 | # You should customize this list as applicable to your project. 3 | # Learn more about .gitignore: 4 | # https://www.atlassian.com/git/tutorials/saving-changes/gitignore 5 | 6 | # Node artifact files 7 | node_modules/ 8 | 9 | # Compiled Java class files 10 | *.class 11 | 12 | # Compiled Python bytecode 13 | *.py[cod] 14 | 15 | # Log files 16 | *.log 17 | 18 | # Package files 19 | *.jar 20 | 21 | # Maven 22 | target/ 23 | dist/ 24 | 25 | # JetBrains IDE 26 | .idea/ 27 | 28 | # Unit test reports 29 | TEST*.xml 30 | 31 | # Generated by MacOS 32 | .DS_Store 33 | 34 | # Generated by Windows 35 | Thumbs.db 36 | 37 | # Applications 38 | *.app 39 | *.exe 40 | *.war 41 | 42 | # Large media files 43 | *.mp4 44 | *.tiff 45 | *.avi 46 | *.flv 47 | *.mov 48 | *.wmv 49 | 50 | -------------------------------------------------------------------------------- /runtime/cssBoundary/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "all", 4 | "singleQuote": false, 5 | "printWidth": 120 6 | } 7 | -------------------------------------------------------------------------------- /runtime/cssBoundary/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Alex Vukov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /runtime/cssBoundary/README.md: -------------------------------------------------------------------------------- 1 | # Microfrontend CSS Boundary 2 | 3 | Block outside CSS from affecting your microfrontend application. 4 | 5 | ## Notes 6 | 7 | This library allows you to completely isolate the CSS of your microfrontend application from the host application by dynamically placing it inside a Web Component (i.e. using the shadow DOM browser feature). The isolation enables you to use different versions of libraries with global CSS rules (such as Bootstrap) in parallel. It can be helpful if you need to split an old monolithic application into microfrontends or for testing purposes. Currently the library works only in conjuntion with [style-loader](https://www.npmjs.com/package/style-loader) (`mini-css-extract-plugin` is not yet supported). 8 | 9 | ## How to use 10 | 11 | > **Warning** 12 | > 13 | > This library is intened for use inside an embedded microfrontend application. It is not intended to be used in the host (a.k.a. shell) application. 14 | 15 | In order for the CSS imports to work properly inside the microfrontend application first you need to pass the `insert` function from the library to the `style-loader` options in the Webpack configuration: 16 | 17 | ```js 18 | // webpack.config.js 19 | const { insert } = require('css-boundary'); 20 | 21 | module.exports = { 22 | // ... 23 | module: { 24 | // ... 25 | rules: [ 26 | // ... 27 | { 28 | test: /\.css$/i, 29 | use: [ 30 | { 31 | loader: 'style-loader', 32 | options: { 33 | insert, 34 | }, 35 | }, 36 | 'css-loader', 37 | ], 38 | }, 39 | ], 40 | }, 41 | }; 42 | ``` 43 | 44 | **Then you have two options:** 45 | 46 | ### 1. If you use React the simplest way is to use the `CssBoundary` component at the root of your application: 47 | 48 | ```jsx 49 | // App.jsx 50 | import { CssBoundary } from 'css-boundary'; 51 | 52 | const App = () => ( 53 | 54 | {/* Application code */} 55 | 56 | ); 57 | ``` 58 | 59 | > **Warning** 60 | > 61 | > Use React version 17 or higher in the microfrontend application. Lower versions of React do not re-render properly when inside a shadow DOM. 62 | 63 | ### 2. If you don't use React or you wish to have more fine control you can use the `createShadowInstance` and `deleteShadowInstance` functions: 64 | 65 | ```js 66 | // SomeComponent.js 67 | import { createShadowInstance, deleteShadowInstance } from 'css-boundary'; 68 | 69 | // After mount: 70 | const parentElement = document.getElementById('some-element'); 71 | const appPlaceholder = createShadowInstance(parentElement); // Creates a shadow DOM and attaches it to the parentElement which is an HTMLElement 72 | 73 | // appPlaceholder is where you can inject your application code. For example: 74 | appPlaceholder.innerHTML = '
My application
'; 75 | 76 | // On unmount: 77 | deleteShadowInstance(parentElement); // parentElement is the HTMLElement where the shadow DOM was attached 78 | ``` 79 | 80 | Both ways will render the microfronend application inside a shadow DOM attached to the app injection HTML element. Any global or local CSS used anywhere in the embedded application will be placed inside the shadowRoot and will not affect or be affected by the host application. 81 | -------------------------------------------------------------------------------- /runtime/cssBoundary/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "css-boundary", 3 | "version": "1.0.1", 4 | "description": "CSS Boundary for Microfrontend Applications", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "scripts": { 8 | "build": "rimraf dist && tsc && rollup -c" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/alex-vukov/css-boundary.git" 13 | }, 14 | "keywords": [ 15 | "web component", 16 | "css", 17 | "isolation", 18 | "style-loader", 19 | "microfrontend", 20 | "micro-frontend" 21 | ], 22 | "author": "Alexander Vukov", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/module-federation/utilities/issues" 26 | }, 27 | "homepage": "https://github.com/module-federation/utilities/runtime/cssBoundary#readme", 28 | "files": [ 29 | "dist" 30 | ], 31 | "devDependencies": { 32 | "@babel/cli": "^7.18.10", 33 | "@babel/core": "^7.18.10", 34 | "@babel/plugin-transform-runtime": "^7.18.10", 35 | "@babel/preset-env": "^7.18.10", 36 | "@babel/preset-react": "^7.18.6", 37 | "@babel/preset-typescript": "^7.18.6", 38 | "@rollup/plugin-babel": "^5.3.1", 39 | "@rollup/plugin-commonjs": "^22.0.2", 40 | "@rollup/plugin-node-resolve": "^13.3.0", 41 | "@types/estree": "^1.0.0", 42 | "@types/react": "^18.0.21", 43 | "@types/react-dom": "^18.0.6", 44 | "rimraf": "^3.0.2", 45 | "rollup": "^2.78.1", 46 | "typescript": "^4.7.4" 47 | }, 48 | "dependencies": { 49 | "@babel/runtime": "^7.18.9" 50 | }, 51 | "peerDependencies": { 52 | "webpack": "^5.25.0", 53 | "react": "^17.0.0 || ^18.0.0", 54 | "react-dom": "^17.0.0 || ^18.0.0" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /runtime/cssBoundary/rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from "@rollup/plugin-node-resolve"; 2 | import { babel } from "@rollup/plugin-babel"; 3 | import commonjs from "@rollup/plugin-commonjs"; 4 | 5 | const extensions = [".js", ".ts", ".tsx", ".json"]; 6 | 7 | const config = { 8 | input: "src/index.ts", 9 | external: [/@babel\/runtime/, "react", "react-dom"], 10 | output: [ 11 | { 12 | dir: "dist/", 13 | format: "cjs", 14 | preserveModules: true, 15 | exports: "auto", 16 | sourcemap: true, 17 | }, 18 | ], 19 | plugins: [ 20 | resolve({ extensions }), 21 | commonjs(), 22 | babel({ 23 | include: "src/**/*", 24 | exclude: "**/node_modules/**", 25 | babelHelpers: "runtime", 26 | extensions, 27 | }), 28 | ], 29 | }; 30 | 31 | export default config; 32 | -------------------------------------------------------------------------------- /runtime/cssBoundary/src/CssBoundary.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode, useEffect, useRef, useState } from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { createShadowInstance, deleteShadowInstance } from "./styleLoader"; 4 | 5 | export const CssBoundary = ({ children }: { children: ReactNode }) => { 6 | const id = useRef(`${Date.now() + Math.random()}`); 7 | const [appPlaceholder, setAppPlaceholder] = useState(); 8 | 9 | useEffect(() => { 10 | const placeholder = createShadowInstance(id.current); 11 | setAppPlaceholder(placeholder); 12 | return () => { 13 | deleteShadowInstance(id.current); 14 | }; 15 | }, []); 16 | 17 | return
{appPlaceholder && ReactDOM.createPortal(children, appPlaceholder)}
; 18 | }; 19 | -------------------------------------------------------------------------------- /runtime/cssBoundary/src/global.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | 3 | declare global { 4 | const __webpack_runtime_id__: string; 5 | } 6 | -------------------------------------------------------------------------------- /runtime/cssBoundary/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./styleLoader"; 2 | export * from "./CssBoundary"; 3 | -------------------------------------------------------------------------------- /runtime/cssBoundary/src/styleLoader.ts: -------------------------------------------------------------------------------- 1 | // Create a shadow container with all styles and a placeholder for the app injection 2 | export const createShadowInstance = function (parentElementId: string) { 3 | const { styles, instances }: { styles: HTMLElement[]; instances: { [key: string]: HTMLElement } } = 4 | window["css-boundary-" + __webpack_runtime_id__]; 5 | const shadowContainer = document.getElementById(parentElementId); 6 | if (!shadowContainer) { 7 | throw new Error(`Could not find element with id ${parentElementId}`); 8 | } 9 | // Block all styles coming from the light DOM 10 | shadowContainer.style.all = "initial"; 11 | try { 12 | shadowContainer.attachShadow({ mode: "open", delegatesFocus: true }); 13 | if (!shadowContainer.shadowRoot) { 14 | throw new Error("Shadow root not available"); 15 | } 16 | } catch (error) { 17 | throw error; 18 | } 19 | shadowContainer.shadowRoot.append(...styles.map((style) => style.cloneNode(true))); 20 | instances[parentElementId] = shadowContainer; 21 | return shadowContainer.shadowRoot; 22 | }; 23 | 24 | export const deleteShadowInstance = function (id: string) { 25 | const { instances } = window["css-boundary-" + __webpack_runtime_id__]; 26 | delete instances[id]; 27 | }; 28 | 29 | export const insert = function (style: HTMLElement) { 30 | const sc = window["css-boundary-" + __webpack_runtime_id__] || {}; 31 | if (typeof sc.isStandalone === "undefined") { 32 | window["css-boundary-" + __webpack_runtime_id__] = { 33 | styles: [], 34 | instances: {}, 35 | isStandalone: false, 36 | }; 37 | } 38 | const { 39 | styles, 40 | instances, 41 | isStandalone, 42 | }: { styles: HTMLElement[]; instances: { [key: string]: HTMLElement }; isStandalone: boolean } = 43 | window["css-boundary-" + __webpack_runtime_id__]; 44 | // Update the style list for newly created shadow instances 45 | styles.push(style); 46 | 47 | if (isStandalone) { 48 | document.head.appendChild(style); 49 | } else { 50 | // Update the style list for already existing shadow instances. 51 | // This will provide them with any lazy loaded styles. 52 | Promise.resolve().then(() => { 53 | Object.values(instances).forEach((instance) => { 54 | if (instance.shadowRoot) { 55 | instance.shadowRoot.appendChild(style.cloneNode(true)); 56 | } 57 | }); 58 | }); 59 | } 60 | }; 61 | 62 | // If this function is called it will make the style loader behave as it normally does 63 | // and insert the styles into the head of the document instead of the shadow DOM 64 | export const runStandalone = function () { 65 | let { styles }: { styles: HTMLElement[] } = window["css-boundary-" + __webpack_runtime_id__]; 66 | window["css-boundary-" + __webpack_runtime_id__].isStandalone = true; 67 | styles.forEach((style) => document.head.appendChild(style)); 68 | }; 69 | -------------------------------------------------------------------------------- /runtime/cssBoundary/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "moduleResolution": "node", 5 | "allowJs": true, 6 | "jsx": "react", 7 | "strict": true, 8 | "esModuleInterop": true, 9 | "declaration": true, 10 | "emitDeclarationOnly": true, 11 | "isolatedModules": false, 12 | "declarationMap": true, 13 | "suppressImplicitAnyIndexErrors": true, 14 | "outDir": "./dist" 15 | }, 16 | "include": ["src"] 17 | } 18 | -------------------------------------------------------------------------------- /runtime/importRemote/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-typescript", 4 | [ 5 | "@babel/preset-env", 6 | { 7 | "corejs": 3, 8 | "useBuiltIns": "entry", 9 | "targets": "> 0.25%, not dead" 10 | } 11 | ] 12 | ], 13 | "plugins": [["@babel/plugin-transform-runtime", { "useESModules": true }]] 14 | } 15 | -------------------------------------------------------------------------------- /runtime/importRemote/.gitignore: -------------------------------------------------------------------------------- 1 | # These are some examples of commonly ignored file patterns. 2 | # You should customize this list as applicable to your project. 3 | # Learn more about .gitignore: 4 | # https://www.atlassian.com/git/tutorials/saving-changes/gitignore 5 | 6 | # Node artifact files 7 | node_modules/ 8 | 9 | # Compiled Java class files 10 | *.class 11 | 12 | # Compiled Python bytecode 13 | *.py[cod] 14 | 15 | # Log files 16 | *.log 17 | 18 | # Package files 19 | *.jar 20 | 21 | # Maven 22 | target/ 23 | dist/ 24 | 25 | # JetBrains IDE 26 | .idea/ 27 | 28 | # Unit test reports 29 | TEST*.xml 30 | 31 | # Generated by MacOS 32 | .DS_Store 33 | 34 | # Generated by Windows 35 | Thumbs.db 36 | 37 | # Applications 38 | *.app 39 | *.exe 40 | *.war 41 | 42 | # Large media files 43 | *.mp4 44 | *.tiff 45 | *.avi 46 | *.flv 47 | *.mov 48 | *.wmv 49 | 50 | -------------------------------------------------------------------------------- /runtime/importRemote/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "all", 4 | "singleQuote": false, 5 | "printWidth": 120 6 | } 7 | -------------------------------------------------------------------------------- /runtime/importRemote/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Alex Vukov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /runtime/importRemote/README.md: -------------------------------------------------------------------------------- 1 | # Module Federation Import Remote 2 | 3 | Allow a host application to dynamically import remotely exposed modules using Webpack Module Federation 4 | 5 | # Notes 6 | 7 | This library exports an **importRemote()** function which will enable dynamic imports of remotely exposed modules using the Module Federation plugin. It uses the method described in the official Webpack configuration under Dynamic Remote Containers. 8 | 9 | # Example 10 | 11 | 1. Expose a module in the remote application's Webpack configuration: 12 | 13 | ``` 14 | new ModuleFederationPlugin({ 15 | name: "Foo", 16 | library: { type: "var", name: "Foo" }, 17 | filename: "remoteEntry.js", 18 | exposes: { 19 | "./Bar": "./src/Bar", 20 | }, 21 | }) 22 | ``` 23 | 24 | 2. Build the remote application and serve it so that remoteEntry.js is accessible for example in the following URL http://localhost:3001. 25 | 26 | 3. Load the remotely exposed module in the host application using **importRemote()** and use it: 27 | 28 | ``` 29 | import { importRemote } from "module-federation-import-remote"; 30 | 31 | // If it's a regular js module: 32 | importRemote({ url: "http://localhost:3001", scope: 'Foo', module: 'Bar' }).then(({/* list of Bar exports */}) => { 33 | // Use Bar exports 34 | }); 35 | 36 | // If Bar is a React component you can use it with lazy and Suspense just like a dynamic import: 37 | const Bar = lazy(() => importRemote({ url: "http://localhost:3001", scope: 'Foo', module: 'Bar' })); 38 | 39 | return ( 40 | Loading Bar...}> 41 | 42 | 43 | ); 44 | ``` 45 | 46 | # Additional Options 47 | 48 | Apart from **url**, **scope** and **module** you can also pass additional options to the **importRemote()** function: 49 | 50 | - **remoteEntryFileName**: The name of the remote entry file. Defaults to "remoteEntry.js". 51 | - **bustRemoteEntryCache**: Whether to add a cache busting query parameter to the remote entry file URL. Defaults to **true**. You can disable it if cachebusting is handled by the server. 52 | -------------------------------------------------------------------------------- /runtime/importRemote/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "module-federation-import-remote", 3 | "version": "1.0.17", 4 | "description": "Allow Dynamic Imports of Remotely Exposed Modules Using Webpack Module Federation", 5 | "module": "dist/esm/index.js", 6 | "main": "dist/cjs/index.js", 7 | "types": "dist/index.d.ts", 8 | "scripts": { 9 | "build": "rimraf dist && tsc && rollup -c" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/module-federation/utilities.git" 14 | }, 15 | "keywords": [ 16 | "module", 17 | "federation", 18 | "importRemote", 19 | "webpack", 20 | "microfrontend", 21 | "micro-frontend" 22 | ], 23 | "author": "Alexander Vukov", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/module-federation/utilities/issues" 27 | }, 28 | "homepage": "https://github.com/module-federation/utilities/runtime/importRemote#readme", 29 | "files": [ 30 | "dist" 31 | ], 32 | "devDependencies": { 33 | "@babel/cli": "^7.18.10", 34 | "@babel/core": "^7.18.10", 35 | "@babel/plugin-transform-runtime": "^7.18.10", 36 | "@babel/preset-env": "^7.18.10", 37 | "@babel/preset-typescript": "^7.18.6", 38 | "@rollup/plugin-babel": "^5.3.1", 39 | "@rollup/plugin-commonjs": "^22.0.2", 40 | "@rollup/plugin-node-resolve": "^13.3.0", 41 | "@types/estree": "^1.0.0", 42 | "rimraf": "^3.0.2", 43 | "rollup": "^2.78.1", 44 | "typescript": "^4.7.4" 45 | }, 46 | "dependencies": { 47 | "@babel/runtime": "^7.18.9" 48 | }, 49 | "peerDependencies": { 50 | "webpack": "^5.0.0" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /runtime/importRemote/rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from "@rollup/plugin-node-resolve"; 2 | import { babel } from "@rollup/plugin-babel"; 3 | import commonjs from "@rollup/plugin-commonjs"; 4 | 5 | const extensions = [".js", ".ts", ".tsx", ".json"]; 6 | 7 | const config = { 8 | input: "src/index.ts", 9 | external: [/@babel\/runtime/], 10 | output: [ 11 | { 12 | dir: "dist/esm", 13 | format: "esm", 14 | preserveModules: true, 15 | sourcemap: true, 16 | }, 17 | { 18 | dir: "dist/cjs", 19 | format: "cjs", 20 | preserveModules: true, 21 | exports: "auto", 22 | sourcemap: true, 23 | }, 24 | ], 25 | plugins: [ 26 | resolve({ extensions }), 27 | commonjs(), 28 | babel({ 29 | include: "src/**/*", 30 | exclude: "**/node_modules/**", 31 | babelHelpers: "runtime", 32 | extensions, 33 | }), 34 | ], 35 | }; 36 | 37 | export default config; 38 | -------------------------------------------------------------------------------- /runtime/importRemote/src/global.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | 3 | declare global { 4 | const __webpack_init_sharing__: (parameter: string) => Promise; 5 | const __webpack_share_scopes__: { default: any }; 6 | const __webpack_require__: { l: (url: string, cb: (event: any) => void, id: string) => {} }; 7 | } 8 | -------------------------------------------------------------------------------- /runtime/importRemote/src/index.ts: -------------------------------------------------------------------------------- 1 | export interface ImportRemoteOptions { 2 | url: string; 3 | scope: string; 4 | module: string; 5 | remoteEntryFileName?: string; 6 | bustRemoteEntryCache?: boolean; 7 | } 8 | 9 | const REMOTE_ENTRY_FILE = "remoteEntry.js"; 10 | 11 | const loadRemote = ( 12 | url: ImportRemoteOptions["url"], 13 | scope: ImportRemoteOptions["scope"], 14 | bustRemoteEntryCache: ImportRemoteOptions["bustRemoteEntryCache"], 15 | ) => 16 | new Promise((resolve, reject) => { 17 | const timestamp = bustRemoteEntryCache ? `?t=${new Date().getTime()}` : ""; 18 | __webpack_require__.l( 19 | `${url}${timestamp}`, 20 | (event) => { 21 | if (event?.type === "load") { 22 | // Script loaded successfully: 23 | return resolve(); 24 | } 25 | const realSrc = event?.target?.src; 26 | const error = new Error(); 27 | error.message = "Loading script failed.\n(missing: " + realSrc + ")"; 28 | error.name = "ScriptExternalLoadError"; 29 | reject(error); 30 | }, 31 | scope, 32 | ); 33 | }); 34 | 35 | const initSharing = async () => { 36 | if (!__webpack_share_scopes__?.default) { 37 | await __webpack_init_sharing__("default"); 38 | } 39 | }; 40 | 41 | // __initialized and __initializing flags prevent some concurrent re-initialization corner cases 42 | const initContainer = async (containerScope: any) => { 43 | try { 44 | if (!containerScope.__initialized && !containerScope.__initializing) { 45 | containerScope.__initializing = true; 46 | await containerScope.init(__webpack_share_scopes__.default); 47 | containerScope.__initialized = true; 48 | delete containerScope.__initializing; 49 | } 50 | } catch (error) { 51 | console.error(error); 52 | } 53 | }; 54 | 55 | /* 56 | Dynamically import a remote module using Webpack's loading mechanism: 57 | https://webpack.js.org/concepts/module-federation/ 58 | */ 59 | export const importRemote = async ({ 60 | url, 61 | scope, 62 | module, 63 | remoteEntryFileName = REMOTE_ENTRY_FILE, 64 | bustRemoteEntryCache = true, 65 | }: ImportRemoteOptions): Promise => { 66 | if (!window[scope]) { 67 | // Load the remote and initialize the share scope if it's empty 68 | await Promise.all([loadRemote(`${url}/${remoteEntryFileName}`, scope, bustRemoteEntryCache), initSharing()]); 69 | if (!window[scope]) { 70 | throw new Error( 71 | `Remote loaded successfully but ${scope} could not be found! Verify that the name is correct in the Webpack configuration!`, 72 | ); 73 | } 74 | // Initialize the container to get shared modules and get the module factory: 75 | const [, moduleFactory] = await Promise.all([ 76 | initContainer(window[scope]), 77 | window[scope].get(module.startsWith("./") ? module : `./${module}`), 78 | ]); 79 | return moduleFactory(); 80 | } else { 81 | const moduleFactory = await window[scope].get(module.startsWith("./") ? module : `./${module}`); 82 | return moduleFactory(); 83 | } 84 | }; 85 | -------------------------------------------------------------------------------- /runtime/importRemote/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "moduleResolution": "node", 5 | "allowJs": true, 6 | "strict": true, 7 | "esModuleInterop": true, 8 | "declaration": true, 9 | "emitDeclarationOnly": true, 10 | "isolatedModules": true, 11 | "declarationMap": true, 12 | "suppressImplicitAnyIndexErrors": true, 13 | "outDir": "./dist" 14 | }, 15 | "include": ["src"] 16 | } 17 | --------------------------------------------------------------------------------