Lesson 05-Advanced Features and Patterns in Micro Frontends

Dynamic and On-Demand Loading

Preloading Strategies

Intersection Observer-Based Preloading

// Implementing preloading for images/components
const preloadObserver = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const lazyComponent = entry.target;
      import(`./components/${lazyComponent.dataset.component}`)
        .then(module => {
          const Component = module.default;
          new Component().mount(lazyComponent);
          preloadObserver.unobserve(lazyComponent);
        });
    }
  });
});

// Usage in HTML
<div data-component="LazyComponent" class="lazy-component"></div>

Route-Based Preloading

// Adding preloading logic in route configuration
const routes = [
  {
    path: '/dashboard',
    component: () => import(/* webpackPrefetch: true */ './Dashboard'),
    // Alternatively, use webpackPreload
    // component: () => import(/* webpackPreload: true */ './Dashboard')
  }
];

Comparison of Preloading Methods

MethodAdvantagesDisadvantagesUse Cases
webpackPrefetchPreloads during browser idle timeMay waste bandwidthLarge but infrequently used components
webpackPreloadHigh-priority preloadingMay block critical resourcesCritical path resources
Intersection ObserverOn-demand preloadingSlightly complex implementationResources in the visible viewport

Lazy Loading Implementation

Intersection Observer-Based Lazy Loading

function lazyLoadImages() {
  const images = document.querySelectorAll('img[data-src]');
  
  const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        const img = entry.target;
        img.src = img.dataset.src;
        observer.unobserve(img);
      }
    });
  });

  images.forEach(img => observer.observe(img));
}

Component Lazy Loading with Dynamic Imports

const LazyComponent = React.lazy(() => import('./LazyComponent'));

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

Comparison of Lazy Loading Strategies

StrategyAdvantagesDisadvantagesUse Cases
Dynamic ImportCode splitting friendlyRequires React supportReact applications
Intersection ObserverGood performanceRequires manual implementationGeneral-purpose solution
Intersection Observer + Dynamic ImportOptimal performanceComplex implementationComplex applications

Dynamic Import Syntax

Basic Dynamic Import

// Dynamically importing a module
import('./module.js')
  .then(module => {
    // Use the module
    module.doSomething();
  })
  .catch(err => {
    // Handle errors
    console.error('Module loading failed', err);
  });

Dynamic Import with Namespace

// Dynamically importing specific exports
import('./module.js').then(({ default: mainExport, namedExport }) => {
  // Use exports
});

Dynamic Import with React

const DynamicComponent = React.lazy(() => import('./DynamicComponent'));

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

Dynamic Import with Error Boundaries

class ErrorBoundary extends React.Component {
  state = { hasError: false };

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  render() {
    if (this.state.hasError) {
      return <div>Loading failed</div>;
    }
    return this.props.children;
  }
}

const DynamicComponent = React.lazy(() => import('./DynamicComponent'));

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

State Management Solutions

Global State Management Tool Selection

ToolFeaturesUse CasesDisadvantages
ReduxPredictable, rich middlewareLarge, complex applicationsComplex configuration
MobXSimple, reactiveSmall to medium applicationsDebugging challenges
ZustandLightweight, simpleSmall to medium applicationsLimited features
Context APINative supportSimple state sharingPerformance issues
RecoilAtomic stateReact ecosystemEmerging tool

State Sharing Strategies

Simple Sharing with Context API

// Create Context
const AppContext = React.createContext();

// Provide Context
function AppProvider({ children }) {
  const [state, setState] = React.useState({});
  return (
    <AppContext.Provider value={{ state, setState }}>
      {children}
    </AppContext.Provider>
  );
}

// Use Context
function Component() {
  const { state, setState } = React.useContext(AppContext);
  // ...
}

Cross-Application Sharing with Redux

// Configure Redux store in main application
const store = createStore(rootReducer);

// Sub-application accessing the store
function ChildApp() {
  const dispatch = useDispatch();
  const state = useSelector(state => state);
  // ...
}

// Pass store to sub-application during rendering
<Provider store={store}>
  <ChildApp />
</Provider>

Comparison of State Sharing Methods

MethodAdvantagesDisadvantagesUse Cases
Context APINative support, simplePerformance issuesSmall applications
ReduxPowerful featuresComplex configurationLarge applications
ZustandSimple and easy to useLimited featuresSmall to medium applications
Custom EventsFlexibleHard to maintainSimple communication

State Isolation Solutions

Module Federation-Based State Isolation

// Main application configuration
new ModuleFederationPlugin({
  name: 'host',
  shared: {
    react: { singleton: true },
    'react-dom': { singleton: true },
    '@app/store': { singleton: false } // Do not share store
  }
});

// Sub-application configuration
new ModuleFederationPlugin({
  name: 'remote',
  filename: 'remoteEntry.js',
  exposes: {
    './store': './src/store' // Expose own store
  },
  shared: {
    react: { singleton: true },
    'react-dom': { singleton: true },
    '@app/store': { singleton: false } // Do not share store
  }
});

Scope-Based State Isolation

// Use Symbol as state identifier
const STATE_SYMBOL = Symbol('appState');

function createState() {
  const state = {};
  return new Proxy(state, {
    get(target, prop) {
      if (prop === STATE_SYMBOL) return state;
      return target[prop];
    }
  });
}

// Each application has its own state instance
const appState1 = createState();
const appState2 = createState();

Comparison of State Isolation Methods

MethodAdvantagesDisadvantagesUse Cases
Module FederationOfficial supportComplex configurationMicro frontend architectures
Scope IsolationSimple and directRequires manual managementSmall applications
Web WorkerTrue isolationHigh communication costComputation-intensive tasks

Styling Solutions

CSS Modules

Basic Usage

// Component styles
import styles from './Button.module.css';

function Button() {
  return <button className={styles.button}>Click me</button>;
}

Global Styles

/* Use :global in CSS Module file */
:global(.global-class) {
  color: red;
}

Dynamic Class Names

function Button({ primary }) {
  return (
    <button 
      className={`${styles.button} ${primary ? styles.primary : ''}`}
    >
      Click me
    </button>
  );
}

Styled Components

Basic Usage

import styled from 'styled-components';

const Button = styled.button`
  background: ${props => props.primary ? 'blue' : 'gray'};
  color: white;
  padding: 10px 20px;
`;

function App() {
  return <Button primary>Click me</Button>;
}

Theme Support

import { ThemeProvider } from 'styled-components';

const theme = {
  colors: {
    primary: 'blue',
    secondary: 'gray',
  },
};

function App() {
  return (
    <ThemeProvider theme={theme}>
      <Button>Click me</Button>
    </ThemeProvider>
  );
}

Emotion

Basic Usage

/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react';

const buttonStyle = css`
  background: blue;
  color: white;
  padding: 10px 20px;
`;

function Button() {
  return <button css={buttonStyle}>Click me</button>;
}

Style Injection Order Control

import { CacheProvider } from '@emotion/react';
import createCache from '@emotion/cache';

const cache = createCache({ key: 'css', prepend: true });

function App() {
  return (
    <CacheProvider value={cache}>
      <Button />
    </CacheProvider>
  );
}

Tailwind CSS Integration

Basic Configuration

// tailwind.config.js
module.exports = {
  content: ['./src/**/*.{js,jsx,ts,tsx}'],
  theme: {
    extend: {},
  },
  plugins: [],
};

Usage Example

function Button() {
  return (
    <button className="bg-blue-500 text-white font-bold py-2 px-4 rounded">
      Click me
    </button>
  );
}

JIT Mode

// tailwind.config.js
module.exports = {
  mode: 'jit',
  // ...
};

Comparison of Styling Solutions

SolutionAdvantagesDisadvantagesUse Cases
CSS ModulesSimple, native supportRequires build toolsTraditional React applications
Styled ComponentsComponent-level stylingRuntime overheadDynamic styling needs
EmotionFlexible, good performanceSlightly complex configurationHigh-performance requirements
Tailwind CSSUtility-firstRequires learningRapid development

Internationalization Support

Multi-Language Solution Design

Implementation with i18next

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';

i18n
  .use(initReactI18next)
  .init({
    resources: {
      en: {
        translation: {
          welcome: 'Welcome',
        },
      },
      zh: {
        translation: {
          welcome: 'Welcome',
        },
      },
    },
    lng: 'en',
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false,
    },
  });

export default i18n;

Dynamic Language Pack Loading

import i18n from './i18n';

async function loadLanguage(lang) {
  if (!i18n.hasResourceBundle(lang, 'translation')) {
    const { default: resources } = await import(`./locales/${lang}.json`);
    i18n.addResourceBundle(lang, 'translation', resources);
  }
  i18n.changeLanguage(lang);
}

Dynamic Language Pack Loading

Code Splitting with Webpack

// Dynamically import language packs
const loadLanguage = async (lang) => {
  const module = await import(
    /* webpackChunkName: "lang-[request]" */ `./locales/${lang}.json`
  );
  return module.default;
};

Pre-Caching with Service Worker

// Pre-cache language packs in Service Worker
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open('i18n-cache').then((cache) => {
      return cache.addAll(['/locales/en.json', '/locales/zh.json']);
    })
  );
});

Time Zone Handling

Using date-fns-tz for Time Zone Handling

import { formatInTimeZone } from 'date-fns-tz';

const date = new Date();
const options = { 
  timeZone: 'America/New_York',
  year: 'numeric', 
  month: 'numeric', 
  day: 'numeric',
  hour: 'numeric', 
  minute: 'numeric',
};

const formatted = formatInTimeZone(date, 'America/New_York', options);

Using Intl API for Time Zone Handling

const date = new Date();
const formatter = new Intl.DateTimeFormat('en-US', {
  timeZone: 'Asia/Shanghai',
  year: 'numeric',
  month: 'numeric',
  day: 'numeric',
  hour: 'numeric',
  minute: 'numeric',
});

const formatted = formatter.format(date);

Security Mechanisms

XSS Protection

Input Sanitization

// Sanitize HTML with DOMPurify
import DOMPurify from 'dompurify';

function sanitizeInput(input) {
  return DOMPurify.sanitize(input);
}

// Use in React
function SafeHtml({ html }) {
  return <div dangerouslySetInnerHTML={{ __html: sanitizeInput(html) }} />;
}

CSP Strategy

<!-- Set CSP in HTML -->
<meta
  http-equiv="Content-Security-Policy"
  content="default-src 'self'; script-src 'self' https://trusted.cdn.com; style-src 'self' 'unsafe-inline'"
/>

CSRF Protection

Using CSRF Token

// Backend generates 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' }),
  });
}

Content Security Policy (CSP)

Complete CSP Configuration Example

<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 CSP Adjustment

// Dynamically set CSP based on environment
function setCSPHeader(req, res, next) {
  const csp = process.env.NODE_ENV === 'production' 
    ? "default-src 'self';" 
    : "default-src *;";
    
  res.setHeader('Content-Security-Policy', csp);
  next();
}

Authentication and Authorization

JWT Implementation

// Return JWT after login
app.post('/login', (req, res) => {
  const user = authenticate(req.body);
  if (user) {
    const token = jwt.sign(user, process.env.JWT_SECRET, { expiresIn: '1h' });
    res.json({ token });
  } else {
    res.status(401).json({ error: 'Invalid credentials' });
  }
});

// Protected route
app.get('/protected', (req, res) => {
  const token = req.headers.authorization?.split(' ')[1];
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    res.json({ user: decoded });
  } catch {
    res.status(401).json({ error: 'Invalid token' });
  }
});

OAuth2 Integration

// Integrate OAuth2 with passport.js
const passport = require('passport');
const OAuth2Strategy = require('passport-oauth2').Strategy;

passport.use(new OAuth2Strategy({
  authorizationURL: 'https://auth.example.com/oauth/authorize',
  tokenURL: 'https://auth.example.com/oauth/token',
  clientID: process.env.CLIENT_ID,
  clientSecret: process.env.CLIENT_SECRET,
  callbackURL: "http://localhost:3000/auth/callback"
},
function(accessToken, refreshToken, profile, cb) {
  // Find or create user
  User.findOrCreate({ oauthId: profile.id }, function (err, user) {
    return cb(err, user);
  });
}));

// Route configuration
app.get('/auth', passport.authenticate('oauth2'));
app.get('/auth/callback', 
  passport.authenticate('oauth2', { failureRedirect: '/' }),
  (req, res) => res.redirect('/dashboard')
);

Authentication in Micro Frontend Environments

// Main application sets authentication state
window.__AUTH_STATE__ = {
  token: '...',
  user: { id: 1, name: 'John' }
};

// Sub-application retrieves authentication state
function getAuthState() {
  return window.__AUTH_STATE__;
}

// Sub-application sets authentication state
window.parent.__AUTH_STATE__ = { ... };

Summary

Engineering practices in micro frontend architectures require comprehensive consideration of multiple aspects:

  1. Dynamic and On-Demand Loading: Optimize resource loading and improve application performance through preloading strategies, lazy loading implementations, and dynamic import syntax.
  2. State Management Solutions: Select appropriate global state management tools, establish state sharing and isolation strategies to ensure state consistency while avoiding pollution.
  3. Styling Solutions: Choose between CSS Modules, Styled Components, Emotion, or Tailwind CSS based on project needs, balancing development efficiency and performance.
  4. Internationalization Support: Design multi-language solutions, implement dynamic language pack loading, and handle time zones to meet global requirements.
  5. Security Mechanisms: Implement XSS protection, CSRF protection, CSP strategies, and authentication/authorization to ensure application security.

Each aspect’s implementation requires trade-offs and adjustments based on specific project needs and technology stacks. Engineering practices in micro frontend architectures are an ongoing optimization process, requiring continuous refinement as projects evolve and teams grow.

Share your love