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:
- Module Registration: Create a mechanism for Vue components to register themselves with a unique name and their component definition.
- 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.
- 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.
- 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:
UserProfileregisters itself as"user-profile".ProductDisplayregisters 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
UserProfilecomponent dynamically.
- It has a button to load
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.vuecomponent 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.TheloadedComponentremainsnull, 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
ModuleManagershould be a singleton or a globally accessible instance. - Error handling for unregistered modules is required.
Notes
- In a real-world scenario, the
componentLoaderfunction would typically use dynamicimport()syntax (e.g.,() => import('./modules/UserProfile.vue')). For this challenge, you can simulate this by returningPromise.resolve(YourComponentDefinition). - Think about how you would structure the
ModuleManagerto 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.