@swissjs/swite 0.3.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 (163) hide show
  1. package/.changeset/config.json +11 -0
  2. package/.github/workflows/ci.yml +59 -0
  3. package/.github/workflows/publish.yml +50 -0
  4. package/.github/workflows/release.yml +53 -0
  5. package/BUILD_ANALYSIS.md +89 -0
  6. package/BUILD_STRATEGY.md +75 -0
  7. package/CHANGELOG.md +53 -0
  8. package/DIRECTIVE.md +488 -0
  9. package/__tests__/css-extraction.test.ts +261 -0
  10. package/__tests__/css-injection-integration.test.ts +247 -0
  11. package/__tests__/css-middleware.test.ts +191 -0
  12. package/__tests__/import-rewriter-bug.test.ts +135 -0
  13. package/dist/builder.d.ts +36 -0
  14. package/dist/builder.d.ts.map +1 -0
  15. package/dist/builder.js +772 -0
  16. package/dist/cache/compilation-cache.d.ts +33 -0
  17. package/dist/cache/compilation-cache.d.ts.map +1 -0
  18. package/dist/cache/compilation-cache.js +130 -0
  19. package/dist/cli.d.ts +3 -0
  20. package/dist/cli.d.ts.map +1 -0
  21. package/dist/cli.js +85 -0
  22. package/dist/config-loader.d.ts +8 -0
  23. package/dist/config-loader.d.ts.map +1 -0
  24. package/dist/config-loader.js +40 -0
  25. package/dist/config.d.ts +29 -0
  26. package/dist/config.d.ts.map +1 -0
  27. package/dist/config.js +7 -0
  28. package/dist/dev/pythonDevManager.d.ts +12 -0
  29. package/dist/dev/pythonDevManager.d.ts.map +1 -0
  30. package/dist/dev/pythonDevManager.js +85 -0
  31. package/dist/env.d.ts +19 -0
  32. package/dist/env.d.ts.map +1 -0
  33. package/dist/env.js +112 -0
  34. package/dist/handlers/base-handler.d.ts +21 -0
  35. package/dist/handlers/base-handler.d.ts.map +1 -0
  36. package/dist/handlers/base-handler.js +38 -0
  37. package/dist/handlers/js-handler.d.ts +10 -0
  38. package/dist/handlers/js-handler.d.ts.map +1 -0
  39. package/dist/handlers/js-handler.js +87 -0
  40. package/dist/handlers/mjs-handler.d.ts +8 -0
  41. package/dist/handlers/mjs-handler.d.ts.map +1 -0
  42. package/dist/handlers/mjs-handler.js +44 -0
  43. package/dist/handlers/node-module-handler.d.ts +16 -0
  44. package/dist/handlers/node-module-handler.d.ts.map +1 -0
  45. package/dist/handlers/node-module-handler.js +267 -0
  46. package/dist/handlers/ts-handler.d.ts +11 -0
  47. package/dist/handlers/ts-handler.d.ts.map +1 -0
  48. package/dist/handlers/ts-handler.js +120 -0
  49. package/dist/handlers/ui-handler.d.ts +12 -0
  50. package/dist/handlers/ui-handler.d.ts.map +1 -0
  51. package/dist/handlers/ui-handler.js +182 -0
  52. package/dist/handlers/uix-handler.d.ts +12 -0
  53. package/dist/handlers/uix-handler.d.ts.map +1 -0
  54. package/dist/handlers/uix-handler.js +135 -0
  55. package/dist/hmr.d.ts +20 -0
  56. package/dist/hmr.d.ts.map +1 -0
  57. package/dist/hmr.js +265 -0
  58. package/dist/import-rewriter.d.ts +3 -0
  59. package/dist/import-rewriter.d.ts.map +1 -0
  60. package/dist/import-rewriter.js +351 -0
  61. package/dist/index.d.ts +14 -0
  62. package/dist/index.d.ts.map +1 -0
  63. package/dist/index.js +13 -0
  64. package/dist/middleware/hmr-routes.d.ts +12 -0
  65. package/dist/middleware/hmr-routes.d.ts.map +1 -0
  66. package/dist/middleware/hmr-routes.js +97 -0
  67. package/dist/middleware/middleware-setup.d.ts +23 -0
  68. package/dist/middleware/middleware-setup.d.ts.map +1 -0
  69. package/dist/middleware/middleware-setup.js +596 -0
  70. package/dist/middleware/static-files.d.ts +15 -0
  71. package/dist/middleware/static-files.d.ts.map +1 -0
  72. package/dist/middleware/static-files.js +585 -0
  73. package/dist/proxy/SwiteProxyError.d.ts +6 -0
  74. package/dist/proxy/SwiteProxyError.d.ts.map +1 -0
  75. package/dist/proxy/SwiteProxyError.js +9 -0
  76. package/dist/proxy/proxyToPython.d.ts +28 -0
  77. package/dist/proxy/proxyToPython.d.ts.map +1 -0
  78. package/dist/proxy/proxyToPython.js +66 -0
  79. package/dist/resolver/bare-import-resolver.d.ts +9 -0
  80. package/dist/resolver/bare-import-resolver.d.ts.map +1 -0
  81. package/dist/resolver/bare-import-resolver.js +363 -0
  82. package/dist/resolver/symlink-registry.d.ts +13 -0
  83. package/dist/resolver/symlink-registry.d.ts.map +1 -0
  84. package/dist/resolver/symlink-registry.js +98 -0
  85. package/dist/resolver/url-resolver.d.ts +11 -0
  86. package/dist/resolver/url-resolver.d.ts.map +1 -0
  87. package/dist/resolver/url-resolver.js +268 -0
  88. package/dist/resolver/workspace-package-resolver.d.ts +10 -0
  89. package/dist/resolver/workspace-package-resolver.d.ts.map +1 -0
  90. package/dist/resolver/workspace-package-resolver.js +185 -0
  91. package/dist/resolver.d.ts +17 -0
  92. package/dist/resolver.d.ts.map +1 -0
  93. package/dist/resolver.js +191 -0
  94. package/dist/router/file-router.d.ts +19 -0
  95. package/dist/router/file-router.d.ts.map +1 -0
  96. package/dist/router/file-router.js +114 -0
  97. package/dist/server.d.ts +22 -0
  98. package/dist/server.d.ts.map +1 -0
  99. package/dist/server.js +122 -0
  100. package/dist/utils/cdn-fallback.d.ts +14 -0
  101. package/dist/utils/cdn-fallback.d.ts.map +1 -0
  102. package/dist/utils/cdn-fallback.js +36 -0
  103. package/dist/utils/file-path-resolver.d.ts +9 -0
  104. package/dist/utils/file-path-resolver.d.ts.map +1 -0
  105. package/dist/utils/file-path-resolver.js +187 -0
  106. package/dist/utils/generate-import-map-cli.d.ts +3 -0
  107. package/dist/utils/generate-import-map-cli.d.ts.map +1 -0
  108. package/dist/utils/generate-import-map-cli.js +32 -0
  109. package/dist/utils/generate-import-map.d.ts +21 -0
  110. package/dist/utils/generate-import-map.d.ts.map +1 -0
  111. package/dist/utils/generate-import-map.js +119 -0
  112. package/dist/utils/package-finder.d.ts +24 -0
  113. package/dist/utils/package-finder.d.ts.map +1 -0
  114. package/dist/utils/package-finder.js +161 -0
  115. package/dist/utils/package-registry.d.ts +36 -0
  116. package/dist/utils/package-registry.d.ts.map +1 -0
  117. package/dist/utils/package-registry.js +159 -0
  118. package/dist/utils/workspace.d.ts +6 -0
  119. package/dist/utils/workspace.d.ts.map +1 -0
  120. package/dist/utils/workspace.js +65 -0
  121. package/docs/IMPORT_REWRITING.md +164 -0
  122. package/docs/IMPORT_REWRITING_TROUBLESHOOTING.md +139 -0
  123. package/docs/PATH_RESOLUTION_GUIDE.md +221 -0
  124. package/package.json +49 -0
  125. package/src/adapters/proxy/SwiteProxyError.ts +12 -0
  126. package/src/adapters/proxy/proxyToPython.ts +88 -0
  127. package/src/build-engine/builder.ts +960 -0
  128. package/src/cli.ts +109 -0
  129. package/src/config/config-loader.ts +46 -0
  130. package/src/config/config.ts +34 -0
  131. package/src/config/env.ts +98 -0
  132. package/src/dev-engine/handlers/base-handler.ts +68 -0
  133. package/src/dev-engine/handlers/js-handler.ts +134 -0
  134. package/src/dev-engine/handlers/mjs-handler.ts +65 -0
  135. package/src/dev-engine/handlers/node-module-handler.ts +339 -0
  136. package/src/dev-engine/handlers/ts-handler.ts +143 -0
  137. package/src/dev-engine/handlers/ui-handler.ts +105 -0
  138. package/src/dev-engine/handlers/uix-handler.ts +90 -0
  139. package/src/dev-engine/hmr/hmr-client-template.ts +122 -0
  140. package/src/dev-engine/hmr/hmr.ts +173 -0
  141. package/src/dev-engine/middleware/hmr-routes.ts +120 -0
  142. package/src/dev-engine/middleware/middleware-setup.ts +351 -0
  143. package/src/dev-engine/middleware/static-files.ts +728 -0
  144. package/src/dev-engine/pythonDevManager.ts +116 -0
  145. package/src/dev-engine/router/file-router.ts +164 -0
  146. package/src/dev-engine/server.ts +152 -0
  147. package/src/index.ts +26 -0
  148. package/src/internal/cache/compilation-cache.ts +182 -0
  149. package/src/internal/generate-import-map-cli.ts +40 -0
  150. package/src/internal/generate-import-map.ts +154 -0
  151. package/src/kernel/package-finder.ts +164 -0
  152. package/src/kernel/package-registry.ts +198 -0
  153. package/src/kernel/workspace.ts +62 -0
  154. package/src/resolution/bare-import-resolver.ts +400 -0
  155. package/src/resolution/cdn/cdn-fallback.ts +37 -0
  156. package/src/resolution/path/file-path-resolver.ts +190 -0
  157. package/src/resolution/path/path-fixup.ts +19 -0
  158. package/src/resolution/resolver.ts +198 -0
  159. package/src/resolution/rewriting/import-rewriter.ts +237 -0
  160. package/src/resolution/symlink-registry.ts +114 -0
  161. package/src/resolution/url-resolver.ts +231 -0
  162. package/src/resolution/workspace-package-resolver.ts +94 -0
  163. package/tsconfig.json +37 -0
@@ -0,0 +1,339 @@
1
+ /*
2
+ * Copyright (c) 2024 Themba Mzumara
3
+ * SWITE - SWISS Development Server
4
+ * Licensed under the MIT License.
5
+ */
6
+
7
+ import type { Response } from "express";
8
+ import { promises as fs } from "node:fs";
9
+ import * as path from "node:path";
10
+ import chalk from "chalk";
11
+ import { rewriteImports } from "../../resolution/rewriting/import-rewriter.js";
12
+ import { BaseHandler, type HandlerContext } from "./base-handler.js";
13
+ import { UIHandler } from "./ui-handler.js";
14
+ import { UIXHandler } from "./uix-handler.js";
15
+ import { TSHandler } from "./ts-handler.js";
16
+ import { findWorkspaceRoot } from "../../kernel/workspace.js";
17
+ import { shouldUseCdnFallback } from "../../resolution/cdn/cdn-fallback.js";
18
+
19
+ export class NodeModuleHandler extends BaseHandler {
20
+ private uiHandler: UIHandler;
21
+ private uixHandler: UIXHandler;
22
+ private tsHandler: TSHandler;
23
+
24
+ constructor(context: HandlerContext) {
25
+ super(context);
26
+ this.uiHandler = new UIHandler(context);
27
+ this.uixHandler = new UIXHandler(context);
28
+ this.tsHandler = new TSHandler(context);
29
+ }
30
+
31
+ async handle(url: string, res: Response): Promise<void> {
32
+ try {
33
+ // Special case: reflect-metadata/reflect.js -> Reflect.js (case fix)
34
+ if (url.includes("/reflect-metadata/reflect.js")) {
35
+ url = url.replace("/reflect.js", "/Reflect.js");
36
+ }
37
+
38
+ // Handle node_modules paths - try multiple locations
39
+ // URL is like /node_modules/reflect-metadata/Reflect.js
40
+ // We need to remove the leading / and join with the appropriate root
41
+ const urlPath = url.startsWith("/") ? url.slice(1) : url;
42
+ let filePath: string | null = null;
43
+ const workspaceRoot =
44
+ this.context.workspaceRoot ||
45
+ (await findWorkspaceRoot(this.context.root));
46
+
47
+ console.log(chalk.blue(`[node_modules] Processing: ${url}`));
48
+ console.log(chalk.blue(`[node_modules] App root: ${this.context.root}`));
49
+ console.log(
50
+ chalk.blue(`[node_modules] Workspace root: ${workspaceRoot || "none"}`),
51
+ );
52
+
53
+ // Walk up directory tree from app root to find node_modules at any level
54
+ // (handles pnpm isolated AND hoisted workspace layouts)
55
+ {
56
+ let current = path.resolve(this.context.root);
57
+ const visited = new Set<string>();
58
+ for (let i = 0; i < 8; i++) {
59
+ const candidate = path.join(current, urlPath);
60
+ if (!visited.has(candidate)) {
61
+ visited.add(candidate);
62
+ console.log(chalk.blue(`[node_modules] Trying path: ${candidate}`));
63
+ try {
64
+ const resolvedPath = await fs.realpath(candidate);
65
+ console.log(chalk.blue(`[node_modules] Resolved to: ${resolvedPath}`));
66
+ await fs.access(resolvedPath);
67
+ filePath = resolvedPath;
68
+ console.log(chalk.green(`[node_modules] ✓ Found: ${urlPath}`));
69
+ break;
70
+ } catch (err) {
71
+ console.log(chalk.yellow(`[node_modules] Path failed: ${err instanceof Error ? err.message : String(err)}`));
72
+ }
73
+ }
74
+ const parent = path.dirname(current);
75
+ if (parent === current) break;
76
+ current = parent;
77
+ }
78
+ }
79
+ if (!filePath) {
80
+ // Try workspace root node_modules
81
+ if (workspaceRoot) {
82
+ const workspaceNodeModulesPath = path.join(workspaceRoot, urlPath);
83
+ console.log(
84
+ chalk.blue(
85
+ `[node_modules] Trying workspace path: ${workspaceNodeModulesPath}`,
86
+ ),
87
+ );
88
+ try {
89
+ // Try to resolve symlinks first (realpath works even if path is a symlink)
90
+ const resolvedPath = await fs.realpath(workspaceNodeModulesPath);
91
+ console.log(
92
+ chalk.blue(`[node_modules] Resolved to: ${resolvedPath}`),
93
+ );
94
+ // Verify the resolved path exists
95
+ await fs.access(resolvedPath);
96
+ filePath = resolvedPath;
97
+ console.log(
98
+ chalk.green(`[node_modules] ✓ Found in workspace: ${urlPath}`),
99
+ );
100
+ } catch (err2) {
101
+ console.log(
102
+ chalk.yellow(
103
+ `[node_modules] Workspace path failed: ${err2 instanceof Error ? err2.message : String(err2)}`,
104
+ ),
105
+ );
106
+ // Try swiss-lib monorepo node_modules (dynamically found)
107
+ const { findSwissLibMonorepo } = await import("../../kernel/package-finder.js");
108
+ const swissLib = await findSwissLibMonorepo(this.context.root);
109
+ if (swissLib) {
110
+ const swissNodeModulesPath = path.join(swissLib, urlPath);
111
+ console.log(
112
+ chalk.blue(
113
+ `[node_modules] Trying swiss-lib path: ${swissNodeModulesPath}`,
114
+ ),
115
+ );
116
+ try {
117
+ // Try to resolve symlinks first (realpath works even if path is a symlink)
118
+ const resolvedPath = await fs.realpath(swissNodeModulesPath);
119
+ console.log(
120
+ chalk.blue(`[node_modules] Resolved to: ${resolvedPath}`),
121
+ );
122
+ // Verify the resolved path exists
123
+ await fs.access(resolvedPath);
124
+ filePath = resolvedPath;
125
+ console.log(
126
+ chalk.green(
127
+ `[node_modules] ✓ Found in swiss-lib monorepo: ${urlPath}`,
128
+ ),
129
+ );
130
+ } catch (err3) {
131
+ console.log(
132
+ chalk.yellow(
133
+ `[node_modules] swiss-lib path failed: ${err3 instanceof Error ? err3.message : String(err3)}`,
134
+ ),
135
+ );
136
+ // File not found in any location, will trigger case-insensitive search below
137
+ filePath = path.join(this.context.root, urlPath);
138
+ }
139
+ } else {
140
+ // File not found in any location, will trigger case-insensitive search below
141
+ filePath = path.join(this.context.root, urlPath);
142
+ }
143
+ }
144
+ } else {
145
+ filePath = path.join(this.context.root, urlPath);
146
+ }
147
+ }
148
+
149
+ console.log(
150
+ chalk.gray(`[node_modules] Resolving: ${url} -> ${filePath}`),
151
+ );
152
+
153
+ // File path is already resolved from above, no need to resolve again
154
+
155
+ // Check if file exists, if .js doesn't exist try case-insensitive match and alternatives
156
+ try {
157
+ await fs.access(filePath);
158
+ } catch (error) {
159
+ console.log(
160
+ chalk.yellow(
161
+ `[node_modules] File not found at ${filePath}, trying case-insensitive match...`,
162
+ ),
163
+ );
164
+ // File doesn't exist with exact case, try case-insensitive match (for Reflect.js vs reflect.js)
165
+ if (url.endsWith(".js")) {
166
+ const dir = path.dirname(filePath);
167
+ const requestedName = path.basename(filePath);
168
+ try {
169
+ // Resolve directory symlink (for pnpm)
170
+ const resolvedDir = await fs.realpath(dir).catch(() => dir);
171
+ // Check if directory exists first
172
+ await fs.access(resolvedDir);
173
+ const files = await fs.readdir(resolvedDir);
174
+ const caseInsensitiveMatch = files.find(
175
+ (f) => f.toLowerCase() === requestedName.toLowerCase(),
176
+ );
177
+ if (caseInsensitiveMatch) {
178
+ filePath = path.join(resolvedDir, caseInsensitiveMatch);
179
+ console.log(
180
+ chalk.yellow(
181
+ `[node_modules] Case-insensitive match: ${requestedName} -> ${caseInsensitiveMatch}`,
182
+ ),
183
+ );
184
+ // Verify the file exists with the correct case
185
+ await fs.access(filePath);
186
+ // File found, continue to serve it below
187
+ } else {
188
+ throw new Error("No case-insensitive match found");
189
+ }
190
+ } catch {
191
+ // Directory doesn't exist or no case-insensitive match, try alternatives
192
+ console.log(
193
+ chalk.gray(
194
+ `[node_modules] Case-insensitive match failed for ${url}, trying alternatives...`,
195
+ ),
196
+ );
197
+ const basePath = filePath.slice(0, -3); // Remove .js
198
+ const alternatives = [
199
+ {
200
+ ext: ".ts",
201
+ handler: () =>
202
+ this.tsHandler.handle(url.replace(/\.js$/, ".ts"), res),
203
+ },
204
+ {
205
+ ext: ".ui",
206
+ handler: () =>
207
+ this.uiHandler.handle(url.replace(/\.js$/, ".ui"), res),
208
+ },
209
+ {
210
+ ext: ".uix",
211
+ handler: () =>
212
+ this.uixHandler.handle(url.replace(/\.js$/, ".uix"), res),
213
+ },
214
+ ];
215
+
216
+ for (const alt of alternatives) {
217
+ try {
218
+ await fs.access(basePath + alt.ext);
219
+ console.log(
220
+ chalk.yellow(
221
+ `[.js→${alt.ext}] ${url} → ${url.replace(/\.js$/, alt.ext)}`,
222
+ ),
223
+ );
224
+ return await alt.handler();
225
+ } catch {
226
+ // Try next alternative
227
+ }
228
+ }
229
+
230
+ // No alternatives found - redirect to CDN instead of 500
231
+ const cdnRedirect = this.getNodeModuleCdnRedirect(url);
232
+ if (cdnRedirect) {
233
+ console.log(chalk.yellow(`[node_modules] Not found locally, redirecting to CDN: ${cdnRedirect}`));
234
+ res.redirect(302, cdnRedirect);
235
+ return;
236
+ }
237
+ res.status(404).send(`Module not found: ${url}`);
238
+ return;
239
+ }
240
+ } else {
241
+ // Not a .js file and doesn't exist - try CDN redirect or 404
242
+ const cdnRedirect = this.getNodeModuleCdnRedirect(url);
243
+ if (cdnRedirect) {
244
+ res.redirect(302, cdnRedirect);
245
+ return;
246
+ }
247
+ res.status(404).send(`Module not found: ${url}`);
248
+ return;
249
+ }
250
+ }
251
+
252
+ // File exists, process it normally
253
+ // For node_modules files, skip import rewriting - they should work as-is
254
+ // and rewriting can cause issues with package internals
255
+ try {
256
+ console.log(chalk.blue(`[node_modules] Reading file: ${filePath}`));
257
+ const source = await fs.readFile(filePath, "utf-8");
258
+ console.log(
259
+ chalk.green(
260
+ `[node_modules] ✓ File read successfully, length: ${source.length}`,
261
+ ),
262
+ );
263
+
264
+ // Skip import rewriting for node_modules - serve as-is
265
+ // This is safer and faster for third-party packages
266
+ res.setHeader("Content-Type", "application/javascript; charset=utf-8");
267
+ res.send(source);
268
+ console.log(chalk.green(`[node_modules] ✓ Served ${url} successfully`));
269
+ } catch (error) {
270
+ console.error(
271
+ chalk.red(`[node_modules] Error processing ${url} at ${filePath}:`),
272
+ );
273
+ console.error(chalk.red(`[node_modules] Error details:`), error);
274
+ if (error instanceof Error) {
275
+ console.error(chalk.red(`[node_modules] Error stack:`), error.stack);
276
+ }
277
+ throw error;
278
+ }
279
+ } catch (outerError) {
280
+ console.error(chalk.red(`[node_modules] FATAL ERROR handling ${url}:`));
281
+ console.error(
282
+ chalk.red(
283
+ `[node_modules] Error type: ${outerError instanceof Error ? outerError.constructor.name : typeof outerError}`,
284
+ ),
285
+ );
286
+ console.error(
287
+ chalk.red(
288
+ `[node_modules] Error message: ${outerError instanceof Error ? outerError.message : String(outerError)}`,
289
+ ),
290
+ );
291
+ if (outerError instanceof Error && outerError.stack) {
292
+ console.error(chalk.red(`[node_modules] Stack trace:`));
293
+ console.error(outerError.stack);
294
+ }
295
+ // Try CDN redirect before giving up with 500
296
+ const cdnRedirect = this.getNodeModuleCdnRedirect(url);
297
+ if (cdnRedirect) {
298
+ console.log(chalk.yellow(`[node_modules] Error handling locally, redirecting to CDN: ${cdnRedirect}`));
299
+ res.redirect(302, cdnRedirect);
300
+ return;
301
+ }
302
+ res.status(404).setHeader("Content-Type", "text/plain").send(
303
+ `Module not found: ${url}. ${outerError instanceof Error ? outerError.message : String(outerError)}`,
304
+ );
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Get CDN URL for a /node_modules/... request when the file is not found locally.
310
+ * Uses jsDelivr (+esm) for reliable ESM delivery; esm.sh can return 500 for some packages.
311
+ * e.g. /node_modules/reflect-metadata/Reflect.js -> https://cdn.jsdelivr.net/npm/reflect-metadata/+esm
312
+ */
313
+ private getNodeModuleCdnRedirect(url: string): string | null {
314
+ const prefix = "/node_modules/";
315
+ if (!url.startsWith(prefix)) return null;
316
+
317
+ // For nested paths like /node_modules/@scope/pkg/node_modules/dep/file.js,
318
+ // find the LAST node_modules segment and extract the package name from there.
319
+ const lastNodeModulesIdx = url.lastIndexOf("/node_modules/");
320
+ const after = url.slice(lastNodeModulesIdx + prefix.length);
321
+ if (!after) return null;
322
+
323
+ // Extract package name: handle @scope/name and plain-name
324
+ let pkgName: string;
325
+ if (after.startsWith("@")) {
326
+ // Scoped package: need TWO path segments — @scope/name
327
+ const secondSlash = after.indexOf("/", after.indexOf("/") + 1);
328
+ pkgName = secondSlash === -1 ? after : after.slice(0, secondSlash);
329
+ } else {
330
+ const firstSlash = after.indexOf("/");
331
+ pkgName = firstSlash === -1 ? after : after.slice(0, firstSlash);
332
+ }
333
+
334
+ if (!pkgName || pkgName === "." || pkgName === "..") return null;
335
+ if (!shouldUseCdnFallback(pkgName)) return null;
336
+ // jsDelivr +esm serves ESM build; works for reflect-metadata and most npm packages
337
+ return `https://cdn.jsdelivr.net/npm/${pkgName}/+esm`;
338
+ }
339
+ }
@@ -0,0 +1,143 @@
1
+ /*
2
+ * Copyright (c) 2024 Themba Mzumara
3
+ * SWITE - SWISS Development Server
4
+ * Licensed under the MIT License.
5
+ */
6
+
7
+ import type { Response } from "express";
8
+ import { promises as fs } from "node:fs";
9
+ import chalk from "chalk";
10
+ import { rewriteImports } from "../../resolution/rewriting/import-rewriter.js";
11
+ import { inlineEnvReferences } from "../../config/env.js";
12
+ import { compilationCache } from "../../internal/cache/compilation-cache.js";
13
+ import {
14
+ BaseHandler,
15
+ setDevHeaders,
16
+ type HandlerContext,
17
+ } from "./base-handler.js";
18
+
19
+ export class TSHandler extends BaseHandler {
20
+ constructor(context: HandlerContext) {
21
+ super(context);
22
+ }
23
+
24
+ async handle(url: string, res: Response): Promise<void> {
25
+ const filePath = await this.resolveFilePath(url);
26
+ console.log(chalk.gray(`[.ts] ${url}`));
27
+
28
+ // Check if .ts file exists, if not try .ui, .uix
29
+ try {
30
+ await fs.access(filePath);
31
+ } catch {
32
+ // .ts doesn't exist, try alternatives
33
+ const basePath = filePath.slice(0, -3); // Remove .ts
34
+ const alternatives = [
35
+ {
36
+ ext: ".ui",
37
+ url: url.replace(/\.ts$/, ".ui"),
38
+ },
39
+ {
40
+ ext: ".uix",
41
+ url: url.replace(/\.ts$/, ".uix"),
42
+ },
43
+ ];
44
+
45
+ for (const alt of alternatives) {
46
+ try {
47
+ const altPath = basePath + alt.ext;
48
+ await fs.access(altPath);
49
+ console.log(
50
+ chalk.yellow(
51
+ `[.ts→${alt.ext}] ${url} → ${alt.url} (file: ${altPath})`,
52
+ ),
53
+ );
54
+ // Import and use the appropriate handler
55
+ if (alt.ext === ".ui") {
56
+ const { UIHandler } = await import("./ui-handler.js");
57
+ const uiHandler = new UIHandler(this.context);
58
+ return await uiHandler.handle(alt.url, res);
59
+ } else if (alt.ext === ".uix") {
60
+ const { UIXHandler } = await import("./uix-handler.js");
61
+ const uixHandler = new UIXHandler(this.context);
62
+ return await uixHandler.handle(alt.url, res);
63
+ }
64
+ } catch {
65
+ // Try next alternative
66
+ console.log(
67
+ chalk.gray(
68
+ `[.ts→${alt.ext}] ${basePath + alt.ext} not found, trying next...`,
69
+ ),
70
+ );
71
+ }
72
+ }
73
+
74
+ // No alternatives found, throw error
75
+ console.error(
76
+ chalk.red(`[.ts] File not found: ${filePath} (and no alternatives found)`),
77
+ );
78
+ res.status(404).send(`File not found: ${url}`);
79
+ return;
80
+ }
81
+
82
+ // Check cache first
83
+ const cached = await compilationCache.get(
84
+ filePath,
85
+ (compiled) => this.getDependencies(compiled),
86
+ );
87
+ if (cached) {
88
+ setDevHeaders(res);
89
+ res.setHeader("Content-Type", "application/javascript; charset=utf-8");
90
+ res.send(cached);
91
+ return;
92
+ }
93
+
94
+ // Cache miss - compile
95
+ const source = await fs.readFile(filePath, "utf-8");
96
+
97
+ // Use esbuild for fast TS transformation
98
+ const esbuild = await import("esbuild");
99
+ const result = await esbuild.transform(source, {
100
+ loader: "ts",
101
+ format: "esm",
102
+ target: "esnext",
103
+ sourcefile: filePath,
104
+ });
105
+
106
+ const inlined = inlineEnvReferences(result.code, this.context.env);
107
+ const rewritten = await rewriteImports(
108
+ inlined,
109
+ filePath,
110
+ this.context.resolver,
111
+ );
112
+
113
+ await compilationCache.set(
114
+ filePath,
115
+ result.code,
116
+ rewritten,
117
+ (compiled) => this.getDependencies(compiled),
118
+ );
119
+
120
+ // Debug: Check for bare imports after rewriting
121
+ const bareImportPattern =
122
+ /(?:import|from|export).*['"](@[^'"]+\/[^'"]+)(?!\/)[^'"]*['"]/;
123
+ if (bareImportPattern.test(rewritten)) {
124
+ console.log(
125
+ chalk.red(
126
+ `[.ts] ERROR: Bare imports still present after rewriting: ${url}`,
127
+ ),
128
+ );
129
+ const matches = Array.from(
130
+ rewritten.matchAll(
131
+ /(?:import|from|export).*['"](@[^'"]+\/[^'"]+)(?!\/)[^'"]*['"]/g,
132
+ ),
133
+ );
134
+ for (const match of matches.slice(0, 3)) {
135
+ console.log(chalk.red(`[.ts] Unresolved import: ${match[1]}`));
136
+ }
137
+ }
138
+
139
+ setDevHeaders(res);
140
+ res.setHeader("Content-Type", "application/javascript; charset=utf-8");
141
+ res.send(rewritten);
142
+ }
143
+ }
@@ -0,0 +1,105 @@
1
+ /*
2
+ * Copyright (c) 2024 Themba Mzumara
3
+ * SWITE - SWISS Development Server
4
+ * Licensed under the MIT License.
5
+ */
6
+
7
+ import type { Response } from "express";
8
+ import { promises as fs } from "node:fs";
9
+ import { UiCompiler } from "@swissjs/compiler";
10
+ import chalk from "chalk";
11
+ import { rewriteImports } from "../../resolution/rewriting/import-rewriter.js";
12
+ import { inlineEnvReferences } from "../../config/env.js";
13
+ import { compilationCache } from "../../internal/cache/compilation-cache.js";
14
+ import { fixSwissLibPaths } from "../../resolution/path/path-fixup.js";
15
+ import {
16
+ BaseHandler,
17
+ setDevHeaders,
18
+ type HandlerContext,
19
+ } from "./base-handler.js";
20
+
21
+ export class UIHandler extends BaseHandler {
22
+ private compiler = new UiCompiler();
23
+
24
+ constructor(context: HandlerContext) {
25
+ super(context);
26
+ }
27
+
28
+ async handle(url: string, res: Response): Promise<void> {
29
+ const filePath = await this.resolveFilePath(url);
30
+ console.log(chalk.blue(`[.ui] ${url} → ${filePath}`));
31
+
32
+ try {
33
+ await fs.access(filePath);
34
+ } catch {
35
+ console.error(chalk.red(`[.ui] File not found: ${filePath}`));
36
+ throw new Error(`File not found: ${url} (resolved to: ${filePath})`);
37
+ }
38
+
39
+ // Cache hit
40
+ const cached = await compilationCache.get(filePath, (compiled) => this.getDependencies(compiled));
41
+ if (cached) {
42
+ const fixed = fixSwissLibPaths(cached);
43
+ setDevHeaders(res);
44
+ res.setHeader("Content-Type", "application/javascript; charset=utf-8");
45
+ res.setHeader("Content-Length", Buffer.byteLength(fixed, "utf-8"));
46
+ res.end(fixed, "utf-8");
47
+ return;
48
+ }
49
+
50
+ // Cache miss — compile
51
+ const source = await fs.readFile(filePath, "utf-8");
52
+ let compiled = await this.compiler.compileAsync(source, filePath);
53
+
54
+ const esbuild = await import("esbuild");
55
+ const tsResult = await esbuild.transform(compiled, {
56
+ loader: "ts",
57
+ format: "esm",
58
+ target: "esnext",
59
+ sourcefile: filePath,
60
+ });
61
+ compiled = tsResult.code;
62
+
63
+ // Fix compiler-emitted wrong paths before import rewriting
64
+ compiled = fixSwissLibPaths(compiled);
65
+
66
+ // Inline import.meta.env references before import rewriting
67
+ compiled = inlineEnvReferences(compiled, this.context.env);
68
+
69
+ // Strip CSS static-asset imports — they are not ES modules
70
+ compiled = stripCssImports(compiled, url);
71
+
72
+ const bareImportPattern = /(?:import|from|export).*['"](@[^'"]+\/[^'"]+)(?!\/)[^'"]*['"]/;
73
+ if (bareImportPattern.test(compiled)) {
74
+ console.warn(`[.ui] Compiled output contains bare imports: ${url}`);
75
+ }
76
+
77
+ const rewritten = await rewriteImports(compiled, filePath, this.context.resolver);
78
+ const finalCode = fixSwissLibPaths(rewritten);
79
+
80
+ await compilationCache.set(filePath, compiled, finalCode, (c) => this.getDependencies(c));
81
+
82
+ if (bareImportPattern.test(finalCode)) {
83
+ console.error(`[.ui] Bare imports still present after rewriting: ${url}`);
84
+ for (const m of Array.from(rewritten.matchAll(/(?:import|from|export).*['"](@[^'"]+\/[^'"]+)(?!\/)[^'"]*['"]/g)).slice(0, 3)) {
85
+ console.error(`[.ui] Unresolved import: ${m[1]}`);
86
+ }
87
+ }
88
+
89
+ setDevHeaders(res);
90
+ res.setHeader("Content-Type", "application/javascript; charset=utf-8");
91
+ res.setHeader("Content-Length", Buffer.byteLength(finalCode, "utf-8"));
92
+ res.end(finalCode, "utf-8");
93
+ }
94
+ }
95
+
96
+ function stripCssImports(code: string, url: string): string {
97
+ // Single well-ordered pass: static imports first, then dynamic imports
98
+ const before = code;
99
+ code = code.replace(/^[^\S\r\n]*import\s[^'"]*['"][^'"]*\.css['"]\s*;?[^\S\r\n]*$/gm, "");
100
+ code = code.replace(/\bimport\s*\(\s*['"][^'"]*\.css['"]\s*\)/g, "undefined");
101
+ if (before !== code) {
102
+ console.log(chalk.blue(`[.ui] Stripped CSS imports from ${url}`));
103
+ }
104
+ return code;
105
+ }
@@ -0,0 +1,90 @@
1
+ /*
2
+ * Copyright (c) 2024 Themba Mzumara
3
+ * SWITE - SWISS Development Server
4
+ * Licensed under the MIT License.
5
+ */
6
+
7
+ import type { Response } from "express";
8
+ import { promises as fs } from "node:fs";
9
+ import { UiCompiler } from "@swissjs/compiler";
10
+ import chalk from "chalk";
11
+ import { rewriteImports } from "../../resolution/rewriting/import-rewriter.js";
12
+ import { inlineEnvReferences } from "../../config/env.js";
13
+ import { compilationCache } from "../../internal/cache/compilation-cache.js";
14
+ import { fixSwissLibPaths } from "../../resolution/path/path-fixup.js";
15
+ import {
16
+ BaseHandler,
17
+ setDevHeaders,
18
+ type HandlerContext,
19
+ } from "./base-handler.js";
20
+
21
+ export class UIXHandler extends BaseHandler {
22
+ private compiler = new UiCompiler();
23
+
24
+ constructor(context: HandlerContext) {
25
+ super(context);
26
+ }
27
+
28
+ async handle(url: string, res: Response): Promise<void> {
29
+ const filePath = await this.resolveFilePath(url);
30
+ console.log(chalk.blue(`[.uix] ${url}`));
31
+
32
+ // Cache hit
33
+ const cached = await compilationCache.get(filePath, (compiled) => this.getDependencies(compiled));
34
+ if (cached) {
35
+ const fixed = fixSwissLibPaths(cached);
36
+ setDevHeaders(res);
37
+ res.setHeader("Content-Type", "application/javascript; charset=utf-8");
38
+ res.send(fixed);
39
+ return;
40
+ }
41
+
42
+ // Cache miss — compile
43
+ const source = await fs.readFile(filePath, "utf-8");
44
+ let compiled = await this.compiler.compileAsync(source, filePath);
45
+
46
+ const esbuild = await import("esbuild");
47
+ const tsResult = await esbuild.transform(compiled, {
48
+ loader: "ts",
49
+ format: "esm",
50
+ target: "esnext",
51
+ sourcefile: filePath,
52
+ });
53
+ compiled = tsResult.code;
54
+
55
+ // Fix compiler-emitted wrong paths before import rewriting
56
+ compiled = fixSwissLibPaths(compiled);
57
+
58
+ // Inline import.meta.env references before import rewriting
59
+ compiled = inlineEnvReferences(compiled, this.context.env);
60
+
61
+ // Strip CSS static-asset imports — they are not ES modules
62
+ const beforeCss = compiled;
63
+ compiled = compiled.replace(/^[^\S\r\n]*import\s[^'"]*['"][^'"]*\.css['"]\s*;?[^\S\r\n]*$/gm, "");
64
+ compiled = compiled.replace(/\bimport\s*\(\s*['"][^'"]*\.css['"]\s*\)/g, "undefined");
65
+ if (beforeCss !== compiled) {
66
+ console.log(chalk.blue(`[.uix] Stripped CSS imports from ${url}`));
67
+ }
68
+
69
+ const bareImportPattern = /(?:import|from|export).*['"](@[^'"]+\/[^'"]+)(?!\/)[^'"]*['"]/;
70
+ if (bareImportPattern.test(compiled)) {
71
+ console.warn(`[.uix] Compiled output contains bare imports: ${url}`);
72
+ }
73
+
74
+ const rewritten = await rewriteImports(compiled, filePath, this.context.resolver);
75
+ const finalCode = fixSwissLibPaths(rewritten);
76
+
77
+ await compilationCache.set(filePath, compiled, finalCode, (c) => this.getDependencies(c));
78
+
79
+ if (bareImportPattern.test(finalCode)) {
80
+ console.error(`[.uix] Bare imports still present after rewriting: ${url}`);
81
+ for (const m of Array.from(rewritten.matchAll(/(?:import|from|export).*['"](@[^'"]+\/[^'"]+)(?!\/)[^'"]*['"]/g)).slice(0, 3)) {
82
+ console.error(`[.uix] Unresolved import: ${m[1]}`);
83
+ }
84
+ }
85
+
86
+ setDevHeaders(res);
87
+ res.setHeader("Content-Type", "application/javascript; charset=utf-8");
88
+ res.send(finalCode);
89
+ }
90
+ }