Deno’s module system is one of its core features, offering modern ES module loading capabilities. This article delves into the working principles of Deno’s module loader, methods for custom implementation, and advanced application scenarios, helping developers master the core technologies of module loading.
Deno Module System Fundamentals
ESM Module Loading Process
Key Features:
- URL-based module identifiers
- Strict caching mechanism
- Asynchronous loading process
- Top-level await support
Built-in Module Loading Mechanism
Deno’s default module loader handles the following scenarios:
- Local file modules (
file://protocol) - Remote HTTP modules (
https://protocol) - Data URL modules (
data:protocol)
// Example: Loading modules with different protocols
import local from "./local.ts"; // Local file
import remote from "https://example.com/module.js"; // Remote HTTP
import data from "data:text/javascript,export const x=1;"; // Data URLCustom Module Loader Implementation
Creating a Basic Loader
Implementation Principle:
Override import.meta.resolve and dynamic import() to implement custom loading logic.
// custom_loader.ts
const originalImport = globalThis.import;
globalThis.import = async (specifier: string) => {
console.log(`[Custom Loader] Loading: ${specifier}`);
// Custom resolve logic
const resolved = await customResolve(specifier);
// Custom fetch logic
const source = await customFetch(resolved);
// Use native import for processing
return originalImport(source);
};
async function customResolve(specifier: string) {
// Implement custom resolution logic
if (specifier.startsWith("custom:")) {
return `https://example.com/modules/${specifier.slice(7)}.js`;
}
return specifier;
}
async function customFetch(url: string) {
// Implement custom fetch logic
if (url.startsWith("https://example.com/")) {
const response = await fetch(url);
return await response.text();
}
throw new Error(`Unsupported URL: ${url}`);
}Complete Loader Implementation
Production-Grade Loader Example:
// advanced_loader.ts
type ModuleCache = Map<string, { source: string; timestamp: number }>;
class CustomModuleLoader {
private cache: ModuleCache = new Map();
private resolver: (specifier: string) => Promise<string>;
private fetcher: (url: string) => Promise<string>;
constructor(
resolver: (specifier: string) => Promise<string>,
fetcher: (url: string) => Promise<string>
) {
this.resolver = resolver;
this.fetcher = fetcher;
}
async import(specifier: string) {
// 1. Check cache
if (this.cache.has(specifier)) {
const entry = this.cache.get(specifier)!;
if (this.isCacheValid(entry.timestamp)) {
return this.evaluate(entry.source);
}
this.cache.delete(specifier);
}
// 2. Resolve module URL
const resolvedUrl = await this.resolver(specifier);
// 3. Fetch module source
const source = await this.fetcher(resolvedUrl);
// 4. Cache module
this.cache.set(specifier, {
source,
timestamp: Date.now(),
});
// 5. Execute module
return this.evaluate(source);
}
private isCacheValid(timestamp: number): boolean {
return Date.now() - timestamp < 60 * 1000; // 1-minute cache
}
private async evaluate(source: string): Promise<any> {
// Create sandbox environment
const sandbox: any = {};
const proxy = new Proxy(sandbox, {
has: () => true, // Trick eval into thinking all variables exist
get: (target, prop) => {
if (prop === Symbol.unscopables) return undefined;
return target[prop] || globalThis[prop];
},
});
// Execute module code
const fn = new Function("exports", "module", "require", "__filename", "__dirname", source);
const module = { exports: {} };
fn(module.exports, module, (spec: string) => this.import(spec), "", "");
return module.exports;
}
}
// Usage example
const loader = new CustomModuleLoader(
async (specifier) => {
if (specifier.startsWith("custom:")) {
return `https://example.com/modules/${specifier.slice(7)}.js`;
}
return specifier;
},
async (url) => {
if (url.startsWith("https://example.com/")) {
const response = await fetch(url);
return await response.text();
}
throw new Error(`Unsupported URL: ${url}`);
}
);
// Dynamically load module
const module = await loader.import("custom:math");
console.log(module.add(1, 2));Advanced Loader Patterns
Hot Module Replacement (HMR)
Implementation Principle:
Achieve module hot updates by listening for file changes and clearing the cache.
// hmr_loader.ts
class HMRLoader extends CustomModuleLoader {
private watchers: Map<string, Deno.FsWatcher> = new Map();
async import(specifier: string) {
// Enable HMR in development environment
if (Deno.env.get("DENO_ENV") === "development") {
this.setupWatcher(specifier);
}
return super.import(specifier);
}
private setupWatcher(specifier: string) {
if (this.watchers.has(specifier)) return;
const filePath = this.resolveLocalPath(specifier);
if (!filePath) return;
const watcher = Deno.watchFs(filePath);
this.watchers.set(specifier, watcher);
(async () => {
for await (const event of watcher) {
if (event.kind === "modify") {
console.log(`[HMR] Detected change: ${specifier}`);
this.cache.delete(specifier); // Clear cache
}
}
})();
}
private resolveLocalPath(specifier: string): string | null {
if (specifier.startsWith("file://")) {
return specifier.slice(7);
}
return null;
}
}Module Preloading
Preloading Strategy Implementation:
// preload_loader.ts
class PreloadLoader extends CustomModuleLoader {
private preloadQueue: string[] = [];
private isPreloading = false;
async import(specifier: string) {
// Add to preload queue
if (!this.cache.has(specifier)) {
this.preloadQueue.push(specifier);
this.startPreloadIfNeeded();
}
// Load current module immediately
return super.import(specifier);
}
private startPreloadIfNeeded() {
if (this.isPreloading || this.preloadQueue.length === 0) return;
this.isPreloading = true;
const nextSpecifier = this.preloadQueue.shift()!;
this.import(nextSpecifier).finally(() => {
this.isPreloading = false;
this.startPreloadIfNeeded();
});
}
}Dependency Analysis Loader
Dependency Visualization Loader:
// dependency_loader.ts
class DependencyLoader extends CustomModuleLoader {
private dependencyGraph: Map<string, string[]> = new Map();
async import(specifier: string) {
const dependencies = await this.analyzeDependencies(specifier);
this.dependencyGraph.set(specifier, dependencies);
return super.import(specifier);
}
private async analyzeDependencies(specifier: string) {
// Implement dependency analysis logic
// Simplified as returning an empty array
return [];
}
printDependencyGraph() {
console.log("Dependency Graph:");
this.dependencyGraph.forEach((deps, module) => {
console.log(`${module} -> ${deps.join(", ")}`);
});
}
}Security-Enhanced Loader
Permission-Aware Loader
Permission-Checking Loader Implementation:
// secure_loader.ts
class SecureModuleLoader extends CustomModuleLoader {
async import(specifier: string) {
await this.checkPermissions(specifier);
return super.import(specifier);
}
private async checkPermissions(specifier: string) {
if (specifier.startsWith("file://")) {
const filePath = specifier.slice(7);
await this.checkFilePermission(filePath);
} else if (specifier.startsWith("https://")) {
await this.checkNetworkPermission(specifier);
}
}
private async checkFilePermission(path: string) {
try {
const status = await Deno.permissions.query({ name: "read", path });
if (status.state !== "granted") {
throw new Error(`No file read permission: ${path}`);
}
} catch (err) {
console.error("Permission check failed:", err);
throw err;
}
}
private async checkNetworkPermission(url: string) {
try {
const urlObj = new URL(url);
const status = await Deno.permissions.query({ name: "net", host: urlObj.host });
if (status.state !== "granted") {
throw new Error(`No network access permission: ${urlObj.host}`);
}
} catch (err) {
console.error("Permission check failed:", err);
throw err;
}
}
}Sandbox Isolation Loader
Secure Sandbox Implementation:
// sandbox_loader.ts
class SandboxModuleLoader extends CustomModuleLoader {
private sandbox: any = {};
async evaluate(source: string) {
// Create fully isolated execution environment
const proxy = new Proxy(this.sandbox, {
has: () => true,
get: (target, prop) => {
if (prop === Symbol.unscopables) return undefined;
return target[prop] || this.getSafeGlobal(prop);
},
});
// Restrict available global objects
const safeGlobals = {
console: {
log: console.log,
error: console.error,
},
setTimeout: setTimeout,
clearTimeout: clearTimeout,
};
const fn = new Function(
"exports",
"module",
"require",
"__filename",
"__dirname",
source
);
const module = { exports: {} };
fn(module.exports, module, this.import.bind(this), "", "");
return module.exports;
}
private getSafeGlobal(prop: string) {
if (prop in safeGlobals) {
return safeGlobals[prop];
}
throw new Error(`Access to global object prohibited: ${prop}`);
}
}Performance-Optimized Loader
Cache Strategy Optimization
Multi-Level Cache Implementation:
// cache_loader.ts
class CacheModuleLoader extends CustomModuleLoader {
private memoryCache: ModuleCache = new Map();
private diskCache: DiskCache;
constructor(
resolver: (specifier: string) => Promise<string>,
fetcher: (url: string) => Promise<string>,
cacheDir: string
) {
super(resolver, fetcher);
this.diskCache = new DiskCache(cacheDir);
}
async import(specifier: string) {
// 1. Check memory cache
if (this.memoryCache.has(specifier)) {
return this.evaluate(this.memoryCache.get(specifier)!.source);
}
// 2. Check disk cache
const diskCached = await this.diskCache.get(specifier);
if (diskCached) {
this.memoryCache.set(specifier, diskCached);
return this.evaluate(diskCached.source);
}
// 3. Fetch new module
const resolvedUrl = await this.resolver(specifier);
const source = await this.fetcher(resolvedUrl);
// 4. Update cache
const cacheEntry = { source, timestamp: Date.now() };
this.memoryCache.set(specifier, cacheEntry);
await this.diskCache.set(specifier, cacheEntry);
return this.evaluate(source);
}
}
class DiskCache {
constructor(private dir: string) {
Deno.mkdirSync(dir, { recursive: true });
}
async get(specifier: string): Promise<{ source: string; timestamp: number } | null> {
const file = this.getCacheFile(specifier);
try {
const content = await Deno.readTextFile(file);
const { source, timestamp } = JSON.parse(content);
if (this.isCacheValid(timestamp)) {
return { source, timestamp };
}
await Deno.remove(file);
return null;
} catch (err) {
return null;
}
}
async set(specifier: string, entry: { source: string; timestamp: number }) {
const file = this.getCacheFile(specifier);
const content = JSON.stringify(entry);
await Deno.writeTextFile(file, content);
}
private getCacheFile(specifier: string): string {
const hash = this.hashSpecifier(specifier);
return `${this.dir}/${hash}.cache`;
}
private hashSpecifier(specifier: string): string {
// Simple hash implementation
let hash = 0;
for (let i = 0; i < specifier.length; i++) {
hash = ((hash << 5) - hash) + specifier.charCodeAt(i);
hash |= 0;
}
return hash.toString(16);
}
private isCacheValid(timestamp: number): boolean {
return Date.now() - timestamp < 5 * 60 * 1000; // 5-minute cache
}
}Parallel Loading Optimization
Concurrency Control Loader:
// parallel_loader.ts
class ParallelModuleLoader extends CustomModuleLoader {
private concurrency = 3;
private queue: Array<() => Promise<void>> = [];
private activeCount = 0;
async import(specifier: string) {
return new Promise((resolve, reject) => {
const task = async () => {
try {
this.activeCount++;
const result = await super.import(specifier);
resolve(result);
} catch (err) {
reject(err);
} finally {
this.activeCount--;
this.next();
}
};
this.queue.push(task);
this.next();
});
}
private next() {
if (this.activeCount < this.concurrency && this.queue.length > 0) {
const task = this.queue.shift()!;
task();
}
}
}Practical Application Cases
Plugin System Implementation
Plugin Architecture Based on Custom Loader:
// plugin_system.ts
class PluginSystem {
private loader: CustomModuleLoader;
private plugins: Map<string, any> = new Map();
constructor(loader: CustomModuleLoader) {
this.loader = loader;
}
async loadPlugin(name: string) {
if (this.plugins.has(name)) {
return this.plugins.get(name);
}
const plugin = await this.loader.import(`plugins/${name}.ts`);
this.plugins.set(name, plugin);
return plugin;
}
async executePlugin(name: string, ...args: any[]) {
const plugin = await this.loadPlugin(name);
if (typeof plugin.execute === "function") {
return plugin.execute(...args);
}
throw new Error(`Plugin ${name} does not implement execute method`);
}
}
// Usage example
const loader = new SecureModuleLoader(/* Permission check logic */);
const pluginSystem = new PluginSystem(loader);
// Dynamically load and execute plugin
await pluginSystem.executePlugin("analytics", { track: true });Multi-Environment Module Loading
Environment-Aware Loader:
// env_aware_loader.ts
class EnvironmentAwareLoader extends CustomModuleLoader {
private env: string;
constructor(
env: string,
resolver: (specifier: string) => Promise<string>,
fetcher: (url: string) => Promise<string>
) {
super(resolver, fetcher);
this.env = env;
}
async resolve(specifier: string) {
if (specifier.startsWith("env:")) {
const envSpecifier = specifier.slice(4);
return `./env/${this.env}/${envSpecifier}.ts`;
}
return specifier;
}
}
// Usage example
const loader = new EnvironmentAwareLoader(
Deno.env.get("DENO_ENV") || "development",
async (specifier) => {
if (specifier.startsWith("env:")) {
return this.resolve(specifier);
}
return specifier;
},
async (url) => {
// Implement fetch logic
return fetch(url).then(res => res.text());
}
);
// Load environment-specific module
const config = await loader.import("env:database");



