| 1 | // Module-resolution patches that stand in for what a Vite build normally does |
| 2 | // for Strudel. Registered by boot.ts. |
| 3 | // |
| 4 | // IMPORTANT: registerHooks() only takes effect for modules resolved *after* it |
| 5 | // runs, and an ESM graph is fully linked before any of it evaluates. A static |
| 6 | // `import '@strudel/core'` anywhere in the same graph as this file would |
| 7 | // therefore be linked before the hooks exist. That's why boot.ts imports the |
| 8 | // app with a dynamic `import()` — it defers resolution until after this module |
| 9 | // has evaluated. Don't turn that into a static import. |
| 10 | import { registerHooks } from 'node:module'; |
| 11 | import { fileURLToPath } from 'node:url'; |
| 12 | |
| 13 | const WORKLET_SUFFIX = '?audioworklet'; |
| 14 | |
| 15 | // @strudel/core imports `SalatRepl` from @kabelsalat/web to support kabelsalat |
| 16 | // DSP graphs. That package ships a browser IIFE bundle, not ESM — Node's |
| 17 | // CJS named-export detection can't see through it, so linking @strudel/core |
| 18 | // fails outright with "does not provide an export named 'SalatRepl'". |
| 19 | // |
| 20 | // We don't use kabelsalat: it's reached only via the `kabel`/mondo code path, |
| 21 | // which is a separate DSP language from the patterns we evaluate. Rather than |
| 22 | // wire up a browser bundle we can't run anyway, hand core a stub that satisfies |
| 23 | // the import and throws only if a pattern actually tries to use it. |
| 24 | // |
| 25 | // The constructor must succeed — repl() instantiates SalatRepl eagerly at setup |
| 26 | // and only calls evaluate() from its compileKabel() path. Throwing from the |
| 27 | // constructor takes the whole REPL down at boot. |
| 28 | const KABELSALAT_STUB = ` |
| 29 | export class SalatRepl { |
| 30 | constructor() {} |
| 31 | evaluate() { |
| 32 | throw new Error('[node-tui] kabelsalat DSP is not available in the Node runtime'); |
| 33 | } |
| 34 | } |
| 35 | export default { SalatRepl }; |
| 36 | `; |
| 37 | |
| 38 | registerHooks({ |
| 39 | resolve(specifier, context, next) { |
| 40 | if (specifier === '@kabelsalat/web') { |
| 41 | return { url: 'node-tui-stub:kabelsalat', shortCircuit: true }; |
| 42 | } |
| 43 | if (specifier.endsWith(WORKLET_SUFFIX)) { |
| 44 | const resolved = next(specifier.slice(0, -WORKLET_SUFFIX.length), context); |
| 45 | return { ...resolved, url: resolved.url + WORKLET_SUFFIX, shortCircuit: true }; |
| 46 | } |
| 47 | return next(specifier, context); |
| 48 | }, |
| 49 | |
| 50 | load(url, context, next) { |
| 51 | if (url === 'node-tui-stub:kabelsalat') { |
| 52 | return { format: 'module', shortCircuit: true, source: KABELSALAT_STUB }; |
| 53 | } |
| 54 | // superdough's *source* does `import workletsUrl from './worklets.mjs?audioworklet'`, |
| 55 | // a Vite-only specifier. Resolve it to the module's path on disk, which |
| 56 | // node-web-audio-api's addModule() accepts directly. |
| 57 | // |
| 58 | // (The published dist bundle inlines the worklets as a data: URL instead and |
| 59 | // never hits this path — but superdough resolves to source in some setups, |
| 60 | // and this costs nothing.) |
| 61 | if (url.endsWith(WORKLET_SUFFIX)) { |
| 62 | const path = fileURLToPath(url.slice(0, -WORKLET_SUFFIX.length)); |
| 63 | return { |
| 64 | format: 'module', |
| 65 | shortCircuit: true, |
| 66 | source: `export default ${JSON.stringify(path)};`, |
| 67 | }; |
| 68 | } |
| 69 | return next(url, context); |
| 70 | }, |
| 71 | }); |