/** * Custom ESM loader to fix missing .js extensions in @excalidraw/excalidraw deps. * * Problems patched: * 1. excalidraw imports 'roughjs/bin/rough' (and other roughjs/* paths) without .js * 2. roughjs/* files import sibling modules as './canvas' (relative, no .js) * 3. JSON files need { type: 'json' } import attribute in Node.js v22+ * * Usage: node --loader ./loader.mjs server.mjs [args...] */ import { fileURLToPath, pathToFileURL } from 'url'; import { dirname, resolve as pathResolve } from 'path'; const __dirname = dirname(fileURLToPath(import.meta.url)); // Modules that have incompatible ESM format — redirect to local stubs const STUBS = { '@excalidraw/laser-pointer': pathToFileURL(pathResolve(__dirname, 'stubs/laser-pointer.mjs')).href, }; export async function resolve(specifier, context, nextResolve) { // 0. Module stubs (incompatible ESM format packages) if (STUBS[specifier]) { return { url: STUBS[specifier], shortCircuit: true }; } // 1. Bare roughjs/* specifiers without .js extension if (/^roughjs\/bin\/[a-z-]+$/.test(specifier)) { return nextResolve(`${specifier}.js`, context); } // 2. Relative imports without extension (e.g. './canvas' from roughjs/bin/rough.js) // These come in as relative paths that resolve to extensionless file URLs. if (specifier.startsWith('./') || specifier.startsWith('../')) { // Try resolving first; if it fails with a missing-extension error, add .js try { return await nextResolve(specifier, context); } catch (err) { if (err.code === 'ERR_MODULE_NOT_FOUND') { // Try appending .js try { return await nextResolve(`${specifier}.js`, context); } catch { // Fall through to original error } } throw err; } } // 3. JSON imports need type: 'json' attribute if (specifier.endsWith('.json')) { const resolved = await nextResolve(specifier, context); if (!resolved.importAttributes?.type) { return { ...resolved, importAttributes: { ...resolved.importAttributes, type: 'json' }, }; } return resolved; } return nextResolve(specifier, context); } export async function load(url, context, nextLoad) { // Ensure JSON files are loaded with json format if (url.endsWith('.json')) { return nextLoad(url, { ...context, importAttributes: { ...context.importAttributes, type: 'json' }, }); } return nextLoad(url, context); }