Lesson 09-Micro Frontend Practical Project

Setting Up a Micro Frontend Project from Scratch

Project Initialization

Technology Stack Selection

  • Main Framework: React/Vue/Angular + Webpack/Vite
  • Micro Frontend Solution: Module Federation/Single-SPA/qiankun/Wujie
  • State Management: Redux/Zustand/Jotai (as needed)
  • Build Tools: Webpack 5 (recommended) or Vite (prioritizing developer experience)

Initialization Commands

# Initialize main application with Vite
npm create vite@latest main-app --template react

# Initialize sub-application with Webpack
npx create-react-app sub-app --template typescript

Directory Structure Planning

micro-frontend/
├── main-app/          # Main application
│   ├── src/
│   │   ├── app/       # Main application routes and layout
│   │   ├── shell/     # Micro frontend mounting points
│   │   └── ...
├── sub-apps/          # Sub-applications directory
│   ├── app1/          # Sub-application 1
│   ├── app2/          # Sub-application 2
│   └── ...
├── shared/            # Shared dependencies/components
└── package.json       # Root package.json

Basic Architecture Setup

Webpack Configuration (Main Application)

// webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  // ...other configurations
  plugins: [
    new ModuleFederationPlugin({
      name: 'mainApp',
      remotes: {
        app1: 'app1@http://localhost:3001/remoteEntry.js',
        app2: 'app2@http://localhost:3002/remoteEntry.js',
      },
      shared: {
        react: { singleton: true, requiredVersion: '^18.2.0' },
        'react-dom': { singleton: true, requiredVersion: '^18.2.0' },
      },
    }),
  ],
};

Sub-Application Configuration

// app1/webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  // ...other configurations
  plugins: [
    new ModuleFederationPlugin({
      name: 'app1',
      filename: 'remoteEntry.js',
      exposes: {
        './App': './src/App',
      },
      shared: {
        react: { singleton: true, requiredVersion: '^18.2.0' },
        'react-dom': { singleton: true, requiredVersion: '^18.2.0' },
      },
    }),
  ],
};

Core Functionality Implementation

Main Application Mounting Sub-Applications

// main-app/src/App.jsx
import { useState, useEffect } from 'react';

function App() {
  const [apps, setApps] = useState([]);

  useEffect(() => {
    // Dynamically load sub-application configurations
    import('./app-config').then(config => {
      setApps(config.default);
    });
  }, []);

  return (
    <div className="main-app">
      <header>Main Application Navigation</header>
      <main>
        {apps.map(app => (
          <div key={app.name} id={`app-${app.name}`} />
        ))}
      </main>
      
      {/* Dynamically load sub-applications */}
      {apps.map(app => {
        const RemoteComponent = React.lazy(() => import(`app1/App`)); // Should use dynamic import in practice
        return (
          <React.Suspense fallback={<div>Loading...</div>}>
            <RemoteComponent />
          </React.Suspense>
        );
      })}
    </div>
  );
}

Sub-Application Entry Point

// app1/src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

// Micro frontend mounting point
if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
  
  export async function bootstrap() {
    console.log('app1 bootstrapped');
  }
  
  export async function mount(props) {
    ReactDOM.render(<App />, document.getElementById('app1'));
  }
  
  export async function unmount() {
    ReactDOM.unmountComponentAtNode(document.getElementById('app1'));
  }
} else {
  // Run independently
  ReactDOM.render(<App />, document.getElementById('root'));
}

Complex Scenario Implementation

Dynamic Sub-Application Loading

On-Demand Loading Implementation

// Dynamic loader
async function loadMicroApp(appName) {
  try {
    const module = await import(`../sub-apps/${appName}/remoteEntry.js`);
    const app = await module[`${appName}App`]; // Adjust based on actual exports
    
    // Mount application
    app.mount(document.getElementById(`app-${appName}`));
    
    return { success: true, app };
  } catch (error) {
    console.error(`Failed to load ${appName}:`, error);
    return { success: false, error };
  }
}

Dynamic Routing Integration

// Main application routing configuration
import { BrowserRouter, Routes, Route } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route 
          path="/app1/*" 
          element={
            <MicroAppLoader name="app1" />
          } 
        />
        {/* Other routes */}
      </Routes>
    </BrowserRouter>
  );
}

Deep Communication Between Main and Sub-Applications

Event Bus Implementation

// event-bus.js
class EventBus {
  constructor() {
    this.events = {};
  }
  
  on(event, listener) {
    if (!this.events[event]) this.events[event] = [];
    this.events[event].push(listener);
  }
  
  emit(event, data) {
    if (this.events[event]) {
      this.events[event].forEach(listener => listener(data));
    }
  }
}

export const eventBus = new EventBus();

Usage Example

// Main application sends message
eventBus.emit('user-login', { userId: 123 });

// Sub-application listens
eventBus.on('user-login', (data) => {
  console.log('Sub-application received login event:', data);
});

Cross-Application State Sharing

Shared Redux Store

// store.js
import { configureStore } from '@reduxjs/toolkit';

const store = configureStore({
  reducer: {
    user: userReducer,
    settings: settingsReducer,
  },
});

export default store;

Sub-Application Access to Shared Store

// Sub-application store configuration
import store from './store';

if (!window.__MICRO_APP_STORE__) {
  window.__MICRO_APP_STORE__ = store;
}

export function getSharedStore() {
  return window.__MICRO_APP_STORE__;
}

Performance Tuning Practice

Load Performance Analysis

Webpack Bundle Analyzer

npm install --save-dev webpack-bundle-analyzer
// webpack.config.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode: 'static',
      openAnalyzer: false,
    }),
  ],
};

Performance Metrics Monitoring

// Main application performance monitoring
const [performanceData, setPerformanceData] = useState({});

useEffect(() => {
  const metrics = {
    tbt: performance.metrics.totalBlockingTime,
    fid: performance.getEntriesByName('first-input')[0]?.processingStart,
    cls: performance.metrics.layoutShift.total,
  };
  
  setPerformanceData(metrics);
  // Send to monitoring system
  trackPerformance(metrics);
}, []);

Runtime Performance Monitoring

React Profiler

import React, { Profiler } from 'react';

function onRenderCallback(
  id, // The "id" of the Profiler tree that has just committed
  phase, // "mount" (initial mount) or "update" (subsequent updates)
  actualDuration, // Time spent rendering the Profiler and its descendants
  baseDuration, // Estimated time to render without memoization
  startTime, // When React began rendering this update
  commitTime, // When React committed this update
  interactions // Set of interactions involved in this update
) {
  console.log({
    id, phase, actualDuration, baseDuration, startTime, commitTime
  });
}

function App() {
  return (
    <Profiler id="App" onRender={onRenderCallback}>
      <YourAppComponent />
    </Profiler>
  );
}

Optimization Strategies Implementation

Code Splitting Strategy

// Dynamic import for lazy loading
const HeavyComponent = React.lazy(() => import('./HeavyComponent'));

function App() {
  return (
    <React.Suspense fallback={<div>Loading...</div>}>
      <HeavyComponent />
    </React.Suspense>
  );
}

Preloading Critical Resources

<!-- Preload during idle time -->
<link rel="prefetch" href="/critical.css" as="style">
<link rel="preload" href="/main.js" as="script">

Image Lazy Loading

import LazyLoad from 'react-lazyload';

function ImageGallery() {
  return (
    <div>
      {images.map(img => (
        <LazyLoad key={img.id} height={200}>
          <img src={img.url} alt={img.alt} />
        </LazyLoad>
      ))}
    </div>
  );
}

Security Hardening Practice

Security Vulnerability Fixes

XSS Protection

// Use DOMPurify to sanitize HTML
import DOMPurify from 'dompurify';

function SafeHtml({ html }) {
  const clean = DOMPurify.sanitize(html);
  return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}

CSRF Protection

// Backend sets CSRF Token
app.get('/api/csrf-token', (req, res) => {
  res.json({ token: req.csrfToken() });
});

// Frontend usage
async function fetchData() {
  const { token } = await fetch('/api/csrf-token').then(res => res.json());
  
  const response = await fetch('/api/data', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-CSRF-Token': token,
    },
    body: JSON.stringify({ data: 'value' }),
  });
}

Security Strategy Implementation

Content Security Policy (CSP)

<!-- Set in HTML head -->
<meta
  http-equiv="Content-Security-Policy"
  content="
    default-src 'self';
    script-src 'self' https://cdn.example.com 'unsafe-inline' 'unsafe-eval';
    style-src 'self' 'unsafe-inline';
    img-src 'self' data:;
    connect-src 'self' https://api.example.com;
    font-src 'self';
    object-src 'none';
    base-uri 'self';
    form-action 'self';
  "
/>

Dynamic Content Security Policy

// Dynamically set CSP (development environment)
if (process.env.NODE_ENV === 'development') {
  const csp = "default-src *; script-src * 'unsafe-inline' 'unsafe-eval'";
  res.setHeader('Content-Security-Policy', csp);
}

Security Monitoring Integration

Error Tracking

// Global error capture
window.addEventListener('error', (event) => {
  trackError({
    message: event.message,
    stack: event.error?.stack,
    url: window.location.href,
  });
});

// Promise error capture
window.addEventListener('unhandledrejection', (event) => {
  trackError({
    message: event.reason?.message || String(event.reason),
    stack: event.reason?.stack,
  });
});

Security Logging

// Log sensitive operations
function logSecurityEvent(eventType, details) {
  fetch('/api/security-logs', {
    method: 'POST',
    body: JSON.stringify({
      eventType,
      details,
      timestamp: new Date().toISOString(),
      userAgent: navigator.userAgent,
    }),
  });
}

Continuous Delivery Pipeline

CI/CD Configuration

GitHub Actions Configuration

# .github/workflows/ci.yml
name: CI Pipeline

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v2
    
    - name: Setup Node.js
      uses: actions/setup-node@v2
      with:
        node-version: '16'
    
    - name: Install dependencies
      run: npm ci
      
    - name: Build
      run: npm run build
      
    - name: Run tests
      run: npm test
      
    - name: Upload artifacts
      uses: actions/upload-artifact@v2
      with:
        name: dist
        path: ./dist

Automated Testing Integration

Test Configuration

// jest.config.js
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'jsdom',
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1',
  },
  setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
  transform: {
    '^.+\\.(js|jsx|ts|tsx)$': 'babel-jest',
  },
};

End-to-End (E2E) Test Example

// cypress/integration/app_spec.js
describe('Microfrontend App', () => {
  beforeEach(() => {
    cy.visit('/');
  });
  
  it('loads the app', () => {
    cy.contains('Welcome to Micro Frontend').should('be.visible');
  });
  
  it('loads remote app', () => {
    cy.get('#app1').should('exist');
    cy.get('#app1').contains('Remote App Content').should('be.visible');
  });
});

Deployment Strategy Design

Blue-Green Deployment Strategy

# Kubernetes deployment configuration example
apiVersion: apps/v1
kind: Deployment
metadata:
  name: micro-frontend
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      labels:
        app: micro-frontend
    spec:
      containers:
      - name: mf-container
        image: your-registry/micro-frontend:v2
        ports:
        - containerPort: 80

Canary Release

# Perform canary release with kubectl
kubectl set image deployment/micro-frontend \
  mf-container=your-registry/micro-frontend:v2 --record

# Gradually increase traffic
kubectl rollout status deployment/micro-frontend

# Rollback
kubectl rollout undo deployment/micro-frontend

Feature Toggles

// Dynamic feature toggles
const FEATURE_FLAGS = {
  newDashboard: process.env.FEATURE_NEW_DASHBOARD === 'true',
  experimentalSearch: false,
};

function Dashboard() {
  if (!FEATURE_FLAGS.newDashboard) {
    return <LegacyDashboard />;
  }
  
  return <NewDashboard />;
}

Summary

The successful implementation of a micro frontend project requires comprehensive consideration of:

  1. Architecture Design: Select an appropriate micro frontend solution (Module Federation, qiankun, etc.).
  2. Performance Optimization: Code splitting, lazy loading, and preloading strategies.
  3. Security Hardening: XSS/CSRF protection, CSP strategies, and security monitoring.
  4. Continuous Delivery: CI/CD pipelines, automated testing, and intelligent deployment strategies.
  5. Observability: Performance monitoring, error tracking, and logging systems.

Each aspect should be tailored to specific business requirements and technology stacks. It is recommended to start with simple scenarios, gradually introduce complex features, and continuously optimize performance and security.

Share your love