tw-framework 1.0.0 → 1.0.1

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.
@@ -0,0 +1,452 @@
1
+ /**
2
+ * TW Framework -- client runtime v2 (~5KB, no dependencies).
3
+ *
4
+ * Loaded on every TW page. Provides:
5
+ * 1. Reactive state (seeded from <script id="__tw_state">)
6
+ * 2. Event delegation for [data-tw-event-*] handlers (.prevent/.stop)
7
+ * 3. Live interpolation spans [data-tw-i]
8
+ * 4. Two-way input binding [data-tw-model]
9
+ * 5. Conditional blocks [data-tw-if] / [data-tw-not]
10
+ * 6. Live list rendering <tw-for data-tw-var data-tw-list data-tw-tpl>
11
+ * 7. SPA client-side router (link interception, prefetch, popstate)
12
+ */
13
+ (function () {
14
+ "use strict";
15
+
16
+ var stateEl = document.getElementById("__tw_state");
17
+ var state = {};
18
+ try { state = stateEl ? JSON.parse(stateEl.textContent) : {}; } catch (e) { state = {}; }
19
+
20
+ // Strict symbol scope (spec): only the page's OWN imports are exposed
21
+ // via __twClient.$page; layout imports carry dependency, not symbols.
22
+ var twClientScope = function () {
23
+ try { var c = globalThis.__twClient; return (c && c.$page) || {}; } catch (e) { return {}; }
24
+ };
25
+
26
+ function evalExpr(expr) {
27
+ try {
28
+ return (new Function("state", "tw", "with (tw) { with (state) { return (" + expr + "); } }"))(state, twClientScope());
29
+ } catch (e) {
30
+ return undefined;
31
+ }
32
+ }
33
+
34
+ function runHandler(code) {
35
+ try {
36
+ (new Function("state", "event", "tw", "with (tw) { with (state) { " + code + "; } }"))(state, window.__twEvent || {}, twClientScope());
37
+ } catch (e) {
38
+ console.error("[tw] handler error:", code, e.message);
39
+ }
40
+ }
41
+
42
+ function setSpanText(el, expr) {
43
+ var val = evalExpr(expr);
44
+ var txt = val === undefined || val === null ? "" : String(val);
45
+ if (el.textContent !== txt) el.textContent = txt;
46
+ }
47
+
48
+ // --- Conditional blocks -----------------------------------------------------
49
+
50
+ function refreshIfs(scope) {
51
+ var ifs = (scope || document).querySelectorAll("[data-tw-if]");
52
+ for (var i = 0; i < ifs.length; i++) {
53
+ var el = ifs[i];
54
+ var val = evalExpr(el.getAttribute("data-tw-if"));
55
+ var show = !!val && val !== "false" && val !== "0" && val !== "";
56
+ el.style.display = show ? "" : "none";
57
+ }
58
+ var nots = (scope || document).querySelectorAll("[data-tw-not]");
59
+ for (var j = 0; j < nots.length; j++) {
60
+ var el2 = nots[j];
61
+ var val2 = evalExpr(el2.getAttribute("data-tw-not"));
62
+ var hide = !!val2 && val2 !== "false" && val2 !== "0" && val2 !== "";
63
+ el2.style.display = hide ? "none" : "";
64
+ }
65
+ }
66
+
67
+ // --- Live lists (<tw-for>) ---------------------------------------------------
68
+
69
+ function renderTemplateString(tpl, loopVar, itemRef) {
70
+ // Evaluate all data-tw-i spans in the template HTML string, resolve
71
+ // nested conditionals, and substitute the loop variable inside event
72
+ // handlers so `todos.filter(x => x !== t)` works per item.
73
+ var holder = document.createElement("tw-render");
74
+ holder.innerHTML = tpl;
75
+ var spans = holder.querySelectorAll("[data-tw-i]");
76
+ for (var i = 0; i < spans.length; i++) setSpanText(spans[i], spans[i].getAttribute("data-tw-i"));
77
+
78
+ // Nested if/not inside the loop body (evaluated while loopVar is bound)
79
+ var all = holder.querySelectorAll("*");
80
+ for (var a = 0; a < all.length; a++) {
81
+ var el = all[a];
82
+ var ifAttr = el.getAttribute && el.getAttribute("data-tw-if");
83
+ if (ifAttr !== null && ifAttr !== undefined && el.hasAttribute("data-tw-if")) {
84
+ var v1 = evalExpr(ifAttr);
85
+ el.style.display = (v1 && v1 !== "false" && v1 !== "0" && v1 !== "") ? "" : "none";
86
+ }
87
+ var notAttr = el.getAttribute("data-tw-not");
88
+ if (notAttr !== null && notAttr !== undefined && el.hasAttribute("data-tw-not")) {
89
+ var v2 = evalExpr(notAttr);
90
+ el.style.display = (v2 && v2 !== "false" && v2 !== "0" && v2 !== "") ? "none" : "";
91
+ }
92
+ }
93
+
94
+ // Substitute the loop variable inside event handler expressions.
95
+ // itemRef is a state key bound to the ACTUAL array item (identity is
96
+ // preserved, so `todos.filter(x => x !== t)` works by reference).
97
+ if (loopVar && itemRef) {
98
+ var re = new RegExp("\\b" + loopVar + "\\b", "g");
99
+ for (var e2 = 0; e2 < all.length; e2++) {
100
+ var el2 = all[e2];
101
+ if (!el2.attributes) continue;
102
+ for (var ai = 0; ai < el2.attributes.length; ai++) {
103
+ var attr = el2.attributes[ai];
104
+ if (attr.name && attr.name.indexOf("data-tw-event-") === 0 && attr.name.indexOf("-prevent") === -1 && attr.name.indexOf("-stop") === -1) {
105
+ var nv = attr.value.replace(re, itemRef);
106
+ if (nv !== attr.value) el2.setAttribute(attr.name, nv);
107
+ }
108
+ }
109
+ }
110
+ }
111
+
112
+ var out = holder.innerHTML;
113
+ return out;
114
+ }
115
+
116
+ var twItemSeq = 0;
117
+
118
+ function refreshLoops() {
119
+ var loops = document.querySelectorAll("tw-for[data-tw-list]");
120
+ for (var i = 0; i < loops.length; i++) {
121
+ var el = loops[i];
122
+ var arr = evalExpr(el.getAttribute("data-tw-list"));
123
+ var v = el.getAttribute("data-tw-var");
124
+ var tpl = el.getAttribute("data-tw-tpl") || "";
125
+ if (!Array.isArray(arr)) arr = [];
126
+ // clean refs from the previous render
127
+ if (el.__twRefs) {
128
+ for (var r = 0; r < el.__twRefs.length; r++) delete state[el.__twRefs[r]];
129
+ }
130
+ el.__twRefs = [];
131
+ var html = "";
132
+ for (var k = 0; k < arr.length; k++) {
133
+ var ref = "__tw_item_" + (++twItemSeq);
134
+ state[ref] = arr[k];
135
+ el.__twRefs.push(ref);
136
+ state[v] = arr[k];
137
+ html += renderTemplateString(tpl, v, ref);
138
+ }
139
+ delete state[v];
140
+ if (el.__twLen !== arr.length + ":" + html.length || el.__twHtml !== html) {
141
+ el.innerHTML = html;
142
+ el.__twLen = arr.length + ":" + html.length;
143
+ el.__twHtml = html;
144
+ }
145
+ }
146
+ }
147
+
148
+ // --- Full refresh --------------------------------------------------------
149
+
150
+ function refresh() {
151
+ try { applyActiveClass(); } catch (e) { /* non-DOM context */ }
152
+ try { scanPrefetch(); } catch (e) { /* non-DOM context */ }
153
+ var spans = document.querySelectorAll("[data-tw-i]");
154
+ for (var i = 0; i < spans.length; i++) setSpanText(spans[i], spans[i].getAttribute("data-tw-i"));
155
+ var inputs = document.querySelectorAll("[data-tw-model]");
156
+ for (var j = 0; j < inputs.length; j++) {
157
+ var key = inputs[j].getAttribute("data-tw-model");
158
+ if (key in state) {
159
+ var v = state[key];
160
+ if (String(inputs[j].value) !== String(v)) inputs[j].value = v;
161
+ }
162
+ }
163
+ refreshLoops();
164
+ refreshIfs();
165
+ }
166
+
167
+ // --- Event delegation ----------------------------------------------------
168
+
169
+ var EVENTS = ["click", "dblclick", "input", "change", "submit", "keydown",
170
+ "keyup", "keypress", "focus", "blur", "mouseenter", "mouseleave", "wheel"];
171
+
172
+ EVENTS.forEach(function (ev) {
173
+ document.addEventListener(ev, function (e) {
174
+ var t = e.target;
175
+ if (!t || !t.closest) return;
176
+ var host = t.closest("[data-tw-event-" + ev + "]");
177
+ if (!host) return;
178
+ var code = host.getAttribute("data-tw-event-" + ev);
179
+ if (!code) return;
180
+ if (host.getAttribute("data-tw-event-" + ev + "-prevent") === "true") e.preventDefault();
181
+ if (host.getAttribute("data-tw-event-" + ev + "-stop") === "true") e.stopPropagation();
182
+ window.__twEvent = { target: t, value: t.value, type: ev };
183
+ runHandler(code);
184
+ refresh();
185
+ }, true);
186
+ });
187
+
188
+ // Two-way: input with data-tw-model writes to state
189
+ document.addEventListener("input", function (e) {
190
+ var t = e.target;
191
+ if (t && t.getAttribute && t.getAttribute("data-tw-model")) {
192
+ state[t.getAttribute("data-tw-model")] = t.value;
193
+ refresh();
194
+ }
195
+ });
196
+
197
+ // --- SPA client-side router ----------------------------------------------
198
+
199
+ var prefetched = {};
200
+
201
+ function sameOrigin(href) {
202
+ return href.charAt(0) === "/" && href.charAt(1) !== "/";
203
+ }
204
+
205
+ function swapPage(html, path) {
206
+ var newDoc = null;
207
+ try {
208
+ newDoc = new DOMParser().parseFromString(html, "text/html");
209
+ } catch (e) { newDoc = null; }
210
+
211
+ // Swap roots must stay symmetric: a page compiled with an interactive
212
+ // root ([data-tw-root]) swaps that node only; a fully static page (no
213
+ // interactive root) swaps the whole <body>, so layout chrome that only
214
+ // exists on the incoming page is not lost.
215
+ var liveRoot = document.querySelector("[data-tw-root]");
216
+ var newRoot = newDoc ? (liveRoot ? (newDoc.querySelector("[data-tw-root]") || newDoc.body) : newDoc.body) : null;
217
+ var oldRoot = liveRoot || document.body;
218
+ if (!newRoot) { window.location.href = path; return; }
219
+
220
+ // Merge new state into the live state object (keep identity)
221
+ var seedEl = newDoc ? newDoc.getElementById("__tw_state") : null;
222
+ if (seedEl) {
223
+ try {
224
+ var fresh = JSON.parse(seedEl.textContent);
225
+ for (var k in state) delete state[k];
226
+ for (var k2 in fresh) state[k2] = fresh[k2];
227
+ } catch (e) { /* keep state */ }
228
+ }
229
+ // The new page may stream signals (or may not) -- re-evaluate the
230
+ // connection and derived specs against the swapped-in DOM.
231
+ loadDerivedSpecs();
232
+ recomputeDerived();
233
+ connectSignalStream();
234
+
235
+ // Sync route stylesheets: SPA navigation must load any stylesheet the
236
+ // incoming page links that the live document does not have yet
237
+ // (route-split css assets). Links already present are kept as-is.
238
+ try {
239
+ var newLinks = newDoc ? newDoc.querySelectorAll('link[rel="stylesheet"]') : [];
240
+ var have = {};
241
+ var cur = document.querySelectorAll('link[rel="stylesheet"]');
242
+ for (var ci = 0; ci < cur.length; ci++) have[cur[ci].getAttribute("href")] = true;
243
+ for (var ni = 0; ni < newLinks.length; ni++) {
244
+ var href = newLinks[ni].getAttribute("href");
245
+ if (href && !have[href]) {
246
+ var l = document.createElement("link");
247
+ l.setAttribute("rel", "stylesheet");
248
+ l.setAttribute("href", href);
249
+ document.head.appendChild(l);
250
+ }
251
+ }
252
+ } catch (e) { /* stylesheet sync is best-effort on SPA swap */ }
253
+
254
+ // Keep the live <body> element (preserves document.body identity and
255
+ // any listeners bound to it); swap its content instead of replacing it.
256
+ if (oldRoot === document.body) {
257
+ oldRoot.innerHTML = newRoot.innerHTML;
258
+ } else {
259
+ var imported = document.importNode ? document.importNode(newRoot, true) : newRoot.cloneNode(true);
260
+ if (oldRoot.replaceWith) oldRoot.replaceWith(imported);
261
+ else oldRoot.innerHTML = newRoot.innerHTML;
262
+ }
263
+
264
+ var t = newDoc ? newDoc.querySelector("title") : null;
265
+ if (t) document.title = t.textContent;
266
+
267
+ // re-run scripts inside the new root? (SSR pages usually have none)
268
+ refresh();
269
+ }
270
+
271
+ function navigate(path, push) {
272
+ fetch(path, { headers: { "X-TW-Navigate": "1" } })
273
+ .then(function (r) { return r.text(); })
274
+ .then(function (html) {
275
+ swapPage(html, path);
276
+ if (push !== false) {
277
+ try { history.pushState({ tw: 1 }, "", path); } catch (e) { /* ignore */ }
278
+ }
279
+ window.scrollTo(0, 0);
280
+ })
281
+ .catch(function () { window.location.href = path; });
282
+ }
283
+
284
+ document.addEventListener("click", function (e) {
285
+ if (e.defaultPrevented || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0) return;
286
+ var a = e.target.closest ? e.target.closest("a[href]") : null;
287
+ if (!a) return;
288
+ var href = a.getAttribute("href");
289
+ if (!href || !sameOrigin(href) || href.startsWith("/api/")) return;
290
+ if (a.hasAttribute("data-tw-native") || a.target === "_blank") return;
291
+ e.preventDefault();
292
+ navigate(href, true);
293
+ });
294
+
295
+ window.addEventListener("popstate", function () {
296
+ navigate(window.location.pathname + window.location.search, false);
297
+ });
298
+
299
+ // Prefetch on hover (warms the HTTP cache)
300
+ document.addEventListener("mouseover", function (e) {
301
+ var a = e.target.closest ? e.target.closest("a[href]") : null;
302
+ if (!a) return;
303
+ var href = a.getAttribute("href");
304
+ if (!href || !sameOrigin(href) || prefetched[href]) return;
305
+ if (a.hasAttribute("data-tw-no-prefetch")) return;
306
+ twPrefetch(href);
307
+ }, true);
308
+
309
+ // RouterLink prefetch strategies. The hover handler below covers the
310
+ // default; these cover the explicit ones:
311
+ // data-tw-prefetch="always" -> fetched immediately on load
312
+ // data-tw-prefetch="viewport" -> fetched when the link scrolls into view
313
+ // data-tw-no-prefetch -> never fetched
314
+ function twPrefetch(href) {
315
+ if (!href || !sameOrigin(href) || prefetched[href]) return;
316
+ prefetched[href] = true;
317
+ try { fetch(href).catch(function () {}); } catch (e) { /* ignore */ }
318
+ }
319
+
320
+ function scanPrefetch() {
321
+ var now = document.querySelectorAll('a[data-tw-prefetch="always"]');
322
+ for (var i = 0; i < now.length; i++) twPrefetch(now[i].getAttribute("href"));
323
+
324
+ var vp = document.querySelectorAll('a[data-tw-prefetch="viewport"]');
325
+ for (var j = 0; j < vp.length; j++) {
326
+ var a = vp[j];
327
+ if (a.__twObserved) continue;
328
+ a.__twObserved = true;
329
+ if (typeof IntersectionObserver === "undefined") {
330
+ twPrefetch(a.getAttribute("href"));
331
+ continue;
332
+ }
333
+ try {
334
+ var io = new IntersectionObserver(function (entries, obs) {
335
+ for (var k = 0; k < entries.length; k++) {
336
+ if (entries[k].isIntersecting) {
337
+ var el = entries[k].target;
338
+ el.__twViewportDone = true;
339
+ twPrefetch(el.getAttribute("href"));
340
+ obs.unobserve(el);
341
+ }
342
+ }
343
+ }, { rootMargin: "200px" });
344
+ io.observe(a);
345
+ } catch (e) { twPrefetch(a.getAttribute("href")); }
346
+ }
347
+ }
348
+
349
+ // RouterLink active-class: highlight the link whose route is current.
350
+ // Exact match wins; "/blog" also matches "/blog/post-1" (prefix).
351
+ function applyActiveClass() {
352
+ var links = document.querySelectorAll("a[data-tw-active-class]");
353
+ var path = window.location.pathname;
354
+ for (var i = 0; i < links.length; i++) {
355
+ var a = links[i];
356
+ var href = a.getAttribute("href") || "";
357
+ var on = href === path || (href !== "/" && path.indexOf(href + "/") === 0);
358
+ var cls = a.getAttribute("data-tw-active-class");
359
+ if (on) a.classList.add(cls);
360
+ else a.classList.remove(cls);
361
+ }
362
+ }
363
+
364
+ // Signal Streaming (docs/183): pages with a __TW_SIGNALS__ manifest open
365
+ // a persistent stream and patch their signal bindings in place. Reconnect
366
+ // resumes from the last applied sequence number.
367
+ var activeStream = null;
368
+
369
+ // Derived signals (docs/183): named computed state, recomputed from the
370
+ // other state vars on boot and after every stream frame.
371
+ var derivedSpecs = null;
372
+ function loadDerivedSpecs() {
373
+ var el = document.getElementById("__TW_DERIVED__");
374
+ derivedSpecs = null;
375
+ if (!el) return;
376
+ try {
377
+ var spec = JSON.parse(el.textContent || "{}");
378
+ derivedSpecs = spec.derived || null;
379
+ } catch (e) { derivedSpecs = null; }
380
+ }
381
+ function recomputeDerived() {
382
+ if (!derivedSpecs) return;
383
+ for (var name in derivedSpecs) {
384
+ try { state[name] = evalExpr(derivedSpecs[name]); } catch (e) { /* keep old */ }
385
+ }
386
+ }
387
+ loadDerivedSpecs();
388
+ recomputeDerived();
389
+
390
+ function connectSignalStream() {
391
+ if (activeStream) { activeStream.close(); activeStream = null; }
392
+ var el = document.getElementById("__TW_SIGNALS__");
393
+ if (!el) return;
394
+ var manifest;
395
+ try { manifest = JSON.parse(el.textContent || "{}"); } catch (e) { return; }
396
+ var names = Object.keys(manifest.signals || {});
397
+ if (!names.length) return;
398
+ var lastSeq = 0;
399
+
400
+ function apply(payload) {
401
+ if (!payload || payload.v !== 1) return;
402
+ if (payload.snapshot) {
403
+ for (var k in payload.snapshot) {
404
+ if (state[k] !== undefined || names.indexOf(k) >= 0) state[k] = payload.snapshot[k];
405
+ }
406
+ }
407
+ var ups = payload.updates || [];
408
+ for (var i = 0; i < ups.length; i++) {
409
+ var name = ups[i][0], value = ups[i][1];
410
+ if (names.indexOf(name) >= 0) state[name] = value;
411
+ }
412
+ if (payload.seq) lastSeq = payload.seq;
413
+ recomputeDerived();
414
+ refresh();
415
+ }
416
+
417
+ var es = null;
418
+ function open() {
419
+ es = new EventSource(
420
+ "/_tw/stream?s=" + encodeURIComponent(names.join(",")) + "&since=" + lastSeq
421
+ );
422
+ es.onmessage = function (ev) {
423
+ try { apply(JSON.parse(ev.data)); } catch (e) { /* malformed frame */ }
424
+ };
425
+ es.onerror = function () {
426
+ // EventSource retries on its own, but with the original URL; close
427
+ // and reopen so the connection resumes from lastSeq.
428
+ try { es.close(); } catch (e) { /* already closed */ }
429
+ setTimeout(open, 1500);
430
+ };
431
+ }
432
+ open();
433
+ activeStream = {
434
+ close: function () {
435
+ try { es.close(); } catch (e) { /* already closed */ }
436
+ activeStream = null;
437
+ },
438
+ };
439
+ window.__tw.signalStream = { reconnect: open, lastSeq: function () { return lastSeq; } };
440
+ }
441
+
442
+ // expose for debugging / userland
443
+ window.__tw = {
444
+ state: state,
445
+ refresh: refresh,
446
+ eval: evalExpr,
447
+ navigate: navigate,
448
+ };
449
+
450
+ refresh();
451
+ connectSignalStream();
452
+ })();
package/dist/tw.mjs CHANGED
@@ -18429,6 +18429,10 @@ function wrapInterpolations(rendered, raw) {
18429
18429
  for (const seg of segs) {
18430
18430
  if (/^\{[^{}]+\}$/.test(seg)) {
18431
18431
  const expr = seg.slice(1, -1);
18432
+ if (ctx_global_inhead) {
18433
+ out += escapeHTML(interpolate2(seg, lastStateVars));
18434
+ continue;
18435
+ }
18432
18436
  out += `<span data-tw-i="${escapeHTML(expr)}"${dataTwSFor(expr)}>${escapeHTML(interpolate2(seg, lastStateVars))}</span>`;
18433
18437
  } else if (seg) {
18434
18438
  out += escapeHTML(seg);
@@ -18438,9 +18442,10 @@ function wrapInterpolations(rendered, raw) {
18438
18442
  }
18439
18443
  function generateText(text, ctx) {
18440
18444
  ctx_global_interactive = ctx.interactive === true;
18445
+ ctx_global_inhead = ctx.inHead === true;
18441
18446
  lastStateVars = ctx.stateVars;
18442
18447
  if (text.isInterpolated) {
18443
- if (ctx.interactive) {
18448
+ if (ctx.interactive && ctx.inHead !== true) {
18444
18449
  return `<span data-tw-i="${escapeHTML(text.value)}"${dataTwSFor(text.value)}>${escapeHTML(interpolate2(`{${text.value}}`, ctx.stateVars))}</span>`;
18445
18450
  }
18446
18451
  return escapeHTML(interpolate2(`{${text.value}}`, ctx.stateVars));
@@ -18645,7 +18650,7 @@ function generateSlot(node, ctx) {
18645
18650
  const fallback = node.fallback.map((c) => generateNode(c, ctx)).join("");
18646
18651
  return `<!-- tw:slot:${node.name} -->${fallback}<!-- /tw:slot:${node.name} -->`;
18647
18652
  }
18648
- var VOID_TAGS5, BOOLEAN_ATTRS2, CSS_CHUNKS, CSS_ROUTE_CHUNKS, CSS_CAPTURE, activeBuiltinImports, SUSPENSE_BOOT_RUNTIME, suspenseCounter, ctx_global_interactive, lastStateVars, componentRegistry;
18653
+ var VOID_TAGS5, BOOLEAN_ATTRS2, CSS_CHUNKS, CSS_ROUTE_CHUNKS, CSS_CAPTURE, activeBuiltinImports, SUSPENSE_BOOT_RUNTIME, suspenseCounter, ctx_global_interactive, ctx_global_inhead, lastStateVars, componentRegistry;
18649
18654
  var init_html2 = __esm({
18650
18655
  "../../packages/compiler/tw/codegen/html.ts"() {
18651
18656
  init_types4();
@@ -18733,6 +18738,7 @@ var init_html2 = __esm({
18733
18738
  ].join("\n");
18734
18739
  suspenseCounter = 0;
18735
18740
  ctx_global_interactive = false;
18741
+ ctx_global_inhead = false;
18736
18742
  lastStateVars = {};
18737
18743
  componentRegistry = /* @__PURE__ */ new Map();
18738
18744
  }
@@ -48334,7 +48340,7 @@ async function buildCommand() {
48334
48340
  const _bundleDir = globalThis.__TW_BUNDLE_DIR;
48335
48341
  const _fu = import.meta.url.replace("file://", "");
48336
48342
  const _srcDir = _fu.substring(0, _fu.lastIndexOf("/"));
48337
- const runtimeSrc = _bundleDir ? resolve6(_bundleDir, "../../..", "packages/runtime/tw/client/hydration-runtime.js") : resolve6(_srcDir, "../../../..", "packages/runtime/tw/client/hydration-runtime.js");
48343
+ const runtimeSrc = _bundleDir ? resolve6(_bundleDir, "hydration-runtime.js") : resolve6(_srcDir, "../../../..", "packages/runtime/tw/client/hydration-runtime.js");
48338
48344
  if (existsSync16(runtimeSrc)) {
48339
48345
  copyFileSync2(runtimeSrc, join15(outDir, "__tw_runtime.js"));
48340
48346
  console.log(" \x1B[36m\u26A1\x1B[0m hydration runtime: .tw/__tw_runtime.js");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tw-framework",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Full-stack web framework \u2014 own template language (.tw), styling (.tss), compiler and VDOM; runs on Bun and Node",
5
5
  "type": "module",
6
6
  "main": "./dist/tw.mjs",