@vyckr/tachyon 1.1.9 → 1.1.10
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/bun.lock +3 -3
- package/components/clicker.html +30 -0
- package/package.json +2 -2
- package/routes/HTML +123 -20
- package/src/client/render.ts +417 -0
- package/src/client/template.js +66 -0
- package/src/client/yon.ts +141 -128
- package/src/serve.ts +34 -34
- package/components/counter.html +0 -13
- package/src/client/render.js +0 -278
- /package/src/client/{hmr.js → hmr.ts} +0 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export default async function(props) {
|
|
2
|
+
|
|
3
|
+
// imports
|
|
4
|
+
|
|
5
|
+
// script
|
|
6
|
+
|
|
7
|
+
if(props) props.split(';').map(prop => eval(prop))
|
|
8
|
+
|
|
9
|
+
const compRenders = new Map()
|
|
10
|
+
|
|
11
|
+
return async function(elemId, event, compId) {
|
|
12
|
+
|
|
13
|
+
const elemIds = new Map()
|
|
14
|
+
|
|
15
|
+
elemIds.set('@', new Map())
|
|
16
|
+
elemIds.set('id', new Map())
|
|
17
|
+
elemIds.set('bind', new Map())
|
|
18
|
+
|
|
19
|
+
const ty_generateId = (hash, source) => {
|
|
20
|
+
|
|
21
|
+
hash = compId ? `${hash}-${compId}` : hash
|
|
22
|
+
|
|
23
|
+
if(elemIds.get(source).has(hash)) {
|
|
24
|
+
|
|
25
|
+
const degree = elemIds.get(source).get(hash)
|
|
26
|
+
|
|
27
|
+
elemIds.get(source).set(hash, degree + 1)
|
|
28
|
+
|
|
29
|
+
return "ty-" + hash + "-" + degree
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
elemIds.get(source).set(hash, 1)
|
|
33
|
+
|
|
34
|
+
return "ty-" + hash + "-0"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const ty_invokeEvent = (hash, action) => {
|
|
38
|
+
|
|
39
|
+
if(elemId === ty_generateId(hash, '@')) {
|
|
40
|
+
|
|
41
|
+
if(event && !action.endsWith(')')) {
|
|
42
|
+
return `${action}('${event}')`
|
|
43
|
+
}
|
|
44
|
+
return action
|
|
45
|
+
}
|
|
46
|
+
return "''"
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const ty_assignValue = (hash, variable) => {
|
|
50
|
+
|
|
51
|
+
if(elemId === ty_generateId(hash, 'bind') && event) {
|
|
52
|
+
return `${variable} = '${event.value}'`
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return variable
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let elements = '';
|
|
59
|
+
|
|
60
|
+
let render;
|
|
61
|
+
|
|
62
|
+
// inners
|
|
63
|
+
|
|
64
|
+
return elements
|
|
65
|
+
}
|
|
66
|
+
}
|
package/src/client/yon.ts
CHANGED
|
@@ -20,12 +20,16 @@ export default class Yon {
|
|
|
20
20
|
|
|
21
21
|
static async createStaticRoutes() {
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
const result = await Bun.build({
|
|
24
|
+
entrypoints: [`${import.meta.dir}/render.ts`, `${import.meta.dir}/hmr.ts`],
|
|
25
|
+
minify: true
|
|
26
|
+
})
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
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
|
+
}
|
|
29
33
|
}
|
|
30
34
|
|
|
31
35
|
Router.reqRoutes["/routes.json"] = {
|
|
@@ -54,158 +58,167 @@ export default class Yon {
|
|
|
54
58
|
return { html, script: html.querySelectorAll('script')[0] }
|
|
55
59
|
}
|
|
56
60
|
|
|
57
|
-
private static parseHTML(
|
|
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
|
+
})
|
|
58
80
|
|
|
59
|
-
const
|
|
81
|
+
const interpolateText = (textContext: string) => textContext.replace(/\{([^{}]+)\}/g, '${$1}').replace(/\{\{([^{}]+)\}\}/g, '{${$1}}')
|
|
60
82
|
|
|
61
|
-
for (const element of elements
|
|
83
|
+
for (const element of Array.from(elements)) {
|
|
62
84
|
|
|
63
|
-
if(element.tagName
|
|
85
|
+
if (element.tagName === "SCRIPT") {
|
|
86
|
+
continue; // Skip script tags as they're handled separately
|
|
87
|
+
}
|
|
64
88
|
|
|
65
|
-
|
|
89
|
+
if(element.tagName === 'STYLE') {
|
|
90
|
+
element.innerHTML = `@scope { ${element.innerHTML} }`
|
|
91
|
+
parsed.push({ element: `\`${element.outerHTML}\`` })
|
|
92
|
+
continue
|
|
93
|
+
}
|
|
66
94
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (attribute.name === ':if') parsed.push({ render: `if(${attribute.value}) {`});
|
|
75
|
-
if (attribute.name === ':else-if') parsed.push({ render: `else if(${attribute.value}) {`});
|
|
76
|
-
if (attribute.name === ':else') parsed.push({ render: `else {`});
|
|
77
|
-
} else {
|
|
78
|
-
|
|
79
|
-
const exports: string[] = []
|
|
80
|
-
|
|
81
|
-
const filepath = Yon.compMapping.get(component)
|
|
82
|
-
|
|
83
|
-
if(filepath) {
|
|
84
|
-
|
|
85
|
-
for(let i = 0; i < element.attributes.length; i++) {
|
|
86
|
-
|
|
87
|
-
if(element.attributes[i].name.startsWith(':')) {
|
|
88
|
-
const propName = element.attributes[i].name.slice(1)
|
|
89
|
-
exports.push(`${propName} = ${"${" + element.attributes[i].value + "}"}`)
|
|
90
|
-
} else {
|
|
91
|
-
const propName = element.attributes[i].name
|
|
92
|
-
exports.push(`${propName} = "${element.attributes[i].value}"`)
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if(imports.has(filepath)) {
|
|
97
|
-
|
|
98
|
-
if(!imports.get(filepath)?.has(component)) {
|
|
99
|
-
parsed.push({ static: `const { default: ${component} } = import('/components/${filepath}')`})
|
|
100
|
-
imports.get(filepath)?.add(component)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
} else {
|
|
104
|
-
|
|
105
|
-
parsed.push({ static: `const { default: ${component} } = await import('/components/${filepath}')`})
|
|
106
|
-
imports.set(filepath, new Set<string>([component]))
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const hash = Bun.randomUUIDv7().split('-')[1]
|
|
110
|
-
|
|
111
|
-
parsed.push({ static: `const comp_${hash} = await ${component}(\`${exports.join(';')}\`)`})
|
|
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) {
|
|
112
102
|
|
|
113
|
-
|
|
103
|
+
if(imports.has(filepath)) {
|
|
114
104
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
105
|
+
if(!imports.get(filepath)?.has(component)) {
|
|
106
|
+
parsed.push({ static: `const { default: ${component} } = import('/components/${filepath}')`})
|
|
107
|
+
imports.get(filepath)?.add(component)
|
|
118
108
|
}
|
|
109
|
+
|
|
110
|
+
} else {
|
|
111
|
+
|
|
112
|
+
parsed.push({ static: `const { default: ${component} } = await import('/components/${filepath}')`})
|
|
113
|
+
imports.set(filepath, new Set<string>([component]))
|
|
119
114
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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()}>\`` })
|
|
134
141
|
} else {
|
|
135
|
-
|
|
136
|
-
for(let i = 0; i < element.attributes.length; i++) {
|
|
137
|
-
|
|
138
|
-
const attr = element.attributes[i]
|
|
139
|
-
|
|
140
|
-
if(attr.name.startsWith(':')) {
|
|
141
|
-
|
|
142
|
-
const attrName = attr.name.slice(1)
|
|
143
|
-
|
|
144
|
-
element.removeAttribute(attr.name)
|
|
145
|
-
element.setAttribute(attrName, "${" + attr.value + "}")
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
parsed.push({ element: `\`${element.outerHTML}\`` })
|
|
142
|
+
parsed.push({ element: `\`<${element.tagName.toLowerCase()} ${parseAttrs(element.attributes, hash).join(" ")} />\`` })
|
|
150
143
|
}
|
|
151
144
|
}
|
|
152
145
|
}
|
|
153
|
-
|
|
154
|
-
return parsed
|
|
146
|
+
|
|
147
|
+
return parsed;
|
|
155
148
|
}
|
|
156
149
|
|
|
157
|
-
private static createJSData(html: { static?: string, render?: string, element?: string }[], scriptTag?: HTMLScriptElement) {
|
|
158
|
-
|
|
159
|
-
const hash = Bun.randomUUIDv7().split('-')[3]
|
|
150
|
+
private static async createJSData(html: { static?: string, render?: string, element?: string }[], scriptTag?: HTMLScriptElement) {
|
|
160
151
|
|
|
161
|
-
const outers: string[] = []
|
|
162
152
|
const inners: string[] = []
|
|
153
|
+
const outers: string[] = []
|
|
163
154
|
|
|
164
155
|
html.forEach(h => {
|
|
165
|
-
if(h.static) outers.push(h.static)
|
|
166
156
|
if(h.element) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
inners.push(`elements += ${temp.innerHTML}`)
|
|
157
|
+
if(h.element.includes('<ty-') || h.element.includes('</ty-')) {
|
|
158
|
+
inners.push(h.element)
|
|
159
|
+
} else inners.push(`elements+=${h.element}`)
|
|
171
160
|
}
|
|
172
|
-
if(h.
|
|
161
|
+
if(h.static) outers.push(h.static)
|
|
173
162
|
})
|
|
174
163
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
192
|
-
|
|
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
|
+
}
|
|
193
210
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
return elements
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
`
|
|
211
|
+
elements += '</div>'
|
|
212
|
+
`
|
|
213
|
+
})
|
|
214
|
+
|
|
202
215
|
}
|
|
203
216
|
|
|
204
217
|
private static async addToStatix(html: HTMLDivElement, script: HTMLScriptElement, route: string, dir: 'pages' | 'components') {
|
|
205
218
|
|
|
206
|
-
const module = Yon.parseHTML(html)
|
|
219
|
+
const module = Yon.parseHTML(html.children)
|
|
207
220
|
|
|
208
|
-
const jsData = Yon.createJSData(module, script)
|
|
221
|
+
const jsData = await Yon.createJSData(module, script)
|
|
209
222
|
|
|
210
223
|
route = route.replace('.html', `.${script?.lang || 'js'}`)
|
|
211
224
|
|
package/src/serve.ts
CHANGED
|
@@ -27,59 +27,59 @@ await configureRoutes()
|
|
|
27
27
|
|
|
28
28
|
const server = Bun.serve({
|
|
29
29
|
routes: Router.reqRoutes,
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
websocket: {
|
|
31
|
+
async open(ws: ServerWebSocket<WebSocketData>) {
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
const { handler, path } = ws.data
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
const proc = Bun.spawn({
|
|
36
|
+
cmd: [handler],
|
|
37
|
+
stdout: 'inherit',
|
|
38
|
+
stderr: "pipe",
|
|
39
|
+
stdin: "pipe"
|
|
40
|
+
})
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
Tach.webSockets.set(ws, proc)
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
console.info(`WebSocket Connected - ${path} - ${proc.pid}`)
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
for await(const ev of watch(`/tmp`)) {
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
if(ev.filename === proc.pid.toString()) {
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
const status = ws.send(Bun.mmap(`/tmp/${proc.pid}`))
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
console.info(`WebSocket Message Sent - ${path} - ${proc.pid} - ${status} byte(s)`)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
async message(ws: ServerWebSocket<WebSocketData>, message: string) {
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
const proc = Tach.webSockets.get(ws)!
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
const { ctx, path } = ws.data
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
ctx.body = message
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
proc.stdin.write(JSON.stringify(ctx))
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
proc.stdin.flush()
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
console.info(`WebSocket Message Received - ${path} - ${proc.pid} - ${message.length} byte(s)`)
|
|
69
|
+
},
|
|
70
|
+
close(ws, code, reason) {
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
const { path } = ws.data
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
const proc = Tach.webSockets.get(ws)!
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
proc.stdin.end()
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
Tach.webSockets.delete(ws)
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
console.info(`WebSocket Disconnected - ${path} - ${proc.pid} - Code (${code}): ${reason}`)
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
83
|
port: process.env.PORT || 8080,
|
|
84
84
|
hostname: process.env.HOSTNAME || '0.0.0.0',
|
|
85
85
|
development: process.env.NODE_ENV === 'development'
|