@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
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the HMR client script served to the browser at /__swite_hmr_client.
|
|
3
|
+
*
|
|
4
|
+
* The client is plain JavaScript (no TS syntax) because it is injected into
|
|
5
|
+
* browser pages as-is. Keeping it in a separate module rather than embedded
|
|
6
|
+
* inside hmr.ts makes it editable with syntax highlighting and avoids
|
|
7
|
+
* template-literal escaping issues.
|
|
8
|
+
*/
|
|
9
|
+
export function buildHmrClientScript(port: number): string {
|
|
10
|
+
return `// SWITE HMR Client
|
|
11
|
+
console.log('[SWITE] HMR enabled');
|
|
12
|
+
|
|
13
|
+
const socket = new WebSocket('ws://' + window.location.hostname + ':${port}');
|
|
14
|
+
const moduleGraph = new Map();
|
|
15
|
+
const hotModules = new Map();
|
|
16
|
+
|
|
17
|
+
socket.addEventListener('open', () => {
|
|
18
|
+
console.log('[SWITE] HMR connected');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
socket.addEventListener('message', async (event) => {
|
|
22
|
+
const data = JSON.parse(event.data);
|
|
23
|
+
|
|
24
|
+
if (data.type === 'update') {
|
|
25
|
+
console.log('[SWITE] Processing update:', data.path, 'Type:', data.updateType);
|
|
26
|
+
|
|
27
|
+
if (data.updateType === 'style') {
|
|
28
|
+
updateStyles();
|
|
29
|
+
console.log('[SWITE] Styles hot updated');
|
|
30
|
+
} else if (data.updateType === 'hot') {
|
|
31
|
+
const moduleName = extractModuleName(data.path);
|
|
32
|
+
|
|
33
|
+
if (moduleName && hotModules.has(moduleName)) {
|
|
34
|
+
try {
|
|
35
|
+
invalidateModule(moduleName);
|
|
36
|
+
invalidateDependents(moduleName);
|
|
37
|
+
|
|
38
|
+
const updatedModule = await import(data.path + '?t=' + Date.now());
|
|
39
|
+
hotModules.set(moduleName, updatedModule);
|
|
40
|
+
|
|
41
|
+
updateComponent(moduleName, updatedModule);
|
|
42
|
+
console.log('[SWITE] Component hot updated:', moduleName);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error('[SWITE] Hot update failed:', error);
|
|
45
|
+
window.location.reload();
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
console.log('[SWITE] New component detected, reloading page');
|
|
49
|
+
window.location.reload();
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
console.log('[SWITE] Full page reload required');
|
|
53
|
+
window.location.reload();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
function updateStyles() {
|
|
59
|
+
const links = document.querySelectorAll('link[rel="stylesheet"]');
|
|
60
|
+
links.forEach(link => {
|
|
61
|
+
const href = link.getAttribute('href');
|
|
62
|
+
if (href) {
|
|
63
|
+
const base = href.replace(/[?&]t=\\d+/, '');
|
|
64
|
+
link.setAttribute('href', base + (base.includes('?') ? '&' : '?') + 't=' + Date.now());
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function extractModuleName(filePath) {
|
|
70
|
+
const parts = filePath.split('/');
|
|
71
|
+
const fileName = parts[parts.length - 1];
|
|
72
|
+
return fileName ? fileName.replace(/\\.[^.]+$/, '') : null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function invalidateModule(moduleName) {
|
|
76
|
+
if (window.__swiss_modules__) {
|
|
77
|
+
delete window.__swiss_modules__[moduleName];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function invalidateDependents(moduleName) {
|
|
82
|
+
const dependents = moduleGraph.get(moduleName);
|
|
83
|
+
if (dependents) {
|
|
84
|
+
for (const dependent of dependents) {
|
|
85
|
+
invalidateModule(dependent);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function updateComponent(moduleName, newModule) {
|
|
91
|
+
if (window.__swiss_instances__) {
|
|
92
|
+
const instances = window.__swiss_instances__[moduleName];
|
|
93
|
+
if (instances && Array.isArray(instances)) {
|
|
94
|
+
instances.forEach(instance => {
|
|
95
|
+
if (instance && typeof instance.update === 'function') {
|
|
96
|
+
instance.update(newModule.default || newModule);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
socket.addEventListener('close', () => {
|
|
104
|
+
console.log('[SWITE] HMR disconnected');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
socket.addEventListener('error', (error) => {
|
|
108
|
+
console.error('[SWITE] HMR error:', error);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
window.__swiss_modules__ = window.__swiss_modules__ || {};
|
|
112
|
+
window.__swiss_instances__ = window.__swiss_instances__ || {};
|
|
113
|
+
|
|
114
|
+
const currentScript = document.currentScript;
|
|
115
|
+
if (currentScript && currentScript.src) {
|
|
116
|
+
const moduleName = extractModuleName(currentScript.src);
|
|
117
|
+
if (moduleName) {
|
|
118
|
+
window.__swiss_modules__[moduleName] = true;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
`;
|
|
122
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* HMR Engine for SWITE
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as chokidar from "chokidar";
|
|
6
|
+
import { WebSocketServer, WebSocket } from "ws";
|
|
7
|
+
import * as net from "net";
|
|
8
|
+
import chalk from "chalk";
|
|
9
|
+
import { buildHmrClientScript } from "./hmr-client-template.js";
|
|
10
|
+
|
|
11
|
+
export class HMREngine {
|
|
12
|
+
private wss!: WebSocketServer;
|
|
13
|
+
private watcher?: chokidar.FSWatcher;
|
|
14
|
+
private clients = new Set<WebSocket>();
|
|
15
|
+
private port: number;
|
|
16
|
+
|
|
17
|
+
constructor(
|
|
18
|
+
private root: string,
|
|
19
|
+
hmrPort?: number,
|
|
20
|
+
) {
|
|
21
|
+
this.port = hmrPort || 24678;
|
|
22
|
+
// WebSocketServer will be created in initialize() method
|
|
23
|
+
// This allows async port checking before server creation
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async initialize(): Promise<void> {
|
|
27
|
+
// Check if port is available, if not find a free one
|
|
28
|
+
const isAvailable = await this.checkPortAvailable(this.port);
|
|
29
|
+
if (!isAvailable) {
|
|
30
|
+
console.warn(
|
|
31
|
+
chalk.yellow(`[HMR] Port ${this.port} is in use, finding free port...`),
|
|
32
|
+
);
|
|
33
|
+
this.port = await this.findFreePort();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
this.wss = new WebSocketServer({ port: this.port });
|
|
37
|
+
this.setupWebSocket();
|
|
38
|
+
console.log(
|
|
39
|
+
chalk.green(`[HMR] WebSocket server started on port ${this.port}`),
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private async checkPortAvailable(port: number): Promise<boolean> {
|
|
44
|
+
return new Promise((resolve) => {
|
|
45
|
+
const server = net.createServer();
|
|
46
|
+
server.listen(port, () => {
|
|
47
|
+
server.close(() => resolve(true));
|
|
48
|
+
});
|
|
49
|
+
server.on("error", () => resolve(false));
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private setupWebSocket() {
|
|
54
|
+
this.wss.on("connection", (ws) => {
|
|
55
|
+
this.clients.add(ws);
|
|
56
|
+
console.log(chalk.green("[HMR] Client connected"));
|
|
57
|
+
|
|
58
|
+
ws.on("close", () => {
|
|
59
|
+
this.clients.delete(ws);
|
|
60
|
+
console.log(chalk.gray("[HMR] Client disconnected"));
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private async findFreePort(): Promise<number> {
|
|
66
|
+
return new Promise((resolve, reject) => {
|
|
67
|
+
const server = net.createServer();
|
|
68
|
+
server.listen(0, () => {
|
|
69
|
+
const address = server.address();
|
|
70
|
+
const port =
|
|
71
|
+
address && typeof address === "object" ? address.port : null;
|
|
72
|
+
server.close(() => {
|
|
73
|
+
if (port) {
|
|
74
|
+
resolve(port);
|
|
75
|
+
} else {
|
|
76
|
+
reject(new Error("Could not find free port"));
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
server.on("error", reject);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
getPort(): number {
|
|
85
|
+
return this.port;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async start() {
|
|
89
|
+
this.watcher = chokidar.watch(this.root, {
|
|
90
|
+
ignored: ["**/node_modules/**", "**/.git/**", "**/dist/**"],
|
|
91
|
+
ignoreInitial: true,
|
|
92
|
+
awaitWriteFinish: {
|
|
93
|
+
stabilityThreshold: 100,
|
|
94
|
+
pollInterval: 100,
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
this.watcher.on("change", (filePath) => {
|
|
99
|
+
console.log(chalk.yellow(`[HMR] ${filePath} changed`));
|
|
100
|
+
|
|
101
|
+
// Determine file type and update type
|
|
102
|
+
const fileExt = filePath.split(".").pop()?.toLowerCase();
|
|
103
|
+
const updateType = this.getUpdateType(fileExt, filePath);
|
|
104
|
+
|
|
105
|
+
this.broadcast({
|
|
106
|
+
type: "update",
|
|
107
|
+
path: filePath,
|
|
108
|
+
updateType,
|
|
109
|
+
timestamp: Date.now(),
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
console.log(chalk.green("[HMR] Watching for file changes..."));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
notifyChange(filePath: string): void {
|
|
117
|
+
const fileExt = filePath.split(".").pop()?.toLowerCase();
|
|
118
|
+
const updateType = this.getUpdateType(fileExt, filePath);
|
|
119
|
+
|
|
120
|
+
this.broadcast({
|
|
121
|
+
type: "update",
|
|
122
|
+
path: filePath,
|
|
123
|
+
updateType,
|
|
124
|
+
timestamp: Date.now(),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
getClientScript(): string {
|
|
129
|
+
return buildHmrClientScript(this.port);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private getUpdateType(
|
|
133
|
+
fileExt?: string,
|
|
134
|
+
filePath?: string,
|
|
135
|
+
): "hot" | "reload" | "style" {
|
|
136
|
+
if (!fileExt || !filePath) return "reload";
|
|
137
|
+
|
|
138
|
+
// CSS files can be hot-swapped
|
|
139
|
+
if (fileExt === "css" || fileExt === "scss" || fileExt === "sass") {
|
|
140
|
+
return "style";
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Component files can be hot-reloaded
|
|
144
|
+
if (["js", "ts", "jsx", "tsx"].includes(fileExt)) {
|
|
145
|
+
// Check if it's in components directory
|
|
146
|
+
if (filePath.includes("/components/") || filePath.includes("/pages/")) {
|
|
147
|
+
return "hot";
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Everything else requires full reload
|
|
152
|
+
return "reload";
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private broadcast(message: {
|
|
156
|
+
type: string;
|
|
157
|
+
path: string;
|
|
158
|
+
updateType?: string;
|
|
159
|
+
timestamp: number;
|
|
160
|
+
}) {
|
|
161
|
+
const payload = JSON.stringify(message);
|
|
162
|
+
this.clients.forEach((client) => {
|
|
163
|
+
if (client.readyState === WebSocket.OPEN) {
|
|
164
|
+
client.send(payload);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async stop() {
|
|
170
|
+
await this.watcher?.close();
|
|
171
|
+
this.wss.close();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2024 Themba Mzumara
|
|
3
|
+
* SWITE - SWISS Development Server
|
|
4
|
+
* Licensed under the MIT License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Express } from "express";
|
|
8
|
+
import type { RouteDefinition } from "@swissjs/core";
|
|
9
|
+
import { HMREngine } from "../hmr/hmr.js";
|
|
10
|
+
|
|
11
|
+
export interface HMRRoutesConfig {
|
|
12
|
+
hmr: HMREngine;
|
|
13
|
+
routes: RouteDefinition[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Setup HMR client endpoint and routes endpoint
|
|
18
|
+
*/
|
|
19
|
+
export function setupHMRRoutes(app: Express, config: HMRRoutesConfig): void {
|
|
20
|
+
// HMR client injection
|
|
21
|
+
app.get("/__swite_hmr_client", (req, res) => {
|
|
22
|
+
res.setHeader("Content-Type", "application/javascript");
|
|
23
|
+
res.send(config.hmr.getClientScript());
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Routes endpoint - expose route definitions to client
|
|
27
|
+
app.get("/__swite_routes", (req, res) => {
|
|
28
|
+
res.setHeader("Content-Type", "application/json");
|
|
29
|
+
// Serialize routes for client - functions can't be serialized, so we include httpUrl in meta
|
|
30
|
+
// The client will use httpUrl to dynamically import the component
|
|
31
|
+
const serializedRoutes = config.routes.map((route) => ({
|
|
32
|
+
path: route.path,
|
|
33
|
+
meta: route.meta,
|
|
34
|
+
// Include httpUrl from meta so client can import it
|
|
35
|
+
componentUrl: route.meta?.httpUrl || null,
|
|
36
|
+
}));
|
|
37
|
+
res.json({ routes: serializedRoutes });
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Diagnostic endpoint - check what the server is actually serving
|
|
41
|
+
app.get("/__swite_diagnose", async (req, res) => {
|
|
42
|
+
const url = req.query.url as string;
|
|
43
|
+
if (!url) {
|
|
44
|
+
res.json({ error: "Missing url query parameter" });
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
// Try to fetch what we would serve
|
|
50
|
+
const testRes = await fetch(
|
|
51
|
+
`http://localhost:${req.socket.localPort}${url}`,
|
|
52
|
+
);
|
|
53
|
+
const content = await testRes.text();
|
|
54
|
+
const hasBareImport =
|
|
55
|
+
/(?:import|from|export).*['"](@[^'"]+\/[^'"]+)(?!\/)[^'"]*['"]/.test(
|
|
56
|
+
content,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
res.json({
|
|
60
|
+
url,
|
|
61
|
+
status: testRes.status,
|
|
62
|
+
hasBareImport,
|
|
63
|
+
contentPreview: content.substring(0, 500),
|
|
64
|
+
imports: Array.from(
|
|
65
|
+
content.matchAll(/(?:import|from|export).*['"]([^'"]+)['"]/g),
|
|
66
|
+
)
|
|
67
|
+
.slice(0, 10)
|
|
68
|
+
.map((m) => m[1]),
|
|
69
|
+
});
|
|
70
|
+
} catch (error) {
|
|
71
|
+
res.json({ error: String(error) });
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Force cache clear endpoint - returns HTML with aggressive cache busting
|
|
76
|
+
app.get("/__swite_clear_cache", async (req, res) => {
|
|
77
|
+
res.setHeader("Content-Type", "text/html");
|
|
78
|
+
res.setHeader(
|
|
79
|
+
"Cache-Control",
|
|
80
|
+
"no-cache, no-store, must-revalidate, max-age=0",
|
|
81
|
+
);
|
|
82
|
+
res.setHeader("Pragma", "no-cache");
|
|
83
|
+
res.setHeader("Expires", "0");
|
|
84
|
+
res.send(`
|
|
85
|
+
<!DOCTYPE html>
|
|
86
|
+
<html>
|
|
87
|
+
<head>
|
|
88
|
+
<title>Cache Cleared</title>
|
|
89
|
+
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
|
90
|
+
<meta http-equiv="Pragma" content="no-cache">
|
|
91
|
+
<meta http-equiv="Expires" content="0">
|
|
92
|
+
<script>
|
|
93
|
+
// Clear all caches
|
|
94
|
+
if ('caches' in window) {
|
|
95
|
+
caches.keys().then(names => {
|
|
96
|
+
names.forEach(name => caches.delete(name));
|
|
97
|
+
console.log('All caches cleared');
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
// Clear service workers
|
|
101
|
+
if ('serviceWorker' in navigator) {
|
|
102
|
+
navigator.serviceWorker.getRegistrations().then(registrations => {
|
|
103
|
+
registrations.forEach(reg => reg.unregister());
|
|
104
|
+
console.log('Service workers unregistered');
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
// Redirect to home
|
|
108
|
+
setTimeout(() => {
|
|
109
|
+
window.location.href = '/?nocache=' + Date.now();
|
|
110
|
+
}, 1000);
|
|
111
|
+
</script>
|
|
112
|
+
</head>
|
|
113
|
+
<body>
|
|
114
|
+
<h1>Clearing cache...</h1>
|
|
115
|
+
<p>Redirecting in 1 second...</p>
|
|
116
|
+
</body>
|
|
117
|
+
</html>
|
|
118
|
+
`);
|
|
119
|
+
});
|
|
120
|
+
}
|