@hyperspan/framework 0.1.6 → 0.1.7

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/build.ts CHANGED
@@ -1,4 +1,4 @@
1
- import {build} from 'bun';
1
+ import { build } from 'bun';
2
2
 
3
3
  const entrypoints = ['./src/server.ts', './src/assets.ts'];
4
4
  const external = ['@hyperspan/html'];
package/dist/assets.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // src/assets.ts
2
2
  import { html } from "@hyperspan/html";
3
+ import { createHash } from "node:crypto";
3
4
  import { readdir } from "node:fs/promises";
4
5
  import { resolve } from "node:path";
5
6
  var IS_PROD = false;
@@ -45,15 +46,7 @@ function hyperspanScriptTags() {
45
46
  const jsFiles = Array.from(clientJSFiles.entries());
46
47
  return html`
47
48
  <script type="importmap">
48
- {
49
- "imports": {
50
- "preact": "https://esm.sh/preact@10.26.4",
51
- "preact/": "https://esm.sh/preact@10.26.4/",
52
- "react": "https://esm.sh/preact@10.26.4/compat",
53
- "react/": "https://esm.sh/preact@10.26.4/compat/",
54
- "react-dom": "https://esm.sh/preact@10.26.4/compat"
55
- }
56
- }
49
+ {"imports": ${Object.fromEntries(clientImportMap)}}
57
50
  </script>
58
51
  ${jsFiles.map(([key, file]) => html`<script
59
52
  id="js-${key}"
@@ -62,9 +55,63 @@ function hyperspanScriptTags() {
62
55
  ></script>`)}
63
56
  `;
64
57
  }
58
+ var PREACT_PUBLIC_FILE_PATH = "/_hs/js/preact.js";
59
+ function md5(content) {
60
+ return createHash("md5").update(content).digest("hex");
61
+ }
62
+ async function copyPreactToPublicFolder() {
63
+ const sourceFile = resolve(PWD, "../", "./src/clientjs/preact.ts");
64
+ const preactClient = Bun.build({
65
+ entrypoints: [sourceFile],
66
+ outdir: "./public/_hs/js",
67
+ minify: true,
68
+ format: "esm",
69
+ target: "browser"
70
+ });
71
+ }
72
+ async function createPreactIsland(file) {
73
+ let filePath = file.replace("file://", "");
74
+ const jsId = md5(filePath);
75
+ if (!clientImportMap.has("preact")) {
76
+ await copyPreactToPublicFolder();
77
+ clientImportMap.set("preact", "" + PREACT_PUBLIC_FILE_PATH);
78
+ clientImportMap.set("preact/compat", "" + PREACT_PUBLIC_FILE_PATH);
79
+ clientImportMap.set("preact/hooks", "" + PREACT_PUBLIC_FILE_PATH);
80
+ clientImportMap.set("preact/jsx-runtime", "" + PREACT_PUBLIC_FILE_PATH);
81
+ }
82
+ if (!clientImportMap.has("react")) {
83
+ clientImportMap.set("react", "." + PREACT_PUBLIC_FILE_PATH);
84
+ clientImportMap.set("react-dom", "." + PREACT_PUBLIC_FILE_PATH);
85
+ }
86
+ let resultStr = 'import{h,render}from"preact";';
87
+ const buildResult = await Bun.build({
88
+ entrypoints: [filePath],
89
+ minify: true,
90
+ external: ["react", "preact"],
91
+ env: "APP_PUBLIC_*"
92
+ });
93
+ for (const output of buildResult.outputs) {
94
+ resultStr += await output.text();
95
+ }
96
+ const r = /export\{([a-zA-Z]+) as default\}/g;
97
+ const matchExport = r.exec(resultStr);
98
+ if (!matchExport) {
99
+ throw new Error("File does not have a default export! Ensure a function has export default to use this.");
100
+ }
101
+ const fn = matchExport[1];
102
+ let _mounted = false;
103
+ return (props) => {
104
+ if (!_mounted) {
105
+ _mounted = true;
106
+ resultStr += `render(h(${fn}, ${JSON.stringify(props)}), document.getElementById("${jsId}"));`;
107
+ }
108
+ return html.raw(`<div id="${jsId}"></div><script type="module" data-source-id="${jsId}">${resultStr}</script>`);
109
+ };
110
+ }
65
111
  export {
66
112
  hyperspanStyleTags,
67
113
  hyperspanScriptTags,
114
+ createPreactIsland,
68
115
  clientJSFiles,
69
116
  clientImportMap,
70
117
  clientCSSFiles,
package/dist/server.js CHANGED
@@ -6,10 +6,10 @@ import {
6
6
  // src/server.ts
7
7
  import { readdir } from "node:fs/promises";
8
8
  import { basename, extname, join } from "node:path";
9
- import { TmplHtml, html, renderStream, renderAsync, render } from "@hyperspan/html";
9
+ import { html, isHSHtml, renderStream, renderAsync, render } from "@hyperspan/html";
10
10
 
11
11
  // node_modules/isbot/index.mjs
12
- var fullPattern = " daum[ /]| deusu/| yadirectfetcher|(?:^|[^g])news(?!sapphire)|(?<! (?:channel/|google/))google(?!(app|/google| pixel))|(?<! cu)bots?(?:\\b|_)|(?<!(?:lib))http|(?<![hg]m)score|@[a-z][\\w-]+\\.|\\(\\)|\\.com\\b|\\btime/|\\||^<|^[\\w \\.\\-\\(?:\\):%]+(?:/v?\\d+(?:\\.\\d+)?(?:\\.\\d{1,10})*?)?(?:,|$)|^[^ ]{50,}$|^\\d+\\b|^\\w*search\\b|^\\w+/[\\w\\(\\)]*$|^active|^ad muncher|^amaya|^avsdevicesdk/|^biglotron|^bot|^bw/|^clamav[ /]|^client/|^cobweb/|^custom|^ddg[_-]android|^discourse|^dispatch/\\d|^downcast/|^duckduckgo|^email|^facebook|^getright/|^gozilla/|^hobbit|^hotzonu|^hwcdn/|^igetter/|^jeode/|^jetty/|^jigsaw|^microsoft bits|^movabletype|^mozilla/\\d\\.\\d\\s[\\w\\.-]+$|^mozilla/\\d\\.\\d\\s\\(compatible;?(?:\\s\\w+\\/\\d+\\.\\d+)?\\)$|^navermailapp|^netsurf|^offline|^openai/|^owler|^php|^postman|^python|^rank|^read|^reed|^rest|^rss|^snapchat|^space bison|^svn|^swcd |^taringa|^thumbor/|^track|^w3c|^webbandit/|^webcopier|^wget|^whatsapp|^wordpress|^xenu link sleuth|^yahoo|^yandex|^zdm/\\d|^zoom marketplace/|^{{.*}}$|adscanner/|analyzer|archive|ask jeeves/teoma|audit|bit\\.ly/|bluecoat drtr|browsex|burpcollaborator|capture|catch|check\\b|checker|chrome-lighthouse|chromeframe|classifier|cloudflare|convertify|cookiehubscan|crawl|cypress/|dareboost|datanyze|dejaclick|detect|dmbrowser|download|evc-batch/|exaleadcloudview|feed|firephp|functionize|gomezagent|headless|httrack|hubspot marketing grader|hydra|ibisbrowser|infrawatch|insight|inspect|iplabel|ips-agent|java(?!;)|jsjcw_scanner|library|linkcheck|mail\\.ru/|manager|measure|neustar wpm|node|nutch|offbyone|onetrust|optimize|pageburst|pagespeed|parser|perl|phantomjs|pingdom|powermarks|preview|proxy|ptst[ /]\\d|retriever|rexx;|rigor|rss\\b|scanner\\.|scrape|server|sogou|sparkler/|speedcurve|spider|splash|statuscake|supercleaner|synapse|synthetic|tools|torrent|transcoder|url|validator|virtuoso|wappalyzer|webglance|webkit2png|whatcms/|zgrab";
12
+ var fullPattern = " daum[ /]| deusu/| yadirectfetcher|(?:^|[^g])news(?!sapphire)|(?<! (?:channel/|google/))google(?!(app|/google| pixel))|(?<! cu)bots?(?:\\b|_)|(?<!(?:lib))http|(?<![hg]m)score|(?<!cam)scan|@[a-z][\\w-]+\\.|\\(\\)|\\.com\\b|\\btime/|\\||^<|^[\\w \\.\\-\\(?:\\):%]+(?:/v?\\d+(?:\\.\\d+)?(?:\\.\\d{1,10})*?)?(?:,|$)|^[^ ]{50,}$|^\\d+\\b|^\\w*search\\b|^\\w+/[\\w\\(\\)]*$|^active|^ad muncher|^amaya|^avsdevicesdk/|^biglotron|^bot|^bw/|^clamav[ /]|^client/|^cobweb/|^custom|^ddg[_-]android|^discourse|^dispatch/\\d|^downcast/|^duckduckgo|^email|^facebook|^getright/|^gozilla/|^hobbit|^hotzonu|^hwcdn/|^igetter/|^jeode/|^jetty/|^jigsaw|^microsoft bits|^movabletype|^mozilla/\\d\\.\\d\\s[\\w\\.-]+$|^mozilla/\\d\\.\\d\\s\\(compatible;?(?:\\s\\w+\\/\\d+\\.\\d+)?\\)$|^navermailapp|^netsurf|^offline|^openai/|^owler|^php|^postman|^python|^rank|^read|^reed|^rest|^rss|^snapchat|^space bison|^svn|^swcd |^taringa|^thumbor/|^track|^w3c|^webbandit/|^webcopier|^wget|^whatsapp|^wordpress|^xenu link sleuth|^yahoo|^yandex|^zdm/\\d|^zoom marketplace/|^{{.*}}$|analyzer|archive|ask jeeves/teoma|audit|bit\\.ly/|bluecoat drtr|browsex|burpcollaborator|capture|catch|check\\b|checker|chrome-lighthouse|chromeframe|classifier|cloudflare|convertify|crawl|cypress/|dareboost|datanyze|dejaclick|detect|dmbrowser|download|evc-batch/|exaleadcloudview|feed|firephp|functionize|gomezagent|grab|headless|httrack|hubspot marketing grader|hydra|ibisbrowser|infrawatch|insight|inspect|iplabel|ips-agent|java(?!;)|library|linkcheck|mail\\.ru/|manager|measure|neustar wpm|node|nutch|offbyone|onetrust|optimize|pageburst|pagespeed|parser|perl|phantomjs|pingdom|powermarks|preview|proxy|ptst[ /]\\d|retriever|rexx;|rigor|rss\\b|scrape|server|sogou|sparkler/|speedcurve|spider|splash|statuscake|supercleaner|synapse|synthetic|tools|torrent|transcoder|url|validator|virtuoso|wappalyzer|webglance|webkit2png|whatcms/";
13
13
  var naivePattern = /bot|crawl|http|lighthouse|scan|search|spider/i;
14
14
  var pattern;
15
15
  function getPattern() {
@@ -841,7 +841,11 @@ var Hono = class {
841
841
  optionHandler = options;
842
842
  } else {
843
843
  optionHandler = options.optionHandler;
844
- replaceRequest = options.replaceRequest;
844
+ if (options.replaceRequest === false) {
845
+ replaceRequest = (request) => request;
846
+ } else {
847
+ replaceRequest = options.replaceRequest;
848
+ }
845
849
  }
846
850
  }
847
851
  const getOptions = optionHandler ? (c) => {
@@ -851,7 +855,8 @@ var Hono = class {
851
855
  let executionContext = undefined;
852
856
  try {
853
857
  executionContext = c.executionCtx;
854
- } catch {}
858
+ } catch {
859
+ }
855
860
  return [c.env, executionContext];
856
861
  };
857
862
  replaceRequest ||= (() => {
@@ -1745,7 +1750,8 @@ var serveStatic2 = (options) => {
1745
1750
  try {
1746
1751
  const stats = await stat(path);
1747
1752
  isDir2 = stats.isDirectory();
1748
- } catch {}
1753
+ } catch {
1754
+ }
1749
1755
  return isDir2;
1750
1756
  };
1751
1757
  return serveStatic({
@@ -1862,7 +1868,7 @@ function createRoute(handler) {
1862
1868
  const streamOpt = context.req.query("__nostream");
1863
1869
  const streamingEnabled = !userIsBot && (streamOpt !== undefined ? streamOpt : true);
1864
1870
  const routeKind = typeof routeContent;
1865
- if (routeContent && routeKind === "object" && (routeContent instanceof TmplHtml || routeContent.constructor.name === "TmplHtml" || routeContent?._kind === "TmplHtml")) {
1871
+ if (isHSHtml(routeContent)) {
1866
1872
  if (streamingEnabled) {
1867
1873
  return new StreamResponse(renderStream(routeContent));
1868
1874
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperspan/framework",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Hyperspan Web Framework",
5
5
  "main": "dist/server.js",
6
6
  "types": "src/server.ts",
@@ -64,10 +64,10 @@
64
64
  "typescript": "^5.0.0"
65
65
  },
66
66
  "dependencies": {
67
- "@hyperspan/html": "^0.1.5",
68
- "@preact/compat": "^18.3.1",
67
+ "@hyperspan/html": "^0.1.6",
69
68
  "hono": "^4.7.4",
70
69
  "isbot": "^5.1.25",
70
+ "preact": "^10.26.5",
71
71
  "zod": "^4.0.0-beta.20250415T232143"
72
72
  }
73
73
  }
@@ -1,7 +1,7 @@
1
1
  import z from 'zod';
2
2
  import { createAction } from './actions';
3
3
  import { describe, it, expect } from 'bun:test';
4
- import { html, render, type TmplHtml } from '@hyperspan/html';
4
+ import { html, render, type HSHtml } from '@hyperspan/html';
5
5
  import type { Context } from 'hono';
6
6
 
7
7
  describe('createAction', () => {
@@ -24,7 +24,7 @@ describe('createAction', () => {
24
24
  });
25
25
  const action = createAction(schema, formWithNameOnly);
26
26
 
27
- const formResponse = render(action.render({ data: { name: 'John' } }) as TmplHtml);
27
+ const formResponse = render(action.render({ data: { name: 'John' } }) as HSHtml);
28
28
  expect(formResponse).toContain('value="John"');
29
29
  });
30
30
  });
@@ -55,7 +55,7 @@ describe('createAction', () => {
55
55
 
56
56
  const response = await action.run('POST', mockContext);
57
57
 
58
- const formResponse = render(response as TmplHtml);
58
+ const formResponse = render(response as HSHtml);
59
59
  expect(formResponse).toContain('Thanks for submitting the form, John!');
60
60
  });
61
61
  });
@@ -87,7 +87,7 @@ describe('createAction', () => {
87
87
 
88
88
  const response = await action.run('POST', mockContext);
89
89
 
90
- const formResponse = render(response as TmplHtml);
90
+ const formResponse = render(response as HSHtml);
91
91
  expect(formResponse).toContain('There was an error!');
92
92
  });
93
93
  });
package/src/actions.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { html, TmplHtml } from '@hyperspan/html';
1
+ import { html, HSHtml } from '@hyperspan/html';
2
2
  import * as z from 'zod';
3
3
  import { HTTPException } from 'hono/http-exception';
4
4
 
@@ -20,7 +20,7 @@ import type { Context } from 'hono';
20
20
  */
21
21
  export interface HSAction<T extends z.ZodTypeAny> {
22
22
  _kind: string;
23
- form(renderForm: ({ data }: { data?: z.infer<T> }) => TmplHtml): HSAction<T>;
23
+ form(renderForm: ({ data }: { data?: z.infer<T> }) => HSHtml): HSAction<T>;
24
24
  post(handler: (c: Context, { data }: { data?: z.infer<T> }) => THSResponseTypes): HSAction<T>;
25
25
  error(
26
26
  handler: (
package/src/assets.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { html } from '@hyperspan/html';
2
+ import { createHash } from 'node:crypto';
2
3
  import { readdir } from 'node:fs/promises';
3
4
  import { resolve } from 'node:path';
4
5
 
@@ -75,15 +76,7 @@ export function hyperspanScriptTags() {
75
76
 
76
77
  return html`
77
78
  <script type="importmap">
78
- {
79
- "imports": {
80
- "preact": "https://esm.sh/preact@10.26.4",
81
- "preact/": "https://esm.sh/preact@10.26.4/",
82
- "react": "https://esm.sh/preact@10.26.4/compat",
83
- "react/": "https://esm.sh/preact@10.26.4/compat/",
84
- "react-dom": "https://esm.sh/preact@10.26.4/compat"
85
- }
86
- }
79
+ {"imports": ${Object.fromEntries(clientImportMap)}}
87
80
  </script>
88
81
  ${jsFiles.map(
89
82
  ([key, file]) =>
@@ -95,3 +88,83 @@ export function hyperspanScriptTags() {
95
88
  )}
96
89
  `;
97
90
  }
91
+
92
+ // External ESM = https://esm.sh/preact@10.26.4/compat
93
+ const PREACT_PUBLIC_FILE_PATH = '/_hs/js/preact.js';
94
+
95
+ function md5(content: string): string {
96
+ return createHash('md5').update(content).digest('hex');
97
+ }
98
+
99
+ /**
100
+ * Build Preact client JS and copy to public folder
101
+ */
102
+ async function copyPreactToPublicFolder() {
103
+ const sourceFile = resolve(PWD, '../', './src/clientjs/preact.ts');
104
+ const preactClient = Bun.build({
105
+ entrypoints: [sourceFile],
106
+ outdir: './public/_hs/js',
107
+ minify: true,
108
+ format: 'esm',
109
+ target: 'browser',
110
+ });
111
+ }
112
+
113
+ /**
114
+ * Return a Preact component, mounted as an island in a <script> tag so it can be embedded into the page response.
115
+ */
116
+ export async function createPreactIsland(file: string) {
117
+ let filePath = file.replace('file://', '');
118
+ const jsId = md5(filePath);
119
+
120
+ // Add Preact to client import map if not already present
121
+ if (!clientImportMap.has('preact')) {
122
+ await copyPreactToPublicFolder();
123
+ clientImportMap.set('preact', '' + PREACT_PUBLIC_FILE_PATH);
124
+ clientImportMap.set('preact/compat', '' + PREACT_PUBLIC_FILE_PATH);
125
+ clientImportMap.set('preact/hooks', '' + PREACT_PUBLIC_FILE_PATH);
126
+ clientImportMap.set('preact/jsx-runtime', '' + PREACT_PUBLIC_FILE_PATH);
127
+ }
128
+ if (!clientImportMap.has('react')) {
129
+ clientImportMap.set('react', '.' + PREACT_PUBLIC_FILE_PATH);
130
+ clientImportMap.set('react-dom', '.' + PREACT_PUBLIC_FILE_PATH);
131
+ }
132
+
133
+ let resultStr = 'import{h,render}from"preact";';
134
+ const buildResult = await Bun.build({
135
+ entrypoints: [filePath],
136
+ minify: true,
137
+ external: ['react', 'preact'],
138
+ // @ts-ignore
139
+ env: 'APP_PUBLIC_*', // Inlines any ENV that starts with 'APP_PUBLIC_'
140
+ });
141
+
142
+ for (const output of buildResult.outputs) {
143
+ resultStr += await output.text(); // string
144
+ }
145
+
146
+ // Find default export - this is our component
147
+ const r = /export\{([a-zA-Z]+) as default\}/g;
148
+ const matchExport = r.exec(resultStr);
149
+
150
+ if (!matchExport) {
151
+ throw new Error(
152
+ 'File does not have a default export! Ensure a function has export default to use this.'
153
+ );
154
+ }
155
+
156
+ // Preact render/mount component
157
+ const fn = matchExport[1];
158
+ let _mounted = false;
159
+
160
+ // Return HTML that will embed this component
161
+ return (props: any) => {
162
+ if (!_mounted) {
163
+ _mounted = true;
164
+ resultStr += `render(h(${fn}, ${JSON.stringify(props)}), document.getElementById("${jsId}"));`;
165
+ }
166
+ return html.raw(
167
+ `<div id="${jsId}"></div><script type="module" data-source-id="${jsId}">${resultStr}</script>`
168
+ );
169
+ };
170
+ }
@@ -26,14 +26,10 @@ function htmlAsyncContentObserver() {
26
26
  const slotEl = document.getElementById(slotId);
27
27
 
28
28
  if (slotEl) {
29
- // Wait until next paint for streaming content to finish writing to DOM
30
- // @TODO: Need a more guaranteed way to know HTML element is done streaming in...
31
- // Maybe some ".end" element that is hidden and then removed before insertion?
32
- requestAnimationFrame(() => {
33
- setTimeout(() => {
34
- Idiomorph.morph(slotEl, el.content.cloneNode(true));
35
- el.parentNode.removeChild(el);
36
- }, 100);
29
+ // Only insert the content if it is done streaming in
30
+ waitForEndContent(el.content).then(() => {
31
+ Idiomorph.morph(slotEl, el.content.cloneNode(true));
32
+ el.parentNode.removeChild(el);
37
33
  });
38
34
  }
39
35
  } catch (e) {
@@ -46,6 +42,25 @@ function htmlAsyncContentObserver() {
46
42
  }
47
43
  htmlAsyncContentObserver();
48
44
 
45
+ /**
46
+ * Wait until ALL of the content inside an element is present from streaming in.
47
+ * Large chunks of content can sometimes take more than a single tick to write to DOM.
48
+ */
49
+ async function waitForEndContent(el: HTMLElement) {
50
+ return new Promise((resolve) => {
51
+ const interval = setInterval(() => {
52
+ const endComment = Array.from(el.childNodes).find((node) => {
53
+ return node.nodeType === Node.COMMENT_NODE && node.nodeValue === 'end';
54
+ });
55
+ if (endComment) {
56
+ el.removeChild(endComment);
57
+ clearInterval(interval);
58
+ resolve(true);
59
+ }
60
+ }, 10);
61
+ });
62
+ }
63
+
49
64
  /**
50
65
  * Server action component to handle the client-side form submission and HTML replacement
51
66
  */
@@ -1 +1,2 @@
1
- export * from '@preact/compat';
1
+ export * from 'preact/compat';
2
+ export { h, render } from 'preact';
package/src/server.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { readdir } from 'node:fs/promises';
2
2
  import { basename, extname, join } from 'node:path';
3
- import { TmplHtml, html, renderStream, renderAsync, render } from '@hyperspan/html';
3
+ import { HSHtml, html, isHSHtml, renderStream, renderAsync, render } from '@hyperspan/html';
4
4
  import { isbot } from 'isbot';
5
5
  import { buildClientJS, buildClientCSS } from './assets';
6
6
  import { Hono, type Context } from 'hono';
@@ -13,7 +13,7 @@ const CWD = process.cwd();
13
13
  /**
14
14
  * Types
15
15
  */
16
- export type THSResponseTypes = TmplHtml | Response | string | null;
16
+ export type THSResponseTypes = HSHtml | Response | string | null;
17
17
  export type THSRouteHandler = (context: Context) => THSResponseTypes | Promise<THSResponseTypes>;
18
18
 
19
19
  export type THSRoute = {
@@ -28,7 +28,7 @@ export type THSRoute = {
28
28
 
29
29
  /**
30
30
  * Define a route that can handle a direct HTTP request.
31
- * Route handlers should return a TmplHtml or Response object
31
+ * Route handlers should return a HSHtml or Response object
32
32
  */
33
33
  export function createRoute(handler?: THSRouteHandler): THSRoute {
34
34
  let _handlers: Record<string, THSRouteHandler> = {};
@@ -78,19 +78,12 @@ export function createRoute(handler?: THSRouteHandler): THSRoute {
78
78
  const streamingEnabled = !userIsBot && (streamOpt !== undefined ? streamOpt : true);
79
79
  const routeKind = typeof routeContent;
80
80
 
81
- // Render TmplHtml if returned from route handler
82
- if (
83
- routeContent &&
84
- routeKind === 'object' &&
85
- (routeContent instanceof TmplHtml ||
86
- routeContent.constructor.name === 'TmplHtml' ||
87
- // @ts-ignore
88
- routeContent?._kind === 'TmplHtml')
89
- ) {
81
+ // Render HSHtml if returned from route handler
82
+ if (isHSHtml(routeContent)) {
90
83
  if (streamingEnabled) {
91
- return new StreamResponse(renderStream(routeContent as TmplHtml)) as Response;
84
+ return new StreamResponse(renderStream(routeContent as HSHtml)) as Response;
92
85
  } else {
93
- const output = await renderAsync(routeContent as TmplHtml);
86
+ const output = await renderAsync(routeContent as HSHtml);
94
87
  return context.html(output);
95
88
  }
96
89
  }