Skip to content

Shared Dependencies

Micro frontends architectures often involve multiple independent teams working on distinct components, leading to shared dependencies across isolated modules. Managing these dependencies effectively is critical to avoid version conflicts, ensure consistent behavior, and optimize bundle sizes. This section explores strategies for shared dependency management, including versioning practices, tree-shaking techniques, and isolation mechanisms.


Versioning Strategies for Shared Dependencies

Shared dependencies in micro frontends must be versioned carefully to ensure compatibility across modules. Key practices include:

1. Semantic Versioning (SemVer)

  • Adopt SemVer to define version ranges (e.g., ^1.2.3) and communicate intent for breaking changes, feature additions, or bug fixes.
  • Example: A shared UI library @company/ui at version 1.2.3 allows micro frontends to depend on ^1.2.0 for backward compatibility while enabling minor updates.

2. Monorepo vs. Polyrepo Approaches

  • Monorepos: Tools like Lerna, Nx, or Turborepo manage shared dependencies in a single repository, enabling versioned packages and centralized updates.
  • Polyrepos: Shared libraries are hosted in separate repositories, requiring strict version pinning (e.g., @company/ui@1.2.3) to avoid conflicts.

3. Dependency Version Pinning

  • Use resolutions (Yarn) or overrides (npm) to enforce specific versions of shared dependencies across micro frontends.
  • Example: In package.json, add:
    "resolutions": {
      "@company/ui": "1.2.3"
    }
    

Tree-Shaking for Optimized Bundles

Tree-shaking eliminates unused code from shared dependencies, reducing bundle sizes and improving performance. This is particularly impactful in micro frontends where only subsets of a library may be used.

1. Bundler-Specific Techniques

  • Webpack: Use mode: 'production' to enable tree-shaking. For dynamic imports, use import() to load only required modules:
    const UI = import('@company/ui').then(module => module.default);
    
  • Vite: Tree-shaking is automatic in production builds when using ES modules.

2. Code Splitting

  • Split shared dependencies into separate chunks using Webpack’s splitChunks or Vite’s rollupOptions:
    // Webpack config
    optimization: {
      splitChunks: {
        chunks: 'all',
        name: 'shared',
      },
    }
    

3. Avoiding Bloat

  • Exclude third-party libraries from the bundle using externals in Webpack:
    externals: {
      react: 'React',
      'react-dom': 'ReactDOM',
    }
    

Dependency Isolation and Conflict Resolution

Micro frontends often require different versions of the same dependency. Isolation techniques prevent conflicts:

1. Scoped Packages

  • Use scoped packages (e.g., @company/ui) to namespace shared libraries, reducing naming collisions.

2. Dynamic Imports

  • Load shared dependencies dynamically at runtime to avoid bundling conflicts:
    import('@company/ui').then(ui => {
      // Use UI components
    });
    

3. Shared Dependency Registries

  • Centralize shared packages in a private npm registry (e.g., npm Enterprise, Verdaccio) to ensure consistent versions across teams.

Tools and Best Practices

Tool/Concept Purpose
Yarn Workspaces Manage monorepos with shared dependencies
Nx Code generation, dependency analysis, and more
Webpack externals Exclude libraries from the bundle
Tree-shaking Minimize bundle size by removing unused code

Key takeaways

  • Versioning: Use SemVer and pinning to ensure compatibility across micro frontends.
  • Tree-shaking: Leverage bundlers like Webpack or Vite to eliminate unused code.
  • Isolation: Use dynamic imports and scoped packages to avoid dependency conflicts.
  • Centralized Management: Adopt monorepos or private registries for consistent shared dependency handling.
  • Optimize: Always validate bundle sizes and ensure tree-shaking is enabled in production builds.