Lesson 04-Modular Application and Ecosystem

Modularization in Front-End Frameworks

React Component Modularization (JSX, Hooks)

React Component Modularization Practices:

  1. File-Based Component Organization:
components/
├── Button/
│   ├── Button.jsx
│   ├── Button.css
│   └── index.js
├── Modal/
│   ├── Modal.jsx
│   ├── Modal.css
│   └── index.js
└── ...
  1. JSX Modularization Example:
// Button.jsx
import React from 'react';
import PropTypes from 'prop-types';
import './Button.css';

const Button = ({ text, onClick, variant }) => {
  return (
    <button 
      className={`btn ${variant}`} 
      onClick={onClick}
>
      {text}
    </button>
  );
};

Button.propTypes = {
  text: PropTypes.string.isRequired,
  onClick: PropTypes.func,
  variant: PropTypes.oneOf(['primary', 'secondary'])
};

Button.defaultProps = {
  variant: 'primary'
};

export default Button;
  1. Hooks Modularization Example:
// useCounter.js
import { useState } from 'react';

export const useCounter = (initialValue = 0) => {
  const [count, setCount] = useState(initialValue);

  const increment = () => setCount(count + 1);
  const decrement = () => setCount(count - 1);
  const reset = () => setCount(initialValue);

  return { count, increment, decrement, reset };
};

// Counter.jsx
import React from 'react';
import { useCounter } from './useCounter';

const Counter = () => {
  const { count, increment, decrement } = useCounter();

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  );
};

export default Counter;

Vue Single File Component (SFC) Modularization

Vue SFC Modular Structure:

<!-- Button.vue -->
<template>
  <button :class="['btn', variant]" @click="onClick">
    <slot></slot>
  </button>
</template>

<script>
export default {
  name: 'AppButton',
  props: {
    variant: {
      type: String,
      default: 'primary',
      validator: value => ['primary', 'secondary'].includes(value)
    }
  },
  methods: {
    onClick() {
      this.$emit('click');
    }
  }
}
</script>

<style scoped>
.btn {
  padding: 8px 16px;
  border-radius: 4px;
  cursor: pointer;
}
.primary {
  background-color: #1890ff;
  color: white;
}
.secondary {
  background-color: #f5f5f5;
  color: #333;
}
</style>

Vue Composition API Modularization:

<!-- useCounter.js -->
import { ref } from 'vue';

export function useCounter(initialValue = 0) {
  const count = ref(initialValue);

  const increment = () => count.value++;
  const decrement = () => count.value--;
  const reset = () => count.value = initialValue;

  return { count, increment, decrement, reset };
}

<!-- Counter.vue -->
<template>
  <div>
    <p>Count: {{ count }}</p>
    <button @click="increment">Increment</button>
    <button @click="decrement">Decrement</button>
  </div>
</template>

<script>
import { useCounter } from './useCounter';

export default {
  setup() {
    const { count, increment, decrement } = useCounter();

    return { count, increment, decrement };
  }
}
</script>

Angular Module System (NgModule)

Angular Modular Architecture:

// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { HttpClientModule } from '@angular/common/http';
import { SharedModule } from './shared/shared.module';
import { FeaturesModule } from './features/features.module';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    SharedModule,
    FeaturesModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Feature Module Example:

// features/user/user.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { UserListComponent } from './user-list/user-list.component';
import { UserDetailsComponent } from './user-details/user-details.component';
import { UserRoutingModule } from './user-routing.module';

@NgModule({
  declarations: [
    UserListComponent,
    UserDetailsComponent
  ],
  imports: [
    CommonModule,
    UserRoutingModule
  ]
})
export class UserModule { }

Shared Module Example:

// shared/shared.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { SharedModule as ThirdPartySharedModule } from 'third-party-library';

@NgModule({
  declarations: [],
  imports: [
    CommonModule,
    FormsModule,
    MatButtonModule,
    ThirdPartySharedModule
  ],
  exports: [
    CommonModule,
    FormsModule,
    MatButtonModule,
    ThirdPartySharedModule
  ]
})
export class SharedModule { }

Modularization Differences and Interoperability Across Frameworks

Key Differences Comparison:

FeatureReactVueAngular
Component DefinitionFunction Components/HooksSingle File Components (SFC)Class Components + Decorators
StylingCSS-in-JS/CSS ModulesScoped CSSComponent Styles
State ManagementHooks/Context APIVuex/PiniaServices + NgRx
Routing ModularizationReact RouterVue RouterAngular Router
Dependency InjectionNone Built-InNone Built-InBuilt-In DI System

Interoperability Solutions:

  • Micro-Frontend Integration:
  • Use Module Federation (Webpack 5) to integrate components from different frameworks.
  • Achieve isolation via iframes or Web Components.
  • Web Components Bridging:
// Expose React component as Web Component
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

class MyReactComponent extends HTMLElement {
  connectedCallback() {
    const mountPoint = document.createElement('div');
    this.attachShadow({ mode: 'open' }).appendChild(mountPoint);

    ReactDOM.render(<App />, mountPoint);
  }
}

customElements.define('my-react-component', MyReactComponent);
  • Universal State Management:
  • Use framework-agnostic state management solutions like Redux or MobX.
  • Communicate via event buses or custom events.

Modularization in Micro-Frontend Architectures

Micro-Frontend Architecture Patterns:

  1. Route-Based Splitting:
    • Different teams manage applications under different route prefixes.
    • Main application handles route distribution.
  2. Function-Based Splitting:
    • Split application into independent functional modules.
    • Each module can be developed and deployed independently.
  3. Technology-Based Splitting:
    • Coexistence of sub-applications with different tech stacks on the same page.
    • Isolation via iframes or Web Components.

Module Federation Implementation:

// Host application Webpack configuration
new ModuleFederationPlugin({
  name: 'host',
  remotes: {
    app1: 'app1@http://localhost:3001/remoteEntry.js',
    app2: 'app2@http://localhost:3002/remoteEntry.js'
  },
  shared: ['react', 'react-dom']
});

// Sub-application Webpack configuration
new ModuleFederationPlugin({
  name: 'app1',
  filename: 'remoteEntry.js',
  exposes: {
    './Button': './src/components/Button'
  },
  shared: ['react', 'react-dom']
});

Micro-Frontend Communication Solutions:

  • Custom Events:
// Publish event
window.dispatchEvent(new CustomEvent('data-updated', {
  detail: { data: newData }
}));

// Listen for event
window.addEventListener('data-updated', (event) => {
  console.log('Received data:', event.detail.data);
});
  • State Sharing:
  • Use state management libraries like Redux or MobX.
  • Communicate via iframe postMessage.
  • Service Discovery:
  • Use dedicated micro-frontend coordination services.
  • Dynamically load remote modules.

Modularization and Package Management

Modular Design of npm Packages (package.json, main/module Fields)

Key package.json Fields:

{
  "name": "my-package",
  "version": "1.0.0",
  "main": "dist/index.cjs.js",  // CommonJS entry
  "module": "dist/index.esm.js", // ES Module entry
  "exports": {
    ".": {
      "import": "./dist/index.esm.js",  // ES Modules import
      "require": "./dist/index.cjs.js"  // CommonJS import
    },
    "./styles": "./dist/styles.css"     // Sub-path export
  },
  "files": [
    "dist",
    "README.md"
  ],
  "sideEffects": false,  // No side effects
  "dependencies": {
    "lodash": "^4.17.21"
  },
  "peerDependencies": {
    "react": ">=16.8.0"
  }
}

Best Practices for Modern Package Design:

  1. Dual-Mode Output: Provide both ESM and CommonJS versions.
  2. Granular Exports: Use exports field for precise control over exports.
  3. Zero Dependencies: Minimize dependencies where possible.
  4. Tree-Shaking Friendly: Avoid side effects and mark as side-effect-free.

Yarn Package Modularization Support (PnP Mode)

Yarn PnP (Plug’n’Play) Features:

  1. Eliminates node_modules: Loads dependencies directly from cache.
  2. Deterministic Resolution: Precise control over dependency versions.
  3. Faster Installation: Skips node_modules creation.

PnP Configuration:

// .yarnrc.yml
nodeLinker: "pnp"

# Custom resolution rules
packageExtensions:
  "some-package@*":
    dependencies:
      "missing-dep": "^1.0.0"

PnP Compatibility Handling:

  1. Use Yarn’s Compatibility Layer:
// In package.json
"dependencies": {
  "@yarnpkg/pnpify": "^3.0.0"
}
  1. Toolchain Support:
  • Babel: babel-plugin-pnp
  • Webpack: pnp-webpack-plugin
  • Jest: jest-environment-jsdom-fifteen (PnP support)

Private Module Repositories (Verdaccio, Nexus)

Verdaccio Configuration Example:

# config.yaml
storage: ./storage
plugins: ./plugins

web:
  title: My Private Registry

auth:
  htpasswd:
    file: ./htpasswd
    max_users: -1

uplinks:
  npmjs:
    url: https://registry.npmjs.org/

packages:
  '@my-org/*':
    access: $authenticated
    publish: $authenticated
    proxy: npmjs

  'private-*':
    access: $authenticated
    publish: $authenticated
    proxy: npmjs

Nexus Repository Configuration:

  1. Install Nexus Repository Manager.
  2. Create npm repository (proxy, hosted, or group).
  3. Configure npm client:
npm config set registry http://nexus.example.com/repository/npm-group/
npm login --registry=http://nexus.example.com/repository/npm-group/

Module Version Management and Semantic Versioning (SemVer)

SemVer Specification:

MAJOR.MINOR.PATCH
- MAJOR: Breaking API changes
- MINOR: Backward-compatible feature additions
- PATCH: Backward-compatible bug fixes

Version Range Specifications:

  • ^1.2.3: Compatible with 1.x.x (≥1.2.3)
  • ~1.2.3: Compatible with 1.2.x (≥1.2.3)
  • 1.2.3: Exact version
  • >1.2.3 <2.0.0: Version range

Version Management Strategies:

  1. Lock Files:
    • package-lock.json (npm)
    • yarn.lock (Yarn)
    • pnp.lock (Yarn PnP)
  2. Version Update Tools:
    • npm-check-updates
    • yarn upgrade-interactive

Module Dependency Conflicts and Solutions

Common Dependency Conflict Scenarios:

  1. Different Versions of the Same Dependency:
A@1.0.0 → B@2.0.0
C@1.0.0 → B@1.0.0
  1. Peer Dependency Mismatches:
React@17.0.0
Some library requires React@16.8.0

Solutions:

  1. Dependency Hoisting:
  • npm/Yarn automatically hoist common dependencies to the top level.
  1. Alias Resolution:
// Webpack configuration
resolve: {
  alias: {
    'lodash': path.resolve(__dirname, 'node_modules/lodash')
  }
}
  1. Selective Dependency Overrides:
// Yarn resolutions
"resolutions": {
  "lodash": "4.17.21"
}
  1. Module Federation:
  • Isolate conflicting dependencies in different sub-applications.

Modularization Performance Fundamentals

Module Loading Performance (Network Requests, Parsing Time)

Module Loading Performance Metrics:

Network Request Overhead:

  • HTTP/1.1 request number limitations.
  • HTTP/2 multiplexing advantages.

Parsing Time:

  • JavaScript parsing and compilation time.
  • WebAssembly initialization time.

Execution Time:

  • Module initialization code execution.
  • Dependency graph construction time.

Optimization Strategies:

  1. Reduce Request Count:
  • Code splitting and lazy loading.
  • Bundle small modules together.
  1. Preload Critical Resources:
<link rel="preload" href="critical.js" as="script">
<link rel="modulepreload" href="esm-module.js">
  1. HTTP/2 Optimizations:
  • Enable server push.
  • Set appropriate cache headers.

Module Bundle Size Optimization (Tree Shaking, Code Splitting)

Tree Shaking Mechanism:

  1. ESM Static Analysis:
  • Identify unused exports.
  • Remove unreferenced code.
  1. Side Effect Marking:
// package.json
"sideEffects": [
  "*.css",
  "*.global.js"
]

Code Splitting Strategies:

  1. Entry Splitting:
// Webpack configuration
entry: {
  app: './src/app.js',
  admin: './src/admin.js'
}
  1. Dynamic Imports:
// Dynamic import creates new chunk
import('./module').then(module => {
  module.doSomething();
});
  1. Common Code Extraction:
optimization: {
  splitChunks: {
    chunks: 'all',
    cacheGroups: {
      vendors: {
        test: /[\\/]node_modules[\\/]/,
        priority: -10
      }
    }
  }
}

Module Caching and Reuse

Browser Caching Mechanisms:

  1. Strong Caching:
    • Cache-Control: max-age=31536000
    • Expires header
  2. Negotiation Caching:
    • ETag/Last-Modified

Service Worker Caching:

// sw.js
self.addEventListener('install', event => {
  event.waitUntil(
    caches.open('v1').then(cache => {
      return cache.addAll([
        '/index.js',
        '/styles.css'
      ]);
    })
  );
});

self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request).then(response => {
      return response || fetch(event.request);
    })
  );
});

Module Caching Optimizations:

  1. Long-Term Caching:
    • Content-hashed filenames.
    • Stable module IDs.
  2. Cache Invalidation Strategies:
    • Versioned cache names.
    • Incremental updates.

Impact of Modularization on First-Screen Loading

First-Screen Optimization Strategies:

  1. Critical Rendering Path Optimization:
    • Inline critical CSS.
    • Defer non-critical JavaScript.
  2. Code Splitting:
    • Split by route.
    • Split by functionality.
  3. Preloading and Prefetching:
<link rel="preload" href="critical.js" as="script">
<link rel="prefetch" href="non-critical.js" as="script">

Performance Metrics Monitoring:

  1. FCP (First Contentful Paint)
  2. LCP (Largest Contentful Paint)
  3. TTI (Time To Interactive)

Modularization and Performance Monitoring Tools

Custom Performance Metrics Collection:

  1. Module Loading Time Measurement:
// Measure module loading performance
const moduleStartTimes = {};

function measureModuleLoad(moduleName) {
  moduleStartTimes[moduleName] = performance.now();

  return {
    end: () => {
      const duration = performance.now() - moduleStartTimes[moduleName];
      console.log(`${moduleName} loaded in ${duration.toFixed(2)}ms`);
      // Can send to monitoring system
    }
  };
}

// Usage example
const moduleTimer = measureModuleLoad('user-profile');
import('./user-profile').then(module => {
  moduleTimer.end();
  // Use module...
});
  1. Dependency Graph Analysis:
// Analyze module dependencies
async function analyzeDependencies(entryModule) {
  const dependencyGraph = {};
  const visited = new Set();

  async function traverse(modulePath) {
    if (visited.has(modulePath)) return;
    visited.add(modulePath);

    const module = await import(/* @vite-ignore */ modulePath);
    dependencyGraph[modulePath] = [];

    // Pseudo-code for dependency extraction
    if (module.__dependencies) {
      for (const dep of module.__dependencies) {
        dependencyGraph[modulePath].push(dep);
        await traverse(dep);
      }
    }
  }

  await traverse(entryModule);
  return dependencyGraph;
}

Performance Optimization Checklist:

  1. Bundling Optimization:
    • Use Tree Shaking to remove unused code
    • Ensure reasonable code splitting to avoid oversized chunks
    • Implement long-term caching strategies
  2. Loading Optimization:
    • Preload critical resources
    • Defer non-critical resource loading
    • Utilize HTTP/2 server push appropriately
  3. Runtime Optimization:
    • Minimize module initialization overhead
    • Optimize module dependency graph
    • Use caching effectively
Share your love