server.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. import { sharedConfig, awaitSuspense, splitProps } from 'solid-js';
  2. export { ErrorBoundary, For, Index, Match, Show, Suspense, SuspenseList, Switch, createComponent, mergeProps } from 'solid-js';
  3. import { Readable } from 'stream';
  4. const booleans = ["allowfullscreen", "allowpaymentrequest", "async", "autofocus", "autoplay", "checked", "controls", "default", "disabled", "formnovalidate", "hidden", "ismap", "itemscope", "loop", "multiple", "muted", "nomodule", "novalidate", "open", "playsinline", "readonly", "required", "reversed", "seamless", "selected", "truespeed"];
  5. const BooleanAttributes = new Set(booleans);
  6. const Aliases = {
  7. className: "class",
  8. htmlFor: "for"
  9. };
  10. function renderToString(code, options = {}) {
  11. sharedConfig.context = {
  12. id: "",
  13. count: 0
  14. };
  15. return {
  16. html: resolveSSRNode(code()),
  17. script: generateHydrationScript(options)
  18. };
  19. }
  20. function renderToStringAsync(code, options = {}) {
  21. options = {
  22. timeoutMs: 30000,
  23. ...options
  24. };
  25. let resources;
  26. sharedConfig.context = {
  27. id: "",
  28. count: 0,
  29. resources: resources = {},
  30. suspense: {},
  31. async: true
  32. };
  33. const timeout = new Promise((_, reject) => setTimeout(() => reject("renderToString timed out"), options.timeoutMs));
  34. return Promise.race([awaitSuspense(code), timeout]).then(res => {
  35. return {
  36. html: resolveSSRNode(res),
  37. script: generateHydrationScript({
  38. resources,
  39. ...options
  40. })
  41. };
  42. });
  43. }
  44. function renderToNodeStream(code, options = {}) {
  45. const stream = new Readable({
  46. read() {}
  47. });
  48. sharedConfig.context = {
  49. id: "",
  50. count: 0,
  51. streaming: true,
  52. suspense: {}
  53. };
  54. let count = 0,
  55. completed = 0,
  56. checkEnd = () => {
  57. if (completed === count) stream.push(null);
  58. };
  59. sharedConfig.context.writeResource = (id, p) => {
  60. count++;
  61. Promise.resolve().then(() => stream.push(`<script${options.nonce ? ` nonce="${options.nonce}"` : ""}>_$HYDRATION.startResource("${id}")</script>`));
  62. p.then(d => {
  63. stream.push(`<script${options.nonce ? ` nonce="${options.nonce}"` : ""}>_$HYDRATION.resolveResource("${id}", ${(JSON.stringify(d) || "undefined").replace(/'/g, "\\'").replace(/\\\"/g, '\\\\\\"')})</script>`);
  64. ++completed && checkEnd();
  65. });
  66. };
  67. stream.push(resolveSSRNode(code()));
  68. setTimeout(checkEnd);
  69. return {
  70. stream,
  71. script: generateHydrationScript({
  72. streaming: true,
  73. ...options
  74. })
  75. };
  76. }
  77. function renderToWebStream(code, options = {}) {
  78. let checkEnd;
  79. const tmp = [];
  80. const encoder = new TextEncoder();
  81. const done = new Promise(resolve => {
  82. checkEnd = () => {
  83. if (completed === count) resolve();
  84. };
  85. });
  86. sharedConfig.context = {
  87. id: "",
  88. count: 0,
  89. streaming: true
  90. };
  91. let count = 0,
  92. completed = 0,
  93. writer = {
  94. write(payload) {
  95. tmp.push(payload);
  96. }
  97. };
  98. sharedConfig.context.writeResource = (id, p) => {
  99. count++;
  100. Promise.resolve().then(() => writer.write(encoder.encode(`<script${options.nonce ? ` nonce="${options.nonce}"` : ""}>_$HYDRATION.startResource("${id}")</script>`)));
  101. p.then(d => {
  102. writer.write(encoder.encode(`<script${options.nonce ? ` nonce="${options.nonce}"` : ""}>_$HYDRATION.resolveResource("${id}", ${(JSON.stringify(d) || "undefined").replace(/'/g, "\\'").replace(/\\\"/g, '\\\\\\"')})</script>`));
  103. ++completed && checkEnd();
  104. });
  105. };
  106. writer.write(encoder.encode(resolveSSRNode(code())));
  107. return {
  108. writeTo(w) {
  109. writer = w;
  110. tmp.map(chunk => writer.write(chunk));
  111. setTimeout(checkEnd);
  112. return done;
  113. },
  114. script: generateHydrationScript({
  115. streaming: true,
  116. ...options
  117. })
  118. };
  119. }
  120. function ssr(t, ...nodes) {
  121. if (nodes.length) {
  122. let result = "";
  123. for (let i = 0; i < t.length; i++) {
  124. result += t[i];
  125. const node = nodes[i];
  126. if (node !== undefined) result += resolveSSRNode(node);
  127. }
  128. t = result;
  129. }
  130. return {
  131. t
  132. };
  133. }
  134. function ssrClassList(value) {
  135. let classKeys = Object.keys(value),
  136. result = "";
  137. for (let i = 0, len = classKeys.length; i < len; i++) {
  138. const key = classKeys[i],
  139. classValue = !!value[key];
  140. if (!key || !classValue) continue;
  141. i && (result += " ");
  142. result += key;
  143. }
  144. return result;
  145. }
  146. function ssrStyle(value) {
  147. if (typeof value === "string") return value;
  148. let result = "";
  149. const k = Object.keys(value);
  150. for (let i = 0; i < k.length; i++) {
  151. const s = k[i];
  152. if (i) result += ";";
  153. result += `${s}:${escape(value[s], true)}`;
  154. }
  155. return result;
  156. }
  157. function ssrSpread(props, isSVG, skipChildren) {
  158. if (typeof props === "function") props = props();
  159. const keys = Object.keys(props);
  160. let result = "";
  161. for (let i = 0; i < keys.length; i++) {
  162. const prop = keys[i];
  163. if (prop === "children") {
  164. !skipChildren && console.warn(`SSR currently does not support spread children.`);
  165. continue;
  166. }
  167. const value = props[prop];
  168. if (prop === "style") {
  169. result += `style="${ssrStyle(value)}"`;
  170. } else if (prop === "classList") {
  171. result += `class="${ssrClassList(value)}"`;
  172. } else if (BooleanAttributes.has(prop)) {
  173. if (value) result += prop;else continue;
  174. } else {
  175. result += `${Aliases[prop] || prop}="${escape(value, true)}"`;
  176. }
  177. if (i !== keys.length - 1) result += " ";
  178. }
  179. return result;
  180. }
  181. function ssrBoolean(key, value) {
  182. return value ? " " + key : "";
  183. }
  184. function escape(s, attr) {
  185. const t = typeof s;
  186. if (t !== "string") {
  187. if (attr && t === "boolean") return String(s);
  188. return s;
  189. }
  190. const delim = attr ? '"' : "<";
  191. const escDelim = attr ? "&quot;" : "&lt;";
  192. let iDelim = s.indexOf(delim);
  193. let iAmp = s.indexOf("&");
  194. if (iDelim < 0 && iAmp < 0) return s;
  195. let left = 0,
  196. out = "";
  197. while (iDelim >= 0 && iAmp >= 0) {
  198. if (iDelim < iAmp) {
  199. if (left < iDelim) out += s.substring(left, iDelim);
  200. out += escDelim;
  201. left = iDelim + 1;
  202. iDelim = s.indexOf(delim, left);
  203. } else {
  204. if (left < iAmp) out += s.substring(left, iAmp);
  205. out += "&amp;";
  206. left = iAmp + 1;
  207. iAmp = s.indexOf("&", left);
  208. }
  209. }
  210. if (iDelim >= 0) {
  211. do {
  212. if (left < iDelim) out += s.substring(left, iDelim);
  213. out += escDelim;
  214. left = iDelim + 1;
  215. iDelim = s.indexOf(delim, left);
  216. } while (iDelim >= 0);
  217. } else while (iAmp >= 0) {
  218. if (left < iAmp) out += s.substring(left, iAmp);
  219. out += "&amp;";
  220. left = iAmp + 1;
  221. iAmp = s.indexOf("&", left);
  222. }
  223. return left < s.length ? out + s.substring(left) : out;
  224. }
  225. function resolveSSRNode(node) {
  226. const t = typeof node;
  227. if (t === "string") return node;
  228. if (node == null || t === "boolean") return "";
  229. if (Array.isArray(node)) return node.map(resolveSSRNode).join("");
  230. if (t === "object") return resolveSSRNode(node.t);
  231. if (t === "function") return resolveSSRNode(node());
  232. return String(node);
  233. }
  234. function getHydrationKey() {
  235. const hydrate = sharedConfig.context;
  236. return `${hydrate.id}${hydrate.count++}`;
  237. }
  238. function generateHydrationScript({
  239. eventNames = ["click", "input"],
  240. streaming,
  241. resources,
  242. nonce
  243. } = {}) {
  244. let s = `<script${nonce ? ` nonce="${nonce}"` : ""}>(()=>{_$HYDRATION={events:[],completed:new WeakSet};const t=e=>e&&e.hasAttribute&&(e.hasAttribute("data-hk")&&e||t(e.host&&e.host instanceof Node?e.host:e.parentNode)),e=e=>{let o=e.composedPath&&e.composedPath()[0]||e.target,s=t(o);s&&!_$HYDRATION.completed.has(s)&&_$HYDRATION.events.push([s,e])};["${eventNames.join('","')}"].forEach(t=>document.addEventListener(t,e))})();`;
  245. if (streaming) {
  246. s += `(()=>{const e=_$HYDRATION,o={};e.startResource=e=>{let r;o[e]=[new Promise(e=>r=e),r]},e.resolveResource=(e,r)=>{const n=o[e];if(!n)return o[e]=[r];n[1](r)},e.loadResource=e=>{const r=o[e];if(r)return r[0]}})();`;
  247. }
  248. if (resources) s += `_$HYDRATION.resources = JSON.parse('${JSON.stringify(Object.keys(resources).reduce((r, k) => {
  249. r[k] = resources[k].data;
  250. return r;
  251. }, {})).replace(/'/g, "\\'").replace(/\\\"/g, '\\\\\\"')}');`;
  252. return s + `</script>`;
  253. }
  254. const isServer = true;
  255. function spread() {}
  256. function Dynamic(props) {
  257. const [p, others] = splitProps(props, ["component"]);
  258. const comp = p.component,
  259. t = typeof comp;
  260. if (comp) {
  261. if (t === "function") return comp(others);else if (t === "string") {
  262. const [local, sOthers] = splitProps(others, ["children"]);
  263. return ssr([`<${comp} `, ">", `</${comp}>`], ssrSpread(sOthers), local.children || "");
  264. }
  265. }
  266. }
  267. function Portal(props) {
  268. return "";
  269. }
  270. export { Dynamic, Portal, escape, getHydrationKey, isServer, renderToNodeStream, renderToString, renderToStringAsync, renderToWebStream, resolveSSRNode, spread, ssr, ssrBoolean, ssrClassList, ssrSpread, ssrStyle };