Hone logo
Hone
Problems

Vue Module System Implementation

You are tasked with building a simplified module system for a Vue.js application written in TypeScript. This system should allow components to register themselves with a central "module manager" and be dynamically imported and accessed by other components. This is a fundamental pattern for organizing larger Vue applications, enabling code splitting and better maintainability.

Problem Description

Your goal is to create a basic module management system within a Vue 3 application using TypeScript. This system should allow different Vue components (modules) to register their names and component definitions with a central service. Other parts of the application should then be able to request these modules by their registered name and dynamically import them.

Key requirements:

  1. Module Registration: Create a mechanism for Vue components to register themselves with a unique name and their component definition.
  2. Module Manager Service: Design a service that acts as a registry for all registered modules. It should store the mapping between module names and their component definitions.
  3. Dynamic Module Loading: Implement a function that takes a module name and returns a Promise that resolves with the corresponding Vue component definition. This function should simulate dynamic loading.
  4. Component Usage: Demonstrate how another Vue component can utilize the module manager to request and render a dynamically loaded module.

Expected Behavior:

  • When a module is registered, it should be stored in the manager.
  • When a module is requested, the manager should return a Promise that eventually resolves with the correct component definition.
  • The dynamically loaded component should render correctly within the parent component.

Edge Cases:

  • What happens if a module is requested that hasn't been registered? The system should handle this gracefully, perhaps by rejecting the Promise or returning a default "not found" component.
  • Consider scenarios where multiple modules are registered and requested.

Examples

Example 1: Basic Module Registration and Usage

Let's assume we have two simple components, UserProfile and ProductDisplay.

  • Module Registration:

    • UserProfile registers itself as "user-profile".
    • ProductDisplay registers itself as "product-display".
  • Parent Component (App.vue):

    • It has a button to load "user-profile".
    • When the button is clicked, it requests the "user-profile" module.
    • Upon successful loading, it renders the UserProfile component dynamically.

Input: (Conceptual - involves Vue component definitions and actions)

// src/modules/UserProfile.vue (simplified)
<template><div>User Profile Component</div></template>

// src/modules/ProductDisplay.vue (simplified)
<template><div>Product Display Component</div></template>

// src/services/ModuleManager.ts
class ModuleManager {
  private modules: Record<string, () => Promise<any>> = {}; // Store module name to async import function

  registerModule(name: string, componentLoader: () => Promise<any>) {
    this.modules[name] = componentLoader;
  }

  async loadModule(name: string): Promise<any> {
    if (!this.modules[name]) {
      return Promise.reject(new Error(`Module "${name}" not found.`));
    }
    // Simulate dynamic import - in a real scenario, this would be an actual import()
    return this.modules[name]();
  }
}

export const moduleManager = new ModuleManager();

// src/main.ts or module registration point
import { moduleManager } from './services/ModuleManager';
import UserProfile from './modules/UserProfile.vue'; // Imagine this is a Vue component definition
import ProductDisplay from './modules/ProductDisplay.vue';

moduleManager.registerModule('user-profile', () => Promise.resolve(UserProfile));
moduleManager.registerModule('product-display', () => Promise.resolve(ProductDisplay));

// src/components/App.vue
<template>
  <div>
    <h1>My App</h1>
    <button @click="loadUserProfile">Load User Profile</button>
    <div v-if="loadedComponent">
      <component :is="loadedComponent"></component>
    </div>
  </div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { moduleManager } from '@/services/ModuleManager';

const loadedComponent = ref<any>(null);

const loadUserProfile = async () => {
  try {
    const UserProfileComponent = await moduleManager.loadModule('user-profile');
    loadedComponent.value = UserProfileComponent;
  } catch (error) {
    console.error(error);
    // Handle error, e.g., show a message
  }
};
</script>

Output: When the "Load User Profile" button is clicked, the following is rendered:

<div>
  <h1>My App</h1>
  <button>Load User Profile</button>
  <div>
    <div>User Profile Component</div> <!-- Dynamically rendered UserProfile -->
  </div>
</div>

Explanation: The App.vue component calls moduleManager.loadModule('user-profile'). The ModuleManager finds the registered loader for "user-profile" and returns a Promise that resolves with the UserProfile component definition. This definition is then assigned to loadedComponent.value, causing Vue's <component :is="..."> to render the UserProfile.

Example 2: Handling Unregistered Modules

  • Input: The App.vue component attempts to load a module named "non-existent-module".

  • Output: An error message is logged to the console: Error: Module "non-existent-module" not found. The loadedComponent remains null, and no component is rendered.

Explanation: The moduleManager.loadModule('non-existent-module') call finds no entry for "non-existent-module" in its registry. It rejects the Promise with an error, which is caught and logged by the App.vue component.

Constraints

  • The solution must be implemented using TypeScript.
  • Vue 3 Composition API should be used for component logic.
  • The module loading mechanism should return a Promise.
  • The ModuleManager should be a singleton or a globally accessible instance.
  • Error handling for unregistered modules is required.

Notes

  • In a real-world scenario, the componentLoader function would typically use dynamic import() syntax (e.g., () => import('./modules/UserProfile.vue')). For this challenge, you can simulate this by returning Promise.resolve(YourComponentDefinition).
  • Think about how you would structure the ModuleManager to be easily accessible across your application.
  • Consider how you might handle different types of modules or dependencies in a more complex system. For this challenge, focusing on component loading is sufficient.
Loading editor...
typescript