Combining Micro Frontends with Monorepos

Loading

Micro frontends and monorepos form a powerful combination that can address many architectural challenges while providing developer productivity benefits. Here’s a comprehensive guide to effectively integrating these approaches:

Architectural Synergy

Why Monorepos for Micro Frontends?

  1. Unified Dependency Management
  • Single source of truth for shared dependencies
  • Eliminates version conflicts across micro frontends
   // Example root package.json
   {
     "workspaces": [
       "packages/shell",
       "packages/mfe-header",
       "packages/mfe-dashboard",
       "packages/shared-libs"
     ]
   }
  1. Cross-MFE Refactoring
  • Atomic commits across multiple micro frontends
  • Simplified dependency graph visualization
  • Safe automated refactoring across boundaries
  1. Standardized Tooling
  • Consistent build, test, and linting configurations
  • Shared CI/CD pipelines
  • Unified code generation

Implementation Patterns

1. Workspace Structure

Recommended Layout

monorepo/
├── packages/
│   ├── shell/            # Container application
│   ├── mfe-auth/         # Authentication MFE
│   ├── mfe-catalog/      # Product catalog MFE
│   ├── shared-ui/        # Common UI components
│   └── shared-utils/     # Shared utilities
├── tools/
│   ├── mfe-builder/      # Custom build tooling
│   └── deploy/          # Deployment scripts
└── package.json         # Root configuration

2. Dependency Management

Yarn/NPM Workspaces

# Install all dependencies at root level
yarn install

# Add dependency to specific MFE
yarn workspace mfe-catalog add lodash

Selective Version Pinning

// packages/shell/package.json
{
  "dependencies": {
    "react": "18.2.0",  // Pinned version
    "shared-ui": "workspace:*"  // Always use local version
  }
}

3. Build Optimization

Incremental Builds

// Using Turborepo
{
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**"]
    },
    "mfe-catalog": {
      "dependsOn": ["shared-ui"],
      "outputs": ["dist/**"]
    }
  }
}

Shared Configurations

// Base webpack config in shared-tools
module.exports = {
  module: {
    rules: [{
      test: /\.(js|jsx)$/,
      exclude: /node_modules/,
      use: ['babel-loader']
    }]
  }
};

// MFE-specific extension
const { merge } = require('webpack-merge');
const baseConfig = require('shared-tools/webpack.base');

module.exports = merge(baseConfig, {
  entry: './src/index.js',
  // MFE-specific overrides
});

Development Experience

1. Local Development Setup

Linked Development

# Start shell and all MFEs in watch mode
yarn workspaces foreach -p run dev

Dynamic Module Replacement

// Shell dev server configuration
devServer: {
  proxy: {
    '/mfe-auth': 'http://localhost:3001',
    '/mfe-catalog': 'http://localhost:3002'
  }
}

2. Code Sharing Patterns

Internal Packages

// shared-ui/components/Button.jsx
export const Button = ({ children }) => (
  <button className="shared-btn">{children}</button>
);

// mfe-catalog/package.json
{
  "dependencies": {
    "shared-ui": "workspace:*"
  }
}

Versioned Contracts

// shared-contracts/src/types.ts
export interface Product {
  id: string;
  name: string;
  price: number;
}

// mfe-catalog/src/components/ProductCard.tsx
import { Product } from 'shared-contracts';

Deployment Strategies

1. Unified CI/CD Pipeline

# .github/workflows/deploy.yml
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: yarn install
      - run: yarn build
      - uses: actions/upload-artifact@v3
        with:
          path: |
            packages/shell/dist
            packages/mfe-auth/dist
            packages/mfe-catalog/dist

2. Independent Deployment

# Deploy specific MFE only
yarn workspace mfe-catalog run deploy -- --env production

3. Versioned Artifacts

// Release script example
const version = require('./lerna.json').version;
const mfeName = process.argv[2];

fs.writeFileSync(
  `packages/${mfeName}/dist/version.json`,
  JSON.stringify({ version, build: Date.now() })
);

Advanced Patterns

1. Module Federation with Monorepo

// shell/webpack.config.js
new ModuleFederationPlugin({
  remotes: {
    mfe_auth: `mfe_auth@http://localhost:3001/remoteEntry.js`,
    mfe_catalog: `mfe_catalog@http://localhost:3002/remoteEntry.js`
  },
  shared: {
    react: { singleton: true },
    'react-dom': { singleton: true }
  }
});

2. Cross-MFE State Management

// shared-state/src/store.ts
import { createStore } from 'redux';
import rootReducer from './reducers';

export const store = createStore(rootReducer);

// mfe-auth/src/App.tsx
import { store } from 'shared-state';
import { Provider } from 'react-redux';

3. Monorepo-aware Testing

{
  "jest": {
    "projects": [
      "<rootDir>/packages/*/jest.config.js"
    ],
    "collectCoverageFrom": [
      "packages/**/src/**/*.{js,jsx,ts,tsx}",
      "!**/node_modules/**"
    ]
  }
}

Challenges and Solutions

ChallengeSolution
Scaling to many MFEsImplement package boundaries and domain grouping
Long CI timesSelective testing based on changed files
Version driftEnforce sync-check tooling
IDE performanceConfigure workspace-aware IDE settings

Migration Path

  1. Phase 1: Convert to monorepo with existing packages
  2. Phase 2: Implement shared tooling and configurations
  3. Phase 3: Introduce Module Federation
  4. Phase 4: Optimize build and test pipelines
  5. Phase 5: Implement advanced code sharing patterns

Tooling Recommendations

  • Package Managers: Yarn Workspaces, pnpm
  • Build Systems: Turborepo, Nx, Lerna
  • Module Federation: Webpack 5
  • Testing: Jest with multi-project config
  • Linting: ESLint with shared configs

Leave a Reply

Your email address will not be published. Required fields are marked *