➜ mkdir module-federation-dynamic-example

➜ cd module-federation-dynamic-example

➜ pnpm init

➜ corepack use [email protected]

➜ code .
# module-federation-basic-example/pnpm-workspace.yaml

packages:
  - "apps/*"

3개의 워크스페이스 생성

➜ mkdir apps

➜ cd apps

➜ pnpm create mf-app # (main-app, 3000)

➜ pnpm create mf-app # (component-app1, 3001)

➜ pnpm create mf-app # (component-app1, 3002)

➜ cd ..

➜ pnpm i
{
  "name": "main-app",
  "version": "1.0.0",
  "scripts": {
    "build": "webpack --mode production",
    "build:dev": "webpack --mode development",
    "build:start": "cd dist && PORT=3000 npx serve",
    "start": "webpack serve --open --mode development",
    "start:live": "webpack serve --open --mode development --live-reload --hot"
  },
  "license": "MIT",
  "author": {
    "name": "Jack Herrington",
    "email": "[email protected]"
  },
  "devDependencies": {
    "@babel/core": "^7.15.8",
    "@babel/plugin-transform-runtime": "^7.15.8",
    "@babel/preset-env": "^7.15.8",
    "@babel/preset-react": "^7.14.5",
    "@babel/preset-typescript": "^7.10.4",
    "@types/react": "^18.2.0",
    "@types/react-dom": "^18.2.0",
    "autoprefixer": "^10.1.0",
    "babel-loader": "^8.2.2",
    "css-loader": "^6.3.0",
    "html-webpack-plugin": "^5.3.2",
    "postcss": "^8.2.1",
    "postcss-loader": "^4.1.0",
    "style-loader": "^3.3.0",
    "typescript": "^4.5.2",
    "webpack": "^5.57.1",
    "webpack-cli": "^4.10.0",
    "webpack-dev-server": "^4.3.1"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  }
}
{
  "name": "component-app1",
  "version": "1.0.0",
  "scripts": {
    "build": "webpack --mode production",
    "build:dev": "webpack --mode development",
    "build:start": "cd dist && PORT=3001 npx serve",
    "start": "webpack serve --open --mode development",
    "start:live": "webpack serve --open --mode development --live-reload --hot"
  },
  "license": "MIT",
  "author": {
    "name": "Jack Herrington",
    "email": "[email protected]"
  },
  "devDependencies": {
    "@babel/core": "^7.15.8",
    "@babel/plugin-transform-runtime": "^7.15.8",
    "@babel/preset-env": "^7.15.8",
    "@babel/preset-react": "^7.14.5",
    "@babel/preset-typescript": "^7.10.4",
    "@types/react": "^18.2.0",
    "@types/react-dom": "^18.2.0",
    "autoprefixer": "^10.1.0",
    "babel-loader": "^8.2.2",
    "css-loader": "^6.3.0",
    "html-webpack-plugin": "^5.3.2",
    "postcss": "^8.2.1",
    "postcss-loader": "^4.1.0",
    "style-loader": "^3.3.0",
    "typescript": "^4.5.2",
    "webpack": "^5.57.1",
    "webpack-cli": "^4.10.0",
    "webpack-dev-server": "^4.3.1"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  }
}
{
  "name": "component-app2",
  "version": "1.0.0",
  "scripts": {
    "build": "webpack --mode production",
    "build:dev": "webpack --mode development",
    "build:start": "cd dist && PORT=3002 npx serve",
    "start": "webpack serve --open --mode development",
    "start:live": "webpack serve --open --mode development --live-reload --hot"
  },
  "license": "MIT",
  "author": {
    "name": "Jack Herrington",
    "email": "[email protected]"
  },
  "devDependencies": {
    "@babel/core": "^7.15.8",
    "@babel/plugin-transform-runtime": "^7.15.8",
    "@babel/preset-env": "^7.15.8",
    "@babel/preset-react": "^7.14.5",
    "@babel/preset-typescript": "^7.10.4",
    "@types/react": "^18.2.0",
    "@types/react-dom": "^18.2.0",
    "autoprefixer": "^10.1.0",
    "babel-loader": "^8.2.2",
    "css-loader": "^6.3.0",
    "html-webpack-plugin": "^5.3.2",
    "postcss": "^8.2.1",
    "postcss-loader": "^4.1.0",
    "style-loader": "^3.3.0",
    "typescript": "^4.5.2",
    "webpack": "^5.57.1",
    "webpack-cli": "^4.10.0",
    "webpack-dev-server": "^4.3.1"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  }
}
➜ pnpm i

component-app1 워크스페이스

// module-federation-dynamic-example/apps/component-app1/src/components/Button.tsx

import React from "react";

const Button: React.FC = () => <button>component-app1 의 버튼</button>;

export default Button;
// module-federation-dynamic-example/apps/component-app1/src/App.tsx

import React from "react";
import ReactDOM from "react-dom/client";

import "./index.css";

import Button from "./components/Button";

const App = () => (
  <div className="container">
    <div>Name: component-app</div>
    <div>Framework: react</div>
    <div>Language: JavaScript</div>
    <div>CSS: Empty CSS</div>
    <Button />
  </div>
);
ReactDOM.createRoot(document.getElementById("app")!).render(<App />);
// module-federation-dynamic-example/apps/component-app1/webpack.config.js

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:3001/>",
  },

  resolve: {
    extensions: [".tsx", ".ts", ".jsx", ".js", ".json"],
  },

  devServer: {
    port: 3001,
    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: "component_app1",
      filename: "remoteEntry.js",
      remotes: {},
      exposes: {
        "./Button": "./src/components/Button",
      },
      shared: {
        ...deps,
        react: {
          singleton: true,
          requiredVersion: deps.react,
        },
        "react-dom": {
          singleton: true,
          requiredVersion: deps["react-dom"],
        },
      },
    }),
    new HtmlWebPackPlugin({
      template: "./src/index.html",
    }),
  ],
});
➜ pnpm --filter component-app1 start:live

http://localhost:3001/

component-app2 워크스페이스

// module-federation-dynamic-example/apps/component-app2/src/components/Button.tsx

import React from "react";

const Button: React.FC = () => <button>component-app2 의 버튼</button>;

export default Button;
// module-federation-dynamic-example/apps/component-app2/src/App.tsx

import React from "react";
import ReactDOM from "react-dom/client";

import "./index.css";

import Button from "./components/Button";

const App = () => (
  <div className="container">
    <div>Name: component-app</div>
    <div>Framework: react</div>
    <div>Language: JavaScript</div>
    <div>CSS: Empty CSS</div>
    <Button />
  </div>
);
ReactDOM.createRoot(document.getElementById("app")!).render(<App />);
// module-federation-dynamic-example/apps/component-app2/webpack.config.js

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:3002/>",
  },

  resolve: {
    extensions: [".tsx", ".ts", ".jsx", ".js", ".json"],
  },

  devServer: {
    port: 3002,
    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: "component_app2",
      filename: "remoteEntry.js",
      remotes: {},
      exposes: {
        "./Button": "./src/components/Button",
      },
      shared: {
        ...deps,
        react: {
          singleton: true,
          requiredVersion: deps.react,
        },
        "react-dom": {
          singleton: true,
          requiredVersion: deps["react-dom"],
        },
      },
    }),
    new HtmlWebPackPlugin({
      template: "./src/index.html",
    }),
  ],
});
➜ pnpm --filter component-app2 start:live

http://localhost:3002/