├── 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 |
--------------------------------------------------------------------------------