@vyckr/tachyon 1.1.10 → 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.
Files changed (49) hide show
  1. package/.env.example +7 -4
  2. package/LICENSE +21 -0
  3. package/README.md +210 -90
  4. package/package.json +50 -33
  5. package/src/cli/bundle.ts +37 -0
  6. package/src/cli/serve.ts +100 -0
  7. package/src/{client/template.js → compiler/render-template.js} +10 -17
  8. package/src/compiler/template-compiler.ts +419 -0
  9. package/src/runtime/hot-reload-client.ts +15 -0
  10. package/src/{client/dev.html → runtime/shells/development.html} +2 -2
  11. package/src/runtime/shells/not-found.html +73 -0
  12. package/src/{client/prod.html → runtime/shells/production.html} +1 -1
  13. package/src/runtime/spa-renderer.ts +439 -0
  14. package/src/server/console-logger.ts +39 -0
  15. package/src/server/process-executor.ts +287 -0
  16. package/src/server/process-pool.ts +80 -0
  17. package/src/server/route-handler.ts +229 -0
  18. package/src/server/schema-validator.ts +161 -0
  19. package/bun.lock +0 -127
  20. package/components/clicker.html +0 -30
  21. package/deno.lock +0 -19
  22. package/go.mod +0 -3
  23. package/lib/gson-2.3.jar +0 -0
  24. package/main.js +0 -13
  25. package/routes/DELETE +0 -18
  26. package/routes/GET +0 -17
  27. package/routes/HTML +0 -131
  28. package/routes/POST +0 -32
  29. package/routes/SOCKET +0 -26
  30. package/routes/api/:version/DELETE +0 -10
  31. package/routes/api/:version/GET +0 -29
  32. package/routes/api/:version/PATCH +0 -24
  33. package/routes/api/GET +0 -29
  34. package/routes/api/POST +0 -16
  35. package/routes/api/PUT +0 -21
  36. package/src/client/404.html +0 -7
  37. package/src/client/dist.ts +0 -20
  38. package/src/client/hmr.ts +0 -12
  39. package/src/client/render.ts +0 -417
  40. package/src/client/routes.json +0 -1
  41. package/src/client/yon.ts +0 -360
  42. package/src/router.ts +0 -186
  43. package/src/serve.ts +0 -147
  44. package/src/server/logger.ts +0 -31
  45. package/src/server/tach.ts +0 -238
  46. package/tests/index.test.ts +0 -110
  47. package/tests/stream.ts +0 -24
  48. package/tests/worker.ts +0 -7
  49. package/tsconfig.json +0 -17
package/src/client/yon.ts DELETED
@@ -1,360 +0,0 @@
1
- import { JSDOM } from 'jsdom'
2
- import Router from "../router.js";
3
- import { BunRequest } from 'bun';
4
- import { exists } from 'node:fs/promises';
5
-
6
- export default class Yon {
7
-
8
- private static htmlMethod = 'HTML'
9
-
10
- private static compMapping = new Map<string, string>()
11
-
12
- static getParams(request: BunRequest, route: string) {
13
-
14
- const url = new URL(request.url)
15
-
16
- const params = url.pathname.split('/').slice(route.split('/').length)
17
-
18
- return { params: Router.parseParams(params) }
19
- }
20
-
21
- static async createStaticRoutes() {
22
-
23
- const result = await Bun.build({
24
- entrypoints: [`${import.meta.dir}/render.ts`, `${import.meta.dir}/hmr.ts`],
25
- minify: true
26
- })
27
-
28
- for(const output of result.outputs) {
29
-
30
- Router.reqRoutes[output.path.replace('./', '/')] = {
31
- GET: async () => new Response(output, { headers: { 'Content-Type': 'application/javascript' } })
32
- }
33
- }
34
-
35
- Router.reqRoutes["/routes.json"] = {
36
- GET: async () => new Response(await Bun.file(`${import.meta.dir}/routes.json`).bytes(), { headers: { 'Content-Type': 'application/json' } })
37
- }
38
-
39
- const main = Bun.file(`${process.cwd()}/main.js`)
40
-
41
- if(await main.exists()) {
42
- Router.reqRoutes["/main.js"] = {
43
- GET: async () => new Response(await main.bytes(), { headers: { 'Content-Type': 'application/javascript' } })
44
- }
45
- }
46
-
47
- await Promise.all([Yon.bundleDependencies(), Yon.bundleComponents(), Yon.bundlePages(), Yon.bundleAssets()])
48
-
49
- await Bun.write(Bun.file(`${import.meta.dir}/routes.json`), JSON.stringify(Router.routeSlugs))
50
- }
51
-
52
- private static extractComponents(data: string) {
53
-
54
- const html = new JSDOM('').window.document.createElement('div')
55
-
56
- html.innerHTML = data
57
-
58
- return { html, script: html.querySelectorAll('script')[0] }
59
- }
60
-
61
- private static parseHTML(
62
- elements: HTMLCollection,
63
- imports: Map<string, Set<string>> = new Map<string, Set<string>>()
64
- ): Array<{ static?: string; render?: string; element?: string }> {
65
-
66
- const parsed: Array<{ static?: string; render?: string; element?: string }> = [];
67
-
68
- const parseAttrs = (attrs: NamedNodeMap, hash: string) => Array.from(attrs).map(attr => {
69
-
70
- if(attr.name.startsWith('@')) {
71
- return `${attr.name}="` + "${eval(ty_invokeEvent('" + hash + "', '" + attr.value + "'))}" + '"'
72
- }
73
-
74
- if(attr.name === ":value") {
75
- return `${attr.name.replace(':', '')}="` + "${eval(ty_assignValue('" + hash + "', '" + attr.value + "'))}" + '"'
76
- }
77
-
78
- return `${attr.name}="${attr.value}"`
79
- })
80
-
81
- const interpolateText = (textContext: string) => textContext.replace(/\{([^{}]+)\}/g, '${$1}').replace(/\{\{([^{}]+)\}\}/g, '{${$1}}')
82
-
83
- for (const element of Array.from(elements)) {
84
-
85
- if (element.tagName === "SCRIPT") {
86
- continue; // Skip script tags as they're handled separately
87
- }
88
-
89
- if(element.tagName === 'STYLE') {
90
- element.innerHTML = `@scope { ${element.innerHTML} }`
91
- parsed.push({ element: `\`${element.outerHTML}\`` })
92
- continue
93
- }
94
-
95
- if(element.tagName.startsWith('TY') && !element.tagName.endsWith('LOOP') && !element.tagName.endsWith('LOGIC')) {
96
-
97
- const component = element.tagName.split('-')[1].toLowerCase()
98
-
99
- const filepath = Yon.compMapping.get(component)
100
-
101
- if(filepath) {
102
-
103
- if(imports.has(filepath)) {
104
-
105
- if(!imports.get(filepath)?.has(component)) {
106
- parsed.push({ static: `const { default: ${component} } = import('/components/${filepath}')`})
107
- imports.get(filepath)?.add(component)
108
- }
109
-
110
- } else {
111
-
112
- parsed.push({ static: `const { default: ${component} } = await import('/components/${filepath}')`})
113
- imports.set(filepath, new Set<string>([component]))
114
- }
115
- }
116
- }
117
-
118
- const hash = Bun.randomUUIDv7().split('-')[3]
119
-
120
- if(!element.id && !element.tagName.startsWith('TY-')) {
121
- element.setAttribute(':id', "ty_generateId('" + hash + "', 'id')")
122
- }
123
-
124
- if(element.children.length > 0) {
125
-
126
- const text = Array.from(element.childNodes).reduce((a, b) => {
127
- return a + (b.nodeType === 3 ? b.textContent : '')
128
- }, '')
129
-
130
- parsed.push({ element: `\`<${element.tagName.toLowerCase()} ${parseAttrs(element.attributes, hash).join(" ")}>\`` })
131
- if(text) parsed.push({ element: `\`${interpolateText(text)}\`` })
132
- parsed.push(...this.parseHTML(element.children, imports))
133
- parsed.push({ element: `\`</${element.tagName.toLowerCase()}>\`` })
134
-
135
- } else {
136
-
137
- if(element.outerHTML.includes('</')) {
138
- parsed.push({ element: `\`<${element.tagName.toLowerCase()} ${parseAttrs(element.attributes, hash).join(" ")}>\`` })
139
- if(element.textContent) parsed.push({ element: `\`${interpolateText(element.textContent)}\`` })
140
- parsed.push({ element: `\`</${element.tagName.toLowerCase()}>\`` })
141
- } else {
142
- parsed.push({ element: `\`<${element.tagName.toLowerCase()} ${parseAttrs(element.attributes, hash).join(" ")} />\`` })
143
- }
144
- }
145
- }
146
-
147
- return parsed;
148
- }
149
-
150
- private static async createJSData(html: { static?: string, render?: string, element?: string }[], scriptTag?: HTMLScriptElement) {
151
-
152
- const inners: string[] = []
153
- const outers: string[] = []
154
-
155
- html.forEach(h => {
156
- if(h.element) {
157
- if(h.element.includes('<ty-') || h.element.includes('</ty-')) {
158
- inners.push(h.element)
159
- } else inners.push(`elements+=${h.element}`)
160
- }
161
- if(h.static) outers.push(h.static)
162
- })
163
-
164
- const tempFile = await Bun.file(`${import.meta.dir}/template.js`).text()
165
-
166
- return tempFile.replaceAll('// imports', outers.join('\n'))
167
- .replaceAll('// script', scriptTag?.innerHTML ?? '')
168
- .replaceAll('// inners', inners.join('\n'))
169
- .replaceAll(/`<ty-loop :for="(.*?)">`|`<\/ty-loop>`/g, (match, p1) => {
170
- if(p1) return `for(${p1}) {`
171
- else return '}'
172
- })
173
- .replaceAll(/`<ty-logic :if="(.*?)">`|`<\/ty-logic>`/g, (match, p1) => {
174
- if(p1) return `if(${p1}) {`
175
- else return '}'
176
- })
177
- .replaceAll(/`<ty-logic :else-if="(.*?)">`|`<\/ty-logic>`/g, (match, p1) => {
178
- if(p1) return `else if(${p1}) {`
179
- else return '}'
180
- })
181
- .replaceAll(/`<ty-logic :else="">`|`<\/ty-logic>`/g, (match, p1) => {
182
- if(p1) return `else {`
183
- else return '}'
184
- })
185
- .replaceAll(/:([^"]*)="([^"]*)"/g, '$1="${$2}"')
186
- .replaceAll(/`<\/ty-(\w+)\s*>`/g, '')
187
- .replaceAll(/`<ty-([a-zA-Z0-9-]+)(?:\s+([^>]*))>`/g, (match, component, atrributes) => {
188
-
189
- const matches = atrributes.matchAll(/([a-zA-Z0-9-]+)="([^"]*)"/g)
190
-
191
- const exports: string[] = []
192
-
193
- for(const [_, key, value] of matches) {
194
- exports.push(`${key}=${value}`)
195
- }
196
-
197
- const hash = Bun.randomUUIDv7().split('-')[3]
198
-
199
- return `
200
- elements += '<div>'
201
-
202
- if(!compRenders.has('${hash}')) {
203
- render = await ${component}(\`${exports.join(';')}\`)
204
- elements += await render(elemId, event, '${hash}')
205
- compRenders.set('${hash}', render)
206
- } else {
207
- render = compRenders.get('${hash}')
208
- elements += await render(elemId, event, '${hash}')
209
- }
210
-
211
- elements += '</div>'
212
- `
213
- })
214
-
215
- }
216
-
217
- private static async addToStatix(html: HTMLDivElement, script: HTMLScriptElement, route: string, dir: 'pages' | 'components') {
218
-
219
- const module = Yon.parseHTML(html.children)
220
-
221
- const jsData = await Yon.createJSData(module, script)
222
-
223
- route = route.replace('.html', `.${script?.lang || 'js'}`)
224
-
225
- await Bun.write(Bun.file(`/tmp/${route}`), jsData)
226
-
227
- const result = await Bun.build({
228
- entrypoints: [`/tmp/${route}`],
229
- external: ["*"],
230
- minify: {
231
- whitespace: true,
232
- syntax: true
233
- }
234
- })
235
-
236
- route = route.replace('.ts', '.js')
237
-
238
- Router.reqRoutes[`/${dir}/${route}`] = {
239
- GET: () => new Response(result.outputs[0], { headers: { 'Content-Type': 'application/javascript' } })
240
- }
241
- }
242
-
243
- private static async bundleAssets() {
244
-
245
- if(await exists(Router.assetsPath)) {
246
-
247
- const routes = Array.from(new Bun.Glob(`**/*`).scanSync({ cwd: Router.assetsPath }))
248
-
249
- for(const route of routes) {
250
-
251
- const file = Bun.file(`${Router.assetsPath}/${route}`)
252
-
253
- Router.reqRoutes[`/assets/${route}`] = {
254
- GET: async () => new Response(await file.bytes(), { headers: { 'Content-Type': file.type }})
255
- }
256
- }
257
- }
258
- }
259
-
260
- private static async bundlePages() {
261
-
262
- if(await exists(Router.routesPath)) {
263
-
264
- const routes = Array.from(new Bun.Glob(`**/${Yon.htmlMethod}`).scanSync({ cwd: Router.routesPath }))
265
-
266
- for(const route of routes) {
267
-
268
- await Router.validateRoute(route)
269
-
270
- const data = await Bun.file(`${Router.routesPath}/${route}`).text()
271
-
272
- const { html, script } = Yon.extractComponents(data)
273
-
274
- await Yon.addToStatix(html, script, `${route}.${script?.lang || 'js'}`, 'pages')
275
- }
276
- }
277
-
278
- const nfFile = Bun.file(`${process.cwd()}/404.html`)
279
-
280
- const data = await nfFile.exists() ? await nfFile.text() : await Bun.file(`${import.meta.dir}/404.html`).text()
281
-
282
- const { html, script } = Yon.extractComponents(data)
283
-
284
- await Yon.addToStatix(html, script, '404.html', 'pages')
285
- }
286
-
287
- private static async bundleComponents() {
288
-
289
- if(await exists(Router.componentsPath)) {
290
-
291
- const components = Array.from(new Bun.Glob(`**/*.html`).scanSync({ cwd: Router.componentsPath }))
292
-
293
- for(let comp of components) {
294
-
295
- const folders = comp.split('/')
296
-
297
- const filename = folders[folders.length - 1].replace('.html', '')
298
-
299
- Yon.compMapping.set(filename, comp.replace('.html', '.js'))
300
-
301
- const data = await Bun.file(`${Router.componentsPath}/${comp}`).text()
302
-
303
- const { html, script } = Yon.extractComponents(data)
304
-
305
- await Yon.addToStatix(html, script, comp, 'components')
306
- }
307
- }
308
- }
309
-
310
- private static async bundleDependencies() {
311
-
312
- const packageFile = Bun.file(`${process.cwd()}/package.json`)
313
-
314
- const otherEntries = ['index.js', 'index', 'index.node']
315
-
316
- if(await packageFile.exists()) {
317
-
318
- const packages = await packageFile.json()
319
-
320
- const modules = Object.keys(packages.dependencies ?? {})
321
-
322
- for(const module of modules) {
323
-
324
- let modPack = await Bun.file(`${process.cwd()}/node_modules/${module}/package.json`).json()
325
-
326
- let idx = 0
327
- let entryExists = false
328
-
329
- while(!modPack.main && !entryExists && idx < otherEntries.length) {
330
-
331
- entryExists = await Bun.file(`${process.cwd()}/node_modules/${module}/${otherEntries[idx]}`).exists()
332
-
333
- if(entryExists) {
334
- modPack.main = otherEntries[idx]
335
- break
336
- }
337
-
338
- idx++
339
- }
340
-
341
- if(!modPack.main) continue
342
-
343
- try {
344
-
345
- const result = await Bun.build({
346
- entrypoints: [`${process.cwd()}/node_modules/${module}/${(modPack.main as string).replace('./', '')}`],
347
- minify: true
348
- })
349
-
350
- for(const output of result.outputs) {
351
- Router.reqRoutes[`/modules/${module}.js`] = {
352
- GET: () => new Response(output, { headers: { 'Content-Type': 'application/javascript' } })
353
- }
354
- }
355
-
356
- } catch(e) {}
357
- }
358
- }
359
- }
360
- }
package/src/router.ts DELETED
@@ -1,186 +0,0 @@
1
- import { $, BunRequest, Server } from 'bun'
2
-
3
- export interface _ctx {
4
- request: Record<string, any>,
5
- slugs?: Record<string, any>,
6
- ipAddress: string,
7
- params?: Array<any>,
8
- query?: Record<string, any>,
9
- body: string
10
- }
11
-
12
- export default class Router {
13
-
14
- static readonly reqRoutes: Record<string, Record<string, (req?: BunRequest, server?: Server) => Promise<Response> | Response>> = {}
15
-
16
- static readonly allRoutes = new Map<string, Set<string>>()
17
-
18
- static readonly routeSlugs: Record<string, Record<string, number>> = {}
19
-
20
- static readonly routesPath = `${process.cwd()}/routes`
21
- static readonly componentsPath = `${process.cwd()}/components`
22
- static readonly assetsPath = `${process.cwd()}/assets`
23
-
24
- private static readonly allMethods = process.env.ALLOW_METHODS ? process.env.ALLOW_METHODS.split(',') : ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']
25
-
26
- static headers = {
27
- "Access-Control-Allow-Headers": process.env.ALLOW_HEADERS || "",
28
- "Access-Control-Allow-Origin": process.env.ALLLOW_ORGINS || "",
29
- "Access-Control-Allow-Credential": process.env.ALLOW_CREDENTIALS || "false",
30
- "Access-Control-Expose-Headers": process.env.ALLOW_EXPOSE_HEADERS || "",
31
- "Access-Control-Max-Age": process.env.ALLOW_MAX_AGE || "",
32
- "Access-Control-Allow-Methods": process.env.ALLOW_METHODS || ""
33
- }
34
-
35
- static async validateRoute(route: string, staticPaths: string[] = []) {
36
-
37
- const paths = route.split('/')
38
-
39
- const pattern = /^:.*/
40
-
41
- const slugs: Record<string, number> = {}
42
-
43
- if(pattern.test(paths[0])) throw new Error(`Invalid route ${route}`)
44
-
45
- paths.forEach((path, idx) => {
46
-
47
- if(pattern.test(path) && (pattern.test(paths[idx - 1]) || pattern.test(paths[idx + 1]))) {
48
- throw new Error(`Invalid route ${route}`)
49
- }
50
-
51
- if(pattern.test(path)) slugs[path] = idx
52
- })
53
-
54
- const staticPath = paths.filter((path) => !pattern.test(path)).join(',')
55
-
56
- if(staticPaths.includes(staticPath)) throw new Error(`Duplicate route ${route}`)
57
-
58
- staticPaths.push(staticPath)
59
-
60
- await $`chmod +x ${Router.routesPath}/${route}`
61
-
62
- const method = paths.pop()!
63
-
64
- route = `/${paths.join('/')}`
65
-
66
- if(!Router.allRoutes.has(route)) Router.allRoutes.set(route, new Set<string>())
67
-
68
- Router.allRoutes.get(route)?.add(method)
69
-
70
- if(Object.keys(slugs).length > 0 || method === 'HTML') Router.routeSlugs[route] = slugs
71
- }
72
-
73
- static parseRequest(request: BunRequest) {
74
-
75
- const req: Record<string, any> = {}
76
-
77
- req.headers = Object.fromEntries(request.headers)
78
- req.cache = request.cache
79
- req.credentials = request.credentials
80
- req.destination = request.destination
81
- req.integrity = request.integrity
82
- req.keepalive = request.keepalive
83
- req.method = request.method
84
- req.mode = request.mode
85
- req.redirect = request.redirect
86
- req.referrer = request.referrer
87
- req.referrerPolicy = request.referrerPolicy
88
- req.url = request.url
89
- req.parans = request.params
90
-
91
- return req
92
- }
93
-
94
- static processRequest(request: BunRequest, route: string, ctx: _ctx) {
95
-
96
- const { params } = Router.getParams(request, route)
97
-
98
- ctx.slugs = request.params
99
-
100
- const searchParams = new URL(request.url).searchParams
101
-
102
- let queryParams: Record<string, any> | undefined;
103
-
104
- if(searchParams.size > 0) queryParams = Router.parseKVParams(searchParams)
105
-
106
- ctx.params = params
107
- ctx.query = queryParams
108
-
109
- return { handler: `${Router.routesPath}${route}/${request.method}`, ctx }
110
- }
111
-
112
- private static getParams(request: BunRequest, route: string) {
113
-
114
- const url = new URL(request.url)
115
-
116
- const params = url.pathname.split("/").slice(route.split("/").length)
117
-
118
- return { params: Router.parseParams(params) }
119
- }
120
-
121
- static parseParams(input: string[]) {
122
-
123
- const params: (string | boolean | number | null | undefined)[] = []
124
-
125
- for(const param of input) {
126
-
127
- const num = Number(param)
128
-
129
- if(!Number.isNaN(num)) params.push(num)
130
-
131
- else if(param === 'true') params.push(true)
132
-
133
- else if(param === 'false') params.push(false)
134
-
135
- else if(param === 'null') params.push(null)
136
-
137
- else if(param === 'undefined') params.push(undefined)
138
-
139
- else params.push(param)
140
- }
141
-
142
- return params
143
- }
144
-
145
- private static parseKVParams(input: URLSearchParams | FormData) {
146
-
147
- const params: Record<string, any> = {}
148
-
149
- for(const [key, val] of input) {
150
-
151
- if(typeof val === "string") {
152
-
153
- try {
154
-
155
- params[key] = JSON.parse(val)
156
-
157
- } catch {
158
-
159
- const num = Number(val)
160
-
161
- if(!Number.isNaN(num)) params[key] = num
162
-
163
- else if(val === 'true') params[key] = true
164
-
165
- else if(val === 'false') params[key] = false
166
-
167
- else if(typeof val === "string" && val.includes(',')) params[key] = Router.parseParams(val.split(','))
168
-
169
- else if(val === 'null') params[key] = null
170
-
171
- if(params[key] === undefined) params[key] = val
172
- }
173
-
174
- } else params[key] = val
175
- }
176
-
177
- return params
178
- }
179
-
180
- static async validateRoutes() {
181
-
182
- const routes = Array.from(new Bun.Glob(`**/{${Router.allMethods.join(',')},SOCKET}`).scanSync({ cwd: Router.routesPath }))
183
-
184
- for(const route of routes) await Router.validateRoute(route)
185
- }
186
- }
package/src/serve.ts DELETED
@@ -1,147 +0,0 @@
1
- #!/usr/bin/env bun
2
- import Tach from "./server/tach.js"
3
- import Router, { _ctx } from "./router.js"
4
- import Yon from "./client/yon.js"
5
- import { Logger } from "./server/logger.js"
6
- import { ServerWebSocket } from "bun"
7
- import { watch, exists } from "fs/promises"
8
- import { watch as watcher } from "node:fs";
9
-
10
- type WebSocketData = {
11
- handler: string,
12
- ctx: _ctx,
13
- path: string
14
- }
15
-
16
- const start = Date.now()
17
-
18
- Logger()
19
-
20
- async function configureRoutes() {
21
- await Router.validateRoutes()
22
- Tach.createServerRoutes()
23
- await Yon.createStaticRoutes()
24
- }
25
-
26
- await configureRoutes()
27
-
28
- const server = Bun.serve({
29
- routes: Router.reqRoutes,
30
- websocket: {
31
- async open(ws: ServerWebSocket<WebSocketData>) {
32
-
33
- const { handler, path } = ws.data
34
-
35
- const proc = Bun.spawn({
36
- cmd: [handler],
37
- stdout: 'inherit',
38
- stderr: "pipe",
39
- stdin: "pipe"
40
- })
41
-
42
- Tach.webSockets.set(ws, proc)
43
-
44
- console.info(`WebSocket Connected - ${path} - ${proc.pid}`)
45
-
46
- for await(const ev of watch(`/tmp`)) {
47
-
48
- if(ev.filename === proc.pid.toString()) {
49
-
50
- const status = ws.send(Bun.mmap(`/tmp/${proc.pid}`))
51
-
52
- console.info(`WebSocket Message Sent - ${path} - ${proc.pid} - ${status} byte(s)`)
53
- }
54
- }
55
- },
56
- async message(ws: ServerWebSocket<WebSocketData>, message: string) {
57
-
58
- const proc = Tach.webSockets.get(ws)!
59
-
60
- const { ctx, path } = ws.data
61
-
62
- ctx.body = message
63
-
64
- proc.stdin.write(JSON.stringify(ctx))
65
-
66
- proc.stdin.flush()
67
-
68
- console.info(`WebSocket Message Received - ${path} - ${proc.pid} - ${message.length} byte(s)`)
69
- },
70
- close(ws, code, reason) {
71
-
72
- const { path } = ws.data
73
-
74
- const proc = Tach.webSockets.get(ws)!
75
-
76
- proc.stdin.end()
77
-
78
- Tach.webSockets.delete(ws)
79
-
80
- console.info(`WebSocket Disconnected - ${path} - ${proc.pid} - Code (${code}): ${reason}`)
81
- },
82
- },
83
- port: process.env.PORT || 8080,
84
- hostname: process.env.HOSTNAME || '0.0.0.0',
85
- development: process.env.NODE_ENV === 'development'
86
- })
87
-
88
- if(server.development) {
89
-
90
- let timeout: Timer
91
-
92
- let websocket: ServerWebSocket<unknown>;
93
-
94
- const socket = Bun.serve({
95
- fetch(req) {
96
- socket.upgrade(req)
97
- return undefined
98
- },
99
- websocket: {
100
- async open(ws) {
101
- console.info("HMR Enabled")
102
- websocket = ws
103
- },
104
- message(ws, message) {
105
-
106
- },
107
- close(ws, code, reason) {
108
- console.info(`HMR Closed ${code} ${reason}`)
109
- },
110
- },
111
- port: 9876
112
- })
113
-
114
- if(await exists(Router.routesPath)) {
115
-
116
- watcher(Router.routesPath, { recursive: true }, () => {
117
-
118
- if(timeout) clearTimeout(timeout)
119
-
120
- timeout = setTimeout(async () => {
121
- console.info("HMR Update")
122
- await configureRoutes()
123
- server.reload({ routes: Router.reqRoutes })
124
- if(websocket) websocket.send('')
125
- }, 1500)
126
- })
127
- }
128
-
129
- if(await exists(Router.componentsPath)) {
130
-
131
- watcher(Router.componentsPath, { recursive: true }, () => {
132
-
133
- if(timeout) clearTimeout(timeout)
134
-
135
- timeout = setTimeout(async () => {
136
- console.info("HMR Update")
137
- await configureRoutes()
138
- server.reload({ routes: Router.reqRoutes })
139
- if(websocket) websocket.send('')
140
- }, 1500)
141
- })
142
- }
143
- }
144
-
145
- const elapsed = Date.now() - start
146
-
147
- console.info(`Live Server is running on http://${server.hostname}:${server.port} (Press CTRL+C to quit) - ${elapsed.toFixed(2)}ms`)