hono 3.10.0-rc.1 β†’ 3.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -26,7 +26,7 @@
26
26
  [![Discord badge](https://img.shields.io/discord/1011308539819597844?label=Discord&logo=Discord)](https://discord.gg/KMh2eNSdxV)
27
27
 
28
28
  Hono - _**\[η‚Ž\] means flameπŸ”₯ in Japanese**_ - is a small, simple, and ultrafast web framework for the Edges.
29
- It works on any JavaScript runtime: Cloudflare Workers, Fastly Compute@Edge, Deno, Bun, Vercel, Lagon, AWS Lambda, Lambda@Edge, and Node.js.
29
+ It works on any JavaScript runtime: Cloudflare Workers, Fastly Compute, Deno, Bun, Vercel, Lagon, AWS Lambda, Lambda@Edge, and Node.js.
30
30
 
31
31
  Fast, but not only fast.
32
32
 
@@ -49,9 +49,9 @@ npm create hono@latest my-app
49
49
 
50
50
  - **Ultrafast** πŸš€ - The router `RegExpRouter` is really fast. Not using linear loops. Fast.
51
51
  - **Lightweight** πŸͺΆ - The `hono/tiny` preset is under 12kB. Hono has zero dependencies and uses only the Web Standard API.
52
- - **Multi-runtime** 🌍 - Works on Cloudflare Workers, Fastly Compute@Edge, Deno, Bun, Lagon, AWS Lambda, Lambda@Edge, or Node.js. The same code runs on all platforms.
52
+ - **Multi-runtime** 🌍 - Works on Cloudflare Workers, Fastly Compute, Deno, Bun, Lagon, AWS Lambda, Lambda@Edge, or Node.js. The same code runs on all platforms.
53
53
  - **Batteries Included** πŸ”‹ - Hono has built-in middleware, custom middleware, and third-party middleware. Batteries included.
54
- - **Delightful DX** πŸ› οΈ - Super clean APIs. First-class TypeScript support. Now, we've got "Types".
54
+ - **Delightful DX** πŸ˜ƒ - Super clean APIs. First-class TypeScript support. Now, we've got "Types".
55
55
 
56
56
  ## Benchmarks
57
57
 
@@ -23,12 +23,15 @@ var streamHandle = (app) => {
23
23
  requestContext,
24
24
  context
25
25
  });
26
- const contentType = res.headers.get("content-type");
27
- if (!contentType) {
28
- console.warn("Content Type is not set in the response.");
29
- }
26
+ const httpResponseMetadata = {
27
+ statusCode: res.status,
28
+ headers: Object.fromEntries(res.headers.entries())
29
+ };
30
30
  if (res.body) {
31
- await streamToNodeStream(res.body.getReader(), responseStream);
31
+ await streamToNodeStream(
32
+ res.body.getReader(),
33
+ awslambda.HttpResponseStream.from(responseStream, httpResponseMetadata)
34
+ );
32
35
  }
33
36
  } catch (error) {
34
37
  console.error("Error processing request:", error);
@@ -54,12 +54,15 @@ const streamHandle = (app) => {
54
54
  requestContext,
55
55
  context
56
56
  });
57
- const contentType = res.headers.get("content-type");
58
- if (!contentType) {
59
- console.warn("Content Type is not set in the response.");
60
- }
57
+ const httpResponseMetadata = {
58
+ statusCode: res.status,
59
+ headers: Object.fromEntries(res.headers.entries())
60
+ };
61
61
  if (res.body) {
62
- await streamToNodeStream(res.body.getReader(), responseStream);
62
+ await streamToNodeStream(
63
+ res.body.getReader(),
64
+ awslambda.HttpResponseStream.from(responseStream, httpResponseMetadata)
65
+ );
63
66
  }
64
67
  } catch (error) {
65
68
  console.error("Error processing request:", error);
@@ -23,9 +23,10 @@ __export(html_exports, {
23
23
  });
24
24
  module.exports = __toCommonJS(html_exports);
25
25
  var import_html = require("../../utils/html");
26
- const raw = (value) => {
26
+ const raw = (value, promises) => {
27
27
  const escapedString = new String(value);
28
28
  escapedString.isEscaped = true;
29
+ escapedString.promises = promises;
29
30
  return escapedString;
30
31
  };
31
32
  const html = (strings, ...values) => {
@@ -52,7 +53,7 @@ const html = (strings, ...values) => {
52
53
  }
53
54
  }
54
55
  buffer[0] += strings[strings.length - 1];
55
- return buffer.length === 1 ? raw(buffer[0]) : (0, import_html.stringBufferToString)(buffer).then((str) => raw(str));
56
+ return buffer.length === 1 ? raw(buffer[0]) : (0, import_html.stringBufferToString)(buffer);
56
57
  };
57
58
  // Annotate the CommonJS export names for ESM import in node:
58
59
  0 && (module.exports = {
@@ -44,13 +44,17 @@ const streamSSE = (c, cb) => {
44
44
  return c.stream(async (originalStream) => {
45
45
  const { readable, writable } = new TransformStream();
46
46
  const stream = new SSEStreamingApi(writable);
47
- originalStream.pipe(readable);
47
+ originalStream.pipe(readable).catch((err) => {
48
+ console.error("Error in stream piping: ", err);
49
+ stream.close();
50
+ });
48
51
  setSSEHeaders(c);
49
52
  try {
50
53
  await cb(stream);
51
54
  } catch (err) {
52
55
  console.error("Error during streaming: ", err);
53
- stream.close();
56
+ } finally {
57
+ await stream.close();
54
58
  }
55
59
  });
56
60
  };
@@ -38,7 +38,7 @@ const errorHandler = (err, c) => {
38
38
  if (err instanceof import_http_exception.HTTPException) {
39
39
  return err.getResponse();
40
40
  }
41
- console.trace(err);
41
+ console.error(err);
42
42
  const message = "Internal Server Error";
43
43
  return c.text(message, 500);
44
44
  };
@@ -26,7 +26,8 @@ __export(jsx_exports, {
26
26
  useContext: () => useContext
27
27
  });
28
28
  module.exports = __toCommonJS(jsx_exports);
29
- var import_html = require("../utils/html");
29
+ var import_html = require("../helper/html");
30
+ var import_html2 = require("../utils/html");
30
31
  const emptyTags = [
31
32
  "area",
32
33
  "base",
@@ -75,7 +76,7 @@ const childrenToStringToBuffer = (children, buffer) => {
75
76
  for (let i = 0, len = children.length; i < len; i++) {
76
77
  const child = children[i];
77
78
  if (typeof child === "string") {
78
- (0, import_html.escapeToBuffer)(child, buffer);
79
+ (0, import_html2.escapeToBuffer)(child, buffer);
79
80
  } else if (typeof child === "boolean" || child === null || child === void 0) {
80
81
  continue;
81
82
  } else if (child instanceof JSXNode) {
@@ -100,7 +101,7 @@ class JSXNode {
100
101
  toString() {
101
102
  const buffer = [""];
102
103
  this.toStringToBuffer(buffer);
103
- return buffer.length === 1 ? buffer[0] : (0, import_html.stringBufferToString)(buffer);
104
+ return buffer.length === 1 ? buffer[0] : (0, import_html2.stringBufferToString)(buffer);
104
105
  }
105
106
  toStringToBuffer(buffer) {
106
107
  const tag = this.tag;
@@ -119,7 +120,7 @@ class JSXNode {
119
120
  buffer[0] += ` style="${styles}"`;
120
121
  } else if (typeof v === "string") {
121
122
  buffer[0] += ` ${key}="`;
122
- (0, import_html.escapeToBuffer)(v, buffer);
123
+ (0, import_html2.escapeToBuffer)(v, buffer);
123
124
  buffer[0] += '"';
124
125
  } else if (typeof v === "number") {
125
126
  buffer[0] += ` ${key}="${v}"`;
@@ -132,12 +133,10 @@ class JSXNode {
132
133
  if (children.length > 0) {
133
134
  throw "Can only set one of `children` or `props.dangerouslySetInnerHTML`.";
134
135
  }
135
- const escapedString = new String(v.__html);
136
- escapedString.isEscaped = true;
137
- children = [escapedString];
136
+ children = [(0, import_html.raw)(v.__html)];
138
137
  } else {
139
138
  buffer[0] += ` ${key}="`;
140
- (0, import_html.escapeToBuffer)(v.toString(), buffer);
139
+ (0, import_html2.escapeToBuffer)(v.toString(), buffer);
141
140
  buffer[0] += '"';
142
141
  }
143
142
  }
@@ -164,7 +163,7 @@ class JSXFunctionNode extends JSXNode {
164
163
  } else if (typeof res === "number" || res.isEscaped) {
165
164
  buffer[0] += res;
166
165
  } else {
167
- (0, import_html.escapeToBuffer)(res, buffer);
166
+ (0, import_html2.escapeToBuffer)(res, buffer);
168
167
  }
169
168
  }
170
169
  }
@@ -216,12 +215,19 @@ const createContext = (defaultValue) => {
216
215
  values,
217
216
  Provider(props) {
218
217
  values.push(props.value);
219
- const res = new String(
220
- props.children ? (Array.isArray(props.children) ? new JSXFragmentNode("", {}, props.children) : props.children).toString() : ""
221
- );
222
- res.isEscaped = true;
218
+ const string = props.children ? (Array.isArray(props.children) ? new JSXFragmentNode("", {}, props.children) : props.children).toString() : "";
223
219
  values.pop();
224
- return res;
220
+ if (string instanceof Promise) {
221
+ return Promise.resolve().then(async () => {
222
+ values.push(props.value);
223
+ const awaited = await string;
224
+ const promiseRes = (0, import_html.raw)(awaited, awaited.promises);
225
+ values.pop();
226
+ return promiseRes;
227
+ });
228
+ } else {
229
+ return (0, import_html.raw)(string);
230
+ }
225
231
  }
226
232
  };
227
233
  };
@@ -25,7 +25,10 @@ module.exports = __toCommonJS(jsx_dev_runtime_exports);
25
25
  var import__ = require(".");
26
26
  var import__2 = require(".");
27
27
  function jsxDEV(tag, props) {
28
- const children = props.children ?? [];
28
+ if (!props?.children) {
29
+ return (0, import__.jsx)(tag, props);
30
+ }
31
+ const children = props.children;
29
32
  delete props["children"];
30
33
  return Array.isArray(children) ? (0, import__.jsx)(tag, props, ...children) : (0, import__.jsx)(tag, props, children);
31
34
  }
@@ -20,14 +20,23 @@ var jsx_runtime_exports = {};
20
20
  __export(jsx_runtime_exports, {
21
21
  Fragment: () => import_jsx_dev_runtime.Fragment,
22
22
  jsx: () => import_jsx_dev_runtime.jsxDEV,
23
+ jsxAttr: () => jsxAttr,
24
+ jsxEscape: () => jsxEscape,
25
+ jsxTemplate: () => import_html.html,
23
26
  jsxs: () => import_jsx_dev_runtime2.jsxDEV
24
27
  });
25
28
  module.exports = __toCommonJS(jsx_runtime_exports);
26
29
  var import_jsx_dev_runtime = require("./jsx-dev-runtime");
27
30
  var import_jsx_dev_runtime2 = require("./jsx-dev-runtime");
31
+ var import_html = require("../helper/html");
32
+ const jsxAttr = (name, value) => (0, import_html.raw)(name + '="' + import_html.html`${value}` + '"');
33
+ const jsxEscape = (value) => value;
28
34
  // Annotate the CommonJS export names for ESM import in node:
29
35
  0 && (module.exports = {
30
36
  Fragment,
31
37
  jsx,
38
+ jsxAttr,
39
+ jsxEscape,
40
+ jsxTemplate,
32
41
  jsxs
33
42
  });
@@ -22,10 +22,11 @@ __export(streaming_exports, {
22
22
  renderToReadableStream: () => renderToReadableStream
23
23
  });
24
24
  module.exports = __toCommonJS(streaming_exports);
25
+ var import_html = require("../helper/html");
25
26
  let suspenseCounter = 0;
26
27
  async function childrenToString(children) {
27
28
  try {
28
- return children.toString();
29
+ return children.map((c) => c.toString());
29
30
  } catch (e) {
30
31
  if (e instanceof Promise) {
31
32
  await e;
@@ -39,26 +40,25 @@ const Suspense = async ({ children, fallback }) => {
39
40
  if (!children) {
40
41
  return fallback.toString();
41
42
  }
42
- let res;
43
+ if (!Array.isArray(children)) {
44
+ children = [children];
45
+ }
46
+ let resArray = [];
43
47
  try {
44
- res = children.toString();
48
+ resArray = children.map((c) => c.toString());
45
49
  } catch (e) {
46
50
  if (e instanceof Promise) {
47
- res = e;
51
+ resArray = [e.then(() => childrenToString(children))];
48
52
  } else {
49
53
  throw e;
50
54
  }
51
- } finally {
55
+ }
56
+ if (resArray.some((res) => res instanceof Promise)) {
52
57
  const index = suspenseCounter++;
53
- if (res instanceof Promise) {
54
- const promise = res;
55
- res = new String(
56
- `<template id="H:${index}"></template>${fallback.toString()}<!--/$-->`
57
- );
58
- res.isEscaped = true;
59
- res.promises = [
60
- promise.then(async () => {
61
- return `<template>${await childrenToString(children)}</template><script>
58
+ return (0, import_html.raw)(`<template id="H:${index}"></template>${fallback.toString()}<!--/$-->`, [
59
+ Promise.all(resArray).then((htmlArray) => {
60
+ htmlArray = htmlArray.flat();
61
+ const html = `<template>${htmlArray.join("")}</template><script>
62
62
  ((d,c,n) => {
63
63
  c=d.currentScript.previousSibling
64
64
  d=d.getElementById('H:${index}')
@@ -66,35 +66,44 @@ do{n=d.nextSibling;n.remove()}while(n.nodeType!=8||n.nodeValue!='/$')
66
66
  d.replaceWith(c.content)
67
67
  })(document)
68
68
  <\/script>`;
69
- })
70
- ];
71
- }
69
+ if (htmlArray.every((html2) => !html2.promises?.length)) {
70
+ return html;
71
+ }
72
+ return (0, import_html.raw)(html, htmlArray.map((html2) => html2.promises || []).flat());
73
+ })
74
+ ]);
75
+ } else {
76
+ return (0, import_html.raw)(resArray.join(""));
72
77
  }
73
- return res;
74
78
  };
75
79
  const textEncoder = new TextEncoder();
76
80
  const renderToReadableStream = (str) => {
77
81
  const reader = new ReadableStream({
78
82
  async start(controller) {
79
- const resolved = await str.toString();
83
+ const resolved = str instanceof Promise ? await str : await str.toString();
80
84
  controller.enqueue(textEncoder.encode(resolved));
81
- let unresolvedCount = resolved.promises?.length || 0;
82
- if (!unresolvedCount) {
83
- controller.close();
84
- return;
85
- }
86
- for (let i = 0; i < unresolvedCount; i++) {
87
- ;
88
- resolved.promises[i].catch((err) => {
89
- console.trace(err);
90
- return "";
91
- }).then((res) => {
92
- controller.enqueue(textEncoder.encode(res));
93
- if (!--unresolvedCount) {
94
- controller.close();
95
- }
96
- });
85
+ let resolvedCount = 0;
86
+ const promises = [];
87
+ const then = (promise) => {
88
+ promises.push(
89
+ promise.catch((err) => {
90
+ console.trace(err);
91
+ return "";
92
+ }).then((res) => {
93
+ if (res.promises) {
94
+ const resPromises = res.promises || [];
95
+ resPromises.forEach(then);
96
+ }
97
+ resolvedCount++;
98
+ controller.enqueue(textEncoder.encode(res));
99
+ })
100
+ );
101
+ };
102
+ resolved.promises?.map(then);
103
+ while (resolvedCount !== promises.length) {
104
+ await Promise.all(promises);
97
105
  }
106
+ controller.close();
98
107
  }
99
108
  });
100
109
  return reader;
@@ -23,17 +23,27 @@ __export(jsx_renderer_exports, {
23
23
  useRequestContext: () => useRequestContext
24
24
  });
25
25
  module.exports = __toCommonJS(jsx_renderer_exports);
26
+ var import_html = require("../../helper/html");
26
27
  var import_jsx = require("../../jsx");
28
+ var import_streaming = require("../../jsx/streaming");
27
29
  const RequestContext = (0, import_jsx.createContext)(null);
28
30
  const createRenderer = (c, component, options) => (children, props) => {
29
31
  const docType = typeof options?.docType === "string" ? options.docType : options?.docType === true ? "<!DOCTYPE html>" : "";
30
- return c.html(
31
- docType + (0, import_jsx.jsx)(
32
- RequestContext.Provider,
33
- { value: c },
34
- component ? component({ children, ...props || {} }) : children
35
- )
36
- );
32
+ const body = import_html.html`${(0, import_html.raw)(docType)}${(0, import_jsx.jsx)(
33
+ RequestContext.Provider,
34
+ { value: c },
35
+ component ? component({ children, ...props || {} }) : children
36
+ )}`;
37
+ if (options?.stream) {
38
+ return c.body((0, import_streaming.renderToReadableStream)(body), {
39
+ headers: options.stream === true ? {
40
+ "Transfer-Encoding": "chunked",
41
+ "Content-Type": "text/html; charset=UTF-8"
42
+ } : options.stream
43
+ });
44
+ } else {
45
+ return c.html(body);
46
+ }
37
47
  };
38
48
  const jsxRenderer = (component, options) => (c, next) => {
39
49
  c.setRenderer(createRenderer(c, component, options));
@@ -22,6 +22,7 @@ __export(html_exports, {
22
22
  stringBufferToString: () => stringBufferToString
23
23
  });
24
24
  module.exports = __toCommonJS(html_exports);
25
+ var import_html = require("../helper/html");
25
26
  const escapeRe = /[&<>'"]/;
26
27
  const stringBufferToString = async (buffer) => {
27
28
  let str = "";
@@ -37,10 +38,7 @@ const stringBufferToString = async (buffer) => {
37
38
  }
38
39
  str += r;
39
40
  }
40
- const res = new String(str);
41
- res.isEscaped = true;
42
- res.promises = promises;
43
- return res;
41
+ return (0, import_html.raw)(str, promises);
44
42
  };
45
43
  const escapeToBuffer = (str, buffer) => {
46
44
  const match = str.search(escapeRe);
@@ -1,8 +1,9 @@
1
1
  // src/helper/html/index.ts
2
2
  import { escapeToBuffer, stringBufferToString } from "../../utils/html.js";
3
- var raw = (value) => {
3
+ var raw = (value, promises) => {
4
4
  const escapedString = new String(value);
5
5
  escapedString.isEscaped = true;
6
+ escapedString.promises = promises;
6
7
  return escapedString;
7
8
  };
8
9
  var html = (strings, ...values) => {
@@ -29,7 +30,7 @@ var html = (strings, ...values) => {
29
30
  }
30
31
  }
31
32
  buffer[0] += strings[strings.length - 1];
32
- return buffer.length === 1 ? raw(buffer[0]) : stringBufferToString(buffer).then((str) => raw(str));
33
+ return buffer.length === 1 ? raw(buffer[0]) : stringBufferToString(buffer);
33
34
  };
34
35
  export {
35
36
  html,
@@ -22,13 +22,17 @@ var streamSSE = (c, cb) => {
22
22
  return c.stream(async (originalStream) => {
23
23
  const { readable, writable } = new TransformStream();
24
24
  const stream = new SSEStreamingApi(writable);
25
- originalStream.pipe(readable);
25
+ originalStream.pipe(readable).catch((err) => {
26
+ console.error("Error in stream piping: ", err);
27
+ stream.close();
28
+ });
26
29
  setSSEHeaders(c);
27
30
  try {
28
31
  await cb(stream);
29
32
  } catch (err) {
30
33
  console.error("Error during streaming: ", err);
31
- stream.close();
34
+ } finally {
35
+ await stream.close();
32
36
  }
33
37
  });
34
38
  };
package/dist/hono-base.js CHANGED
@@ -16,7 +16,7 @@ var errorHandler = (err, c) => {
16
16
  if (err instanceof HTTPException) {
17
17
  return err.getResponse();
18
18
  }
19
- console.trace(err);
19
+ console.error(err);
20
20
  const message = "Internal Server Error";
21
21
  return c.text(message, 500);
22
22
  };
package/dist/jsx/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  // src/jsx/index.ts
2
+ import { raw } from "../helper/html/index.js";
2
3
  import { escapeToBuffer, stringBufferToString } from "../utils/html.js";
3
4
  var emptyTags = [
4
5
  "area",
@@ -105,9 +106,7 @@ var JSXNode = class {
105
106
  if (children.length > 0) {
106
107
  throw "Can only set one of `children` or `props.dangerouslySetInnerHTML`.";
107
108
  }
108
- const escapedString = new String(v.__html);
109
- escapedString.isEscaped = true;
110
- children = [escapedString];
109
+ children = [raw(v.__html)];
111
110
  } else {
112
111
  buffer[0] += ` ${key}="`;
113
112
  escapeToBuffer(v.toString(), buffer);
@@ -189,12 +188,19 @@ var createContext = (defaultValue) => {
189
188
  values,
190
189
  Provider(props) {
191
190
  values.push(props.value);
192
- const res = new String(
193
- props.children ? (Array.isArray(props.children) ? new JSXFragmentNode("", {}, props.children) : props.children).toString() : ""
194
- );
195
- res.isEscaped = true;
191
+ const string = props.children ? (Array.isArray(props.children) ? new JSXFragmentNode("", {}, props.children) : props.children).toString() : "";
196
192
  values.pop();
197
- return res;
193
+ if (string instanceof Promise) {
194
+ return Promise.resolve().then(async () => {
195
+ values.push(props.value);
196
+ const awaited = await string;
197
+ const promiseRes = raw(awaited, awaited.promises);
198
+ values.pop();
199
+ return promiseRes;
200
+ });
201
+ } else {
202
+ return raw(string);
203
+ }
198
204
  }
199
205
  };
200
206
  };
@@ -2,7 +2,10 @@
2
2
  import { jsx } from "./index.js";
3
3
  import { Fragment } from "./index.js";
4
4
  function jsxDEV(tag, props) {
5
- const children = props.children ?? [];
5
+ if (!props?.children) {
6
+ return jsx(tag, props);
7
+ }
8
+ const children = props.children;
6
9
  delete props["children"];
7
10
  return Array.isArray(children) ? jsx(tag, props, ...children) : jsx(tag, props, children);
8
11
  }
@@ -1,8 +1,14 @@
1
1
  // src/jsx/jsx-runtime.ts
2
2
  import { jsxDEV, Fragment } from "./jsx-dev-runtime.js";
3
3
  import { jsxDEV as jsxDEV2 } from "./jsx-dev-runtime.js";
4
+ import { raw, html } from "../helper/html/index.js";
5
+ var jsxAttr = (name, value) => raw(name + '="' + html`${value}` + '"');
6
+ var jsxEscape = (value) => value;
4
7
  export {
5
8
  Fragment,
6
9
  jsxDEV as jsx,
10
+ jsxAttr,
11
+ jsxEscape,
12
+ html as jsxTemplate,
7
13
  jsxDEV2 as jsxs
8
14
  };
@@ -1,8 +1,9 @@
1
1
  // src/jsx/streaming.ts
2
+ import { raw } from "../helper/html/index.js";
2
3
  var suspenseCounter = 0;
3
4
  async function childrenToString(children) {
4
5
  try {
5
- return children.toString();
6
+ return children.map((c) => c.toString());
6
7
  } catch (e) {
7
8
  if (e instanceof Promise) {
8
9
  await e;
@@ -16,26 +17,25 @@ var Suspense = async ({ children, fallback }) => {
16
17
  if (!children) {
17
18
  return fallback.toString();
18
19
  }
19
- let res;
20
+ if (!Array.isArray(children)) {
21
+ children = [children];
22
+ }
23
+ let resArray = [];
20
24
  try {
21
- res = children.toString();
25
+ resArray = children.map((c) => c.toString());
22
26
  } catch (e) {
23
27
  if (e instanceof Promise) {
24
- res = e;
28
+ resArray = [e.then(() => childrenToString(children))];
25
29
  } else {
26
30
  throw e;
27
31
  }
28
- } finally {
32
+ }
33
+ if (resArray.some((res) => res instanceof Promise)) {
29
34
  const index = suspenseCounter++;
30
- if (res instanceof Promise) {
31
- const promise = res;
32
- res = new String(
33
- `<template id="H:${index}"></template>${fallback.toString()}<!--/$-->`
34
- );
35
- res.isEscaped = true;
36
- res.promises = [
37
- promise.then(async () => {
38
- return `<template>${await childrenToString(children)}</template><script>
35
+ return raw(`<template id="H:${index}"></template>${fallback.toString()}<!--/$-->`, [
36
+ Promise.all(resArray).then((htmlArray) => {
37
+ htmlArray = htmlArray.flat();
38
+ const html = `<template>${htmlArray.join("")}</template><script>
39
39
  ((d,c,n) => {
40
40
  c=d.currentScript.previousSibling
41
41
  d=d.getElementById('H:${index}')
@@ -43,35 +43,44 @@ do{n=d.nextSibling;n.remove()}while(n.nodeType!=8||n.nodeValue!='/$')
43
43
  d.replaceWith(c.content)
44
44
  })(document)
45
45
  <\/script>`;
46
- })
47
- ];
48
- }
46
+ if (htmlArray.every((html2) => !html2.promises?.length)) {
47
+ return html;
48
+ }
49
+ return raw(html, htmlArray.map((html2) => html2.promises || []).flat());
50
+ })
51
+ ]);
52
+ } else {
53
+ return raw(resArray.join(""));
49
54
  }
50
- return res;
51
55
  };
52
56
  var textEncoder = new TextEncoder();
53
57
  var renderToReadableStream = (str) => {
54
58
  const reader = new ReadableStream({
55
59
  async start(controller) {
56
- const resolved = await str.toString();
60
+ const resolved = str instanceof Promise ? await str : await str.toString();
57
61
  controller.enqueue(textEncoder.encode(resolved));
58
- let unresolvedCount = resolved.promises?.length || 0;
59
- if (!unresolvedCount) {
60
- controller.close();
61
- return;
62
- }
63
- for (let i = 0; i < unresolvedCount; i++) {
64
- ;
65
- resolved.promises[i].catch((err) => {
66
- console.trace(err);
67
- return "";
68
- }).then((res) => {
69
- controller.enqueue(textEncoder.encode(res));
70
- if (!--unresolvedCount) {
71
- controller.close();
72
- }
73
- });
62
+ let resolvedCount = 0;
63
+ const promises = [];
64
+ const then = (promise) => {
65
+ promises.push(
66
+ promise.catch((err) => {
67
+ console.trace(err);
68
+ return "";
69
+ }).then((res) => {
70
+ if (res.promises) {
71
+ const resPromises = res.promises || [];
72
+ resPromises.forEach(then);
73
+ }
74
+ resolvedCount++;
75
+ controller.enqueue(textEncoder.encode(res));
76
+ })
77
+ );
78
+ };
79
+ resolved.promises?.map(then);
80
+ while (resolvedCount !== promises.length) {
81
+ await Promise.all(promises);
74
82
  }
83
+ controller.close();
75
84
  }
76
85
  });
77
86
  return reader;
@@ -1,15 +1,25 @@
1
1
  // src/middleware/jsx-renderer/index.ts
2
+ import { html, raw } from "../../helper/html/index.js";
2
3
  import { jsx, createContext, useContext } from "../../jsx/index.js";
4
+ import { renderToReadableStream } from "../../jsx/streaming.js";
3
5
  var RequestContext = createContext(null);
4
6
  var createRenderer = (c, component, options) => (children, props) => {
5
7
  const docType = typeof options?.docType === "string" ? options.docType : options?.docType === true ? "<!DOCTYPE html>" : "";
6
- return c.html(
7
- docType + jsx(
8
- RequestContext.Provider,
9
- { value: c },
10
- component ? component({ children, ...props || {} }) : children
11
- )
12
- );
8
+ const body = html`${raw(docType)}${jsx(
9
+ RequestContext.Provider,
10
+ { value: c },
11
+ component ? component({ children, ...props || {} }) : children
12
+ )}`;
13
+ if (options?.stream) {
14
+ return c.body(renderToReadableStream(body), {
15
+ headers: options.stream === true ? {
16
+ "Transfer-Encoding": "chunked",
17
+ "Content-Type": "text/html; charset=UTF-8"
18
+ } : options.stream
19
+ });
20
+ } else {
21
+ return c.html(body);
22
+ }
13
23
  };
14
24
  var jsxRenderer = (component, options) => (c, next) => {
15
25
  c.setRenderer(createRenderer(c, component, options));
@@ -91,7 +91,7 @@ export declare class Context<E extends Env = any, P extends string = any, I exte
91
91
  status: (status: StatusCode) => void;
92
92
  set: Set<E>;
93
93
  get: Get<E>;
94
- get var(): Readonly<E['Variables']>;
94
+ get var(): Readonly<E['Variables'] & ContextVariableMap>;
95
95
  newResponse: NewResponse;
96
96
  body: BodyRespond;
97
97
  text: TextRespond;
@@ -1,4 +1,4 @@
1
1
  import type { Context } from '../../context';
2
2
  export declare type Runtime = 'node' | 'deno' | 'bun' | 'workerd' | 'fastly' | 'edge-light' | 'lagon' | 'other';
3
- export declare const env: <T extends Record<string, string>, C extends Context<any, any, {}> = Context<{}, any, {}>>(c: C, runtime?: Runtime) => T & C["env"];
3
+ export declare const env: <T extends Record<string, unknown>, C extends Context<any, any, {}> = Context<{}, any, {}>>(c: C, runtime?: Runtime) => T & C["env"];
4
4
  export declare const getRuntimeKey: () => "other" | "node" | "deno" | "bun" | "workerd" | "fastly" | "edge-light" | "lagon";
@@ -1,7 +1,2 @@
1
1
  import type { Env, Input, MiddlewareHandler } from '../../types';
2
- /**
3
- * @experimental
4
- * `createMiddleware()` is an experimental feature.
5
- * The API might be changed.
6
- */
7
2
  export declare const createMiddleware: <E extends Env = any, P extends string = any, I extends Input = {}>(middleware: MiddlewareHandler<E, P, I>) => MiddlewareHandler<E, P, I>;
@@ -1,3 +1,3 @@
1
1
  import type { HtmlEscapedString } from '../../utils/html';
2
- export declare const raw: (value: unknown) => HtmlEscapedString;
2
+ export declare const raw: (value: unknown, promises?: Promise<string>[]) => HtmlEscapedString;
3
3
  export declare const html: (strings: TemplateStringsArray, ...values: unknown[]) => HtmlEscapedString | Promise<HtmlEscapedString>;
@@ -43,11 +43,6 @@ declare class Hono<E extends Env = Env, S extends Schema = {}, BasePath extends
43
43
  onError(handler: ErrorHandler<E>): this;
44
44
  notFound(handler: NotFoundHandler<E>): this;
45
45
  showRoutes(): void;
46
- /**
47
- * @experimental
48
- * `app.mount()` is an experimental feature.
49
- * The API might be changed.
50
- */
51
46
  mount(path: string, applicationHandler: (request: Request, ...args: any) => Response | Promise<Response>, optionHandler?: (c: Context) => unknown): Hono<E, S, BasePath>;
52
47
  get routerName(): string;
53
48
  /**
@@ -1,2 +1,6 @@
1
1
  export { jsxDEV as jsx, Fragment } from './jsx-dev-runtime';
2
2
  export { jsxDEV as jsxs } from './jsx-dev-runtime';
3
+ import { html } from '../helper/html';
4
+ export { html as jsxTemplate };
5
+ export declare const jsxAttr: (name: string, value: string) => import("../utils/html").HtmlEscapedString;
6
+ export declare const jsxEscape: (value: string) => string;
@@ -5,6 +5,7 @@ export declare const RequestContext: import("../../jsx").Context<Context<any, an
5
5
  declare type PropsForRenderer = [...Required<Parameters<Renderer>>] extends [unknown, infer Props] ? Props : unknown;
6
6
  declare type RendererOptions = {
7
7
  docType?: boolean | string;
8
+ stream?: boolean | Record<string, string>;
8
9
  };
9
10
  export declare const jsxRenderer: (component?: FC<PropsForRenderer>, options?: RendererOptions) => MiddlewareHandler;
10
11
  export declare const useRequestContext: <E extends Env = any, P extends string = any, I extends Input = {}>() => Context<E, P, I>;
@@ -22,39 +22,39 @@ export declare type H<E extends Env = any, P extends string = any, I extends Inp
22
22
  export declare type NotFoundHandler<E extends Env = any> = (c: Context<E>) => Response | Promise<Response>;
23
23
  export declare type ErrorHandler<E extends Env = any> = (err: Error, c: Context<E>) => Response | Promise<Response>;
24
24
  export interface HandlerInterface<E extends Env = Env, M extends string = string, S extends Schema = {}, BasePath extends string = '/'> {
25
- <P extends string = ExtractKey<S> extends never ? BasePath : ExtractKey<S>, I extends Input = {}, R extends HandlerResponse<any> = any, E2 extends Env = E>(handler: H<E2, P, I, R>): Hono<E, S & ToSchema<M, P, I['in'], MergeTypedResponseData<R>>, BasePath>;
26
- <P extends string = ExtractKey<S> extends never ? BasePath : ExtractKey<S>, I extends Input = {}, R extends HandlerResponse<any> = any, E2 extends Env = E, E3 extends Env = IntersectNonAnyTypes<[E, E2]>>(...handlers: [H<E2, P, I, R>, H<E3, P, I, R>]): Hono<E, S & ToSchema<M, P, I['in'], MergeTypedResponseData<R>>, BasePath>;
27
- <P extends string = ExtractKey<S> extends never ? BasePath : ExtractKey<S>, R extends HandlerResponse<any> = any, I extends Input = {}, I2 extends Input = I, I3 extends Input = I & I2, E2 extends Env = E, E3 extends Env = E, E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>>(...handlers: [H<E2, P, I, R>, H<E3, P, I2, R>, H<E4, P, I3, R>]): Hono<E, S & ToSchema<M, P, I3['in'], MergeTypedResponseData<R>>, BasePath>;
28
- <P extends string = ExtractKey<S> extends never ? BasePath : ExtractKey<S>, R extends HandlerResponse<any> = any, I extends Input = {}, I2 extends Input = I, I3 extends Input = I & I2, I4 extends Input = I & I2 & I3, E2 extends Env = E, E3 extends Env = E, E4 extends Env = E, E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>>(...handlers: [H<E2, P, I, R>, H<E3, P, I2, R>, H<E4, P, I3, R>, H<E5, P, I3, R>]): Hono<E, S & ToSchema<M, P, I4['in'], MergeTypedResponseData<R>>, BasePath>;
25
+ <P extends string = ExtractKey<S> extends never ? BasePath : ExtractKey<S>, I extends Input = {}, R extends HandlerResponse<any> = any, E2 extends Env = E>(handler: H<E2, P, I, R>): Hono<E & E2, S & ToSchema<M, P, I['in'], MergeTypedResponseData<R>>, BasePath>;
26
+ <P extends string = ExtractKey<S> extends never ? BasePath : ExtractKey<S>, I extends Input = {}, R extends HandlerResponse<any> = any, E2 extends Env = E, E3 extends Env = IntersectNonAnyTypes<[E, E2]>>(...handlers: [H<E2, P, I, R>, H<E3, P, I, R>]): Hono<E & E2 & E3, S & ToSchema<M, P, I['in'], MergeTypedResponseData<R>>, BasePath>;
27
+ <P extends string = ExtractKey<S> extends never ? BasePath : ExtractKey<S>, R extends HandlerResponse<any> = any, I extends Input = {}, I2 extends Input = I, I3 extends Input = I & I2, E2 extends Env = E, E3 extends Env = E, E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>>(...handlers: [H<E2, P, I, R>, H<E3, P, I2, R>, H<E4, P, I3, R>]): Hono<E & E2 & E3 & E4, S & ToSchema<M, P, I3['in'], MergeTypedResponseData<R>>, BasePath>;
28
+ <P extends string = ExtractKey<S> extends never ? BasePath : ExtractKey<S>, R extends HandlerResponse<any> = any, I extends Input = {}, I2 extends Input = I, I3 extends Input = I & I2, I4 extends Input = I & I2 & I3, E2 extends Env = E, E3 extends Env = E, E4 extends Env = E, E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>>(...handlers: [H<E2, P, I, R>, H<E3, P, I2, R>, H<E4, P, I3, R>, H<E5, P, I3, R>]): Hono<E & E2 & E3 & E4 & E5, S & ToSchema<M, P, I4['in'], MergeTypedResponseData<R>>, BasePath>;
29
29
  <P extends string = ExtractKey<S> extends never ? BasePath : ExtractKey<S>, R extends HandlerResponse<any> = any, I extends Input = {}, I2 extends Input = I, I3 extends Input = I & I2, I4 extends Input = I2 & I3, I5 extends Input = I & I2 & I3 & I4, E2 extends Env = E, E3 extends Env = E, E4 extends Env = E, E5 extends Env = E, E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>>(...handlers: [
30
30
  H<E2, P, I, R>,
31
31
  H<E3, P, I2, R>,
32
32
  H<E4, P, I3, R>,
33
33
  H<E5, P, I3, R>,
34
34
  H<E6, P, I3, R>
35
- ]): Hono<E, S & ToSchema<M, P, I5['in'], MergeTypedResponseData<R>>, BasePath>;
35
+ ]): Hono<E & E2 & E3 & E4 & E5 & E6, S & ToSchema<M, P, I5['in'], MergeTypedResponseData<R>>, BasePath>;
36
36
  <P extends string = ExtractKey<S> extends never ? BasePath : ExtractKey<S>, I extends Input = {}, R extends HandlerResponse<any> = any>(...handlers: H<E, P, I, R>[]): Hono<E, S & ToSchema<M, P, I['in'], MergeTypedResponseData<R>>, BasePath>;
37
37
  <P extends string, R extends HandlerResponse<any> = any, I extends Input = {}>(path: P): Hono<E, S & ToSchema<M, MergePath<BasePath, P>, I['in'], MergeTypedResponseData<R>>, BasePath>;
38
- <P extends string, P2 extends string = P, R extends HandlerResponse<any> = any, I extends Input = {}, E2 extends Env = E>(path: P, handler: H<E2, MergePath<BasePath, P2>, I, R>): Hono<E, S & ToSchema<M, MergePath<BasePath, P>, I['in'], MergeTypedResponseData<R>>, BasePath>;
39
- <P extends string, P2 extends string = P, P3 extends string = P, R extends HandlerResponse<any> = any, I extends Input = {}, E2 extends Env = E, E3 extends Env = IntersectNonAnyTypes<[E, E2]>>(path: P, ...handlers: [H<E2, MergePath<BasePath, P2>, I, R>, H<E3, MergePath<BasePath, P3>, I, R>]): Hono<E, S & ToSchema<M, MergePath<BasePath, P>, I['in'], MergeTypedResponseData<R>>, BasePath>;
38
+ <P extends string, P2 extends string = P, R extends HandlerResponse<any> = any, I extends Input = {}, E2 extends Env = E>(path: P, handler: H<E2, MergePath<BasePath, P2>, I, R>): Hono<E & E2, S & ToSchema<M, MergePath<BasePath, P>, I['in'], MergeTypedResponseData<R>>, BasePath>;
39
+ <P extends string, P2 extends string = P, P3 extends string = P, R extends HandlerResponse<any> = any, I extends Input = {}, E2 extends Env = E, E3 extends Env = IntersectNonAnyTypes<[E, E2]>>(path: P, ...handlers: [H<E2, MergePath<BasePath, P2>, I, R>, H<E3, MergePath<BasePath, P3>, I, R>]): Hono<E & E2 & E3, S & ToSchema<M, MergePath<BasePath, P>, I['in'], MergeTypedResponseData<R>>, BasePath>;
40
40
  <P extends string, P2 extends string = P, P3 extends string = P, P4 extends string = P, R extends HandlerResponse<any> = any, I extends Input = {}, I2 extends Input = I, I3 extends Input = I & I2, E2 extends Env = E, E3 extends Env = E, E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>>(path: P, ...handlers: [
41
41
  H<E2, MergePath<BasePath, P2>, I, R>,
42
42
  H<E3, MergePath<BasePath, P3>, I2, R>,
43
43
  H<E4, MergePath<BasePath, P4>, I3, R>
44
- ]): Hono<E, S & ToSchema<M, MergePath<BasePath, P>, I3['in'], MergeTypedResponseData<R>>, BasePath>;
44
+ ]): Hono<E & E2 & E3 & E4, S & ToSchema<M, MergePath<BasePath, P>, I3['in'], MergeTypedResponseData<R>>, BasePath>;
45
45
  <P extends string, P2 extends string = P, P3 extends string = P, P4 extends string = P, P5 extends string = P, R extends HandlerResponse<any> = any, I extends Input = {}, I2 extends Input = I, I3 extends Input = I & I2, I4 extends Input = I & I2 & I3, E2 extends Env = E, E3 extends Env = E, E4 extends Env = E, E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>>(path: P, ...handlers: [
46
46
  H<E2, MergePath<BasePath, P2>, I, R>,
47
47
  H<E3, MergePath<BasePath, P3>, I2, R>,
48
48
  H<E4, MergePath<BasePath, P4>, I3, R>,
49
49
  H<E5, MergePath<BasePath, P5>, I4, R>
50
- ]): Hono<E, S & ToSchema<M, MergePath<BasePath, P>, I4['in'], MergeTypedResponseData<R>>, BasePath>;
50
+ ]): Hono<E & E2 & E3 & E4 & E5, S & ToSchema<M, MergePath<BasePath, P>, I4['in'], MergeTypedResponseData<R>>, BasePath>;
51
51
  <P extends string, P2 extends string = P, P3 extends string = P, P4 extends string = P, P5 extends string = P, P6 extends string = P, R extends HandlerResponse<any> = any, I extends Input = {}, I2 extends Input = I, I3 extends Input = I & I2, I4 extends Input = I2 & I3, I5 extends Input = I & I2 & I3 & I4, E2 extends Env = E, E3 extends Env = E, E4 extends Env = E, E5 extends Env = E, E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>>(path: P, ...handlers: [
52
52
  H<E2, MergePath<BasePath, P2>, I, R>,
53
53
  H<E3, MergePath<BasePath, P3>, I2, R>,
54
54
  H<E4, MergePath<BasePath, P4>, I3, R>,
55
55
  H<E5, MergePath<BasePath, P5>, I4, R>,
56
56
  H<E6, MergePath<BasePath, P6>, I5, R>
57
- ]): Hono<E, S & ToSchema<M, MergePath<BasePath, P>, I5['in'], MergeTypedResponseData<R>>, BasePath>;
57
+ ]): Hono<E & E2 & E3 & E4 & E5 & E6, S & ToSchema<M, MergePath<BasePath, P>, I5['in'], MergeTypedResponseData<R>>, BasePath>;
58
58
  <P extends string, I extends Input = {}, R extends HandlerResponse<any> = any>(path: P, ...handlers: H<E, MergePath<BasePath, P>, I, R>[]): Hono<E, S & ToSchema<M, MergePath<BasePath, P>, I['in'], MergeTypedResponseData<R>>, BasePath>;
59
59
  }
60
60
  export interface MiddlewareHandlerInterface<E extends Env = Env, S extends Schema = {}, BasePath extends string = '/'> {
@@ -1,4 +1,5 @@
1
1
  // src/utils/html.ts
2
+ import { raw } from "../helper/html/index.js";
2
3
  var escapeRe = /[&<>'"]/;
3
4
  var stringBufferToString = async (buffer) => {
4
5
  let str = "";
@@ -14,10 +15,7 @@ var stringBufferToString = async (buffer) => {
14
15
  }
15
16
  str += r;
16
17
  }
17
- const res = new String(str);
18
- res.isEscaped = true;
19
- res.promises = promises;
20
- return res;
18
+ return raw(str, promises);
21
19
  };
22
20
  var escapeToBuffer = (str, buffer) => {
23
21
  const match = str.search(escapeRe);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hono",
3
- "version": "3.10.0-rc.1",
3
+ "version": "3.10.0",
4
4
  "description": "Ultrafast web framework for the Edges",
5
5
  "main": "dist/cjs/index.js",
6
6
  "type": "module",
@@ -11,7 +11,7 @@
11
11
  ],
12
12
  "scripts": {
13
13
  "test": "tsc --noEmit && vitest --run",
14
- "test:deno": "env NAME=Deno deno test --allow-read --allow-env runtime_tests/deno",
14
+ "test:deno": "env NAME=Deno deno test --allow-read --allow-env runtime_tests/deno && deno test --no-lock -c runtime_tests/deno-jsx/deno.precompile.json runtime_tests/deno-jsx && deno test --no-lock -c runtime_tests/deno-jsx/deno.react-jsx.json runtime_tests/deno-jsx",
15
15
  "test:bun": "env NAME=Bun bun test --jsx-import-source ../../src/jsx runtime_tests/bun/index.test.tsx",
16
16
  "test:fastly": "jest --config ./runtime_tests/fastly/jest.config.js",
17
17
  "test:lagon": "start-server-and-test \"lagon dev runtime_tests/lagon/index.ts -e runtime_tests/lagon/.env.lagon\" http://127.0.0.1:1234 \"yarn vitest --run runtime_tests/lagon/index.test.ts --config runtime_tests/lagon/vitest.config.ts\"",