Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Federate module hot reload not working #3038

Closed
1 of 2 tasks
iMinosGFX opened this issue Feb 21, 2021 · 7 comments
Closed
1 of 2 tasks

Federate module hot reload not working #3038

iMinosGFX opened this issue Feb 21, 2021 · 7 comments

Comments

@iMinosGFX
Copy link

iMinosGFX commented Feb 21, 2021

  • Operating System: Windows
  • Node Version: v12.16.1
  • NPM Version: 6.13.4
  • webpack Version: 5.18.0
  • webpack-dev-server Version : 3.11.2
  • Browser: All
  • This is a bug
  • This is a modification request

App Container config

const ReactRefreshPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ModuleFederationPlugin = require("webpack").container
  .ModuleFederationPlugin;
const path = require("path");
const deps = require("./package.json").dependencies;

module.exports = {
  entry: "./src/index",
  target:"web",
  mode: "development",
  devServer: {
    contentBase: path.join(__dirname, "public"),
    port: 3001,
    hotOnly:true,
    open:true
  },
  output: {
    publicPath: "auto",
  },
  resolve: {
    extensions: [".js", ".jsx", ".json", ".ts", ".tsx"],
  },
  module: {
    rules: [
      {
        test: /bootstrap\.tsx$/,
        loader: "bundle-loader",
        options: {
          lazy: true,
        },
      },
      {
        test: /\.tsx?$/,
        loader: "babel-loader",
        exclude: /node_modules/,
        options: {
          plugins: ['react-refresh/babel'],
          presets: ["@babel/preset-react", "@babel/preset-typescript"],
        },
      },
      {
        enforce: "pre",
        test: /\.js$/,
        loader: "source-map-loader",
      },
      {
        test: /\.css$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              sourceMap: true,
            },
          },
            {
              loader: 'postcss-loader',
              options: {
                options: {}
              }
            },
        ],
      },
      {
        test: /\.s[ac]ss$/i,
        use: [
          'style-loader',
          'css-loader',
          'resolve-url-loader',
          'sass-loader',
        ],
      },
      {
        test: /\.(png|svg|jpg|gif)$/,
        use: [
          'file-loader',
        ],
      },
    ],
  },
  plugins: [
    new ModuleFederationPlugin({
      name: "appshell",
      filename: "remoteEntry.js",
      remotes: {
        mfsectors: "mfsectors@http://localhost:3002/remoteEntry.js",
      },
      exposes: {
        "./routes": "./src/routes",
        "./src/store/**": "./src/store"
      },
      shared: {
        ...deps,
        react: {
          eager: true,
          singleton: true,
          requiredVersion: deps.react,
        },
        "react-dom": {
          eager: true,
          singleton: true,
          requiredVersion: deps["react-dom"],
        },
      },
    }),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "public", "index.html"),
    }),
    new ReactRefreshPlugin({
      exclude: [/node_modules/, /bootstrap\.tsx$/],
    }),
  ],
};

Federate appconfig

const ReactRefreshPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ModuleFederationPlugin = require("webpack").container
  .ModuleFederationPlugin;
const path = require("path");
const deps = require("./package.json").dependencies;

module.exports = {
  entry: "./src/index.ts",
  mode: "development",
  devServer: {
    contentBase: path.join(__dirname, "public"),
    port: 3002,
    hotOnly:true
  },
  output: {
    publicPath: "auto",
  },
  resolve: {
    extensions: [".js", ".jsx", ".json", ".ts", ".tsx"],
  },
  module: {
    rules: [
      {
        test: /bootstrap\.tsx$/,
        loader: "bundle-loader",
        options: {
          lazy: true,
        },
      },
      {
        test: /\.tsx?$/,
        loader: "babel-loader",
        exclude: /node_modules/,
        options: {
          plugins: ['react-refresh/babel'],
          presets: ["@babel/preset-react", "@babel/preset-typescript"],
        },
      },
      {
        enforce: "pre",
        test: /\.js$/,
        loader: "source-map-loader",
      },
      {
        test: /\.css$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              sourceMap: true,
            },
          },
            {
              loader: 'postcss-loader',
              options: {
                options: {}
              }
            },
        ],
      },
      {
        test: /\.s[ac]ss$/i,
        use: [
          'style-loader',
          'css-loader',
          'resolve-url-loader',
          'sass-loader',
        ],
      },
      {
        test: /\.(png|svg|jpg|gif)$/,
        use: [
          'file-loader',
        ],
      },
    ],
  },
  plugins: [
    new ModuleFederationPlugin({
      name: "mfsectors",
      filename: "remoteEntry.js",
      remotes: {
        appshell: "appshell@http://localhost:3001/remoteEntry.js",
      },
      exposes: {
        "./routes": "./src/routes",
        "./Pages/SectorsList" : "./src/Pages/SectorsList.tsx"
      },
      shared: {
        ...deps,
        react: {
          eager: true,
          singleton: true,
          requiredVersion: deps.react,
        },
        "react-dom": {
          eager: true,
          singleton: true,
          requiredVersion: deps["react-dom"],
        },
      },
    }),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "public", "index.html"),
    }),
    new ReactRefreshPlugin({
      exclude: [/node_modules/, /bootstrap\.tsx$/],
    }),
  ],
};

Expected Behavior

App Container auto reload when federate app change

Actual Behavior

Hello,
I have finally succeeded in setting up my software environment (Dashboard...) in micro-frontend thanks to Module Federation. Only I notice that the live & hot reloading doesn't work when I modify a federate module:

I explain myself, I work on an App Shell which contains all my micro frontends (an app which manages the connection, the authorization in RBAC, the redux provider, etc...) and which will control all the other federated applications, only the automatic refresh will not reload the port 3001(AppShell) if I work on an app of the port 3002.

While searching on the internet I could find some unanswered questions about this, I would like to understand how is it possible to reload the app container automatically?

For Bugs; How can we reproduce the behavior?

For Features; What is the motivation and/or use-case for the feature?

@bedrich-schindler
Copy link

bedrich-schindler commented Feb 22, 2021

I met the same issue last week. Hot reload module is not working when using webpack-dev-server v3 with webpack v5. You need to upgrade webpack-dev-server to latest beta version which includes bug fix (unfortunately, it will not be fixed in v3)

See: #2758 and #2961

Hope this helps.

@alexander-akait
Copy link
Member

Yep, only v4 (beta right now supports module federation), we are working on the next beta release with more fixes, stable release will be in the end of month

@wdfinch
Copy link

wdfinch commented Apr 12, 2021

Hi, I'm trying to use HMR in a module federation POC app. I've been following a bunch of related issues on this and I'm not sure where this stands. While I can get my app to start via web-pack-dev server, I get an error whenever I edit a file:

Cannot set property './src/App.tsx' of undefined

I'm currently using:
webpack-dev-server 4.0.0-beta.2
webpack: 5.31.0

My module federation config is as follows:

new ModuleFederationPlugin({
  name: 'child',
  filename: 'remoteEntry.js',
  exposes: {
    './ChildIndex': './src/bootstrap'
  },
}),

Is HMR replacement possible with module federation right now or is this still under development?

@clyfish
Copy link

clyfish commented Aug 19, 2022

@wdfinch I have a workaround for your problem by excluding the module federation entry in html-webpack-plugin, because it's included in the output HTML by mistake.

You can use the chunks or excludeChunks options of html-webpack-plugin.

{
  plugins: [
    new HtmlWebpackPlugin({
      excludeChunks: ['child'],
    }),
  ],
}

@ItayElgazar
Copy link

@clyfish

@wdfinch I have a workaround for your problem by excluding the module federation entry in html-webpack-plugin, because it's included in the output HTML by mistake.

You can use the chunks or excludeChunks options of html-webpack-plugin.

{
  plugins: [
    new HtmlWebpackPlugin({
      excludeChunks: ['child'],
    }),
  ],
}

Based on your example, could you tell me exactly what these chunks are? Can you paste here a real-world example?

Thanks!

@clyfish
Copy link

clyfish commented Jan 16, 2023

@clyfish

@wdfinch I have a workaround for your problem by excluding the module federation entry in html-webpack-plugin, because it's included in the output HTML by mistake.
You can use the chunks or excludeChunks options of html-webpack-plugin.

{
  plugins: [
    new HtmlWebpackPlugin({
      excludeChunks: ['child'],
    }),
  ],
}

Based on your example, could you tell me exactly what these chunks are? Can you paste here a real-world example?

Thanks!

@ItayElgazar You can check the HTML outputted by webpack yourself. Right-click and choose View Page Source.

ModuleFederationPlugin creates an entry to be used by other apps, but html-webpack-plugin includes that entry into HTML by mistake.

@ESoch
Copy link

ESoch commented Mar 9, 2023

For clarity's sake, as someone who went through this thread and was also confused with understanding what chunk names should be and was looking for a real-world example of the HtmlWebpack excludeChunks fix, here it is.

Let's say you have a simple Webpack configuration that looks like this:

module.exports = {
  entry: './src/index.ts',
  output: {
    clean: true,
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash].bundle.js',
    chunkFilename: '[name].[contenthash].bundle.js',
  },
  plugins: [
    new ModuleFederationPlugin({
        name: 'mfe-chunk',
        filename: 'remoteEntry.js',
        exposes: {
          './ChildIndex': './src/bootstrap'
        },
    }),
    new HtmlWebpackPlugin({
      excludeChunks: ['mfe-chunk'],
      template: 'src/index.html',
      publicPath: '/',
    }),
  ],
}

The above is an example where we're excluding the remoteEntry.js (i.e module federation entry) from the host's own index.html.

An alternative implementation where the chunk: configuration is used is as follows:

    new HtmlWebpackPlugin({
      chunks: ['main'],
      template: 'src/index.html',
      publicPath: '/',
    }),

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants