Modular TypeScript: A Type-Safe Module System
This challenge focuses on building a basic, type-safe module system in TypeScript. Modern JavaScript development heavily relies on modules to organize code and prevent naming conflicts. Implementing a module system with strong typing ensures that dependencies are correctly resolved and that code behaves as expected, leading to more robust and maintainable applications.
Problem Description
You are tasked with creating a simple module system in TypeScript that allows you to define, import, and export functionality between modules. The system should enforce type safety during imports and exports, preventing errors at runtime. The core components are:
- Module Definition: A
Moduletype that encapsulates a module's exports. - Exporting: A function
createModulethat takes an object containing the module's exports and returns aModuleobject. - Importing: A function
importModulethat takes aModuleobject and returns a typed object representing the imported exports. - Type Safety: The
importModulefunction must correctly infer the types of the imported exports based on theModuleobject.
The module system should not rely on external libraries or built-in module features (like import and export keywords). It should be a purely TypeScript-based implementation.
Key Requirements:
- The
Moduletype should be a simple object. createModuleshould accept an object representing the module's exports and return aModuleobject.importModuleshould accept aModuleobject and return a typed object with the same structure as the module's exports.- The returned object from
importModuleshould have the correct types for each exported property. - The system should handle modules with various export types (strings, numbers, booleans, objects, arrays, functions).
Expected Behavior:
When importing a module, the resulting object should have the same properties as the module's exports, with the correct types inferred from the module definition. Attempting to access a non-existent property should result in a TypeScript compile-time error.
Edge Cases to Consider:
- Modules with no exports.
- Modules with exports of different types.
- Modules with function exports.
- Modules with object exports containing properties of different types.
Examples
Example 1:
// Module Definition
interface MyModuleExports {
message: string;
count: number;
isActive: boolean;
}
// Module Creation
const myModule = createModule<MyModuleExports>({
message: "Hello, world!",
count: 42,
isActive: true,
});
// Module Import
const importedModule = importModule(myModule);
// Usage
console.log(importedModule.message); // Output: Hello, world!
console.log(importedModule.count); // Output: 42
console.log(importedModule.isActive); // Output: true
// Error: Property 'nonExistentProperty' does not exist on type MyModuleExports.
// console.log(importedModule.nonExistentProperty);
Example 2:
// Module Definition
interface MathModuleExports {
add: (a: number, b: number) => number;
multiply: (a: number, b: number) => number;
}
// Module Creation
const mathModule = createModule<MathModuleExports>({
add: (a: number, b: number) => a + b,
multiply: (a: number, b: number) => a * b,
});
// Module Import
const importedMathModule = importModule(mathModule);
// Usage
console.log(importedMathModule.add(2, 3)); // Output: 5
console.log(importedMathModule.multiply(4, 5)); // Output: 20
// Error: Argument of type 'string' is not assignable to parameter of type 'number'.
// console.log(importedMathModule.add("hello", 5));
Example 3:
// Module Definition
interface EmptyModuleExports {}
// Module Creation
const emptyModule = createModule<EmptyModuleExports>({});
// Module Import
const importedEmptyModule = importModule(emptyModule);
// Usage - No errors, but no properties to access.
console.log(importedEmptyModule); // Output: {}
Constraints
- The solution must be written in TypeScript.
- The
createModuleandimportModulefunctions must be generic, accepting a type parameter representing the module's exports. - The module system should not rely on any external libraries or built-in module features (e.g.,
import,export). - The solution should be reasonably efficient. While performance is not the primary concern, avoid unnecessary complexity.
- The code should be well-documented and easy to understand.
Notes
- Consider using generics to achieve type safety.
- Think about how to represent the module's exports in a way that allows for type inference during import.
- The
importModulefunction should return a new object, not a reference to the original module's exports. This prevents accidental modification of the original module. - This is a simplified module system. Real-world module systems have more features (e.g., circular dependencies, dynamic imports). Focus on the core concepts of type safety and modularity.
- Start by defining the
Moduletype and then implementcreateModuleandimportModulebased on that definition.