From 00507bb89ce085439f4cac676b9c2621555a4e85 Mon Sep 17 00:00:00 2001 From: Ayonix Date: Fri, 12 Jan 2024 16:06:42 +0100 Subject: [PATCH] testing with @module-federation/runtime --- host-app/components/useFederatedComponent.js | 90 ++++++++++++++++++++ host-app/package.json | 3 +- host-app/pages/index.js | 26 ++---- host-app/pnpm-lock.yaml | 57 +++++++++---- remote-app/webpack.config.js | 4 - 5 files changed, 139 insertions(+), 41 deletions(-) create mode 100644 host-app/components/useFederatedComponent.js diff --git a/host-app/components/useFederatedComponent.js b/host-app/components/useFederatedComponent.js new file mode 100644 index 0000000..f4630e4 --- /dev/null +++ b/host-app/components/useFederatedComponent.js @@ -0,0 +1,90 @@ +import { init, loadRemote } from "@module-federation/runtime"; +import React from "react"; + +init({ + name: "host", + remotes: [ + { + name: "remote", + entry: "http://localhost:3000/MFRemote/remote.js", + }, + ], +}); + +function loadComponent(scope, module) { + return async () => { + // Initializes the share scope. This fills it with known provided modules from this build and all remotes + const Module = await loadRemote(`${scope}/${module.slice(2)}`); + return Module; + }; +} + +const urlCache = new Set(); +const useDynamicScript = (url) => { + const [ready, setReady] = React.useState(false); + const [errorLoading, setErrorLoading] = React.useState(false); + + React.useEffect(() => { + if (!url) return; + + if (urlCache.has(url)) { + setReady(true); + setErrorLoading(false); + return; + } + + setReady(false); + setErrorLoading(false); + + const element = document.createElement("script"); + + element.src = url; + element.type = "text/javascript"; + element.async = true; + + element.onload = () => { + urlCache.add(url); + setReady(true); + }; + + element.onerror = () => { + setReady(false); + setErrorLoading(true); + }; + + document.head.appendChild(element); + + return () => { + urlCache.delete(url); + document.head.removeChild(element); + }; + }, [url]); + + return { + errorLoading, + ready, + }; +}; + +const componentCache = new Map(); +export const useFederatedComponent = (remoteUrl, scope, module) => { + const key = `${remoteUrl}-${scope}-${module}`; + const [Component, setComponent] = React.useState(null); + + const { ready, errorLoading } = useDynamicScript(remoteUrl); + React.useEffect(() => { + if (Component) setComponent(null); + // Only recalculate when key changes + }, [key]); + + React.useEffect(() => { + if (ready && !Component) { + const Comp = React.lazy(loadComponent(scope, module)); + componentCache.set(key, Comp); + setComponent(Comp); + } + // key includes all dependencies (scope/module) + }, [Component, ready, key]); + + return { errorLoading, Component }; +}; diff --git a/host-app/package.json b/host-app/package.json index 3b92851..e6b224f 100644 --- a/host-app/package.json +++ b/host-app/package.json @@ -8,7 +8,8 @@ "lint": "next lint" }, "dependencies": { - "@module-federation/nextjs-mf": "8.1.3", + "@module-federation/nextjs-mf": "8.1.5", + "@module-federation/runtime": "^0.0.8", "@module-federation/utilities": "3.0.5", "@stitches/react": "^1.2.8", "next": "^13.0.7", diff --git a/host-app/pages/index.js b/host-app/pages/index.js index 720474d..ff9b9d6 100644 --- a/host-app/pages/index.js +++ b/host-app/pages/index.js @@ -1,33 +1,19 @@ -import { importRemote } from "@module-federation/utilities"; -import { lazy, useEffect, useState } from "react"; import Button from "../components/Button"; +import { useFederatedComponent } from "../components/useFederatedComponent"; export default function Home() { - const [Component, setComponent] = useState(null); + const module = "./Button"; + const scope = "remote"; + const url = "http://localhost:3000/MFRemote/remote.js"; + const { Component, errorLoading } = useFederatedComponent(url, scope, module); - const module = "Button"; - - useEffect(() => { - if (typeof window !== "undefined") { - setComponent( - lazy( - async () => - await importRemote({ - url: "http://localhost:3000/MFRemote", - scope: "remote", - remoteEntryFileName: "remote.js", - module: module, - }) - ) - ); - } - }, []); return (

Next JS and React

Host - Button

); diff --git a/host-app/pnpm-lock.yaml b/host-app/pnpm-lock.yaml index 0c1f629..3dfbabb 100644 --- a/host-app/pnpm-lock.yaml +++ b/host-app/pnpm-lock.yaml @@ -6,8 +6,11 @@ settings: dependencies: '@module-federation/nextjs-mf': - specifier: 8.1.3 - version: 8.1.3(next@13.5.6)(react-dom@18.2.0)(react@18.2.0)(webpack@5.89.0) + specifier: 8.1.5 + version: 8.1.5(next@13.5.6)(react-dom@18.2.0)(react@18.2.0)(webpack@5.89.0) + '@module-federation/runtime': + specifier: ^0.0.8 + version: 0.0.8 '@module-federation/utilities': specifier: 3.0.5 version: 3.0.5(next@13.5.6)(react-dom@18.2.0)(react@18.2.0)(webpack@5.89.0) @@ -150,17 +153,18 @@ packages: /@leichtgewicht/ip-codec@2.0.4: resolution: {integrity: sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==} - /@module-federation/enhanced@0.0.7(webpack@5.89.0): - resolution: {integrity: sha512-l/RaHhdhnhz32926Sr3iRx98n4INJBQypbKcfjMYBMoevKLJM59p10q/9BYn6l5uwSkz6UMb5gJke9T5OtT7hQ==} + /@module-federation/enhanced@0.0.8(webpack@5.89.0): + resolution: {integrity: sha512-gK783ienOhWbXL0xPJMuObSVexc2KogsjLgYmQy8zb3w0qwymechV9JBcVzJ1QApsmq1e3zywPqak24TNvdYdA==} peerDependencies: webpack: ^5.0.0 dependencies: - '@module-federation/sdk': 0.0.7 + '@module-federation/runtime-tools': 0.0.8 + '@module-federation/sdk': 0.0.8 webpack: 5.89.0(webpack-cli@5.1.4) dev: false - /@module-federation/nextjs-mf@8.1.3(next@13.5.6)(react-dom@18.2.0)(react@18.2.0)(webpack@5.89.0): - resolution: {integrity: sha512-De1IY2OvhO08OIVnyNevZd9X3g7fqkm3LmRJTnMWBuQQb+wSKxyzw/EmW0s+8sPfd6jCFhauqI0E4NFaY4mwrw==} + /@module-federation/nextjs-mf@8.1.5(next@13.5.6)(react-dom@18.2.0)(react@18.2.0)(webpack@5.89.0): + resolution: {integrity: sha512-vVTuTreFsSJJOc3asJY1HsKLXi4XQMHzc+m6EOpyfUmsJuPLBZSabPjCj24pbx2vo2yGv+lv2xjaYBhkRJDKFw==} peerDependencies: next: ^12 || ^13 || ^14 react: ^17 || ^18 @@ -171,9 +175,10 @@ packages: styled-jsx: optional: true dependencies: - '@module-federation/enhanced': 0.0.7(webpack@5.89.0) - '@module-federation/node': 2.0.5(next@13.5.6)(react-dom@18.2.0)(react@18.2.0)(webpack@5.89.0) - '@module-federation/sdk': 0.0.7 + '@module-federation/enhanced': 0.0.8(webpack@5.89.0) + '@module-federation/node': 2.0.6(next@13.5.6)(react-dom@18.2.0)(react@18.2.0)(webpack@5.89.0) + '@module-federation/runtime': 0.0.8 + '@module-federation/sdk': 0.0.8 '@module-federation/utilities': 3.0.5(next@13.5.6)(react-dom@18.2.0)(react@18.2.0)(webpack@5.89.0) '@module-federation/webpack-type': 0.0.1 eventemitter3: 5.0.1 @@ -187,8 +192,8 @@ packages: - encoding dev: false - /@module-federation/node@2.0.5(next@13.5.6)(react-dom@18.2.0)(react@18.2.0)(webpack@5.89.0): - resolution: {integrity: sha512-+7sF0wAkISlTCaFEKnxYGiQjVUCS0VD5tOds2yQ5thX3p57P/LMd0u0Ze094GrVraF4zkE/4o0Zd8kOCY2GhZQ==} + /@module-federation/node@2.0.6(next@13.5.6)(react-dom@18.2.0)(react@18.2.0)(webpack@5.89.0): + resolution: {integrity: sha512-evE7Y60rbKyvlU8MAqnNI1QBPqJV9UD31sE7rGNR4TyeuNNM7uzll6fK+1Cw9cQktEZRSOEOnX0Y+xAwGOOEmQ==} peerDependencies: next: ^12||^13 react: ^16||^17||^18 @@ -202,8 +207,8 @@ packages: react-dom: optional: true dependencies: - '@module-federation/enhanced': 0.0.7(webpack@5.89.0) - '@module-federation/sdk': 0.0.7 + '@module-federation/enhanced': 0.0.8(webpack@5.89.0) + '@module-federation/sdk': 0.0.8 '@module-federation/utilities': 3.0.5(next@13.5.6)(react-dom@18.2.0)(react@18.2.0)(webpack@5.89.0) next: 13.5.6(react-dom@18.2.0)(react@18.2.0) node-fetch: 2.7.0 @@ -214,8 +219,21 @@ packages: - encoding dev: false - /@module-federation/sdk@0.0.7: - resolution: {integrity: sha512-GSbM4c7lg+v1+Llgx7OxUZM4DKo+UlLp+2FwN3CqJDHwBq6y8dchhr8i0esmBXZvoF1+w5arm2p/SV18I0YFqw==} + /@module-federation/runtime-tools@0.0.8: + resolution: {integrity: sha512-tqx3wlVHnpWLk+vn22c0x9Nv1BqdZnoS6vdMb53IsVpbQIFP70nhhvymHUyFuPkoLzMFidS7GpG58DYT/4lvCw==} + dependencies: + '@module-federation/runtime': 0.0.8 + '@module-federation/webpack-bundler-runtime': 0.0.8 + dev: false + + /@module-federation/runtime@0.0.8: + resolution: {integrity: sha512-Hi9g10aHxHdQ7CbchSvke07YegYwkf162XPOmixNmJr5Oy4wVa2d9yIVSrsWFhBRbbvM5iJP6GrSuEq6HFO3ug==} + dependencies: + '@module-federation/sdk': 0.0.8 + dev: false + + /@module-federation/sdk@0.0.8: + resolution: {integrity: sha512-lkasywBItjUTNT0T0IskonDE2E/2tXE9UhUCPVoDL3NteDUSFGg4tpkF+cey1pD8mHh0XJcGrCuOW7s96peeAg==} dev: false /@module-federation/utilities@3.0.5(next@13.5.6)(react-dom@18.2.0)(react@18.2.0)(webpack@5.89.0): @@ -239,6 +257,13 @@ packages: webpack: 5.89.0(webpack-cli@5.1.4) dev: false + /@module-federation/webpack-bundler-runtime@0.0.8: + resolution: {integrity: sha512-ULwrTVzF47+6XnWybt6SIq97viEYJRv4P/DByw5h7PSX9PxSGyMm5pHfXdhcb7tno7VknL0t2V8F48fetVL9kA==} + dependencies: + '@module-federation/runtime': 0.0.8 + '@module-federation/sdk': 0.0.8 + dev: false + /@module-federation/webpack-type@0.0.1: resolution: {integrity: sha512-n6Mx9BoVEDNADJ5MiYlNzN6UHsOclSla/qSc3nQREBtoZ2OzUChHkIJijsQ4qTJX+tb+Zhp1Ws2xKE9DuSLOZA==} dev: false diff --git a/remote-app/webpack.config.js b/remote-app/webpack.config.js index ceff4ae..ceceb3d 100644 --- a/remote-app/webpack.config.js +++ b/remote-app/webpack.config.js @@ -40,13 +40,9 @@ module.exports = { }, react: { singleton: true, - version: '0', - requiredVersion: false, }, 'react-dom': { - requiredVersion: false, singleton: true, - version: '0', }, }, }),