DEV Community

Alex Spinov
Alex Spinov

Posted on

Webpack 5 Has a Free Module Federation API That Enables Micro-Frontends

Webpack 5 introduced Module Federation — a game-changing API that lets separate applications share code at runtime. This is the foundation of micro-frontend architecture.

Module Federation: Share Code at Runtime

// webpack.config.js — Host Application
const { ModuleFederationPlugin } = require("webpack").container;

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: "host",
      remotes: {
        dashboard: "dashboard@https://dashboard.example.com/remoteEntry.js",
        analytics: "analytics@https://analytics.example.com/remoteEntry.js",
      },
      shared: ["react", "react-dom"],
    }),
  ],
};

// Load remote component dynamically
const Dashboard = React.lazy(() => import("dashboard/Widget"));
const Analytics = React.lazy(() => import("analytics/Chart"));
Enter fullscreen mode Exit fullscreen mode
// webpack.config.js — Remote Application
new ModuleFederationPlugin({
  name: "dashboard",
  filename: "remoteEntry.js",
  exposes: {
    "./Widget": "./src/components/DashboardWidget",
    "./utils": "./src/utils/dataHelpers",
  },
  shared: {
    react: { singleton: true, requiredVersion: "^18.0.0" },
    "react-dom": { singleton: true },
  },
});
Enter fullscreen mode Exit fullscreen mode

Asset Modules: No Loaders Needed

module.exports = {
  module: {
    rules: [
      { test: /\.png$/, type: "asset/resource" },      // Emit file
      { test: /\.svg$/, type: "asset/inline" },         // Base64 inline
      { test: /\.txt$/, type: "asset/source" },         // Raw string
      { test: /\.jpg$/, type: "asset", parser: {        // Auto (size threshold)
        dataUrlCondition: { maxSize: 8 * 1024 }
      }},
    ],
  },
};
Enter fullscreen mode Exit fullscreen mode

Persistent Caching

module.exports = {
  cache: {
    type: "filesystem",
    buildDependencies: {
      config: [__filename], // Invalidate on config change
    },
    version: `${process.env.NODE_ENV}-v1`,
  },
};
// Second build: 10x faster
Enter fullscreen mode Exit fullscreen mode

Custom Plugin API

class BuildStatsPlugin {
  apply(compiler) {
    compiler.hooks.done.tap("BuildStatsPlugin", (stats) => {
      const { assets, time } = stats.toJson({ assets: true });
      console.log(`Build completed in ${time}ms`);
      console.log(`Assets: ${assets.length}`);
      assets.forEach(a => console.log(`  ${a.name}: ${(a.size / 1024).toFixed(1)}KB`));
    });

    compiler.hooks.emit.tapAsync("BuildStatsPlugin", (compilation, callback) => {
      const manifest = {};
      for (const [name, source] of compilation.assets) {
        manifest[name] = source.size();
      }
      compilation.assets["build-manifest.json"] = {
        source: () => JSON.stringify(manifest, null, 2),
        size: () => JSON.stringify(manifest).length
      };
      callback();
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

Tree Shaking with Side Effects

// package.json
{
  "sideEffects": ["*.css", "./src/polyfills.js"]
}
Enter fullscreen mode Exit fullscreen mode
// webpack.config.js
module.exports = {
  optimization: {
    usedExports: true,
    minimize: true,
    splitChunks: {
      chunks: "all",
      cacheGroups: {
        vendor: {
          test: /[\/]node_modules[\/]/,
          name: "vendors",
          priority: -10,
        },
      },
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

Building data-driven micro-frontends? My Apify tools feed structured data into any frontend architecture.

Custom build solution? Email spinov001@gmail.com

Top comments (0)