@vyckr/tachyon 1.1.11 → 1.2.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/.env.example +7 -4
- package/LICENSE +21 -0
- package/README.md +210 -90
- package/package.json +50 -33
- package/src/cli/bundle.ts +37 -0
- package/src/cli/serve.ts +100 -0
- package/src/{client/template.js → compiler/render-template.js} +10 -17
- package/src/compiler/template-compiler.ts +419 -0
- package/src/runtime/hot-reload-client.ts +15 -0
- package/src/{client/dev.html → runtime/shells/development.html} +2 -2
- package/src/runtime/shells/not-found.html +73 -0
- package/src/{client/prod.html → runtime/shells/production.html} +1 -1
- package/src/runtime/spa-renderer.ts +439 -0
- package/src/server/console-logger.ts +39 -0
- package/src/server/process-executor.ts +287 -0
- package/src/server/process-pool.ts +80 -0
- package/src/server/route-handler.ts +229 -0
- package/src/server/schema-validator.ts +161 -0
- package/bun.lock +0 -127
- package/components/clicker.html +0 -30
- package/deno.lock +0 -19
- package/go.mod +0 -3
- package/lib/gson-2.3.jar +0 -0
- package/main.js +0 -13
- package/routes/DELETE +0 -18
- package/routes/GET +0 -17
- package/routes/HTML +0 -135
- package/routes/POST +0 -32
- package/routes/SOCKET +0 -26
- package/routes/api/:version/DELETE +0 -10
- package/routes/api/:version/GET +0 -29
- package/routes/api/:version/PATCH +0 -24
- package/routes/api/GET +0 -29
- package/routes/api/POST +0 -16
- package/routes/api/PUT +0 -21
- package/src/client/404.html +0 -7
- package/src/client/dist.ts +0 -20
- package/src/client/hmr.ts +0 -12
- package/src/client/render.ts +0 -417
- package/src/client/routes.json +0 -1
- package/src/client/yon.ts +0 -364
- package/src/router.ts +0 -186
- package/src/serve.ts +0 -147
- package/src/server/logger.ts +0 -31
- package/src/server/tach.ts +0 -238
- package/tests/index.test.ts +0 -110
- package/tests/stream.ts +0 -24
- package/tests/worker.ts +0 -7
- package/tsconfig.json +0 -17
package/routes/api/GET
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env dotnet-script
|
|
2
|
-
#r "nuget: System.Text.Json, 9.0.0"
|
|
3
|
-
using System;
|
|
4
|
-
using System.Text.Json;
|
|
5
|
-
using System.Text.Json.Nodes;
|
|
6
|
-
using System.IO;
|
|
7
|
-
|
|
8
|
-
Console.WriteLine("Executing .NET Script....");
|
|
9
|
-
|
|
10
|
-
// Parse the JSON context
|
|
11
|
-
var ctx = JsonNode.Parse(Console.ReadLine())?.AsObject();
|
|
12
|
-
|
|
13
|
-
// Create a new empty JsonObject
|
|
14
|
-
var result = new JsonObject();
|
|
15
|
-
|
|
16
|
-
// Copy each property from ctx to result
|
|
17
|
-
foreach (var prop in ctx)
|
|
18
|
-
{
|
|
19
|
-
result[prop.Key] = JsonNode.Parse(prop.Value.ToJsonString());
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Add the message
|
|
23
|
-
result["message"] = "Hello from .NET Script!";
|
|
24
|
-
|
|
25
|
-
// Write to file
|
|
26
|
-
File.WriteAllText("/tmp/" + Environment.ProcessId, result.ToJsonString(new JsonSerializerOptions
|
|
27
|
-
{
|
|
28
|
-
WriteIndented = false
|
|
29
|
-
}));
|
package/routes/api/POST
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
import json
|
|
3
|
-
import sys
|
|
4
|
-
import os
|
|
5
|
-
|
|
6
|
-
print("Executing Python....")
|
|
7
|
-
|
|
8
|
-
ctx = json.loads(sys.stdin.read())
|
|
9
|
-
|
|
10
|
-
ctx["message"] = "Hello from Python!"
|
|
11
|
-
|
|
12
|
-
file = open(f"/tmp/{os.getpid()}", "w")
|
|
13
|
-
|
|
14
|
-
file.write(json.dumps(ctx))
|
|
15
|
-
|
|
16
|
-
file.close()
|
package/routes/api/PUT
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env php
|
|
2
|
-
<?php
|
|
3
|
-
|
|
4
|
-
while(FALSE !== ($line = fgets(STDIN))) {
|
|
5
|
-
|
|
6
|
-
$ctx = json_decode($line, true);
|
|
7
|
-
|
|
8
|
-
echo "Executing PHP...\n";
|
|
9
|
-
|
|
10
|
-
$ctx["message"] = "Hello from PHP!";
|
|
11
|
-
|
|
12
|
-
$pid = getmypid();
|
|
13
|
-
|
|
14
|
-
$file = fopen("/tmp/$pid", "w");
|
|
15
|
-
|
|
16
|
-
fwrite($file, json_encode($ctx));
|
|
17
|
-
|
|
18
|
-
fclose($file);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
?>
|
package/src/client/404.html
DELETED
package/src/client/dist.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
import Router from "../router.js";
|
|
3
|
-
import Yon from "./yon.js";
|
|
4
|
-
|
|
5
|
-
const start = Date.now()
|
|
6
|
-
|
|
7
|
-
await Yon.createStaticRoutes()
|
|
8
|
-
|
|
9
|
-
for(const route in Router.reqRoutes) {
|
|
10
|
-
|
|
11
|
-
if(route.includes('hmr')) continue
|
|
12
|
-
|
|
13
|
-
const res = await Router.reqRoutes[route][`GET`]()
|
|
14
|
-
|
|
15
|
-
await Bun.write(Bun.file(`${process.cwd()}/dist/${route}`), await res.blob())
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
await Bun.write(Bun.file(`${process.cwd()}/dist/index.html`), await Bun.file(`${import.meta.dir}/prod.html`).text())
|
|
19
|
-
|
|
20
|
-
console.log(`Built in ${Date.now() - start}ms`)
|
package/src/client/hmr.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
const url = new URL(window.location.href);
|
|
2
|
-
|
|
3
|
-
const ws = new WebSocket(`ws://localhost:9876${url.pathname}`);
|
|
4
|
-
|
|
5
|
-
ws.onopen = () => {
|
|
6
|
-
console.log('HMR Enabled');
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
ws.onmessage = (event) => {
|
|
10
|
-
console.log('HMR Update');
|
|
11
|
-
window.location.reload()
|
|
12
|
-
}
|
package/src/client/render.ts
DELETED
|
@@ -1,417 +0,0 @@
|
|
|
1
|
-
let render: Function
|
|
2
|
-
const routes = new Map<string, Record<string, number>>()
|
|
3
|
-
let params: any[] = []
|
|
4
|
-
const slugs: Record<string, any> = {}
|
|
5
|
-
let previousRender: string
|
|
6
|
-
let madeRequest: boolean = false
|
|
7
|
-
let elementId: string | null;
|
|
8
|
-
let selectionStart: number | null;
|
|
9
|
-
const parser = new DOMParser()
|
|
10
|
-
let firstRender = routes.size === 0
|
|
11
|
-
const elementEvents: Record<string, EventListener> = {}
|
|
12
|
-
|
|
13
|
-
if(firstRender) {
|
|
14
|
-
|
|
15
|
-
fetch('/routes.json')
|
|
16
|
-
.then(res => res.json())
|
|
17
|
-
.then(data => {
|
|
18
|
-
for (const [path, slugs] of Object.entries(data)) {
|
|
19
|
-
routes.set(path, slugs as Record<string, number>)
|
|
20
|
-
}
|
|
21
|
-
setPageTemplate(window.location.pathname)
|
|
22
|
-
firstRender = false
|
|
23
|
-
})
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function mergeBodyHTML(html: string) {
|
|
27
|
-
|
|
28
|
-
if(html === previousRender) return
|
|
29
|
-
|
|
30
|
-
previousRender = html
|
|
31
|
-
|
|
32
|
-
// document.body.innerHTML = html
|
|
33
|
-
|
|
34
|
-
if(madeRequest) {
|
|
35
|
-
document.body.innerHTML = html
|
|
36
|
-
} else {
|
|
37
|
-
const nextDom = parser.parseFromString(html, 'text/html')
|
|
38
|
-
updateDOM(document.body, nextDom.body)
|
|
39
|
-
deleteDOM(document.body, nextDom.body)
|
|
40
|
-
insertDOM(document.body, nextDom.body)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
addEvents()
|
|
44
|
-
|
|
45
|
-
if(elementId) {
|
|
46
|
-
|
|
47
|
-
const element = document.getElementById(elementId)
|
|
48
|
-
|
|
49
|
-
if(element) {
|
|
50
|
-
|
|
51
|
-
element.focus()
|
|
52
|
-
|
|
53
|
-
if(selectionStart && 'setSelectionRange' in element) {
|
|
54
|
-
|
|
55
|
-
(element as HTMLInputElement).setSelectionRange(selectionStart, selectionStart)
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
elementId = null;
|
|
60
|
-
selectionStart = null
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function addEvents(elements?: HTMLCollection) {
|
|
65
|
-
|
|
66
|
-
if(!elements) elements = document.body.children
|
|
67
|
-
|
|
68
|
-
for (const element of elements) {
|
|
69
|
-
|
|
70
|
-
const allEvents: string[] = []
|
|
71
|
-
|
|
72
|
-
for (const attribute of element.attributes) {
|
|
73
|
-
|
|
74
|
-
if (attribute.name.startsWith('@')) {
|
|
75
|
-
|
|
76
|
-
const event = attribute.name.substring(1)
|
|
77
|
-
|
|
78
|
-
allEvents.push(event)
|
|
79
|
-
|
|
80
|
-
if(elementEvents[`${element.id}_${event}`]) {
|
|
81
|
-
element.removeEventListener(event, elementEvents[`${element.id}_${event}`])
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
elementEvents[`${element.id}_${event}`] = async function(e) {
|
|
85
|
-
|
|
86
|
-
elementId = element.id
|
|
87
|
-
|
|
88
|
-
if (e.target) {
|
|
89
|
-
selectionStart = (e.target as HTMLInputElement).selectionStart;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
mergeBodyHTML(await render(elementId))
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
element.addEventListener(event, elementEvents[`${element.id}_${event}`])
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if(attribute.name.endsWith('ed') && attribute.value === "false") element.removeAttribute(attribute.name)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if(element instanceof HTMLSelectElement || element instanceof HTMLInputElement) {
|
|
102
|
-
|
|
103
|
-
element.oninput = async (ev) => {
|
|
104
|
-
if (ev.target) {
|
|
105
|
-
|
|
106
|
-
elementId = element.id
|
|
107
|
-
|
|
108
|
-
if (ev.target) {
|
|
109
|
-
selectionStart = (ev.target as HTMLInputElement).selectionStart;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const evt = { value: (ev.target as any).value, defaultValue: (ev.target as any).defaultValue }
|
|
113
|
-
|
|
114
|
-
mergeBodyHTML(await render(elementId, evt))
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
addEvents(element.children)
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
async function onClickEvent(ev: MouseEvent) {
|
|
124
|
-
const target = ev.target as HTMLAnchorElement;
|
|
125
|
-
if(target?.href) {
|
|
126
|
-
const url = new URL(target?.href)
|
|
127
|
-
if(url.origin !== location.origin) return
|
|
128
|
-
ev.preventDefault()
|
|
129
|
-
setPageTemplate(url.pathname)
|
|
130
|
-
} else mergeBodyHTML(await render())
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function setPageTemplate(pathname: string) {
|
|
134
|
-
|
|
135
|
-
let url;
|
|
136
|
-
|
|
137
|
-
try {
|
|
138
|
-
|
|
139
|
-
let handler = getHandler(pathname)
|
|
140
|
-
|
|
141
|
-
if(handler === '/') handler = ''
|
|
142
|
-
|
|
143
|
-
url = `${handler}/HTML.js`
|
|
144
|
-
|
|
145
|
-
} catch(err) {
|
|
146
|
-
url = `/404.js`
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
import(`/pages${url}`).then(async module => {
|
|
150
|
-
window.history.replaceState({}, '', pathname)
|
|
151
|
-
render = await module.default()
|
|
152
|
-
madeRequest = true
|
|
153
|
-
mergeBodyHTML(await render())
|
|
154
|
-
madeRequest = false
|
|
155
|
-
})
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
function getHandler(pathname: string) {
|
|
160
|
-
|
|
161
|
-
let handler;
|
|
162
|
-
|
|
163
|
-
if (pathname === '/') return pathname
|
|
164
|
-
|
|
165
|
-
const paths = pathname.split('/').slice(1);
|
|
166
|
-
|
|
167
|
-
let bestMatchKey = '';
|
|
168
|
-
let bestMatchLength = -1;
|
|
169
|
-
|
|
170
|
-
for (const [routeKey] of routes) {
|
|
171
|
-
|
|
172
|
-
const routeSegs = routeKey.split('/')
|
|
173
|
-
|
|
174
|
-
const isMatch = pathsMatch(routeSegs, paths.slice(0, routeSegs.length));
|
|
175
|
-
|
|
176
|
-
if (isMatch && routeSegs.length > bestMatchLength) {
|
|
177
|
-
bestMatchKey = routeKey;
|
|
178
|
-
bestMatchLength = routeSegs.length;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (bestMatchKey) {
|
|
183
|
-
|
|
184
|
-
handler = bestMatchKey
|
|
185
|
-
|
|
186
|
-
params = parseParams(paths.slice(bestMatchLength))
|
|
187
|
-
|
|
188
|
-
const slugMap = routes.get(bestMatchKey) ?? {}
|
|
189
|
-
|
|
190
|
-
Object.entries(slugMap).forEach(([key, idx]) => {
|
|
191
|
-
key = key.replace(':', '')
|
|
192
|
-
slugs[key] = paths[idx]
|
|
193
|
-
})
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if (!handler) throw new Error(`Route ${pathname} not found`, { cause: 404 });
|
|
197
|
-
|
|
198
|
-
return handler
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
function pathsMatch(routeSegs: string[], pathSegs: string[]) {
|
|
203
|
-
|
|
204
|
-
if (routeSegs.length !== pathSegs.length) {
|
|
205
|
-
return false;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const slugs = routes.get(routeSegs.join('/')) || {}
|
|
209
|
-
|
|
210
|
-
for (let i = 0; i < routeSegs.length; i++) {
|
|
211
|
-
if (!slugs[routeSegs[i]] && routeSegs[i] !== pathSegs[i]) {
|
|
212
|
-
return false;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
return true;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
function parseParams(input: string[]) {
|
|
221
|
-
|
|
222
|
-
const params = []
|
|
223
|
-
|
|
224
|
-
for(const param of input) {
|
|
225
|
-
|
|
226
|
-
const num = Number(param)
|
|
227
|
-
|
|
228
|
-
if(!Number.isNaN(num)) params.push(num)
|
|
229
|
-
|
|
230
|
-
else if(param === 'true') params.push(true)
|
|
231
|
-
|
|
232
|
-
else if(param === 'false') params.push(false)
|
|
233
|
-
|
|
234
|
-
else if(param === 'null') params.push(null)
|
|
235
|
-
|
|
236
|
-
else if(param === 'undefined') params.push(undefined)
|
|
237
|
-
|
|
238
|
-
else params.push(param)
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
return params
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
Object.keys(window).forEach(key => {
|
|
245
|
-
if(/^on/.test(key)) {
|
|
246
|
-
document.addEventListener(key.slice(2), async ev => {
|
|
247
|
-
|
|
248
|
-
switch(ev.type) {
|
|
249
|
-
case 'click':
|
|
250
|
-
await onClickEvent(ev as MouseEvent)
|
|
251
|
-
break
|
|
252
|
-
case 'popstate':
|
|
253
|
-
setPageTemplate(window.location.pathname)
|
|
254
|
-
break
|
|
255
|
-
default:
|
|
256
|
-
mergeBodyHTML(await render())
|
|
257
|
-
break
|
|
258
|
-
}
|
|
259
|
-
})
|
|
260
|
-
}
|
|
261
|
-
})
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Updates existing nodes in oldDOM with properties from newDOM
|
|
267
|
-
* @param {Node} oldDOM - The current DOM
|
|
268
|
-
* @param {Node} newDOM - The desired DOM state
|
|
269
|
-
*/
|
|
270
|
-
function updateDOM(oldDOM: Node, newDOM: Node): void {
|
|
271
|
-
// Skip if nodes are not of the same type
|
|
272
|
-
if (!areNodesOfSameType(oldDOM, newDOM)) {
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// If it's a text node, update the content
|
|
277
|
-
if (oldDOM.nodeType === Node.TEXT_NODE && newDOM.nodeType === Node.TEXT_NODE) {
|
|
278
|
-
if (oldDOM.textContent !== newDOM.textContent) {
|
|
279
|
-
oldDOM.textContent = newDOM.textContent;
|
|
280
|
-
}
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// If it's an element node, update attributes
|
|
285
|
-
if (oldDOM.nodeType === Node.ELEMENT_NODE && newDOM.nodeType === Node.ELEMENT_NODE) {
|
|
286
|
-
// Type assertion since we've already checked nodeType
|
|
287
|
-
const oldElement = oldDOM as Element;
|
|
288
|
-
const newElement = newDOM as Element;
|
|
289
|
-
|
|
290
|
-
// Update attributes
|
|
291
|
-
updateAttributes(oldElement, newElement);
|
|
292
|
-
|
|
293
|
-
// Recursively update children that exist in both DOMs
|
|
294
|
-
const oldChildren = Array.from(oldDOM.childNodes);
|
|
295
|
-
const newChildren = Array.from(newDOM.childNodes);
|
|
296
|
-
|
|
297
|
-
const minLength = Math.min(oldChildren.length, newChildren.length);
|
|
298
|
-
|
|
299
|
-
for (let i = 0; i < minLength; i++) {
|
|
300
|
-
updateDOM(oldChildren[i], newChildren[i]);
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* Deletes nodes from oldDOM that don't exist in newDOM
|
|
307
|
-
* @param {Node} oldDOM - The current DOM
|
|
308
|
-
* @param {Node} newDOM - The desired DOM state
|
|
309
|
-
*/
|
|
310
|
-
function deleteDOM(oldDOM: Node, newDOM: Node): void {
|
|
311
|
-
// If nodes are not of the same type, skip (will be handled by insertDOM)
|
|
312
|
-
if (!areNodesOfSameType(oldDOM, newDOM)) {
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// If it's an element node, check children
|
|
317
|
-
if (oldDOM.nodeType === Node.ELEMENT_NODE && newDOM.nodeType === Node.ELEMENT_NODE) {
|
|
318
|
-
const oldChildren = Array.from(oldDOM.childNodes);
|
|
319
|
-
const newChildren = Array.from(newDOM.childNodes);
|
|
320
|
-
|
|
321
|
-
// Remove children that exist in oldDOM but not in newDOM
|
|
322
|
-
// Start from the end to avoid index shifting issues
|
|
323
|
-
for (let i = oldChildren.length - 1; i >= newChildren.length; i--) {
|
|
324
|
-
oldDOM.removeChild(oldChildren[i]);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// Recursively delete for remaining children
|
|
328
|
-
const minLength = Math.min(oldChildren.length, newChildren.length);
|
|
329
|
-
for (let i = 0; i < minLength; i++) {
|
|
330
|
-
deleteDOM(oldChildren[i], newChildren[i]);
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* Inserts nodes from newDOM that don't exist in oldDOM
|
|
337
|
-
* @param {Node} oldDOM - The current DOM
|
|
338
|
-
* @param {Node} newDOM - The desired DOM state
|
|
339
|
-
*/
|
|
340
|
-
function insertDOM(oldDOM: Node, newDOM: Node): void {
|
|
341
|
-
// If nodes are not of the same type, replace the entire node
|
|
342
|
-
if (!areNodesOfSameType(oldDOM, newDOM)) {
|
|
343
|
-
if (oldDOM.parentNode) {
|
|
344
|
-
oldDOM.parentNode.replaceChild(newDOM.cloneNode(true), oldDOM);
|
|
345
|
-
}
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
// If it's an element node, check children
|
|
350
|
-
if (oldDOM.nodeType === Node.ELEMENT_NODE && newDOM.nodeType === Node.ELEMENT_NODE) {
|
|
351
|
-
const oldChildren = Array.from(oldDOM.childNodes);
|
|
352
|
-
const newChildren = Array.from(newDOM.childNodes);
|
|
353
|
-
|
|
354
|
-
// Add children that exist in newDOM but not in oldDOM
|
|
355
|
-
for (let i = oldChildren.length; i < newChildren.length; i++) {
|
|
356
|
-
oldDOM.appendChild(newChildren[i].cloneNode(true));
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// Recursively insert for existing children
|
|
360
|
-
const minLength = Math.min(oldChildren.length, newChildren.length);
|
|
361
|
-
for (let i = 0; i < minLength; i++) {
|
|
362
|
-
insertDOM(oldChildren[i], newChildren[i]);
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
/**
|
|
368
|
-
* Helper function to update attributes of an element
|
|
369
|
-
* @param {Element} oldElement - The element to update
|
|
370
|
-
* @param {Element} newElement - The element with the desired attributes
|
|
371
|
-
*/
|
|
372
|
-
function updateAttributes(oldElement: Element, newElement: Element): void {
|
|
373
|
-
// Remove attributes not in newElement
|
|
374
|
-
for (const attr of Array.from(oldElement.attributes)) {
|
|
375
|
-
if (!newElement.hasAttribute(attr.name)) {
|
|
376
|
-
oldElement.removeAttribute(attr.name);
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// Add or update attributes from newElement
|
|
381
|
-
for (const attr of Array.from(newElement.attributes)) {
|
|
382
|
-
// Use strict equality to ensure empty strings are properly handled
|
|
383
|
-
if (!attr.name.startsWith('@') &&
|
|
384
|
-
(oldElement.getAttribute(attr.name) !== attr.value ||
|
|
385
|
-
(!oldElement.hasAttribute(attr.name) && attr.value === ""))) {
|
|
386
|
-
if(oldElement.children.length === 0) {
|
|
387
|
-
oldElement.outerHTML = newElement.outerHTML
|
|
388
|
-
} else oldElement.setAttribute(attr.name, attr.value)
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
/**
|
|
393
|
-
* Helper function to check if two nodes are of the same type
|
|
394
|
-
* @param {Node} oldNode - The old node
|
|
395
|
-
* @param {Node} newNode - The new node
|
|
396
|
-
* @returns {boolean} - Whether the nodes are of the same type
|
|
397
|
-
*/
|
|
398
|
-
function areNodesOfSameType(oldNode: Node, newNode: Node): boolean {
|
|
399
|
-
// Check if both nodes are defined
|
|
400
|
-
if (!oldNode || !newNode) {
|
|
401
|
-
return false;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// Check if node types match
|
|
405
|
-
if (oldNode.nodeType !== newNode.nodeType) {
|
|
406
|
-
return false;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// For element nodes, check if tag names match
|
|
410
|
-
if (oldNode.nodeType === Node.ELEMENT_NODE && newNode.nodeType === Node.ELEMENT_NODE) {
|
|
411
|
-
const oldElement = oldNode as Element;
|
|
412
|
-
const newElement = newNode as Element;
|
|
413
|
-
return oldElement.tagName === newElement.tagName;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
return true;
|
|
417
|
-
}
|
package/src/client/routes.json
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"/api/:version":{":version":1},"/":{}}
|