@swissjs/swite 0.4.1 → 0.4.2
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/CHANGELOG.md +7 -0
- package/__tests__/import-rewriter-bug.test.ts +122 -122
- package/__tests__/security-r001-r002.test.ts +190 -190
- package/dist/cli.js +0 -0
- package/dist/dev-engine/hmr/hmr-client-template.js +111 -111
- package/dist/dev-engine/middleware/middleware-setup.js +4 -3
- package/docs/architecture/build-pipeline.md +97 -97
- package/docs/architecture/dev-server.md +87 -87
- package/docs/architecture/hmr.md +78 -78
- package/docs/architecture/import-rewriting.md +101 -101
- package/docs/architecture/index.md +16 -16
- package/docs/architecture/python-integration.md +93 -93
- package/docs/architecture/resolution.md +92 -92
- package/docs/cli/build.md +78 -78
- package/docs/cli/dev.md +90 -90
- package/docs/cli/index.md +15 -15
- package/docs/cli/start.md +45 -45
- package/docs/development/contributing.md +74 -74
- package/docs/development/index.md +12 -12
- package/docs/development/internals.md +101 -101
- package/docs/guide/configuration.md +89 -89
- package/docs/guide/index.md +13 -13
- package/docs/guide/project-structure.md +75 -75
- package/docs/guide/quickstart.md +113 -113
- package/docs/index.md +16 -16
- package/package.json +10 -9
- package/src/config/env.ts +98 -98
- package/src/dev-engine/handlers/ui-handler.ts +30 -30
- package/src/dev-engine/handlers/uix-handler.ts +21 -21
- package/src/dev-engine/hmr/hmr-client-template.ts +122 -122
- package/src/dev-engine/middleware/middleware-setup.ts +354 -354
- package/src/dev-engine/middleware/static-files.ts +813 -813
- package/src/resolution/cdn/cdn-fallback.ts +40 -40
- package/src/resolution/path/path-fixup.ts +27 -27
- package/src/resolution/rewriting/import-rewriter.ts +237 -237
- package/src/resolution/symlink-registry.ts +114 -114
|
@@ -1,30 +1,30 @@
|
|
|
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 { BaseHandler, setDevHeaders, type HandlerContext } from "./base-handler.js";
|
|
11
|
-
|
|
12
|
-
export class UIHandler extends BaseHandler {
|
|
13
|
-
constructor(context: HandlerContext) {
|
|
14
|
-
super(context);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
async handle(url: string, res: Response): Promise<void> {
|
|
18
|
-
const filePath = await this.resolveFilePath(url);
|
|
19
|
-
console.log(chalk.blue(`[.ui] ${url} → ${filePath}`));
|
|
20
|
-
|
|
21
|
-
try {
|
|
22
|
-
await fs.access(filePath);
|
|
23
|
-
} catch {
|
|
24
|
-
console.error(chalk.red(`[.ui] File not found: ${filePath}`));
|
|
25
|
-
throw new Error(`File not found: ${url} (resolved to: ${filePath})`);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
await this.compileAndServe(url, filePath, res, ".ui");
|
|
29
|
-
}
|
|
30
|
-
}
|
|
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 { BaseHandler, setDevHeaders, type HandlerContext } from "./base-handler.js";
|
|
11
|
+
|
|
12
|
+
export class UIHandler extends BaseHandler {
|
|
13
|
+
constructor(context: HandlerContext) {
|
|
14
|
+
super(context);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async handle(url: string, res: Response): Promise<void> {
|
|
18
|
+
const filePath = await this.resolveFilePath(url);
|
|
19
|
+
console.log(chalk.blue(`[.ui] ${url} → ${filePath}`));
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
await fs.access(filePath);
|
|
23
|
+
} catch {
|
|
24
|
+
console.error(chalk.red(`[.ui] File not found: ${filePath}`));
|
|
25
|
+
throw new Error(`File not found: ${url} (resolved to: ${filePath})`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
await this.compileAndServe(url, filePath, res, ".ui");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -1,21 +1,21 @@
|
|
|
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 chalk from "chalk";
|
|
9
|
-
import { BaseHandler, type HandlerContext } from "./base-handler.js";
|
|
10
|
-
|
|
11
|
-
export class UIXHandler extends BaseHandler {
|
|
12
|
-
constructor(context: HandlerContext) {
|
|
13
|
-
super(context);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
async handle(url: string, res: Response): Promise<void> {
|
|
17
|
-
const filePath = await this.resolveFilePath(url);
|
|
18
|
-
console.log(chalk.blue(`[.uix] ${url}`));
|
|
19
|
-
await this.compileAndServe(url, filePath, res, ".uix");
|
|
20
|
-
}
|
|
21
|
-
}
|
|
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 chalk from "chalk";
|
|
9
|
+
import { BaseHandler, type HandlerContext } from "./base-handler.js";
|
|
10
|
+
|
|
11
|
+
export class UIXHandler extends BaseHandler {
|
|
12
|
+
constructor(context: HandlerContext) {
|
|
13
|
+
super(context);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async handle(url: string, res: Response): Promise<void> {
|
|
17
|
+
const filePath = await this.resolveFilePath(url);
|
|
18
|
+
console.log(chalk.blue(`[.uix] ${url}`));
|
|
19
|
+
await this.compileAndServe(url, filePath, res, ".uix");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -1,122 +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
|
-
}
|
|
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
|
+
}
|