File size: 6,099 Bytes
488d96b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
/**
 * The Node-compatibility table, in one place. Two consumers share it, and they
 * must resolve to the same module instances:
 *   - the worker vite build aliases these specifiers for code bundled statically
 *     into the worker (vendored loader, Connection, …);
 *   - the worker module loader answers `require('node:fs')` from VFS-loaded
 *     modules out of this table, before bare-name resolution.
 * Anything absent here fails loudly at resolution instead of resolving to an
 * empty module. `process` is deliberately absent: the worker host installs that
 * global itself and fills it into this table at assembly time.
 *
 * Import paths carry the classification: `./implemented/<module>.ts` backs the
 * module's real semantics over a worker data source, while `./mock/<module>.ts`
 * is a structural placeholder whose calls report the missing capability. File
 * names match their Node module specifiers exactly, nesting included.
 *
 * Every value is a {@link StaticModuleFactory}, so the loader reads a table
 * entry only when a `require` names that specifier. What a factory defers is the
 * table read, not module evaluation: each one answers a namespace object of the
 * static ESM graph below, which the worker bundle evaluates at load like any
 * other import. Deferring a shim's own start-up cost therefore belongs inside
 * that shim, on the path that first needs it.
 */
import * as nodeAsyncHooks from './builtin_modules/implemented/async_hooks.ts'
import * as nodeBuffer from './builtin_modules/implemented/buffer.ts'
import * as nodeCrypto from './builtin_modules/implemented/crypto.ts'
import * as nodeDnsPromises from './builtin_modules/mock/dns/promises.ts'
import * as nodeEvents from './builtin_modules/implemented/events.ts'
import * as nodeFs from './builtin_modules/implemented/fs.ts'
import * as nodeFsPromises from './builtin_modules/implemented/fs/promises.ts'
import * as nodeHttp from './builtin_modules/implemented/http.ts'
import * as nodeModule from './builtin_modules/implemented/module.ts'
import * as nodeOs from './builtin_modules/implemented/os.ts'
import * as nodePath from './builtin_modules/implemented/path.ts'
import * as nodePerfHooks from './builtin_modules/implemented/perf_hooks.ts'
import * as nodeStream from './builtin_modules/implemented/stream.ts'
import * as nodeTimersPromises from './builtin_modules/implemented/timers/promises.ts'
import * as nodeTty from './builtin_modules/implemented/tty.ts'
import * as nodeUrl from './builtin_modules/implemented/url.ts'
import * as nodeUtil from './builtin_modules/implemented/util.ts'
import * as nodeUtilTypes from './builtin_modules/implemented/util/types.ts'
import * as nodeZlib from './builtin_modules/implemented/zlib.ts'
import * as nodeChildProcess from './builtin_modules/implemented/child_process.ts'
import * as nodeNet from './builtin_modules/mock/net.ts'
import * as nodeSqlite from './builtin_modules/mock/sqlite.ts'
import * as nodeVm from './builtin_modules/mock/vm.ts'
import * as nodeWorkerThreads from './builtin_modules/mock/worker_threads.ts'
import * as systemFlock from './external_packages/node-addon-system-flock.ts'
import * as koffi from './external_packages/koffi.ts'
import * as nodePty from './external_packages/node-pty.ts'
import * as piAi from './external_packages/pi-ai.ts'
import * as ripgrep from './external_packages/ripgrep.ts'
import * as sharp from './external_packages/sharp.ts'
import * as ws from './external_packages/ws.ts'
import { REPLACED_EXTERNAL_PACKAGES } from './external_packages/replaced-externals.ts'
import type { StaticModuleFactory } from '../module-system/module-loader.ts'

/** Builtin modules, keyed with and without the `node:` prefix. */
const BUILTINS: Record<string, StaticModuleFactory> = {
  async_hooks: () => nodeAsyncHooks,
  buffer: () => nodeBuffer,
  child_process: () => nodeChildProcess,
  crypto: () => nodeCrypto,
  'dns/promises': () => nodeDnsPromises,
  events: () => nodeEvents,
  fs: () => nodeFs,
  'fs/promises': () => nodeFsPromises,
  http: () => nodeHttp,
  module: () => nodeModule,
  net: () => nodeNet,
  os: () => nodeOs,
  path: () => nodePath,
  'path/posix': () => nodePath,
  perf_hooks: () => nodePerfHooks,
  sqlite: () => nodeSqlite,
  stream: () => nodeStream,
  'timers/promises': () => nodeTimersPromises,
  tty: () => nodeTty,
  url: () => nodeUrl,
  util: () => nodeUtil,
  'util/types': () => nodeUtilTypes,
  vm: () => nodeVm,
  worker_threads: () => nodeWorkerThreads,
  zlib: () => nodeZlib,
}

/** Exact package or subpath specifiers served by worker stubs and fakes. */
const EXTERNALS: Record<string, StaticModuleFactory> = {
  '@deepseek-ai/node-addon-system/flock': () => systemFlock,
  'koffi': () => koffi,
  'sharp': () => sharp,
  'node-pty': () => nodePty,
  'ws': () => ws,
  '@vscode/ripgrep': () => ripgrep,
  '@earendil-works/pi-ai': () => piAi,
}

/**
 * Prefixes whose every subpath resolves to one replacement module. The loader
 * matches the longest prefix after its exact table misses, so pi-ai's
 * `/providers/*` and `/api/*.lazy` entries need no enumeration.
 */
export const REPLACED_PREFIXES: Record<string, StaticModuleFactory> = {
  '@earendil-works/pi-ai/': () => piAi,
}

// One list, two consumers: a package replaced here must also be kept out of the
// VFS image, so any divergence fails at worker start rather than at first require.
const declared = [...REPLACED_EXTERNAL_PACKAGES].sort().join(',')
const wired = Object.keys(EXTERNALS).sort().join(',')
if (declared !== wired) {
  throw new Error(`web-preview: replaced-external lists diverge — declared [${declared}] vs wired [${wired}]`)
}

/**
 * Build the specifier → factory table the worker module loader consults first.
 * @returns every replaced specifier, including its `node:`-prefixed alias.
 */
export function createNodeBuiltins(): Record<string, StaticModuleFactory> {
  const table: Record<string, StaticModuleFactory> = { ...EXTERNALS }
  for (const [name, factory] of Object.entries(BUILTINS)) {
    table[name] = factory
    table[`node:${name}`] = factory
  }
  return table
}