almostnode 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +731 -0
  3. package/dist/__sw__.js +394 -0
  4. package/dist/ai-chatbot-demo-entry.d.ts +6 -0
  5. package/dist/ai-chatbot-demo-entry.d.ts.map +1 -0
  6. package/dist/ai-chatbot-demo.d.ts +42 -0
  7. package/dist/ai-chatbot-demo.d.ts.map +1 -0
  8. package/dist/assets/runtime-worker-D9x_Ddwz.js +60543 -0
  9. package/dist/assets/runtime-worker-D9x_Ddwz.js.map +1 -0
  10. package/dist/convex-app-demo-entry.d.ts +6 -0
  11. package/dist/convex-app-demo-entry.d.ts.map +1 -0
  12. package/dist/convex-app-demo.d.ts +68 -0
  13. package/dist/convex-app-demo.d.ts.map +1 -0
  14. package/dist/cors-proxy.d.ts +46 -0
  15. package/dist/cors-proxy.d.ts.map +1 -0
  16. package/dist/create-runtime.d.ts +42 -0
  17. package/dist/create-runtime.d.ts.map +1 -0
  18. package/dist/demo.d.ts +6 -0
  19. package/dist/demo.d.ts.map +1 -0
  20. package/dist/dev-server.d.ts +97 -0
  21. package/dist/dev-server.d.ts.map +1 -0
  22. package/dist/frameworks/next-dev-server.d.ts +202 -0
  23. package/dist/frameworks/next-dev-server.d.ts.map +1 -0
  24. package/dist/frameworks/vite-dev-server.d.ts +85 -0
  25. package/dist/frameworks/vite-dev-server.d.ts.map +1 -0
  26. package/dist/index.cjs +14965 -0
  27. package/dist/index.cjs.map +1 -0
  28. package/dist/index.d.ts +71 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.mjs +14867 -0
  31. package/dist/index.mjs.map +1 -0
  32. package/dist/next-demo.d.ts +49 -0
  33. package/dist/next-demo.d.ts.map +1 -0
  34. package/dist/npm/index.d.ts +71 -0
  35. package/dist/npm/index.d.ts.map +1 -0
  36. package/dist/npm/registry.d.ts +66 -0
  37. package/dist/npm/registry.d.ts.map +1 -0
  38. package/dist/npm/resolver.d.ts +52 -0
  39. package/dist/npm/resolver.d.ts.map +1 -0
  40. package/dist/npm/tarball.d.ts +29 -0
  41. package/dist/npm/tarball.d.ts.map +1 -0
  42. package/dist/runtime-interface.d.ts +90 -0
  43. package/dist/runtime-interface.d.ts.map +1 -0
  44. package/dist/runtime.d.ts +103 -0
  45. package/dist/runtime.d.ts.map +1 -0
  46. package/dist/sandbox-helpers.d.ts +43 -0
  47. package/dist/sandbox-helpers.d.ts.map +1 -0
  48. package/dist/sandbox-runtime.d.ts +65 -0
  49. package/dist/sandbox-runtime.d.ts.map +1 -0
  50. package/dist/server-bridge.d.ts +89 -0
  51. package/dist/server-bridge.d.ts.map +1 -0
  52. package/dist/shims/assert.d.ts +51 -0
  53. package/dist/shims/assert.d.ts.map +1 -0
  54. package/dist/shims/async_hooks.d.ts +37 -0
  55. package/dist/shims/async_hooks.d.ts.map +1 -0
  56. package/dist/shims/buffer.d.ts +20 -0
  57. package/dist/shims/buffer.d.ts.map +1 -0
  58. package/dist/shims/child_process-browser.d.ts +92 -0
  59. package/dist/shims/child_process-browser.d.ts.map +1 -0
  60. package/dist/shims/child_process.d.ts +93 -0
  61. package/dist/shims/child_process.d.ts.map +1 -0
  62. package/dist/shims/chokidar.d.ts +55 -0
  63. package/dist/shims/chokidar.d.ts.map +1 -0
  64. package/dist/shims/cluster.d.ts +52 -0
  65. package/dist/shims/cluster.d.ts.map +1 -0
  66. package/dist/shims/crypto.d.ts +122 -0
  67. package/dist/shims/crypto.d.ts.map +1 -0
  68. package/dist/shims/dgram.d.ts +34 -0
  69. package/dist/shims/dgram.d.ts.map +1 -0
  70. package/dist/shims/diagnostics_channel.d.ts +80 -0
  71. package/dist/shims/diagnostics_channel.d.ts.map +1 -0
  72. package/dist/shims/dns.d.ts +87 -0
  73. package/dist/shims/dns.d.ts.map +1 -0
  74. package/dist/shims/domain.d.ts +25 -0
  75. package/dist/shims/domain.d.ts.map +1 -0
  76. package/dist/shims/esbuild.d.ts +105 -0
  77. package/dist/shims/esbuild.d.ts.map +1 -0
  78. package/dist/shims/events.d.ts +37 -0
  79. package/dist/shims/events.d.ts.map +1 -0
  80. package/dist/shims/fs.d.ts +115 -0
  81. package/dist/shims/fs.d.ts.map +1 -0
  82. package/dist/shims/fsevents.d.ts +67 -0
  83. package/dist/shims/fsevents.d.ts.map +1 -0
  84. package/dist/shims/http.d.ts +217 -0
  85. package/dist/shims/http.d.ts.map +1 -0
  86. package/dist/shims/http2.d.ts +81 -0
  87. package/dist/shims/http2.d.ts.map +1 -0
  88. package/dist/shims/https.d.ts +36 -0
  89. package/dist/shims/https.d.ts.map +1 -0
  90. package/dist/shims/inspector.d.ts +25 -0
  91. package/dist/shims/inspector.d.ts.map +1 -0
  92. package/dist/shims/module.d.ts +22 -0
  93. package/dist/shims/module.d.ts.map +1 -0
  94. package/dist/shims/net.d.ts +100 -0
  95. package/dist/shims/net.d.ts.map +1 -0
  96. package/dist/shims/os.d.ts +159 -0
  97. package/dist/shims/os.d.ts.map +1 -0
  98. package/dist/shims/path.d.ts +72 -0
  99. package/dist/shims/path.d.ts.map +1 -0
  100. package/dist/shims/perf_hooks.d.ts +50 -0
  101. package/dist/shims/perf_hooks.d.ts.map +1 -0
  102. package/dist/shims/process.d.ts +93 -0
  103. package/dist/shims/process.d.ts.map +1 -0
  104. package/dist/shims/querystring.d.ts +23 -0
  105. package/dist/shims/querystring.d.ts.map +1 -0
  106. package/dist/shims/readdirp.d.ts +52 -0
  107. package/dist/shims/readdirp.d.ts.map +1 -0
  108. package/dist/shims/readline.d.ts +62 -0
  109. package/dist/shims/readline.d.ts.map +1 -0
  110. package/dist/shims/rollup.d.ts +34 -0
  111. package/dist/shims/rollup.d.ts.map +1 -0
  112. package/dist/shims/sentry.d.ts +163 -0
  113. package/dist/shims/sentry.d.ts.map +1 -0
  114. package/dist/shims/stream.d.ts +181 -0
  115. package/dist/shims/stream.d.ts.map +1 -0
  116. package/dist/shims/tls.d.ts +53 -0
  117. package/dist/shims/tls.d.ts.map +1 -0
  118. package/dist/shims/tty.d.ts +30 -0
  119. package/dist/shims/tty.d.ts.map +1 -0
  120. package/dist/shims/url.d.ts +64 -0
  121. package/dist/shims/url.d.ts.map +1 -0
  122. package/dist/shims/util.d.ts +106 -0
  123. package/dist/shims/util.d.ts.map +1 -0
  124. package/dist/shims/v8.d.ts +73 -0
  125. package/dist/shims/v8.d.ts.map +1 -0
  126. package/dist/shims/vfs-adapter.d.ts +126 -0
  127. package/dist/shims/vfs-adapter.d.ts.map +1 -0
  128. package/dist/shims/vm.d.ts +45 -0
  129. package/dist/shims/vm.d.ts.map +1 -0
  130. package/dist/shims/worker_threads.d.ts +66 -0
  131. package/dist/shims/worker_threads.d.ts.map +1 -0
  132. package/dist/shims/ws.d.ts +66 -0
  133. package/dist/shims/ws.d.ts.map +1 -0
  134. package/dist/shims/zlib.d.ts +161 -0
  135. package/dist/shims/zlib.d.ts.map +1 -0
  136. package/dist/transform.d.ts +24 -0
  137. package/dist/transform.d.ts.map +1 -0
  138. package/dist/virtual-fs.d.ts +226 -0
  139. package/dist/virtual-fs.d.ts.map +1 -0
  140. package/dist/vite-demo.d.ts +35 -0
  141. package/dist/vite-demo.d.ts.map +1 -0
  142. package/dist/vite-sw.js +132 -0
  143. package/dist/worker/runtime-worker.d.ts +8 -0
  144. package/dist/worker/runtime-worker.d.ts.map +1 -0
  145. package/dist/worker-runtime.d.ts +50 -0
  146. package/dist/worker-runtime.d.ts.map +1 -0
  147. package/package.json +85 -0
  148. package/src/ai-chatbot-demo-entry.ts +244 -0
  149. package/src/ai-chatbot-demo.ts +509 -0
  150. package/src/convex-app-demo-entry.ts +1107 -0
  151. package/src/convex-app-demo.ts +1316 -0
  152. package/src/cors-proxy.ts +81 -0
  153. package/src/create-runtime.ts +147 -0
  154. package/src/demo.ts +304 -0
  155. package/src/dev-server.ts +274 -0
  156. package/src/frameworks/next-dev-server.ts +2224 -0
  157. package/src/frameworks/vite-dev-server.ts +702 -0
  158. package/src/index.ts +101 -0
  159. package/src/next-demo.ts +1784 -0
  160. package/src/npm/index.ts +347 -0
  161. package/src/npm/registry.ts +152 -0
  162. package/src/npm/resolver.ts +385 -0
  163. package/src/npm/tarball.ts +209 -0
  164. package/src/runtime-interface.ts +103 -0
  165. package/src/runtime.ts +1046 -0
  166. package/src/sandbox-helpers.ts +173 -0
  167. package/src/sandbox-runtime.ts +252 -0
  168. package/src/server-bridge.ts +426 -0
  169. package/src/shims/assert.ts +664 -0
  170. package/src/shims/async_hooks.ts +86 -0
  171. package/src/shims/buffer.ts +75 -0
  172. package/src/shims/child_process-browser.ts +217 -0
  173. package/src/shims/child_process.ts +463 -0
  174. package/src/shims/chokidar.ts +313 -0
  175. package/src/shims/cluster.ts +67 -0
  176. package/src/shims/crypto.ts +830 -0
  177. package/src/shims/dgram.ts +47 -0
  178. package/src/shims/diagnostics_channel.ts +196 -0
  179. package/src/shims/dns.ts +172 -0
  180. package/src/shims/domain.ts +58 -0
  181. package/src/shims/esbuild.ts +805 -0
  182. package/src/shims/events.ts +195 -0
  183. package/src/shims/fs.ts +803 -0
  184. package/src/shims/fsevents.ts +63 -0
  185. package/src/shims/http.ts +904 -0
  186. package/src/shims/http2.ts +96 -0
  187. package/src/shims/https.ts +86 -0
  188. package/src/shims/inspector.ts +30 -0
  189. package/src/shims/module.ts +82 -0
  190. package/src/shims/net.ts +359 -0
  191. package/src/shims/os.ts +195 -0
  192. package/src/shims/path.ts +199 -0
  193. package/src/shims/perf_hooks.ts +92 -0
  194. package/src/shims/process.ts +346 -0
  195. package/src/shims/querystring.ts +97 -0
  196. package/src/shims/readdirp.ts +228 -0
  197. package/src/shims/readline.ts +110 -0
  198. package/src/shims/rollup.ts +80 -0
  199. package/src/shims/sentry.ts +133 -0
  200. package/src/shims/stream.ts +1126 -0
  201. package/src/shims/tls.ts +95 -0
  202. package/src/shims/tty.ts +64 -0
  203. package/src/shims/url.ts +171 -0
  204. package/src/shims/util.ts +312 -0
  205. package/src/shims/v8.ts +113 -0
  206. package/src/shims/vfs-adapter.ts +402 -0
  207. package/src/shims/vm.ts +83 -0
  208. package/src/shims/worker_threads.ts +111 -0
  209. package/src/shims/ws.ts +382 -0
  210. package/src/shims/zlib.ts +289 -0
  211. package/src/transform.ts +313 -0
  212. package/src/types/external.d.ts +67 -0
  213. package/src/virtual-fs.ts +903 -0
  214. package/src/vite-demo.ts +577 -0
  215. package/src/worker/runtime-worker.ts +128 -0
  216. package/src/worker-runtime.ts +145 -0
@@ -0,0 +1,805 @@
1
+ /**
2
+ * esbuild shim - Uses esbuild-wasm for transforms in the browser
3
+ * Provides VFS integration for file access
4
+ */
5
+
6
+ import type { VirtualFS } from '../virtual-fs';
7
+
8
+ // ============================================================================
9
+ // Type Definitions
10
+ // ============================================================================
11
+
12
+ /**
13
+ * Represents a package.json exports map entry.
14
+ * Can be a direct path string or a conditional exports object with nested conditions.
15
+ *
16
+ * @example Direct string entry:
17
+ * ```json
18
+ * { "exports": "./dist/index.js" }
19
+ * ```
20
+ *
21
+ * @example Conditional exports:
22
+ * ```json
23
+ * {
24
+ * "exports": {
25
+ * ".": {
26
+ * "import": "./dist/esm/index.js",
27
+ * "require": "./dist/cjs/index.js"
28
+ * }
29
+ * }
30
+ * }
31
+ * ```
32
+ *
33
+ * @example Nested conditions (e.g., convex package):
34
+ * ```json
35
+ * {
36
+ * "exports": {
37
+ * "./server": {
38
+ * "convex": {
39
+ * "import": "./dist/server.js"
40
+ * },
41
+ * "default": "./dist/server.js"
42
+ * }
43
+ * }
44
+ * }
45
+ * ```
46
+ */
47
+ type ExportEntry = string | ExportConditions;
48
+
49
+ /**
50
+ * Conditional exports object mapping condition names to export entries.
51
+ * Conditions can be nested to support complex resolution scenarios.
52
+ */
53
+ interface ExportConditions {
54
+ [condition: string]: ExportEntry;
55
+ }
56
+
57
+ /**
58
+ * Result of resolving a node module import.
59
+ */
60
+ interface NodeModuleResolution {
61
+ /** The resolved absolute path to the module file */
62
+ path: string;
63
+ /** Plugin data to pass to the onLoad handler */
64
+ pluginData: { fromVFS: boolean };
65
+ }
66
+
67
+ // esbuild-wasm types
68
+ export interface TransformOptions {
69
+ loader?: 'js' | 'jsx' | 'ts' | 'tsx' | 'json' | 'css';
70
+ format?: 'iife' | 'cjs' | 'esm';
71
+ target?: string | string[];
72
+ minify?: boolean;
73
+ sourcemap?: boolean | 'inline' | 'external';
74
+ jsx?: 'transform' | 'preserve';
75
+ jsxFactory?: string;
76
+ jsxFragment?: string;
77
+ }
78
+
79
+ export interface TransformResult {
80
+ code: string;
81
+ map: string;
82
+ warnings: unknown[];
83
+ }
84
+
85
+ export interface BuildOptions {
86
+ entryPoints?: string[];
87
+ bundle?: boolean;
88
+ outdir?: string;
89
+ outfile?: string;
90
+ format?: 'iife' | 'cjs' | 'esm';
91
+ platform?: 'browser' | 'node' | 'neutral';
92
+ target?: string | string[];
93
+ minify?: boolean;
94
+ sourcemap?: boolean | 'inline' | 'external';
95
+ external?: string[];
96
+ write?: boolean;
97
+ plugins?: unknown[];
98
+ absWorkingDir?: string;
99
+ }
100
+
101
+ export interface BuildResult {
102
+ errors: unknown[];
103
+ warnings: unknown[];
104
+ outputFiles?: Array<{ path: string; contents: Uint8Array; text: string }>;
105
+ }
106
+
107
+ // Window.__esbuild type is declared in src/types/external.d.ts
108
+
109
+ // ============================================================================
110
+ // Export Condition Resolution
111
+ // ============================================================================
112
+
113
+ /**
114
+ * The priority order for export conditions when resolving package exports.
115
+ *
116
+ * Order rationale:
117
+ * 1. "convex" - Framework-specific condition for Convex packages (highest priority)
118
+ * 2. "module" - ESM entry point (preferred for modern bundlers)
119
+ * 3. "import" - ESM import condition (standard Node.js condition)
120
+ * 4. "require" - CJS require condition (fallback for CommonJS)
121
+ * 5. "default" - Fallback condition (lowest priority)
122
+ */
123
+ const EXPORT_CONDITION_PRIORITY = ['convex', 'module', 'import', 'require', 'default'] as const;
124
+
125
+ /**
126
+ * Resolves a package.json exports entry to a file path by evaluating export conditions.
127
+ *
128
+ * This function handles the Node.js package exports map resolution algorithm,
129
+ * supporting both simple string exports and conditional exports with nested structures.
130
+ *
131
+ * @param entry - The exports map entry to resolve. Can be:
132
+ * - A string path (e.g., "./dist/index.js")
133
+ * - A conditional exports object with condition keys
134
+ * - Nested conditional objects for complex packages
135
+ *
136
+ * @returns The resolved file path relative to the package root, or undefined if
137
+ * no matching condition is found.
138
+ *
139
+ * @example Simple string entry:
140
+ * ```ts
141
+ * resolveExportConditions("./dist/index.js")
142
+ * // Returns: "./dist/index.js"
143
+ * ```
144
+ *
145
+ * @example Conditional exports:
146
+ * ```ts
147
+ * resolveExportConditions({
148
+ * import: "./dist/esm/index.js",
149
+ * require: "./dist/cjs/index.js",
150
+ * default: "./dist/index.js"
151
+ * })
152
+ * // Returns: "./dist/esm/index.js" (import has higher priority)
153
+ * ```
154
+ *
155
+ * @example Nested conditions (convex package style):
156
+ * ```ts
157
+ * resolveExportConditions({
158
+ * convex: { import: "./dist/convex.js" },
159
+ * default: "./dist/default.js"
160
+ * })
161
+ * // Returns: "./dist/convex.js" (convex condition with nested import)
162
+ * ```
163
+ */
164
+ function resolveExportConditions(entry: ExportEntry): string | undefined {
165
+ // Direct string path - return as-is
166
+ if (typeof entry === 'string') {
167
+ return entry;
168
+ }
169
+
170
+ // Conditional exports object - evaluate conditions in priority order
171
+ if (typeof entry === 'object' && entry !== null) {
172
+ for (const condition of EXPORT_CONDITION_PRIORITY) {
173
+ const conditionValue = entry[condition];
174
+ if (conditionValue !== undefined) {
175
+ // Recursively resolve nested conditions
176
+ const result = resolveExportConditions(conditionValue);
177
+ if (result) {
178
+ return result;
179
+ }
180
+ }
181
+ }
182
+ }
183
+
184
+ return undefined;
185
+ }
186
+
187
+ // ============================================================================
188
+ // Node Modules Resolution
189
+ // ============================================================================
190
+
191
+ /**
192
+ * Resolves a bare import (e.g., "convex/server", "react") to an absolute file path
193
+ * in the VFS node_modules directory.
194
+ *
195
+ * ## Why resolve from VFS instead of marking as external?
196
+ *
197
+ * We resolve modules from the VFS node_modules directory instead of marking them
198
+ * as external because:
199
+ *
200
+ * 1. **Browser bundling**: In the browser environment, we need to bundle all
201
+ * dependencies since there's no Node.js runtime to resolve modules at runtime.
202
+ *
203
+ * 2. **VFS isolation**: The virtual file system contains a snapshot of dependencies
204
+ * that may differ from what's available in the host environment.
205
+ *
206
+ * 3. **Consistent builds**: By resolving from VFS, we ensure builds are
207
+ * reproducible regardless of the host machine's node_modules.
208
+ *
209
+ * ## Resolution Algorithm
210
+ *
211
+ * 1. Parse the import path to extract the package name and subpath
212
+ * - Scoped packages: "@scope/pkg/sub" -> name="@scope/pkg", subpath="sub"
213
+ * - Regular packages: "pkg/sub" -> name="pkg", subpath="sub"
214
+ *
215
+ * 2. Check if the package exists in /project/node_modules/{name}
216
+ *
217
+ * 3. Read the package.json and resolve the entry point:
218
+ * a. For subpath imports (e.g., "convex/server"):
219
+ * - Check exports map for "./{subpath}" key
220
+ * - Fall back to direct file path resolution
221
+ * b. For main imports (e.g., "convex"):
222
+ * - Check exports map for "." key
223
+ * - Fall back to "module" or "main" fields
224
+ *
225
+ * 4. Verify the resolved file exists with supported extensions
226
+ *
227
+ * @param vfs - The virtual file system instance
228
+ * @param importPath - The bare import path (e.g., "convex/server", "react")
229
+ * @param extensions - File extensions to try when resolving (e.g., ['', '.ts', '.js'])
230
+ *
231
+ * @returns Resolution result with the absolute path, or null if the module
232
+ * cannot be resolved (should be marked as external)
233
+ */
234
+ function resolveNodeModuleImport(
235
+ vfs: VirtualFS,
236
+ importPath: string,
237
+ extensions: string[]
238
+ ): NodeModuleResolution | null {
239
+ // Parse the import path to extract package name and subpath
240
+ // Scoped packages: "@scope/pkg/sub" -> ["@scope", "pkg", "sub"]
241
+ // Regular packages: "pkg/sub" -> ["pkg", "sub"]
242
+ const pathParts = importPath.split('/');
243
+ const isScoped = pathParts[0].startsWith('@');
244
+
245
+ const moduleName = isScoped
246
+ ? pathParts.slice(0, 2).join('/') // "@scope/pkg"
247
+ : pathParts[0]; // "pkg"
248
+
249
+ const subPath = isScoped
250
+ ? pathParts.slice(2).join('/') // Everything after "@scope/pkg/"
251
+ : pathParts.slice(1).join('/'); // Everything after "pkg/"
252
+
253
+ // Check if the package exists in VFS node_modules
254
+ const nodeModulesBase = '/project/node_modules/' + moduleName;
255
+
256
+ if (!vfs.existsSync(nodeModulesBase)) {
257
+ return null;
258
+ }
259
+
260
+ // Read package.json to determine the entry point
261
+ const packageJsonPath = nodeModulesBase + '/package.json';
262
+ if (!vfs.existsSync(packageJsonPath)) {
263
+ return null;
264
+ }
265
+
266
+ try {
267
+ const packageJsonContent = vfs.readFileSync(packageJsonPath, 'utf8');
268
+ const packageJson = JSON.parse(packageJsonContent) as {
269
+ exports?: Record<string, ExportEntry> | ExportEntry;
270
+ module?: string;
271
+ main?: string;
272
+ };
273
+
274
+ let resolvedPath: string | null = null;
275
+
276
+ if (subPath) {
277
+ // Importing a subpath like "convex/server"
278
+ resolvedPath = resolveSubpathImport(
279
+ vfs,
280
+ packageJson,
281
+ nodeModulesBase,
282
+ subPath,
283
+ extensions
284
+ );
285
+ } else {
286
+ // Importing the main module like "convex"
287
+ resolvedPath = resolveMainImport(
288
+ vfs,
289
+ packageJson,
290
+ nodeModulesBase,
291
+ extensions
292
+ );
293
+ }
294
+
295
+ if (resolvedPath) {
296
+ return { path: resolvedPath, pluginData: { fromVFS: true } };
297
+ }
298
+ } catch {
299
+ // Failed to read package.json, fall through to return null
300
+ }
301
+
302
+ return null;
303
+ }
304
+
305
+ /**
306
+ * Resolves a subpath import (e.g., "convex/server" -> "./server" subpath).
307
+ *
308
+ * Resolution order:
309
+ * 1. Check exports map for "./{subpath}" key with condition resolution
310
+ * 2. Fall back to direct file path resolution with extensions
311
+ */
312
+ function resolveSubpathImport(
313
+ vfs: VirtualFS,
314
+ packageJson: { exports?: Record<string, ExportEntry> | ExportEntry },
315
+ nodeModulesBase: string,
316
+ subPath: string,
317
+ extensions: string[]
318
+ ): string | null {
319
+ // Try exports map first
320
+ if (packageJson.exports && typeof packageJson.exports === 'object') {
321
+ const exportKey = './' + subPath;
322
+ const exportsMap = packageJson.exports as Record<string, ExportEntry>;
323
+ const exportEntry = exportsMap[exportKey];
324
+
325
+ if (exportEntry) {
326
+ const exportPath = resolveExportConditions(exportEntry);
327
+ if (exportPath) {
328
+ const resolvedPath = nodeModulesBase + '/' + exportPath.replace(/^\.\//, '');
329
+ const foundPath = findVFSFile(vfs, resolvedPath, ['', '.js', '.ts', '.mjs']);
330
+ if (foundPath) {
331
+ return foundPath;
332
+ }
333
+ }
334
+ }
335
+ }
336
+
337
+ // Fall back to direct path resolution
338
+ const directPath = nodeModulesBase + '/' + subPath;
339
+ return findVFSFile(vfs, directPath, extensions);
340
+ }
341
+
342
+ /**
343
+ * Resolves the main entry point of a package (e.g., "convex" without subpath).
344
+ *
345
+ * Resolution order:
346
+ * 1. Check exports map "." key with condition resolution
347
+ * 2. Fall back to "module" field (ESM entry)
348
+ * 3. Fall back to "main" field (CJS entry)
349
+ * 4. Default to "index.js"
350
+ */
351
+ function resolveMainImport(
352
+ vfs: VirtualFS,
353
+ packageJson: {
354
+ exports?: Record<string, ExportEntry> | ExportEntry;
355
+ module?: string;
356
+ main?: string;
357
+ },
358
+ nodeModulesBase: string,
359
+ extensions: string[]
360
+ ): string | null {
361
+ // Try exports map first
362
+ if (packageJson.exports) {
363
+ // The main export can be at "." key or be the exports value itself
364
+ const mainExport = typeof packageJson.exports === 'object' && !Array.isArray(packageJson.exports)
365
+ ? (packageJson.exports['.'] || packageJson.exports)
366
+ : packageJson.exports;
367
+
368
+ const exportPath = resolveExportConditions(mainExport as ExportEntry);
369
+ if (exportPath) {
370
+ const resolvedPath = nodeModulesBase + '/' + exportPath.replace(/^\.\//, '');
371
+ const foundPath = findVFSFile(vfs, resolvedPath, ['', '.js', '.ts', '.mjs']);
372
+ if (foundPath) {
373
+ return foundPath;
374
+ }
375
+ }
376
+ }
377
+
378
+ // Fall back to module/main fields
379
+ // Prefer "module" (ESM) over "main" (CJS) for better tree-shaking
380
+ const mainField = packageJson.module || packageJson.main || 'index.js';
381
+ const resolvedPath = nodeModulesBase + '/' + mainField.replace(/^\.\//, '');
382
+ return findVFSFile(vfs, resolvedPath, extensions);
383
+ }
384
+
385
+ // ============================================================================
386
+ // Module State
387
+ // ============================================================================
388
+
389
+ // State
390
+ let esbuildInstance: typeof import('esbuild-wasm') | null = null;
391
+ let initPromise: Promise<void> | null = null;
392
+ let wasmURL = 'https://unpkg.com/esbuild-wasm@0.20.0/esbuild.wasm';
393
+ let globalVFS: VirtualFS | null = null;
394
+
395
+ /**
396
+ * Set the VirtualFS instance for file access
397
+ */
398
+ export function setVFS(vfs: VirtualFS): void {
399
+ globalVFS = vfs;
400
+ }
401
+
402
+ /**
403
+ * Set the URL for the esbuild WASM file
404
+ */
405
+ export function setWasmURL(url: string): void {
406
+ wasmURL = url;
407
+ }
408
+
409
+ /**
410
+ * Initialize esbuild-wasm
411
+ * Must be called before using transform or build
412
+ */
413
+ export async function initialize(options?: { wasmURL?: string }): Promise<void> {
414
+ if (esbuildInstance) {
415
+ return; // Already initialized
416
+ }
417
+
418
+ // Check for shared esbuild instance from transform.ts
419
+ if (typeof window !== 'undefined' && window.__esbuild) {
420
+ esbuildInstance = window.__esbuild;
421
+ return;
422
+ }
423
+
424
+ // Wait for any in-progress initialization from transform.ts
425
+ if (typeof window !== 'undefined' && window.__esbuildInitPromise) {
426
+ await window.__esbuildInitPromise;
427
+ if (window.__esbuild) {
428
+ esbuildInstance = window.__esbuild;
429
+ return;
430
+ }
431
+ }
432
+
433
+ if (initPromise) {
434
+ return initPromise; // Our initialization in progress
435
+ }
436
+
437
+ initPromise = (async () => {
438
+ try {
439
+ // Dynamically import esbuild-wasm from CDN
440
+ const esbuild = await import(
441
+ /* @vite-ignore */
442
+ 'https://unpkg.com/esbuild-wasm@0.20.0/esm/browser.min.js'
443
+ );
444
+
445
+ await esbuild.initialize({
446
+ wasmURL: options?.wasmURL || wasmURL,
447
+ });
448
+
449
+ esbuildInstance = esbuild;
450
+ } catch (error) {
451
+ initPromise = null;
452
+ throw new Error(`Failed to initialize esbuild-wasm: ${error}`);
453
+ }
454
+ })();
455
+
456
+ return initPromise;
457
+ }
458
+
459
+ /**
460
+ * Check if esbuild is initialized
461
+ */
462
+ export function isInitialized(): boolean {
463
+ return esbuildInstance !== null;
464
+ }
465
+
466
+ // ============================================================================
467
+ // Transform API
468
+ // ============================================================================
469
+
470
+ /**
471
+ * Transform code using esbuild
472
+ */
473
+ export async function transform(
474
+ code: string,
475
+ options?: TransformOptions
476
+ ): Promise<TransformResult> {
477
+ if (!esbuildInstance) {
478
+ await initialize();
479
+ }
480
+
481
+ if (!esbuildInstance) {
482
+ throw new Error('esbuild not initialized');
483
+ }
484
+
485
+ return esbuildInstance.transform(code, options);
486
+ }
487
+
488
+ /**
489
+ * Transform code synchronously (requires prior initialization)
490
+ */
491
+ export function transformSync(
492
+ code: string,
493
+ options?: TransformOptions
494
+ ): TransformResult {
495
+ if (!esbuildInstance) {
496
+ throw new Error('esbuild not initialized. Call initialize() first.');
497
+ }
498
+
499
+ // esbuild-wasm doesn't have sync API in browser, so we throw
500
+ throw new Error('transformSync is not available in browser. Use transform() instead.');
501
+ }
502
+
503
+ /**
504
+ * Transform ESM to CJS
505
+ */
506
+ export async function transformToCommonJS(
507
+ code: string,
508
+ options?: { loader?: TransformOptions['loader'] }
509
+ ): Promise<string> {
510
+ const result = await transform(code, {
511
+ loader: options?.loader || 'js',
512
+ format: 'cjs',
513
+ target: 'es2020',
514
+ });
515
+
516
+ return result.code;
517
+ }
518
+
519
+ // ============================================================================
520
+ // VFS Path Resolution Helpers
521
+ // ============================================================================
522
+
523
+ /**
524
+ * Apply path remapping for VFS (e.g., /convex/ -> /project/convex/)
525
+ * This keeps the original path for esbuild output naming but finds the actual file.
526
+ */
527
+ function remapVFSPath(path: string): string {
528
+ // Remap /convex/ to /project/convex/
529
+ if (path === '/convex' || path.startsWith('/convex/')) {
530
+ return '/project' + path;
531
+ }
532
+ return path;
533
+ }
534
+
535
+ /**
536
+ * Check if file exists at path or remapped path
537
+ * Returns the original path if found (to preserve output naming)
538
+ */
539
+ function findVFSFile(vfs: VirtualFS, originalPath: string, extensions: string[]): string | null {
540
+ for (const ext of extensions) {
541
+ const pathWithExt = originalPath + ext;
542
+ // First check original path
543
+ if (vfs.existsSync(pathWithExt)) {
544
+ return pathWithExt;
545
+ }
546
+ // Then try remapped path
547
+ const remapped = remapVFSPath(pathWithExt);
548
+ if (remapped !== pathWithExt && vfs.existsSync(remapped)) {
549
+ // Return original path (not remapped) to preserve esbuild output naming
550
+ return pathWithExt;
551
+ }
552
+ }
553
+ return null;
554
+ }
555
+
556
+ // ============================================================================
557
+ // VFS Plugin for esbuild
558
+ // ============================================================================
559
+
560
+ /**
561
+ * Create a VFS plugin for esbuild to read files from VirtualFS.
562
+ *
563
+ * This plugin enables esbuild to resolve and load files from the virtual file system,
564
+ * which is essential for browser-based bundling where we don't have access to the
565
+ * real file system.
566
+ *
567
+ * The plugin handles three types of imports:
568
+ * 1. Absolute paths (/project/src/file.ts)
569
+ * 2. Relative paths (./file.ts, ../file.ts)
570
+ * 3. Bare imports (convex/server, react)
571
+ */
572
+ function createVFSPlugin(): unknown {
573
+ if (!globalVFS) {
574
+ return null;
575
+ }
576
+
577
+ const vfs = globalVFS;
578
+
579
+ return {
580
+ name: 'vfs-loader',
581
+ setup(build: unknown) {
582
+ const b = build as {
583
+ onResolve: (options: { filter: RegExp; namespace?: string }, callback: (args: { path: string; importer: string; kind: string }) => unknown) => void;
584
+ onLoad: (options: { filter: RegExp; namespace?: string }, callback: (args: { path: string }) => unknown) => void;
585
+ };
586
+
587
+ // Resolve file paths - handles both imports and entry points
588
+ b.onResolve({ filter: /.*/ }, (args: { path: string; importer: string }) => {
589
+ const { path: importPath, importer } = args;
590
+
591
+ // Skip external modules (node_modules, bare imports)
592
+ if (importPath.startsWith('node_modules/')) {
593
+ return { external: true };
594
+ }
595
+
596
+ const extensions = ['', '.ts', '.tsx', '.js', '.jsx', '.json'];
597
+
598
+ // Absolute paths - check if file exists in VFS (or remapped location)
599
+ if (importPath.startsWith('/')) {
600
+ const foundPath = findVFSFile(vfs, importPath, extensions);
601
+ if (foundPath) {
602
+ return { path: foundPath, pluginData: { fromVFS: true } };
603
+ }
604
+ // File not found
605
+ return { external: true };
606
+ }
607
+
608
+ // Relative paths
609
+ if (importPath.startsWith('.')) {
610
+ let resolved = importPath;
611
+ if (importer) {
612
+ const importerDir = importer.substring(0, importer.lastIndexOf('/'));
613
+ resolved = importerDir + '/' + importPath;
614
+ }
615
+ // Normalize path
616
+ const parts = resolved.split('/').filter(Boolean);
617
+ const normalized: string[] = [];
618
+ for (const part of parts) {
619
+ if (part === '..') {
620
+ normalized.pop();
621
+ } else if (part !== '.') {
622
+ normalized.push(part);
623
+ }
624
+ }
625
+ resolved = '/' + normalized.join('/');
626
+
627
+ // Try to find the file with various extensions
628
+ const foundPath = findVFSFile(vfs, resolved, extensions);
629
+ if (foundPath) {
630
+ return { path: foundPath, pluginData: { fromVFS: true } };
631
+ }
632
+
633
+ // Try index files
634
+ for (const ext of ['.ts', '.tsx', '.js', '.jsx']) {
635
+ const indexPath = resolved + '/index' + ext;
636
+ const foundIndex = findVFSFile(vfs, indexPath, ['']);
637
+ if (foundIndex) {
638
+ return { path: foundIndex, pluginData: { fromVFS: true } };
639
+ }
640
+ }
641
+ }
642
+
643
+ // Bare imports (no ./ or ../ or /) - resolve from node_modules in VFS
644
+ // See resolveNodeModuleImport() JSDoc for why we resolve from VFS instead of
645
+ // marking as external (browser bundling, VFS isolation, consistent builds)
646
+ const resolution = resolveNodeModuleImport(vfs, importPath, extensions);
647
+ if (resolution) {
648
+ return resolution;
649
+ }
650
+
651
+ // Could not resolve from node_modules, treat as external
652
+ return { external: true };
653
+ });
654
+
655
+ // Load file contents from VFS
656
+ // Apply path remapping when reading to find the actual file
657
+ b.onLoad({ filter: /^\/.*/ }, (args: { path: string; pluginData?: { fromVFS?: boolean } }) => {
658
+ // Only handle files that were resolved by our plugin
659
+ if (!args.pluginData?.fromVFS) {
660
+ return null; // Let other loaders handle it
661
+ }
662
+ try {
663
+ // Try original path first, then remapped path
664
+ let contents: string;
665
+ const originalPath = args.path;
666
+ const remappedPath = remapVFSPath(originalPath);
667
+
668
+ if (vfs.existsSync(originalPath)) {
669
+ contents = vfs.readFileSync(originalPath, 'utf8');
670
+ } else if (remappedPath !== originalPath && vfs.existsSync(remappedPath)) {
671
+ contents = vfs.readFileSync(remappedPath, 'utf8');
672
+ } else {
673
+ throw new Error(`File not found: ${originalPath} (tried ${remappedPath})`);
674
+ }
675
+
676
+ const ext = originalPath.substring(originalPath.lastIndexOf('.'));
677
+ let loader: 'ts' | 'tsx' | 'js' | 'jsx' | 'json' = 'ts';
678
+ if (ext === '.tsx') loader = 'tsx';
679
+ else if (ext === '.js') loader = 'js';
680
+ else if (ext === '.jsx') loader = 'jsx';
681
+ else if (ext === '.json') loader = 'json';
682
+
683
+ return { contents, loader };
684
+ } catch (err) {
685
+ return { errors: [{ text: `Failed to load ${args.path}: ${err}` }] };
686
+ }
687
+ });
688
+ },
689
+ };
690
+ }
691
+
692
+ // ============================================================================
693
+ // Build API
694
+ // ============================================================================
695
+
696
+ /**
697
+ * Build/bundle code (limited support in browser)
698
+ */
699
+ export async function build(options: BuildOptions): Promise<BuildResult> {
700
+ if (!esbuildInstance) {
701
+ await initialize();
702
+ }
703
+
704
+ if (!esbuildInstance) {
705
+ throw new Error('esbuild not initialized');
706
+ }
707
+
708
+ // Add VFS plugin if VFS is available
709
+ const vfsPlugin = createVFSPlugin();
710
+ const plugins = [...(options.plugins || [])];
711
+ if (vfsPlugin) {
712
+ plugins.unshift(vfsPlugin);
713
+ }
714
+
715
+ // Resolve entry points to absolute paths (but DON'T remap /convex/ to /project/convex/)
716
+ // The path remapping happens in the VFS plugin's onLoad handler instead.
717
+ // This preserves the original paths for esbuild's output file naming.
718
+ let entryPoints = options.entryPoints;
719
+ if (entryPoints && globalVFS) {
720
+ const absWorkingDir = options.absWorkingDir || '/';
721
+ entryPoints = entryPoints.map(ep => {
722
+ // Handle paths that came from previous builds with vfs: namespace prefix
723
+ if (ep.includes('vfs:')) {
724
+ const vfsIndex = ep.indexOf('vfs:');
725
+ ep = ep.substring(vfsIndex + 4);
726
+ }
727
+
728
+ // If already absolute, use as-is
729
+ if (ep.startsWith('/')) {
730
+ return ep;
731
+ }
732
+
733
+ if (ep.startsWith('./')) {
734
+ // Join with absWorkingDir but DO NOT remap paths
735
+ const base = absWorkingDir.endsWith('/') ? absWorkingDir.slice(0, -1) : absWorkingDir;
736
+ const relative = ep.slice(2);
737
+ const resolved = base + '/' + relative;
738
+ return resolved;
739
+ }
740
+ if (ep.startsWith('../')) {
741
+ const base = absWorkingDir.endsWith('/') ? absWorkingDir.slice(0, -1) : absWorkingDir;
742
+ const parts = base.split('/').filter(Boolean);
743
+ parts.pop();
744
+ const relative = ep.slice(3);
745
+ const resolved = '/' + parts.join('/') + '/' + relative;
746
+ return resolved;
747
+ }
748
+ return ep;
749
+ });
750
+ }
751
+
752
+ // In browser, we need write: false to get outputFiles
753
+ const result = await esbuildInstance.build({
754
+ ...options,
755
+ entryPoints,
756
+ plugins,
757
+ write: false,
758
+ }) as BuildResult;
759
+
760
+ // Strip 'vfs:' namespace prefix from output file paths
761
+ // The namespace prefix causes issues when the CLI uses these paths
762
+ if (result.outputFiles) {
763
+ for (const file of result.outputFiles) {
764
+ if (file.path.includes('vfs:')) {
765
+ file.path = file.path.replace(/vfs:/g, '');
766
+ }
767
+ }
768
+ }
769
+
770
+ return result;
771
+ }
772
+
773
+ /**
774
+ * Build synchronously (not supported in browser, throws error)
775
+ */
776
+ export function buildSync(_options: BuildOptions): BuildResult {
777
+ throw new Error('buildSync is not available in browser. Use build() instead.');
778
+ }
779
+
780
+ /**
781
+ * Get the esbuild version
782
+ */
783
+ export function version(): string {
784
+ return '0.20.0'; // Version of esbuild-wasm we're using
785
+ }
786
+
787
+ // Context API (minimal stub for compatibility)
788
+ export async function context(_options: BuildOptions): Promise<unknown> {
789
+ throw new Error('esbuild context API is not supported in browser');
790
+ }
791
+
792
+ // Default export matching esbuild's API
793
+ export default {
794
+ initialize,
795
+ isInitialized,
796
+ transform,
797
+ transformSync,
798
+ transformToCommonJS,
799
+ build,
800
+ buildSync,
801
+ context,
802
+ version,
803
+ setWasmURL,
804
+ setVFS,
805
+ };