components.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. // src/jsx/components.ts
  2. import { raw } from "../helper/html/index.js";
  3. import { HtmlEscapedCallbackPhase, resolveCallback } from "../utils/html.js";
  4. import { jsx, Fragment } from "./base.js";
  5. import { DOM_RENDERER } from "./constants.js";
  6. import { useContext } from "./context.js";
  7. import { ErrorBoundary as ErrorBoundaryDomRenderer } from "./dom/components.js";
  8. import { StreamingContext } from "./streaming.js";
  9. var errorBoundaryCounter = 0;
  10. var childrenToString = async (children) => {
  11. try {
  12. return children.flat().map((c) => c == null || typeof c === "boolean" ? "" : c.toString());
  13. } catch (e) {
  14. if (e instanceof Promise) {
  15. await e;
  16. return childrenToString(children);
  17. } else {
  18. throw e;
  19. }
  20. }
  21. };
  22. var resolveChildEarly = (c) => {
  23. if (c == null || typeof c === "boolean") {
  24. return "";
  25. } else if (typeof c === "string") {
  26. return c;
  27. } else {
  28. const str = c.toString();
  29. if (!(str instanceof Promise)) {
  30. return raw(str);
  31. } else {
  32. return str;
  33. }
  34. }
  35. };
  36. var ErrorBoundary = async ({ children, fallback, fallbackRender, onError }) => {
  37. if (!children) {
  38. return raw("");
  39. }
  40. if (!Array.isArray(children)) {
  41. children = [children];
  42. }
  43. const nonce = useContext(StreamingContext)?.scriptNonce;
  44. let fallbackStr;
  45. const resolveFallbackStr = async () => {
  46. const awaitedFallback = await fallback;
  47. if (typeof awaitedFallback === "string") {
  48. fallbackStr = awaitedFallback;
  49. } else {
  50. fallbackStr = await awaitedFallback?.toString();
  51. if (typeof fallbackStr === "string") {
  52. fallbackStr = raw(fallbackStr);
  53. }
  54. }
  55. };
  56. const fallbackRes = (error) => {
  57. onError?.(error);
  58. return fallbackStr || fallbackRender && jsx(Fragment, {}, fallbackRender(error)) || "";
  59. };
  60. let resArray = [];
  61. try {
  62. resArray = children.map(resolveChildEarly);
  63. } catch (e) {
  64. await resolveFallbackStr();
  65. if (e instanceof Promise) {
  66. resArray = [
  67. e.then(() => childrenToString(children)).catch((e2) => fallbackRes(e2))
  68. ];
  69. } else {
  70. resArray = [fallbackRes(e)];
  71. }
  72. }
  73. if (resArray.some((res) => res instanceof Promise)) {
  74. await resolveFallbackStr();
  75. const index = errorBoundaryCounter++;
  76. const replaceRe = RegExp(`(<template id="E:${index}"></template>.*?)(.*?)(<!--E:${index}-->)`);
  77. const caught = false;
  78. const catchCallback = async ({ error: error2, buffer }) => {
  79. if (caught) {
  80. return "";
  81. }
  82. const fallbackResString = await Fragment({
  83. children: fallbackRes(error2)
  84. }).toString();
  85. if (buffer) {
  86. buffer[0] = buffer[0].replace(replaceRe, fallbackResString);
  87. }
  88. return buffer ? "" : `<template data-hono-target="E:${index}">${fallbackResString}</template><script>
  89. ((d,c,n) => {
  90. c=d.currentScript.previousSibling
  91. d=d.getElementById('E:${index}')
  92. if(!d)return
  93. do{n=d.nextSibling;n.remove()}while(n.nodeType!=8||n.nodeValue!='E:${index}')
  94. d.replaceWith(c.content)
  95. })(document)
  96. </script>`;
  97. };
  98. let error;
  99. const promiseAll = Promise.all(resArray).catch((e) => error = e);
  100. return raw(`<template id="E:${index}"></template><!--E:${index}-->`, [
  101. ({ phase, buffer, context }) => {
  102. if (phase === HtmlEscapedCallbackPhase.BeforeStream) {
  103. return;
  104. }
  105. return promiseAll.then(async (htmlArray) => {
  106. if (error) {
  107. throw error;
  108. }
  109. htmlArray = htmlArray.flat();
  110. const content = htmlArray.join("");
  111. let html = buffer ? "" : `<template data-hono-target="E:${index}">${content}</template><script${nonce ? ` nonce="${nonce}"` : ""}>
  112. ((d,c) => {
  113. c=d.currentScript.previousSibling
  114. d=d.getElementById('E:${index}')
  115. if(!d)return
  116. d.parentElement.insertBefore(c.content,d.nextSibling)
  117. })(document)
  118. </script>`;
  119. if (htmlArray.every((html2) => !html2.callbacks?.length)) {
  120. if (buffer) {
  121. buffer[0] = buffer[0].replace(replaceRe, content);
  122. }
  123. return html;
  124. }
  125. if (buffer) {
  126. buffer[0] = buffer[0].replace(
  127. replaceRe,
  128. (_all, pre, _, post) => `${pre}${content}${post}`
  129. );
  130. }
  131. const callbacks = htmlArray.map((html2) => html2.callbacks || []).flat();
  132. if (phase === HtmlEscapedCallbackPhase.Stream) {
  133. html = await resolveCallback(
  134. html,
  135. HtmlEscapedCallbackPhase.BeforeStream,
  136. true,
  137. context
  138. );
  139. }
  140. let resolvedCount = 0;
  141. const promises = callbacks.map(
  142. (c) => (...args) => c(...args)?.then((content2) => {
  143. resolvedCount++;
  144. if (buffer) {
  145. if (resolvedCount === callbacks.length) {
  146. buffer[0] = buffer[0].replace(replaceRe, (_all, _pre, content3) => content3);
  147. }
  148. buffer[0] += content2;
  149. return raw("", content2.callbacks);
  150. }
  151. return raw(
  152. content2 + (resolvedCount !== callbacks.length ? "" : `<script>
  153. ((d,c,n) => {
  154. d=d.getElementById('E:${index}')
  155. if(!d)return
  156. n=d.nextSibling
  157. while(n.nodeType!=8||n.nodeValue!='E:${index}'){n=n.nextSibling}
  158. n.remove()
  159. d.remove()
  160. })(document)
  161. </script>`),
  162. content2.callbacks
  163. );
  164. }).catch((error2) => catchCallback({ error: error2, buffer }))
  165. );
  166. return raw(html, promises);
  167. }).catch((error2) => catchCallback({ error: error2, buffer }));
  168. }
  169. ]);
  170. } else {
  171. return Fragment({ children: resArray });
  172. }
  173. };
  174. ErrorBoundary[DOM_RENDERER] = ErrorBoundaryDomRenderer;
  175. export {
  176. ErrorBoundary,
  177. childrenToString
  178. };