@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.
- package/.changeset/config.json +11 -0
- package/.github/workflows/ci.yml +59 -0
- package/.github/workflows/publish.yml +50 -0
- package/.github/workflows/release.yml +53 -0
- package/BUILD_ANALYSIS.md +89 -0
- package/BUILD_STRATEGY.md +75 -0
- package/CHANGELOG.md +53 -0
- package/DIRECTIVE.md +488 -0
- package/__tests__/css-extraction.test.ts +261 -0
- package/__tests__/css-injection-integration.test.ts +247 -0
- package/__tests__/css-middleware.test.ts +191 -0
- package/__tests__/import-rewriter-bug.test.ts +135 -0
- package/dist/builder.d.ts +36 -0
- package/dist/builder.d.ts.map +1 -0
- package/dist/builder.js +772 -0
- package/dist/cache/compilation-cache.d.ts +33 -0
- package/dist/cache/compilation-cache.d.ts.map +1 -0
- package/dist/cache/compilation-cache.js +130 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +85 -0
- package/dist/config-loader.d.ts +8 -0
- package/dist/config-loader.d.ts.map +1 -0
- package/dist/config-loader.js +40 -0
- package/dist/config.d.ts +29 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +7 -0
- package/dist/dev/pythonDevManager.d.ts +12 -0
- package/dist/dev/pythonDevManager.d.ts.map +1 -0
- package/dist/dev/pythonDevManager.js +85 -0
- package/dist/env.d.ts +19 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +112 -0
- package/dist/handlers/base-handler.d.ts +21 -0
- package/dist/handlers/base-handler.d.ts.map +1 -0
- package/dist/handlers/base-handler.js +38 -0
- package/dist/handlers/js-handler.d.ts +10 -0
- package/dist/handlers/js-handler.d.ts.map +1 -0
- package/dist/handlers/js-handler.js +87 -0
- package/dist/handlers/mjs-handler.d.ts +8 -0
- package/dist/handlers/mjs-handler.d.ts.map +1 -0
- package/dist/handlers/mjs-handler.js +44 -0
- package/dist/handlers/node-module-handler.d.ts +16 -0
- package/dist/handlers/node-module-handler.d.ts.map +1 -0
- package/dist/handlers/node-module-handler.js +267 -0
- package/dist/handlers/ts-handler.d.ts +11 -0
- package/dist/handlers/ts-handler.d.ts.map +1 -0
- package/dist/handlers/ts-handler.js +120 -0
- package/dist/handlers/ui-handler.d.ts +12 -0
- package/dist/handlers/ui-handler.d.ts.map +1 -0
- package/dist/handlers/ui-handler.js +182 -0
- package/dist/handlers/uix-handler.d.ts +12 -0
- package/dist/handlers/uix-handler.d.ts.map +1 -0
- package/dist/handlers/uix-handler.js +135 -0
- package/dist/hmr.d.ts +20 -0
- package/dist/hmr.d.ts.map +1 -0
- package/dist/hmr.js +265 -0
- package/dist/import-rewriter.d.ts +3 -0
- package/dist/import-rewriter.d.ts.map +1 -0
- package/dist/import-rewriter.js +351 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/middleware/hmr-routes.d.ts +12 -0
- package/dist/middleware/hmr-routes.d.ts.map +1 -0
- package/dist/middleware/hmr-routes.js +97 -0
- package/dist/middleware/middleware-setup.d.ts +23 -0
- package/dist/middleware/middleware-setup.d.ts.map +1 -0
- package/dist/middleware/middleware-setup.js +596 -0
- package/dist/middleware/static-files.d.ts +15 -0
- package/dist/middleware/static-files.d.ts.map +1 -0
- package/dist/middleware/static-files.js +585 -0
- package/dist/proxy/SwiteProxyError.d.ts +6 -0
- package/dist/proxy/SwiteProxyError.d.ts.map +1 -0
- package/dist/proxy/SwiteProxyError.js +9 -0
- package/dist/proxy/proxyToPython.d.ts +28 -0
- package/dist/proxy/proxyToPython.d.ts.map +1 -0
- package/dist/proxy/proxyToPython.js +66 -0
- package/dist/resolver/bare-import-resolver.d.ts +9 -0
- package/dist/resolver/bare-import-resolver.d.ts.map +1 -0
- package/dist/resolver/bare-import-resolver.js +363 -0
- package/dist/resolver/symlink-registry.d.ts +13 -0
- package/dist/resolver/symlink-registry.d.ts.map +1 -0
- package/dist/resolver/symlink-registry.js +98 -0
- package/dist/resolver/url-resolver.d.ts +11 -0
- package/dist/resolver/url-resolver.d.ts.map +1 -0
- package/dist/resolver/url-resolver.js +268 -0
- package/dist/resolver/workspace-package-resolver.d.ts +10 -0
- package/dist/resolver/workspace-package-resolver.d.ts.map +1 -0
- package/dist/resolver/workspace-package-resolver.js +185 -0
- package/dist/resolver.d.ts +17 -0
- package/dist/resolver.d.ts.map +1 -0
- package/dist/resolver.js +191 -0
- package/dist/router/file-router.d.ts +19 -0
- package/dist/router/file-router.d.ts.map +1 -0
- package/dist/router/file-router.js +114 -0
- package/dist/server.d.ts +22 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +122 -0
- package/dist/utils/cdn-fallback.d.ts +14 -0
- package/dist/utils/cdn-fallback.d.ts.map +1 -0
- package/dist/utils/cdn-fallback.js +36 -0
- package/dist/utils/file-path-resolver.d.ts +9 -0
- package/dist/utils/file-path-resolver.d.ts.map +1 -0
- package/dist/utils/file-path-resolver.js +187 -0
- package/dist/utils/generate-import-map-cli.d.ts +3 -0
- package/dist/utils/generate-import-map-cli.d.ts.map +1 -0
- package/dist/utils/generate-import-map-cli.js +32 -0
- package/dist/utils/generate-import-map.d.ts +21 -0
- package/dist/utils/generate-import-map.d.ts.map +1 -0
- package/dist/utils/generate-import-map.js +119 -0
- package/dist/utils/package-finder.d.ts +24 -0
- package/dist/utils/package-finder.d.ts.map +1 -0
- package/dist/utils/package-finder.js +161 -0
- package/dist/utils/package-registry.d.ts +36 -0
- package/dist/utils/package-registry.d.ts.map +1 -0
- package/dist/utils/package-registry.js +159 -0
- package/dist/utils/workspace.d.ts +6 -0
- package/dist/utils/workspace.d.ts.map +1 -0
- package/dist/utils/workspace.js +65 -0
- package/docs/IMPORT_REWRITING.md +164 -0
- package/docs/IMPORT_REWRITING_TROUBLESHOOTING.md +139 -0
- package/docs/PATH_RESOLUTION_GUIDE.md +221 -0
- package/package.json +49 -0
- package/src/adapters/proxy/SwiteProxyError.ts +12 -0
- package/src/adapters/proxy/proxyToPython.ts +88 -0
- package/src/build-engine/builder.ts +960 -0
- package/src/cli.ts +109 -0
- package/src/config/config-loader.ts +46 -0
- package/src/config/config.ts +34 -0
- package/src/config/env.ts +98 -0
- package/src/dev-engine/handlers/base-handler.ts +68 -0
- package/src/dev-engine/handlers/js-handler.ts +134 -0
- package/src/dev-engine/handlers/mjs-handler.ts +65 -0
- package/src/dev-engine/handlers/node-module-handler.ts +339 -0
- package/src/dev-engine/handlers/ts-handler.ts +143 -0
- package/src/dev-engine/handlers/ui-handler.ts +105 -0
- package/src/dev-engine/handlers/uix-handler.ts +90 -0
- package/src/dev-engine/hmr/hmr-client-template.ts +122 -0
- package/src/dev-engine/hmr/hmr.ts +173 -0
- package/src/dev-engine/middleware/hmr-routes.ts +120 -0
- package/src/dev-engine/middleware/middleware-setup.ts +351 -0
- package/src/dev-engine/middleware/static-files.ts +728 -0
- package/src/dev-engine/pythonDevManager.ts +116 -0
- package/src/dev-engine/router/file-router.ts +164 -0
- package/src/dev-engine/server.ts +152 -0
- package/src/index.ts +26 -0
- package/src/internal/cache/compilation-cache.ts +182 -0
- package/src/internal/generate-import-map-cli.ts +40 -0
- package/src/internal/generate-import-map.ts +154 -0
- package/src/kernel/package-finder.ts +164 -0
- package/src/kernel/package-registry.ts +198 -0
- package/src/kernel/workspace.ts +62 -0
- package/src/resolution/bare-import-resolver.ts +400 -0
- package/src/resolution/cdn/cdn-fallback.ts +37 -0
- package/src/resolution/path/file-path-resolver.ts +190 -0
- package/src/resolution/path/path-fixup.ts +19 -0
- package/src/resolution/resolver.ts +198 -0
- package/src/resolution/rewriting/import-rewriter.ts +237 -0
- package/src/resolution/symlink-registry.ts +114 -0
- package/src/resolution/url-resolver.ts +231 -0
- package/src/resolution/workspace-package-resolver.ts +94 -0
- package/tsconfig.json +37 -0
package/src/cli.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { SwiteServer } from "./dev-engine/server.js";
|
|
5
|
+
import { loadUserConfig } from "./config/config-loader.js";
|
|
6
|
+
import {
|
|
7
|
+
startPythonDevService,
|
|
8
|
+
stopPythonDevService,
|
|
9
|
+
} from "./dev-engine/pythonDevManager.js";
|
|
10
|
+
import { setProductionMode } from "./adapters/proxy/proxyToPython.js";
|
|
11
|
+
|
|
12
|
+
const [, , command, ...args] = process.argv;
|
|
13
|
+
const root = resolve(process.cwd());
|
|
14
|
+
|
|
15
|
+
async function dev(): Promise<void> {
|
|
16
|
+
const config = await loadUserConfig(root);
|
|
17
|
+
const python = config.services?.python;
|
|
18
|
+
|
|
19
|
+
if (python?.autoStart) {
|
|
20
|
+
await startPythonDevService(python, root);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Relay SIGINT: kill Python, then exit cleanly
|
|
24
|
+
process.on("SIGINT", () => {
|
|
25
|
+
stopPythonDevService();
|
|
26
|
+
process.exit(0);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Ensure Python is killed if Node crashes
|
|
30
|
+
process.on("exit", () => {
|
|
31
|
+
stopPythonDevService();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const server = new SwiteServer({
|
|
35
|
+
root,
|
|
36
|
+
port: config.server?.port ?? 3000,
|
|
37
|
+
host: config.server?.host ?? "localhost",
|
|
38
|
+
publicDir: "public",
|
|
39
|
+
open: false,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
await server.start();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function start(): Promise<void> {
|
|
46
|
+
const config = await loadUserConfig(root);
|
|
47
|
+
const python = config.services?.python;
|
|
48
|
+
|
|
49
|
+
setProductionMode();
|
|
50
|
+
|
|
51
|
+
if (python && !process.env["PYTHON_SERVICE_URL"]) {
|
|
52
|
+
console.warn(
|
|
53
|
+
chalk.yellow(
|
|
54
|
+
"[swite] WARNING: services.python is configured but PYTHON_SERVICE_URL is not set.\n" +
|
|
55
|
+
" Proxy calls to Python will fail. Set PYTHON_SERVICE_URL to the running service URL.",
|
|
56
|
+
),
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const server = new SwiteServer({
|
|
61
|
+
root,
|
|
62
|
+
port: config.server?.port ?? 3000,
|
|
63
|
+
host: config.server?.host ?? "localhost",
|
|
64
|
+
publicDir: "public",
|
|
65
|
+
open: false,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
await server.start();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function build(): Promise<void> {
|
|
72
|
+
const { SwiteBuilder } = await import("./builder.js");
|
|
73
|
+
const config = await loadUserConfig(root);
|
|
74
|
+
const builder = new SwiteBuilder({
|
|
75
|
+
root,
|
|
76
|
+
entry: resolve(root, "src/index.ui"),
|
|
77
|
+
outDir: resolve(root, "dist"),
|
|
78
|
+
});
|
|
79
|
+
await builder.build();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
switch (command) {
|
|
83
|
+
case "dev":
|
|
84
|
+
dev().catch((err: unknown) => {
|
|
85
|
+
console.error(chalk.red("[swite] fatal:"), err);
|
|
86
|
+
stopPythonDevService();
|
|
87
|
+
process.exit(1);
|
|
88
|
+
});
|
|
89
|
+
break;
|
|
90
|
+
|
|
91
|
+
case "start":
|
|
92
|
+
start().catch((err: unknown) => {
|
|
93
|
+
console.error(chalk.red("[swite] fatal:"), err);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
});
|
|
96
|
+
break;
|
|
97
|
+
|
|
98
|
+
case "build":
|
|
99
|
+
build().catch((err: unknown) => {
|
|
100
|
+
console.error(chalk.red("[swite] build failed:"), err);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
});
|
|
103
|
+
break;
|
|
104
|
+
|
|
105
|
+
default:
|
|
106
|
+
console.error(chalk.red(`[swite] unknown command: ${command ?? "(none)"}`));
|
|
107
|
+
console.error("Usage: swite <dev|build|start>");
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { build } from "esbuild";
|
|
2
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
import { existsSync } from "node:fs";
|
|
7
|
+
import type { SwiteUserConfig } from "./config.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Load swiss.config.ts from the project root.
|
|
11
|
+
* Transpiles to a temp ESM file via esbuild, imports it, then cleans up.
|
|
12
|
+
* Returns empty config if no config file found.
|
|
13
|
+
*/
|
|
14
|
+
export async function loadUserConfig(root: string): Promise<SwiteUserConfig> {
|
|
15
|
+
const tsConfig = join(root, "swiss.config.ts");
|
|
16
|
+
const jsConfig = join(root, "swiss.config.js");
|
|
17
|
+
|
|
18
|
+
const configPath = existsSync(tsConfig)
|
|
19
|
+
? tsConfig
|
|
20
|
+
: existsSync(jsConfig)
|
|
21
|
+
? jsConfig
|
|
22
|
+
: null;
|
|
23
|
+
|
|
24
|
+
if (!configPath) {
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const tempDir = await mkdtemp(join(tmpdir(), "swite-config-"));
|
|
29
|
+
const outFile = join(tempDir, "swiss.config.mjs");
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
await build({
|
|
33
|
+
entryPoints: [configPath],
|
|
34
|
+
bundle: false,
|
|
35
|
+
format: "esm",
|
|
36
|
+
outfile: outFile,
|
|
37
|
+
platform: "node",
|
|
38
|
+
logLevel: "silent",
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const mod = await import(pathToFileURL(outFile).href);
|
|
42
|
+
return (mod.default ?? {}) as SwiteUserConfig;
|
|
43
|
+
} finally {
|
|
44
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface PythonServiceConfig {
|
|
2
|
+
/** Path to the Python entry file, relative to project root */
|
|
3
|
+
entry: string;
|
|
4
|
+
/** Port the Python service listens on */
|
|
5
|
+
port: number;
|
|
6
|
+
/** Whether swite dev should spawn the Python process automatically */
|
|
7
|
+
autoStart: boolean;
|
|
8
|
+
/** Health check endpoint polled before Node server starts */
|
|
9
|
+
healthCheck: string;
|
|
10
|
+
/** Additional environment variables passed to the Python process */
|
|
11
|
+
env?: Record<string, string>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ServicesConfig {
|
|
15
|
+
python?: PythonServiceConfig;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ServerConfig {
|
|
19
|
+
port?: number;
|
|
20
|
+
host?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface SwiteUserConfig {
|
|
24
|
+
server?: ServerConfig;
|
|
25
|
+
services?: ServicesConfig;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Define swite configuration with full TypeScript validation.
|
|
30
|
+
* Unknown fields are rejected at compile time.
|
|
31
|
+
*/
|
|
32
|
+
export function defineConfig(config: SwiteUserConfig): SwiteUserConfig {
|
|
33
|
+
return config;
|
|
34
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Environment Variable Support for SWITE
|
|
3
|
+
* Provides import.meta.env replacement for SWITE
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
|
|
9
|
+
export interface EnvConfig {
|
|
10
|
+
mode?: "development" | "production";
|
|
11
|
+
prefix?: string; // Default: "SWITE_"
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Load environment variables from .env files.
|
|
16
|
+
* Supports .env, .env.local, .env.[mode], .env.[mode].local
|
|
17
|
+
*/
|
|
18
|
+
export function loadEnv(
|
|
19
|
+
root: string,
|
|
20
|
+
mode: string = "development",
|
|
21
|
+
prefix: string = "SWITE_",
|
|
22
|
+
): Record<string, string> {
|
|
23
|
+
const env: Record<string, string> = {};
|
|
24
|
+
const envFiles = [`.env.${mode}.local`, `.env.${mode}`, `.env.local`, `.env`];
|
|
25
|
+
|
|
26
|
+
for (const file of envFiles) {
|
|
27
|
+
const envPath = join(root, file);
|
|
28
|
+
if (!existsSync(envPath)) continue;
|
|
29
|
+
const content = readFileSync(envPath, "utf-8");
|
|
30
|
+
for (const line of content.split("\n")) {
|
|
31
|
+
const trimmed = line.trim();
|
|
32
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
33
|
+
const match = trimmed.match(/^([^=]+)=(.*)$/);
|
|
34
|
+
if (!match) continue;
|
|
35
|
+
const key = match[1].trim();
|
|
36
|
+
const value = match[2].replace(/^["']|["']$/g, "");
|
|
37
|
+
if (key.startsWith(prefix) || key.startsWith("PUBLIC_")) {
|
|
38
|
+
env[key] = value;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return env;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Replace all import.meta.env references in compiled code with their literal values.
|
|
48
|
+
*
|
|
49
|
+
* This is the only correct approach for ES modules — import.meta is sealed and
|
|
50
|
+
* import.meta.env cannot be assigned at runtime. All substitution must happen at
|
|
51
|
+
* transform time (here, after esbuild strips TypeScript).
|
|
52
|
+
*
|
|
53
|
+
* Handles:
|
|
54
|
+
* - import.meta.env.KEY → JSON.stringify(env[KEY]) or "undefined"
|
|
55
|
+
* - import.meta.env.DEV → true/false literal
|
|
56
|
+
* - import.meta.env.PROD → true/false literal
|
|
57
|
+
* - import.meta.env.MODE → "development"/"production" literal
|
|
58
|
+
* - bare import.meta.env → serialized object literal (for spread, typeof, etc.)
|
|
59
|
+
*/
|
|
60
|
+
export function inlineEnvReferences(
|
|
61
|
+
code: string,
|
|
62
|
+
env: Record<string, string>,
|
|
63
|
+
mode: string = "development",
|
|
64
|
+
): string {
|
|
65
|
+
if (!code.includes("import.meta.env")) return code;
|
|
66
|
+
|
|
67
|
+
const isDev = mode !== "production";
|
|
68
|
+
|
|
69
|
+
// Named key access first (most specific)
|
|
70
|
+
code = code.replace(/\bimport\.meta\.env\.([A-Z_][A-Z0-9_]*)\b/g, (_, key: string) => {
|
|
71
|
+
if (key === "DEV") return String(isDev);
|
|
72
|
+
if (key === "PROD") return String(!isDev);
|
|
73
|
+
if (key === "MODE") return JSON.stringify(mode);
|
|
74
|
+
if (key === "SSR") return "false";
|
|
75
|
+
if (key in env) return JSON.stringify(env[key]);
|
|
76
|
+
return "undefined";
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Bare import.meta.env (spread/typeof patterns)
|
|
80
|
+
if (code.includes("import.meta.env")) {
|
|
81
|
+
const envLiteral = buildEnvLiteral(env, mode);
|
|
82
|
+
code = code.replace(/\bimport\.meta\.env\b/g, envLiteral);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return code;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function buildEnvLiteral(env: Record<string, string>, mode: string): string {
|
|
89
|
+
const isDev = mode !== "production";
|
|
90
|
+
const entries: string[] = [
|
|
91
|
+
`MODE:${JSON.stringify(mode)}`,
|
|
92
|
+
`DEV:${isDev}`,
|
|
93
|
+
`PROD:${!isDev}`,
|
|
94
|
+
`SSR:false`,
|
|
95
|
+
...Object.entries(env).map(([k, v]) => `${JSON.stringify(k)}:${JSON.stringify(v)}`),
|
|
96
|
+
];
|
|
97
|
+
return `({${entries.join(",")}})`;
|
|
98
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
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 { ModuleResolver } from "../../resolution/resolver.js";
|
|
10
|
+
import { resolveFilePath } from "../../resolution/path/file-path-resolver.js";
|
|
11
|
+
|
|
12
|
+
export interface HandlerContext {
|
|
13
|
+
resolver: ModuleResolver;
|
|
14
|
+
root: string;
|
|
15
|
+
workspaceRoot: string | null;
|
|
16
|
+
env: Record<string, string>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Set cache-busting headers for development
|
|
21
|
+
*/
|
|
22
|
+
export function setDevHeaders(res: Response): void {
|
|
23
|
+
// Prevent all caching during development
|
|
24
|
+
res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate");
|
|
25
|
+
res.setHeader("Pragma", "no-cache");
|
|
26
|
+
res.setHeader("Expires", "0");
|
|
27
|
+
res.setHeader("Surrogate-Control", "no-store");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Base handler utilities
|
|
32
|
+
*/
|
|
33
|
+
export class BaseHandler {
|
|
34
|
+
constructor(protected context: HandlerContext) {}
|
|
35
|
+
|
|
36
|
+
protected async resolveFilePath(url: string): Promise<string> {
|
|
37
|
+
return resolveFilePath(url, this.context.root, this.context.workspaceRoot);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
protected async fileExists(filePath: string): Promise<boolean> {
|
|
41
|
+
try {
|
|
42
|
+
await fs.access(filePath);
|
|
43
|
+
return true;
|
|
44
|
+
} catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
protected async getDependencies(compiled: string): Promise<string[]> {
|
|
50
|
+
const deps: string[] = [];
|
|
51
|
+
const importPattern = /(?:import|from|export).*['"]([^'"]+)['"]/g;
|
|
52
|
+
let match;
|
|
53
|
+
while ((match = importPattern.exec(compiled)) !== null) {
|
|
54
|
+
const specifier = match[1];
|
|
55
|
+
if (specifier.startsWith("/") || specifier.startsWith("@")) {
|
|
56
|
+
try {
|
|
57
|
+
const resolved = await this.context.resolver.resolve(specifier, "");
|
|
58
|
+
if (resolved && !resolved.startsWith("http")) {
|
|
59
|
+
deps.push(resolved);
|
|
60
|
+
}
|
|
61
|
+
} catch {
|
|
62
|
+
// ignore resolution errors during dependency tracking
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return deps;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
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 {
|
|
12
|
+
BaseHandler,
|
|
13
|
+
setDevHeaders,
|
|
14
|
+
type HandlerContext,
|
|
15
|
+
} from "./base-handler.js";
|
|
16
|
+
import { UIHandler } from "./ui-handler.js";
|
|
17
|
+
import { UIXHandler } from "./uix-handler.js";
|
|
18
|
+
import { TSHandler } from "./ts-handler.js";
|
|
19
|
+
|
|
20
|
+
export class JSHandler extends BaseHandler {
|
|
21
|
+
private uiHandler: UIHandler;
|
|
22
|
+
private uixHandler: UIXHandler;
|
|
23
|
+
private tsHandler: TSHandler;
|
|
24
|
+
|
|
25
|
+
constructor(context: HandlerContext) {
|
|
26
|
+
super(context);
|
|
27
|
+
this.uiHandler = new UIHandler(context);
|
|
28
|
+
this.uixHandler = new UIXHandler(context);
|
|
29
|
+
this.tsHandler = new TSHandler(context);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async handle(url: string, res: Response): Promise<void> {
|
|
33
|
+
const filePath = await this.resolveFilePath(url);
|
|
34
|
+
|
|
35
|
+
// Check if .js file exists, if not try .ts, .ui, .uix
|
|
36
|
+
try {
|
|
37
|
+
await fs.access(filePath);
|
|
38
|
+
} catch {
|
|
39
|
+
// .js doesn't exist, try alternatives
|
|
40
|
+
const basePath = filePath.slice(0, -3); // Remove .js
|
|
41
|
+
const alternatives = [
|
|
42
|
+
{
|
|
43
|
+
ext: ".ts",
|
|
44
|
+
handler: () =>
|
|
45
|
+
this.tsHandler.handle(url.replace(/\.js$/, ".ts"), res),
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
ext: ".ui",
|
|
49
|
+
handler: () =>
|
|
50
|
+
this.uiHandler.handle(url.replace(/\.js$/, ".ui"), res),
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
ext: ".uix",
|
|
54
|
+
handler: () =>
|
|
55
|
+
this.uixHandler.handle(url.replace(/\.js$/, ".uix"), res),
|
|
56
|
+
},
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
for (const alt of alternatives) {
|
|
60
|
+
try {
|
|
61
|
+
const altPath = basePath + alt.ext;
|
|
62
|
+
await fs.access(altPath);
|
|
63
|
+
console.log(
|
|
64
|
+
chalk.yellow(
|
|
65
|
+
`[.js→${alt.ext}] ${url} → ${url.replace(/\.js$/, alt.ext)} (file: ${altPath})`,
|
|
66
|
+
),
|
|
67
|
+
);
|
|
68
|
+
return await alt.handler();
|
|
69
|
+
} catch {
|
|
70
|
+
// Try next alternative
|
|
71
|
+
console.log(
|
|
72
|
+
chalk.gray(
|
|
73
|
+
`[.js→${alt.ext}] ${basePath + alt.ext} not found, trying next...`,
|
|
74
|
+
),
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// No alternatives found, throw error
|
|
80
|
+
console.error(
|
|
81
|
+
chalk.red(`[.js] File not found: ${url} (tried .js, .ts, .ui, .uix)`),
|
|
82
|
+
);
|
|
83
|
+
console.error(chalk.red(`[.js] filePath was: ${filePath}`));
|
|
84
|
+
console.error(chalk.red(`[.js] basePath was: ${basePath}`));
|
|
85
|
+
throw new Error(`File not found: ${url} (tried .js, .ts, .ui, .uix)`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// .js file exists, process it normally
|
|
89
|
+
const source = await fs.readFile(filePath, "utf-8");
|
|
90
|
+
|
|
91
|
+
// Debug: Check for bare imports (including simple npm packages like bcryptjs)
|
|
92
|
+
const bareImportPattern =
|
|
93
|
+
/(?:import|from|export).*['"](@[^'"]+\/[^'"]+)[^'"]*['"]/;
|
|
94
|
+
const simpleNpmPattern =
|
|
95
|
+
/(?:import|from|export).*['"]([a-zA-Z][a-zA-Z0-9_-]*)[^'"]*['"]/;
|
|
96
|
+
if (bareImportPattern.test(source) || simpleNpmPattern.test(source)) {
|
|
97
|
+
console.log(chalk.yellow(`[.js] Found imports in ${url}, rewriting...`));
|
|
98
|
+
// Log the actual imports found
|
|
99
|
+
const importMatches = source.matchAll(
|
|
100
|
+
/(?:import|from)\s+['"]([^'"]+)['"]/g,
|
|
101
|
+
);
|
|
102
|
+
for (const match of importMatches) {
|
|
103
|
+
console.log(chalk.cyan(`[.js] Found import: ${match[1]}`));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const rewritten = await rewriteImports(
|
|
108
|
+
source,
|
|
109
|
+
filePath,
|
|
110
|
+
this.context.resolver,
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
// Debug: Verify no bare imports remain after rewriting
|
|
114
|
+
if (bareImportPattern.test(rewritten)) {
|
|
115
|
+
console.error(
|
|
116
|
+
chalk.red(
|
|
117
|
+
`[.js] WARNING: Bare imports still present in ${url} after rewriting!`,
|
|
118
|
+
),
|
|
119
|
+
);
|
|
120
|
+
const matches = Array.from(
|
|
121
|
+
rewritten.matchAll(
|
|
122
|
+
/(?:import|from|export).*['"](@[^'"]+\/[^'"]+)[^'"]*['"]/g,
|
|
123
|
+
),
|
|
124
|
+
);
|
|
125
|
+
for (const match of matches.slice(0, 3)) {
|
|
126
|
+
console.error(chalk.red(`[.js] Unresolved: ${match[1]}`));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
setDevHeaders(res);
|
|
131
|
+
res.setHeader("Content-Type", "application/javascript; charset=utf-8");
|
|
132
|
+
res.send(rewritten);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
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 {
|
|
12
|
+
BaseHandler,
|
|
13
|
+
setDevHeaders,
|
|
14
|
+
type HandlerContext,
|
|
15
|
+
} from "./base-handler.js";
|
|
16
|
+
import { JSHandler } from "./js-handler.js";
|
|
17
|
+
|
|
18
|
+
export class MJSHandler extends BaseHandler {
|
|
19
|
+
private jsHandler: JSHandler;
|
|
20
|
+
|
|
21
|
+
constructor(context: HandlerContext) {
|
|
22
|
+
super(context);
|
|
23
|
+
this.jsHandler = new JSHandler(context);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async handle(url: string, res: Response): Promise<void> {
|
|
27
|
+
const filePath = await this.resolveFilePath(url);
|
|
28
|
+
|
|
29
|
+
// Check if .mjs file exists
|
|
30
|
+
try {
|
|
31
|
+
await fs.access(filePath);
|
|
32
|
+
} catch {
|
|
33
|
+
// .mjs doesn't exist, try .js as fallback
|
|
34
|
+
const jsPath = filePath.replace(/\.mjs$/, ".js");
|
|
35
|
+
try {
|
|
36
|
+
await fs.access(jsPath);
|
|
37
|
+
console.log(
|
|
38
|
+
chalk.yellow(
|
|
39
|
+
`[.mjs→.js] ${url} → ${url.replace(/\.mjs$/, ".js")} (file: ${jsPath})`,
|
|
40
|
+
),
|
|
41
|
+
);
|
|
42
|
+
return await this.jsHandler.handle(url.replace(/\.mjs$/, ".js"), res);
|
|
43
|
+
} catch {
|
|
44
|
+
console.error(
|
|
45
|
+
chalk.red(`[.mjs] File not found: ${url} (tried .mjs, .js)`),
|
|
46
|
+
);
|
|
47
|
+
throw new Error(`File not found: ${url} (tried .mjs, .js)`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// .mjs file exists, process it normally
|
|
52
|
+
const source = await fs.readFile(filePath, "utf-8");
|
|
53
|
+
const rewritten = await rewriteImports(
|
|
54
|
+
source,
|
|
55
|
+
filePath,
|
|
56
|
+
this.context.resolver,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// Set proper MIME type for ES modules (.mjs)
|
|
60
|
+
// According to MDN Web Standards: .mjs files should use "application/javascript"
|
|
61
|
+
setDevHeaders(res);
|
|
62
|
+
res.setHeader("Content-Type", "application/javascript; charset=utf-8");
|
|
63
|
+
res.send(rewritten);
|
|
64
|
+
}
|
|
65
|
+
}
|