mono-jsx 0.10.0-beta.13 → 0.10.0-beta.15
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/README.md +16 -14
- package/jsx-runtime.mjs +193 -98
- package/package.json +1 -1
- package/types/index.d.ts +3 -0
- package/types/render.d.ts +12 -1
package/README.md
CHANGED
|
@@ -1340,11 +1340,6 @@ const routes = {
|
|
|
1340
1340
|
}
|
|
1341
1341
|
```
|
|
1342
1342
|
|
|
1343
|
-
> [!TIP]
|
|
1344
|
-
> You can use `:invalid` CSS selector to style the form elements with invalid state.
|
|
1345
|
-
|
|
1346
|
-
### Handling Route Form Submissions
|
|
1347
|
-
|
|
1348
1343
|
You can return regular HTML elements in the form handler function, by default it will be appended to the form element. You can add the `mode` attribute on the `<form route>` element to decide where the content should be inserted.
|
|
1349
1344
|
|
|
1350
1345
|
- **"append"** (default): Append the handler output into the form element.
|
|
@@ -1366,7 +1361,12 @@ MyRoute.FormHandler = function(this: FC, data: FormData) {
|
|
|
1366
1361
|
}
|
|
1367
1362
|
```
|
|
1368
1363
|
|
|
1369
|
-
|
|
1364
|
+
> [!TIP]
|
|
1365
|
+
> You can use `:invalid` CSS selector to style the form elements with invalid state.
|
|
1366
|
+
|
|
1367
|
+
### Using `<formslot>` element
|
|
1368
|
+
|
|
1369
|
+
mono-jsx provides a built-in `<formslot>` element that is used to control where the form handler content should be inserted. If any `<formslot>` element exists the `mode` attribute on the `<form route>` element will be ignored. It accepts the following modes:
|
|
1370
1370
|
|
|
1371
1371
|
- **"replaceChildren"** (default): Replace children of the `<formslot>` element with the returned HTML.
|
|
1372
1372
|
- **"insertafter"**: Insert HTML after the `<formslot>` element.
|
|
@@ -1393,26 +1393,28 @@ MyRoute.FormHandler = function(this: FC, data: FormData) {
|
|
|
1393
1393
|
}
|
|
1394
1394
|
```
|
|
1395
1395
|
|
|
1396
|
-
You can add the `name` prop to specify the name of the formslot element. And `formslot` prop to specify the name of the slot to insert the HTML into.
|
|
1396
|
+
You can add the `name` prop to specify the name of the formslot element. And use `formslot` prop in the form handler function to specify the name of the slot to insert the HTML into.
|
|
1397
1397
|
|
|
1398
1398
|
```tsx
|
|
1399
1399
|
function MyRoute(this: FC) {
|
|
1400
1400
|
return (
|
|
1401
1401
|
<div>
|
|
1402
|
-
<formslot name="info" /> { /* <- "This is info message" will be inserted into the formslot element after submitting the form */ }
|
|
1403
1402
|
<form route>
|
|
1404
1403
|
<button type="submit">Send</button>
|
|
1405
|
-
<formslot name="
|
|
1404
|
+
<formslot name="info" /> { /* <- "This is info message" will be inserted here */ }
|
|
1405
|
+
<formslot name="error" /> { /* <- "This is error message" will be inserted here */ }
|
|
1406
1406
|
</form>
|
|
1407
1407
|
</div>
|
|
1408
1408
|
)
|
|
1409
1409
|
}
|
|
1410
1410
|
|
|
1411
1411
|
MyRoute.FormHandler = function(this: FC, data: FormData) {
|
|
1412
|
-
return
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1412
|
+
return (
|
|
1413
|
+
<>
|
|
1414
|
+
<p formslot="info">This is info message</p>
|
|
1415
|
+
<p formslot="error">This is error message</p>
|
|
1416
|
+
</>
|
|
1417
|
+
)
|
|
1416
1418
|
}
|
|
1417
1419
|
```
|
|
1418
1420
|
|
|
@@ -1424,7 +1426,7 @@ function MyRoute(this: FC) {
|
|
|
1424
1426
|
<form>
|
|
1425
1427
|
<input type="text" name="message" placeholder="Type Message..." />
|
|
1426
1428
|
<button type="submit">Send</button>
|
|
1427
|
-
<formslot
|
|
1429
|
+
<formslot hidden onUpdate={(evt) => console.log("message updated:", evt.target.textContent)} />
|
|
1428
1430
|
</form>
|
|
1429
1431
|
)
|
|
1430
1432
|
}
|
package/jsx-runtime.mjs
CHANGED
|
@@ -16,7 +16,7 @@ var $setup = /* @__PURE__ */ Symbol.for("mono.setup");
|
|
|
16
16
|
var $rpc = /* @__PURE__ */ Symbol.for("mono.rpc");
|
|
17
17
|
|
|
18
18
|
// version.ts
|
|
19
|
-
var VERSION = "0.10.0-beta.
|
|
19
|
+
var VERSION = "0.10.0-beta.15";
|
|
20
20
|
|
|
21
21
|
// runtime/index.ts
|
|
22
22
|
var EVENT = 1;
|
|
@@ -43,7 +43,7 @@ var SUSPENSE_JS = `{const i=new Map,o=e=>e.getAttribute("chunk-id"),l=(e,t)=>cus
|
|
|
43
43
|
var COMPONENT_JS = `{const e=document,a=(t,s)=>t.getAttribute(s);customElements.define("m-component",class extends HTMLElement{static observedAttributes=["name","props"];#t;#s;#r;#h;#i;#e=new Map;#a=!0;async#l(){if(!this.#t){this.#n("");return}const t=this.#s||"{}",s=this.#t+t,i={"x-component":this.#t,"x-props":t,"x-flags":$FLAGS},n=new AbortController;if(this.#h?.abort(),this.#h=n,this.#e.has(s)){this.#n(this.#e.get(s));return}this.#r?.length&&this.#n(this.#r);const r=await fetch(location.href,{headers:i,signal:n.signal});if(!r.ok)throw this.#n(""),new Error("Failed to fetch component '"+this.#t+"'");const[h,o]=await r.json();this.#e.set(s,h),this.#n(h),o&&(e.body.appendChild(e.createElement("script")).textContent=o)}#n(t){const s=()=>typeof t=="string"?this.innerHTML=t:this.replaceChildren(...t);this.hasAttribute("vt")&&e.startViewTransition&&!this.#a?e.startViewTransition(s):s(),this.#a=!1}get name(){return this.#t??null}set name(t){t&&t!==this.#t&&(this.#t=t,this.#o())}get props(){return this.#s?JSON.parse(this.#s):void 0}set props(t){const s=typeof t=="string"?t:JSON.stringify(t);s&&s!==this.#s&&(this.#s=s,this.#o())}attributeChangedCallback(t,s,i){this.#t&&i&&(t==="name"?this.name=i:t==="props"&&(this.props=i))}connectedCallback(){setTimeout(()=>{if(!this.#r){const t=a(this,"props");this.#t=a(this,"name"),this.#s=t?.startsWith("base64,")?atob(t.slice(7)):void 0,this.#r=[...this.childNodes]}this.#l()})}disconnectedCallback(){this.#e.clear(),this.#h?.abort(),this.#h=void 0,this.#i&&clearTimeout(this.#i),this.#i=void 0}#o(){this.#i&&clearTimeout(this.#i),this.#i=setTimeout(()=>{this.#i=void 0,this.#l()},50)}refresh(){this.#t&&this.#e.delete(this.#t+(this.#s||"{}")),this.#o()}});}`;
|
|
44
44
|
var ROUTER_JS = `{const o=window,r=document,n=location,d=e=>e.origin===n.origin&&l(e)===l(n),l=({pathname:e,search:t})=>e+t;customElements.define("m-router",class extends HTMLElement{#e;#i=new Map;#n=l(n);#t;#a=!0;#s;#r;async#c(e){this.#e?.abort(),this.#e=new AbortController;const t=await fetch(e,{headers:{"x-route":"true","x-flags":$FLAGS},signal:this.#e.signal});if(t.status===404)return null;if(!t.ok)throw this.replaceChildren(),new Error("Failed to fetch route: "+t.status+" "+t.statusText);const i=await t.json();if(!Array.isArray(i))throw new Error(i?.error?i.error:"Invalid response from server");return i}#o(e){const t=()=>typeof e=="string"?this.innerHTML=e:this.replaceChildren(...e);this.hasAttribute("vt")&&r.startViewTransition&&!this.#a?r.startViewTransition(t):t(),this.#a=!1}#l(){r.querySelectorAll("nav a").forEach(e=>{const{href:t,classList:i}=e,s=e.closest("nav")?.getAttribute("data-active-class")??"active";d(new URL(t))?i.add(s):i.remove(s)})}async#h(e,t){this.#n=l(e);let i,s=this.#i.get(this.#n);if(s!==void 0&&!t?.refresh&&!this.hasAttribute("no-cache"))this.#o(s);else{const h=await this.#c(e);typeof $signals<"u"&&($signals(0).url=e);let a,c;h?[a,i,c]=h:a=this.#t??[],c||this.#i.set(this.#n,a),this.#o(a)}history[t?.replace?"replaceState":"pushState"]({},"",e),this.#l(),window.scrollTo(0,0),i&&(r.body.appendChild(r.createElement("script")).textContent=i)}navigate(e,t){const i=new URL(e,n.href);if(i.origin!==n.origin||e.startsWith("#")){n.href=e;return}d(i)||this.#h(i,t)}connectedCallback(){if(o.$router)throw new Error("Only one <m-router> element is allowed on the page");o.$router=this,setTimeout(()=>{if(!this.#t)if(this.hasAttribute("fallback"))this.removeAttribute("fallback"),this.#t=[...this.childNodes];else{this.#t=[];for(const e of this.childNodes)if(e.nodeType===1&&e.tagName==="TEMPLATE"&&e.hasAttribute("m-fallback")){this.#t.push(...e.content.childNodes),e.remove();break}}this.#i.set(n.href,[...this.childNodes])}),this.#s=e=>{if(e.defaultPrevented||e.altKey||e.ctrlKey||e.metaKey||e.shiftKey||!(e.target instanceof HTMLAnchorElement))return;const t=e.target.getAttribute("href");if(!t||t.startsWith("#"))return;const{download:i,href:s,rel:h,target:a}=e.target;i||h==="external"||a==="_blank"||!s.startsWith(n.origin)||(e.preventDefault(),this.navigate(s))},this.#r=()=>{l(n)!==this.#n&&this.#h(new URL(n.href))},o.addEventListener("popstate",this.#r),r.addEventListener("click",this.#s),setTimeout(()=>this.#l())}disconnectedCallback(){o.removeEventListener("popstate",this.#r),r.removeEventListener("click",this.#s),delete o.$router,this.#e?.abort(),this.#e=void 0,this.#i.clear(),this.#s=void 0,this.#r=void 0}});}`;
|
|
45
45
|
var REDIRECT_JS = `{customElements.define("m-redirect",class extends HTMLElement{connectedCallback(){const e=this.getAttribute("to"),t=this.hasAttribute("replace");e&&($router?$router.navigate(e,{replace:t}):location.href=e)}});}`;
|
|
46
|
-
var FORM_JS = `{const{document:
|
|
46
|
+
var FORM_JS = `{const{document:d}=window,r=(l,e)=>l.getAttribute(e);customElements.define("m-invalid",class extends HTMLElement{connectedCallback(){const l=r(this,"for"),e=this.closest("form"),i=this.textContent;if(l&&e&&i)for(const f of l.split(",")){const n=e.elements.namedItem(f.trim());if(n){const t=()=>{n.removeEventListener("input",t),n.setCustomValidity("")};n.addEventListener("input",t),n.setCustomValidity(i),n.focus()}}this.remove()}}),window.$onRFS=async l=>{l.preventDefault();const e=l.target;if(!e.checkValidity())return;const i=r(e,"data-submitting-class")??"submitting",f=new FormData(e),n=[...e.elements];for(const t of n)t._disabled=t.disabled,t.disabled=!0;e.querySelectorAll("formslot").forEach(t=>t.innerHTML=""),e.classList.add(i);try{const t=await fetch(location.href,{method:"POST",headers:{"x-route-form":"true","x-flags":$FLAGS},body:f});if(t.ok){const[y,p]=await t.json(),u=new Map,E=d.createElement("template");E.innerHTML=y;const{content:a}=E;a.querySelectorAll("[formslot]").forEach(s=>{const o=r(s,"formslot");if(o){const m='m-formslot[name="'+o+'"]',c=e.querySelector(m)??d.querySelector(m);c&&(s.remove(),u.set(s,c))}});const b=e.querySelector("m-formslot:not([name])");if(b)u.set(a,b);else{const s=r(e,"mode");s==="replace"?e.replaceWith(a):s==="prepend"?e.prepend(a):e.append(a)}for(const[s,o]of u){const m=r(o,"scope"),c=r(o,"mode"),h=r(o,"onupdate");c==="insertbefore"?o.before(s):c==="insertafter"?o.after(s):o.replaceChildren(s),h&&$fmap.get(Number(h))?.call($signals?.(Number(m))??o,{type:"update",target:o})}setTimeout(()=>e.checkValidity()&&e.reset()),p&&(d.body.appendChild(d.createElement("script")).textContent=p+";document.currentScript.remove();")}}finally{e.classList.remove(i);for(const t of n)t.disabled=t._disabled,delete t._disabled}};}`;
|
|
47
47
|
var RPC_JS = `{window.$RPC=(e,t)=>new Proxy(Object.create(null),{get(s,r){if(t.includes(r))return(...i)=>fetch(location.href,{method:"POST",body:JSON.stringify({fn:r,args:i}),headers:{"x-rpc":"true","x-rpc-id":e.toString()}}).then(async o=>{const{error:n,result:u}=await o.json();if(n)throw new Error(n);return u})}});}`;
|
|
48
48
|
|
|
49
49
|
// runtime/utils.ts
|
|
@@ -172,19 +172,34 @@ var escapeHTML = (str) => {
|
|
|
172
172
|
|
|
173
173
|
// render.ts
|
|
174
174
|
var FunctionIdGenerator = class extends Map {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
this.seq = seq;
|
|
178
|
-
}
|
|
175
|
+
seq = 0;
|
|
176
|
+
#fnRef = /* @__PURE__ */ new Map();
|
|
179
177
|
genId(fn) {
|
|
178
|
+
if (typeof fn === "string") {
|
|
179
|
+
let id2 = this.get(fn);
|
|
180
|
+
if (id2 === void 0) {
|
|
181
|
+
id2 = this.seq++;
|
|
182
|
+
this.set(fn, id2);
|
|
183
|
+
}
|
|
184
|
+
return id2;
|
|
185
|
+
}
|
|
186
|
+
const cached = this.#fnRef.get(fn);
|
|
187
|
+
if (cached !== void 0) {
|
|
188
|
+
return cached;
|
|
189
|
+
}
|
|
180
190
|
const fnStr = String(fn);
|
|
181
191
|
let id = this.get(fnStr);
|
|
182
192
|
if (id === void 0) {
|
|
183
193
|
id = this.seq++;
|
|
184
194
|
this.set(fnStr, id);
|
|
185
195
|
}
|
|
196
|
+
this.#fnRef.set(fn, id);
|
|
186
197
|
return id;
|
|
187
198
|
}
|
|
199
|
+
clear() {
|
|
200
|
+
super.clear();
|
|
201
|
+
this.#fnRef.clear();
|
|
202
|
+
}
|
|
188
203
|
};
|
|
189
204
|
var Signal = class {
|
|
190
205
|
constructor(scope, key, value) {
|
|
@@ -209,28 +224,34 @@ var Ref = class {
|
|
|
209
224
|
};
|
|
210
225
|
var IdGen = class extends Map {
|
|
211
226
|
#seq = 0;
|
|
227
|
+
#byId = /* @__PURE__ */ new Map();
|
|
212
228
|
gen(v) {
|
|
213
|
-
|
|
229
|
+
const existing = this.get(v);
|
|
230
|
+
if (existing !== void 0) {
|
|
231
|
+
return existing;
|
|
232
|
+
}
|
|
233
|
+
const id = this.#seq++;
|
|
234
|
+
this.set(v, id);
|
|
235
|
+
this.#byId.set(id, v);
|
|
236
|
+
return id;
|
|
214
237
|
}
|
|
215
238
|
getById(id) {
|
|
216
|
-
|
|
217
|
-
if (i === id) {
|
|
218
|
-
return v;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
239
|
+
return this.#byId.get(id);
|
|
221
240
|
}
|
|
222
241
|
};
|
|
223
|
-
var
|
|
242
|
+
var stringify = JSON.stringify;
|
|
243
|
+
var subtle = crypto.subtle;
|
|
224
244
|
var encoder = new TextEncoder();
|
|
245
|
+
var identifierRegex = /^[A-Za-z_$][0-9A-Za-z_$]*$/;
|
|
246
|
+
var cdn = "https://raw.esm.sh";
|
|
225
247
|
var voidTags = new Set("area,base,br,col,embed,hr,img,input,keygen,link,meta,param,source,track,wbr".split(","));
|
|
226
248
|
var defaultMetadata = { viewport: "width=device-width, initial-scale=1.0" };
|
|
227
|
-
var cache = /* @__PURE__ */ new Map();
|
|
228
249
|
var componentsMap = new IdGen();
|
|
229
|
-
var
|
|
230
|
-
var
|
|
250
|
+
var cache = /* @__PURE__ */ new Map();
|
|
251
|
+
var urlPatternCache = /* @__PURE__ */ new Map();
|
|
252
|
+
var hmacKeyCache = /* @__PURE__ */ new Map();
|
|
231
253
|
var isVNode = (v) => Array.isArray(v) && v.length === 3 && v[2] === $vnode;
|
|
232
254
|
var isReactive = (v) => v instanceof Signal || v instanceof Compute;
|
|
233
|
-
var identifierRegex = /^[A-Za-z_$][0-9A-Za-z_$]*$/;
|
|
234
255
|
var escapeCSSText = (str) => str.replace(/[><]/g, (m) => m.charCodeAt(0) === 60 ? "<" : ">");
|
|
235
256
|
var toAttrStringLit = (str) => '"' + escapeHTML(str) + '"';
|
|
236
257
|
var errorStringify = (err) => err instanceof Error ? err.message : String(err);
|
|
@@ -241,6 +262,15 @@ function renderToWebStream(root, options) {
|
|
|
241
262
|
const reqHeaders = request?.headers;
|
|
242
263
|
const componentName = reqHeaders?.get("x-component");
|
|
243
264
|
const routeForm = reqHeaders?.has("x-route-form");
|
|
265
|
+
if (request) {
|
|
266
|
+
request.URL = new URL(request.url);
|
|
267
|
+
if (options.onFetch) {
|
|
268
|
+
const res = options.onFetch(request, options.context);
|
|
269
|
+
if (res !== void 0) {
|
|
270
|
+
return res;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
244
274
|
if (reqHeaders?.has("x-rpc")) {
|
|
245
275
|
if (!request || request.method !== "POST") {
|
|
246
276
|
return new Response(null, { status: 405 });
|
|
@@ -289,28 +319,24 @@ function renderToWebStream(root, options) {
|
|
|
289
319
|
let status = options.status;
|
|
290
320
|
let routeFC = request ? Reflect.get(request, "routeFC") : void 0;
|
|
291
321
|
let component = componentName ? componentName.startsWith("@comp_") ? componentsMap.getById(Number(componentName.slice(6))) : components?.[componentName] : null;
|
|
292
|
-
if (request) {
|
|
293
|
-
request.URL = new URL(request.url);
|
|
294
|
-
}
|
|
295
322
|
if (routes && !routeFC) {
|
|
296
323
|
if (request) {
|
|
297
|
-
|
|
298
|
-
const dynamicPatterns = [];
|
|
299
|
-
for (const pattern of patterns) {
|
|
300
|
-
if (pattern.includes(":") || pattern.includes("*")) {
|
|
301
|
-
dynamicPatterns.push(pattern);
|
|
302
|
-
} else if (request.URL.pathname === pattern) {
|
|
303
|
-
routeFC = routes[pattern];
|
|
304
|
-
break;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
324
|
+
routeFC = routes[request.URL.pathname];
|
|
307
325
|
if (!routeFC) {
|
|
308
|
-
for (const
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
326
|
+
for (const pattern of Object.keys(routes)) {
|
|
327
|
+
if (pattern.includes(":") || pattern.includes("*")) {
|
|
328
|
+
let urlPattern = urlPatternCache.get(pattern);
|
|
329
|
+
let match;
|
|
330
|
+
if (!urlPattern) {
|
|
331
|
+
urlPattern = new URLPattern({ pathname: pattern });
|
|
332
|
+
urlPatternCache.set(pattern, urlPattern);
|
|
333
|
+
}
|
|
334
|
+
match = urlPattern.exec(request.URL);
|
|
335
|
+
if (match) {
|
|
336
|
+
routeFC = routes[pattern];
|
|
337
|
+
request.params = match.pathname.groups;
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
314
340
|
}
|
|
315
341
|
}
|
|
316
342
|
}
|
|
@@ -352,8 +378,8 @@ function renderToWebStream(root, options) {
|
|
|
352
378
|
}
|
|
353
379
|
let propsHeader = reqHeaders?.get("x-props");
|
|
354
380
|
let props = propsHeader ? JSON.parse(propsHeader) : {};
|
|
355
|
-
|
|
356
|
-
|
|
381
|
+
const htmlChunks = [];
|
|
382
|
+
const jsChunks = [];
|
|
357
383
|
let buf = "";
|
|
358
384
|
let vnode = [component, props, $vnode];
|
|
359
385
|
if (routeForm && request?.method === "POST") {
|
|
@@ -366,11 +392,17 @@ function renderToWebStream(root, options) {
|
|
|
366
392
|
await render(
|
|
367
393
|
vnode,
|
|
368
394
|
options,
|
|
369
|
-
(chunk) =>
|
|
370
|
-
|
|
395
|
+
(chunk) => {
|
|
396
|
+
htmlChunks.push(chunk);
|
|
397
|
+
},
|
|
398
|
+
(chunk) => {
|
|
399
|
+
jsChunks.push(chunk);
|
|
400
|
+
},
|
|
371
401
|
true,
|
|
372
402
|
routeForm
|
|
373
403
|
);
|
|
404
|
+
const html2 = htmlChunks.join("");
|
|
405
|
+
const js = jsChunks.join("");
|
|
374
406
|
buf = "[" + stringify(html2);
|
|
375
407
|
if (js) {
|
|
376
408
|
buf += "," + stringify(js);
|
|
@@ -397,7 +429,19 @@ function renderToWebStream(root, options) {
|
|
|
397
429
|
return new Response(
|
|
398
430
|
new ReadableStream({
|
|
399
431
|
async start(controller) {
|
|
400
|
-
|
|
432
|
+
let buffer = "";
|
|
433
|
+
const flush = () => {
|
|
434
|
+
if (buffer.length) {
|
|
435
|
+
controller.enqueue(encoder.encode(buffer));
|
|
436
|
+
buffer = "";
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
const write = (chunk) => {
|
|
440
|
+
buffer += chunk;
|
|
441
|
+
if (buffer.length >= 64 * 1024) {
|
|
442
|
+
flush();
|
|
443
|
+
}
|
|
444
|
+
};
|
|
401
445
|
Reflect.set(options, "routeFC", routeFC);
|
|
402
446
|
try {
|
|
403
447
|
write("<!DOCTYPE html>");
|
|
@@ -414,6 +458,7 @@ function renderToWebStream(root, options) {
|
|
|
414
458
|
console.error(err);
|
|
415
459
|
write("<script>console.error(" + stringify(errorStringify(err)) + ")<\/script>");
|
|
416
460
|
} finally {
|
|
461
|
+
flush();
|
|
417
462
|
controller.close();
|
|
418
463
|
}
|
|
419
464
|
}
|
|
@@ -541,7 +586,7 @@ async function render(node, options, write, writeJS, componentMode, routeForm) {
|
|
|
541
586
|
]);
|
|
542
587
|
const signature = await subtle.sign(
|
|
543
588
|
"HMAC",
|
|
544
|
-
await
|
|
589
|
+
await importHmacKey(secret),
|
|
545
590
|
encoder.encode(data)
|
|
546
591
|
);
|
|
547
592
|
cookie += btoa(data) + "." + btoa(String.fromCharCode(...new Uint8Array(signature)));
|
|
@@ -643,16 +688,23 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
643
688
|
case "slot": {
|
|
644
689
|
const fcSlots = rc.fc?.slots;
|
|
645
690
|
if (fcSlots) {
|
|
646
|
-
|
|
691
|
+
const slots = [];
|
|
647
692
|
if (props.name) {
|
|
648
|
-
|
|
693
|
+
for (let i = 0, n = fcSlots.length; i < n; i++) {
|
|
694
|
+
const v = fcSlots[i];
|
|
695
|
+
if (isVNode(v) && v[1].slot === props.name) {
|
|
696
|
+
slots.push(v);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
649
699
|
} else {
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
700
|
+
for (let i = 0, n = fcSlots.length; i < n; i++) {
|
|
701
|
+
const v = fcSlots[i];
|
|
702
|
+
if (!isVNode(v) || !v[1].slot) {
|
|
703
|
+
slots.push(v);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
654
706
|
}
|
|
655
|
-
await renderChildren(rc, slots, true);
|
|
707
|
+
await renderChildren(rc, slots.length === 0 ? props.children : slots, true);
|
|
656
708
|
}
|
|
657
709
|
break;
|
|
658
710
|
}
|
|
@@ -780,10 +832,16 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
780
832
|
attrs += renderViewTransitionAttr(props);
|
|
781
833
|
let buf = "<m-component" + attrs + ">";
|
|
782
834
|
if (pending) {
|
|
783
|
-
const
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
835
|
+
const chunks = [];
|
|
836
|
+
await renderChildren(
|
|
837
|
+
forkRenderContext(rc, {
|
|
838
|
+
write: (chunk) => {
|
|
839
|
+
chunks.push(chunk);
|
|
840
|
+
}
|
|
841
|
+
}),
|
|
842
|
+
pending
|
|
843
|
+
);
|
|
844
|
+
buf += chunks.join("");
|
|
787
845
|
}
|
|
788
846
|
buf += "</m-component>";
|
|
789
847
|
if (attrModifiers) {
|
|
@@ -820,10 +878,16 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
820
878
|
if (routeFC) {
|
|
821
879
|
buf += "<template m-fallback>";
|
|
822
880
|
}
|
|
823
|
-
const
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
881
|
+
const chunks = [];
|
|
882
|
+
await renderChildren(
|
|
883
|
+
forkRenderContext(rc, {
|
|
884
|
+
write: (chunk) => {
|
|
885
|
+
chunks.push(chunk);
|
|
886
|
+
}
|
|
887
|
+
}),
|
|
888
|
+
children
|
|
889
|
+
);
|
|
890
|
+
buf += chunks.join("");
|
|
827
891
|
if (routeFC) {
|
|
828
892
|
buf += "</template>";
|
|
829
893
|
}
|
|
@@ -848,7 +912,8 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
848
912
|
metadata = await getMetadata.call(createInvokeScope(request, context, session));
|
|
849
913
|
}
|
|
850
914
|
let buf = '<meta charset="utf-8">';
|
|
851
|
-
|
|
915
|
+
const mergedMeta = Object.assign({}, defaultMetadata, rc.metadata, metadata);
|
|
916
|
+
for (const [key, value] of Object.entries(mergedMeta)) {
|
|
852
917
|
if (value) {
|
|
853
918
|
if (key === "title") {
|
|
854
919
|
buf += "<title>" + escapeHTML(value) + "</title>";
|
|
@@ -873,17 +938,17 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
873
938
|
write(value.html);
|
|
874
939
|
write("<!-- /" + tag + " -->");
|
|
875
940
|
} else {
|
|
876
|
-
|
|
941
|
+
const chunks = [];
|
|
877
942
|
await renderChildren(
|
|
878
|
-
{
|
|
879
|
-
...rc,
|
|
943
|
+
forkRenderContext(rc, {
|
|
880
944
|
write: (chunk) => {
|
|
881
|
-
|
|
945
|
+
chunks.push(chunk);
|
|
882
946
|
}
|
|
883
|
-
},
|
|
947
|
+
}),
|
|
884
948
|
children,
|
|
885
949
|
true
|
|
886
950
|
);
|
|
951
|
+
const buf = chunks.join("");
|
|
887
952
|
cache.set(key, { html: buf, expiresAt: typeof maxAge === "number" && maxAge > 0 ? now + maxAge * 1e3 : void 0 });
|
|
888
953
|
rc.write(buf);
|
|
889
954
|
}
|
|
@@ -936,12 +1001,16 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
936
1001
|
}
|
|
937
1002
|
buf += ">";
|
|
938
1003
|
if (children) {
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
1004
|
+
const chunks = [];
|
|
1005
|
+
await renderChildren(
|
|
1006
|
+
forkRenderContext(rc, {
|
|
1007
|
+
write: (chunk) => {
|
|
1008
|
+
chunks.push(chunk);
|
|
1009
|
+
}
|
|
1010
|
+
}),
|
|
1011
|
+
children
|
|
1012
|
+
);
|
|
1013
|
+
buf += chunks.join("");
|
|
945
1014
|
}
|
|
946
1015
|
write(buf + "</m-" + tag + ">");
|
|
947
1016
|
rc.flags.runtime |= FORM;
|
|
@@ -992,7 +1061,11 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
992
1061
|
write(attrModifiers);
|
|
993
1062
|
}
|
|
994
1063
|
if (!noChildren) {
|
|
995
|
-
|
|
1064
|
+
if (tag === "svg") {
|
|
1065
|
+
await renderChildren(forkRenderContext(rc, { svg: true }), props.children);
|
|
1066
|
+
} else {
|
|
1067
|
+
await renderChildren(rc, props.children);
|
|
1068
|
+
}
|
|
996
1069
|
}
|
|
997
1070
|
if (!isSvgSelfClosingElement) {
|
|
998
1071
|
write("</" + tag + ">");
|
|
@@ -1020,6 +1093,9 @@ async function renderChildren(rc, children, stripSlotProp) {
|
|
|
1020
1093
|
await renderNode(rc, children, stripSlotProp);
|
|
1021
1094
|
}
|
|
1022
1095
|
}
|
|
1096
|
+
function forkRenderContext(rc, overrides) {
|
|
1097
|
+
return Object.assign(Object.create(rc), overrides);
|
|
1098
|
+
}
|
|
1023
1099
|
async function renderFC(rc, fcFn, props, eager) {
|
|
1024
1100
|
const { write } = rc;
|
|
1025
1101
|
const { children } = props;
|
|
@@ -1037,8 +1113,9 @@ async function renderFC(rc, fcFn, props, eager) {
|
|
|
1037
1113
|
promise = promise.catch(catchFn);
|
|
1038
1114
|
}
|
|
1039
1115
|
if (eager || (props.rendering ?? fcFn.rendering) === "eager") {
|
|
1040
|
-
|
|
1041
|
-
|
|
1116
|
+
const fcRc = forkRenderContext(rc, { fc });
|
|
1117
|
+
await renderNode(fcRc, await promise);
|
|
1118
|
+
markSignals(fcRc, signals);
|
|
1042
1119
|
} else {
|
|
1043
1120
|
const chunkIdAttr = 'chunk-id="' + (rc.flags.chunk++).toString(36) + '"';
|
|
1044
1121
|
write("<m-portal " + chunkIdAttr + ">");
|
|
@@ -1047,22 +1124,26 @@ async function renderFC(rc, fcFn, props, eager) {
|
|
|
1047
1124
|
}
|
|
1048
1125
|
write("</m-portal>");
|
|
1049
1126
|
rc.suspenses.push(promise.then(async (node) => {
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1127
|
+
const chunks = [];
|
|
1128
|
+
const chunkRc = forkRenderContext(rc, {
|
|
1129
|
+
fc,
|
|
1130
|
+
write: (chunk) => {
|
|
1131
|
+
chunks.push(chunk);
|
|
1132
|
+
}
|
|
1133
|
+
});
|
|
1134
|
+
chunks.push("<m-chunk " + chunkIdAttr + "><template>");
|
|
1135
|
+
await renderNode(chunkRc, node);
|
|
1136
|
+
markSignals(chunkRc, signals);
|
|
1137
|
+
return chunks.join("") + "</template></m-chunk>";
|
|
1058
1138
|
}));
|
|
1059
1139
|
}
|
|
1060
1140
|
} else if (Symbol.asyncIterator in v) {
|
|
1061
1141
|
if (eager || (props.rendering ?? fcFn.rendering) === "eager") {
|
|
1142
|
+
const fcRc = forkRenderContext(rc, { fc });
|
|
1062
1143
|
for await (const c of v) {
|
|
1063
|
-
await renderNode(
|
|
1144
|
+
await renderNode(fcRc, c);
|
|
1064
1145
|
}
|
|
1065
|
-
markSignals(
|
|
1146
|
+
markSignals(fcRc, signals);
|
|
1066
1147
|
} else {
|
|
1067
1148
|
const chunkIdAttr = 'chunk-id="' + (rc.flags.chunk++).toString(36) + '"';
|
|
1068
1149
|
write("<m-portal " + chunkIdAttr + ">");
|
|
@@ -1072,32 +1153,37 @@ async function renderFC(rc, fcFn, props, eager) {
|
|
|
1072
1153
|
write("</m-portal>");
|
|
1073
1154
|
const iter = () => rc.suspenses.push(
|
|
1074
1155
|
v.next().then(async ({ done, value }) => {
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1156
|
+
const chunks = [];
|
|
1157
|
+
const chunkRc = forkRenderContext(rc, {
|
|
1158
|
+
fc,
|
|
1159
|
+
write: (chunk) => {
|
|
1160
|
+
chunks.push(chunk);
|
|
1161
|
+
}
|
|
1162
|
+
});
|
|
1079
1163
|
if (done) {
|
|
1080
|
-
|
|
1081
|
-
markSignals(
|
|
1082
|
-
return
|
|
1164
|
+
chunks.push("<m-chunk " + chunkIdAttr + " done>");
|
|
1165
|
+
markSignals(chunkRc, signals);
|
|
1166
|
+
return chunks.join("") + "</m-chunk>";
|
|
1083
1167
|
}
|
|
1084
|
-
|
|
1085
|
-
await renderNode(
|
|
1168
|
+
chunks.push("<m-chunk " + chunkIdAttr + " next><template>");
|
|
1169
|
+
await renderNode(chunkRc, value);
|
|
1086
1170
|
iter();
|
|
1087
|
-
return
|
|
1171
|
+
return chunks.join("") + "</template></m-chunk>";
|
|
1088
1172
|
})
|
|
1089
1173
|
);
|
|
1090
1174
|
iter();
|
|
1091
1175
|
}
|
|
1092
1176
|
} else if (Symbol.iterator in v) {
|
|
1177
|
+
const fcRc = forkRenderContext(rc, { fc });
|
|
1093
1178
|
for (const node of v) {
|
|
1094
|
-
await renderNode(
|
|
1179
|
+
await renderNode(fcRc, node);
|
|
1095
1180
|
}
|
|
1096
|
-
markSignals(
|
|
1181
|
+
markSignals(fcRc, signals);
|
|
1097
1182
|
}
|
|
1098
1183
|
} else if (v) {
|
|
1099
|
-
|
|
1100
|
-
|
|
1184
|
+
const fcRc = forkRenderContext(rc, { fc });
|
|
1185
|
+
await renderNode(fcRc, v);
|
|
1186
|
+
markSignals(fcRc, signals);
|
|
1101
1187
|
}
|
|
1102
1188
|
} catch (err) {
|
|
1103
1189
|
if (catchFn) {
|
|
@@ -1457,7 +1543,7 @@ async function createSession(request, options) {
|
|
|
1457
1543
|
signature = atob(signature);
|
|
1458
1544
|
const verified = await subtle.verify(
|
|
1459
1545
|
"HMAC",
|
|
1460
|
-
await
|
|
1546
|
+
await importHmacKey(secret),
|
|
1461
1547
|
Uint8Array.from(signature, (char) => char.charCodeAt(0)),
|
|
1462
1548
|
encoder.encode(data)
|
|
1463
1549
|
);
|
|
@@ -1500,6 +1586,14 @@ function traverseProps(obj, callback, path = []) {
|
|
|
1500
1586
|
}
|
|
1501
1587
|
return copy;
|
|
1502
1588
|
}
|
|
1589
|
+
function importHmacKey(secret) {
|
|
1590
|
+
let key = hmacKeyCache.get(secret);
|
|
1591
|
+
if (!key) {
|
|
1592
|
+
key = subtle.importKey("raw", encoder.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign", "verify"]);
|
|
1593
|
+
hmacKeyCache.set(secret, key);
|
|
1594
|
+
}
|
|
1595
|
+
return key;
|
|
1596
|
+
}
|
|
1503
1597
|
|
|
1504
1598
|
// jsx-runtime.ts
|
|
1505
1599
|
var Fragment = $fragment;
|
|
@@ -1524,7 +1618,8 @@ var jsx = (tag, props = new NullPrototypeObject(), key) => {
|
|
|
1524
1618
|
"status",
|
|
1525
1619
|
"headers",
|
|
1526
1620
|
"htmx",
|
|
1527
|
-
"metadata"
|
|
1621
|
+
"metadata",
|
|
1622
|
+
"onFetch"
|
|
1528
1623
|
]);
|
|
1529
1624
|
for (const [key2, value] of Object.entries(props)) {
|
|
1530
1625
|
if (optionsKeys.has(key2) || key2.startsWith("htmx-ext-")) {
|
package/package.json
CHANGED
package/types/index.d.ts
CHANGED
|
@@ -5,6 +5,9 @@ export function buildRoutes(
|
|
|
5
5
|
handler: (req: Request) => Response,
|
|
6
6
|
): Record<string, (req: Request) => Response>;
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* `createRPC` creates a RPC for the given functions.
|
|
10
|
+
*/
|
|
8
11
|
export function createRPC<V extends Record<string, (...args: any[]) => any>>(
|
|
9
12
|
rpcFunctions: V,
|
|
10
13
|
): { [K in keyof V]: (...args: Parameters<V[K]>) => Promise<Awaited<ReturnType<V[K]>>> };
|
package/types/render.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference path="./htmx.d.ts" />
|
|
2
2
|
|
|
3
|
-
import type { ComponentType, MaybeModule, Metadata } from "./jsx.d.ts";
|
|
3
|
+
import type { ComponentType, MaybeModule, MaybePromise, Metadata } from "./jsx.d.ts";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Htmx extensions.
|
|
@@ -120,4 +120,15 @@ export interface RenderOptions extends Partial<HtmxExts> {
|
|
|
120
120
|
* @defaultValue `false`
|
|
121
121
|
*/
|
|
122
122
|
htmx?: number | string | boolean;
|
|
123
|
+
/**
|
|
124
|
+
* Runs once per render when {@link RenderOptions.request | `request`} is provided, immediately
|
|
125
|
+
* after `request.URL` is set and before RPC handling, routing, and HTML streaming.
|
|
126
|
+
*
|
|
127
|
+
* Return a `Response` or `Promise<Response>` to send that response and skip the rest of the
|
|
128
|
+
* pipeline. Return `undefined` or omit a return value to continue with normal rendering.
|
|
129
|
+
*
|
|
130
|
+
* @param request - The current request; `URL` is a parsed {@link URL} for `request.url`.
|
|
131
|
+
* @param context - The same object as the {@link RenderOptions.context | `context`} option, if any.
|
|
132
|
+
*/
|
|
133
|
+
onFetch?: (request: Request & { URL?: URL }, context: RenderOptions["context"]) => MaybePromise<Response> | void;
|
|
123
134
|
}
|