import("dynamic/Button")); const App = () => { const [button, setButton] = React.useState<{ url?: string; scope?: string; module?: string; }>({}); function setButtonFromComponentApp1() { setButton({ url: "http://localhost:3001", scope: "component_app1", module: "./Button", }); } function setButtonFromComponentApp2() { setButton({ url: "http://localhost:3002", scope: "component_app2", module: "./Button", }); } return (
Module Federation | 웹팩
// module-federation-dynamic-example/apps/main-app/src/App.tsx import React, { Suspense } from "react"; import ReactDOM from "react-dom/client"; import "./index.css"; import DynamicButton from "./components/DynamicButton"; **// @ts-ignore const Button = React.lazy(() => import("dynamic/Button"));** const App = () => { const [button, setButton] = React.useState<{ url?: string; scope?: string; module?: string; }>({}); function setButtonFromComponentApp1() { setButton({ url: "<http://localhost:3001>", scope: "component_app1", module: "./Button", }); } function setButtonFromComponentApp2() { setButton({ url: "<http://localhost:3002>", scope: "component_app2", module: "./Button", }); } return ( <div className="container"> <div>Name: main-app</div> <div>Framework: react</div> <div>Language: TypeScript</div> <div>CSS: Empty CSS</div> <button onClick={setButtonFromComponentApp1}> Load Component App 1 Button </button> <button onClick={setButtonFromComponentApp2}> Load Component App 2 Button </button> <div> <DynamicButton button={button} /> </div> <div> **<Suspense fallback={<div>Loading...</div>}> <Button /> </Suspense>** </div> </div> ); }; ReactDOM.createRoot(document.getElementById("app")!).render(<App />);
const HtmlWebPackPlugin = require("html-webpack-plugin"); const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); const deps = require("./package.json").dependencies; module.exports = (_, argv) => ({ output: { publicPath: "<http://localhost:3000/>", }, resolve: { extensions: [".tsx", ".ts", ".jsx", ".js", ".json"], fallback: { path: false, }, }, devServer: { port: 3000, historyApiFallback: true, }, module: { rules: [ { test: /\\.m?js/, type: "javascript/auto", resolve: { fullySpecified: false, }, }, { test: /\\.(css|s[ac]ss)$/i, use: ["style-loader", "css-loader", "postcss-loader"], }, { test: /\\.(ts|tsx|js|jsx)$/, exclude: /node_modules/, use: { loader: "babel-loader", }, }, ], }, plugins: [ new ModuleFederationPlugin({ name: "main_app", filename: "remoteEntry.js", remotes: { dynamic: "component_app1@<http://localhost:3001/remoteEntry.js>", }, exposes: {}, shared: { ...deps, react: { singleton: true, requiredVersion: deps.react, }, "react-dom": { singleton: true, requiredVersion: deps["react-dom"], }, }, }), new HtmlWebPackPlugin({ template: "./src/index.html", }), ], });
➜ pnpm --filter main-app start:live
➜ cd apps ➜ mkdir api-server ➜ cd api-server ➜ pnpm init ➜ cd ... ➜ pnpm --filter api-server add serve
{ "scope": "component_app1", "remoteUrl": "<http://localhost:3001/remoteEntry.js>" }
{ "name": "api-server", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "dev": "serve public -p 4000 --cors" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "serve": "^14.2.1" } }
➜ pnpm --filter api-server dev
const HtmlWebPackPlugin = require("html-webpack-plugin"); const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); const deps = require("./package.json").dependencies; module.exports = (_, argv) => ({ output: { publicPath: "<http://localhost:3000/>", }, resolve: { extensions: [".tsx", ".ts", ".jsx", ".js", ".json"], fallback: { path: false, }, }, devServer: { port: 3000, historyApiFallback: true, }, module: { rules: [ { test: /\\.m?js/, type: "javascript/auto", resolve: { fullySpecified: false, }, }, { test: /\\.(css|s[ac]ss)$/i, use: ["style-loader", "css-loader", "postcss-loader"], }, { test: /\\.(ts|tsx|js|jsx)$/, exclude: /node_modules/, use: { loader: "babel-loader", }, }, ], }, plugins: [ new ModuleFederationPlugin({ name: "main_app", filename: "remoteEntry.js", remotes: { dynamic: `promise new Promise(resolve => { // 이 부분은 통합 모듈 호스팅 및 버전의 관리 계획에 따라 달라집니다. fetch("<http://localhost:4000/remote.json>").then(res => res.json()).then(({remoteUrl, scope}) => { console.log(remoteUrl, scope); const script = document.createElement('script') script.src = remoteUrl; script.onload = () => { const proxy = { get: (request) => window[scope].get(request), init: (arg) => { try { return window[scope].component_app1.init(arg) } catch(e) { console.log('remote container already initialized') } } } resolve(proxy) } document.head.appendChild(script); }); }) `, }, exposes: {}, shared: { ...deps, react: { singleton: true, requiredVersion: deps.react, }, "react-dom": { singleton: true, requiredVersion: deps["react-dom"], }, }, }), new HtmlWebPackPlugin({ template: "./src/index.html", }), ], });