@hyperspan/framework 0.3.1 → 0.3.2

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/dist/assets.js CHANGED
@@ -60,17 +60,12 @@ function assetHash(content) {
60
60
  }
61
61
  var ISLAND_PUBLIC_PATH = "/_hs/js/islands";
62
62
  var ISLAND_DEFAULTS = () => ({
63
- ssr: true
63
+ ssr: true,
64
+ loading: undefined
64
65
  });
65
66
  function renderIsland(Component, props, options = ISLAND_DEFAULTS()) {
66
67
  if (Component.__HS_ISLAND?.render) {
67
- return Component.__HS_ISLAND.render(props, options);
68
- }
69
- if (Component.__HS_ISLAND?.ssr && options.ssr) {
70
- return Component.__HS_ISLAND.ssr(props);
71
- }
72
- if (Component.__HS_ISLAND?.clientOnly) {
73
- return Component.__HS_ISLAND.clientOnly(props);
68
+ return html.raw(Component.__HS_ISLAND.render(props, options));
74
69
  }
75
70
  throw new Error(`Module ${Component.name} was not loaded with an island plugin! Did you forget to install an island plugin and add it to the createServer() 'islandPlugins' config?`);
76
71
  }
package/dist/server.js CHANGED
@@ -1847,18 +1847,6 @@ function createRoute(handler) {
1847
1847
  _handlers["POST"] = handler2;
1848
1848
  return api;
1849
1849
  },
1850
- put(handler2) {
1851
- _handlers["PUT"] = handler2;
1852
- return api;
1853
- },
1854
- delete(handler2) {
1855
- _handlers["DELETE"] = handler2;
1856
- return api;
1857
- },
1858
- patch(handler2) {
1859
- _handlers["PATCH"] = handler2;
1860
- return api;
1861
- },
1862
1850
  middleware(middleware) {
1863
1851
  _middleware = middleware;
1864
1852
  return api;
@@ -1909,7 +1897,7 @@ function createAPIRoute(handler) {
1909
1897
  _handlers["GET"] = handler;
1910
1898
  }
1911
1899
  const api = {
1912
- _kind: "hsRoute",
1900
+ _kind: "hsAPIRoute",
1913
1901
  get(handler2) {
1914
1902
  _handlers["GET"] = handler2;
1915
1903
  return api;
@@ -1988,21 +1976,40 @@ function getRunnableRoute(route) {
1988
1976
  throw new Error(`Route not runnable. Use "export default createRoute()" to create a Hyperspan route. Exported methods found were: ${Object.keys(route).join(", ")}`);
1989
1977
  }
1990
1978
  function isRunnableRoute(route) {
1991
- return typeof route === "object" && "_getRouteHandlers" in route;
1979
+ if (typeof route !== "object") {
1980
+ return false;
1981
+ }
1982
+ const obj = route;
1983
+ const runnableKind = ["hsRoute", "hsAPIRoute", "hsAction"].includes(obj?._kind);
1984
+ return runnableKind && "_getRouteHandlers" in obj;
1992
1985
  }
1993
1986
  async function showErrorReponse(context, err) {
1994
- const output = render(html`
1995
- <main>
1996
- <h1>Error</h1>
1997
- <pre>${err.message}</pre>
1998
- <pre>${!IS_PROD && err.stack ? err.stack.split(`
1987
+ let status = 500;
1988
+ const message = err.message || "Internal Server Error";
1989
+ if (err instanceof HTTPException) {
1990
+ status = err.status;
1991
+ }
1992
+ const stack = !IS_PROD && err.stack ? err.stack.split(`
1999
1993
  `).slice(1).join(`
2000
- `) : ""}</pre>
2001
- </main>
1994
+ `) : "";
1995
+ const output = render(html`
1996
+ <!DOCTYPE html>
1997
+ <html lang="en">
1998
+ <head>
1999
+ <meta charset="UTF-8" />
2000
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
2001
+ <title>Application Error</title>
2002
+ </head>
2003
+ <body>
2004
+ <main>
2005
+ <h1>Application Error</h1>
2006
+ <strong>${message}</strong>
2007
+ ${stack ? html`<pre>${stack}</pre>` : ""}
2008
+ </main>
2009
+ </body>
2010
+ </html>
2002
2011
  `);
2003
- return context.html(output, {
2004
- status: 500
2005
- });
2012
+ return context.html(output, { status });
2006
2013
  }
2007
2014
  var ROUTE_SEGMENT = /(\[[a-zA-Z_\.]+\])/g;
2008
2015
  async function buildRoutes(config) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperspan/framework",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Hyperspan Web Framework",
5
5
  "main": "dist/server.ts",
6
6
  "types": "src/server.ts",
package/src/assets.ts CHANGED
@@ -3,6 +3,11 @@ import { createHash } from 'node:crypto';
3
3
  import { readdir } from 'node:fs/promises';
4
4
  import { resolve } from 'node:path';
5
5
 
6
+ export type THSIslandOptions = {
7
+ ssr?: boolean;
8
+ loading?: 'lazy' | undefined;
9
+ };
10
+
6
11
  const IS_PROD = process.env.NODE_ENV === 'production';
7
12
  const PWD = import.meta.dir;
8
13
 
@@ -97,24 +102,15 @@ export function assetHash(content: string): string {
97
102
  * Island defaults
98
103
  */
99
104
  export const ISLAND_PUBLIC_PATH = '/_hs/js/islands';
100
- export const ISLAND_DEFAULTS = () => ({
105
+ export const ISLAND_DEFAULTS: () => THSIslandOptions = () => ({
101
106
  ssr: true,
107
+ loading: undefined,
102
108
  });
103
109
 
104
110
  export function renderIsland(Component: any, props: any, options = ISLAND_DEFAULTS()) {
105
- // Render is an OPTIONAL override that allows you to render the island with your own logic
111
+ // Render island with its own logic
106
112
  if (Component.__HS_ISLAND?.render) {
107
- return Component.__HS_ISLAND.render(props, options);
108
- }
109
-
110
- // If ssr is true, render the island with the ssr function
111
- if (Component.__HS_ISLAND?.ssr && options.ssr) {
112
- return Component.__HS_ISLAND.ssr(props);
113
- }
114
-
115
- // If ssr is false, render the island with the clientOnly function
116
- if (Component.__HS_ISLAND?.clientOnly) {
117
- return Component.__HS_ISLAND.clientOnly(props);
113
+ return html.raw(Component.__HS_ISLAND.render(props, options));
118
114
  }
119
115
 
120
116
  throw new Error(
@@ -31,6 +31,9 @@ function htmlAsyncContentObserver() {
31
31
  Idiomorph.morph(slotEl, el.content.cloneNode(true));
32
32
  el.parentNode.removeChild(el);
33
33
  });
34
+
35
+ // Lazy load scripts (if any) after the content is inserted
36
+ lazyLoadScripts();
34
37
  }
35
38
  } catch (e) {
36
39
  console.error(e);
@@ -122,5 +125,37 @@ function formSubmitToRoute(e: Event, form: HTMLFormElement) {
122
125
  });
123
126
  }
124
127
 
128
+ /**
129
+ * Intersection observer for lazy loading <script> tags
130
+ */
131
+ const lazyLoadScriptObserver = new IntersectionObserver(
132
+ (entries, observer) => {
133
+ entries
134
+ .filter((entry) => entry.isIntersecting)
135
+ .forEach((entry) => {
136
+ observer.unobserve(entry.target);
137
+ // @ts-ignore
138
+ if (entry.target.children[0]?.content) {
139
+ // @ts-ignore
140
+ entry.target.replaceWith(entry.target.children[0].content);
141
+ }
142
+ });
143
+ },
144
+ { rootMargin: '0px 0px -200px 0px' }
145
+ );
146
+
147
+ /**
148
+ * Lazy load <script> tags in the current document
149
+ */
150
+ function lazyLoadScripts() {
151
+ document
152
+ .querySelectorAll('div[data-loading=lazy]')
153
+ .forEach((el) => lazyLoadScriptObserver.observe(el));
154
+ }
155
+
156
+ window.addEventListener('load', () => {
157
+ lazyLoadScripts();
158
+ });
159
+
125
160
  // @ts-ignore
126
161
  window.html = html;
package/src/server.ts CHANGED
@@ -7,6 +7,7 @@ import { Hono, type Context } from 'hono';
7
7
  import { serveStatic } from 'hono/bun';
8
8
  import { HTTPException } from 'hono/http-exception';
9
9
  import type { HandlerResponse, MiddlewareHandler } from 'hono/types';
10
+ import type { ContentfulStatusCode } from 'hono/utils/http-status';
10
11
 
11
12
  export const IS_PROD = process.env.NODE_ENV === 'production';
12
13
  const CWD = process.cwd();
@@ -22,9 +23,6 @@ export type THSRoute = {
22
23
  _kind: 'hsRoute';
23
24
  get: (handler: THSRouteHandler) => THSRoute;
24
25
  post: (handler: THSRouteHandler) => THSRoute;
25
- put: (handler: THSRouteHandler) => THSRoute;
26
- delete: (handler: THSRouteHandler) => THSRoute;
27
- patch: (handler: THSRouteHandler) => THSRoute;
28
26
  middleware: (middleware: Array<MiddlewareHandler>) => THSRoute;
29
27
  _getRouteHandlers: () => Array<MiddlewareHandler | ((context: Context) => HandlerResponse<any>)>;
30
28
  };
@@ -65,18 +63,6 @@ export function createRoute(handler?: THSRouteHandler): THSRoute {
65
63
  _handlers['POST'] = handler;
66
64
  return api;
67
65
  },
68
- put(handler: THSRouteHandler) {
69
- _handlers['PUT'] = handler;
70
- return api;
71
- },
72
- delete(handler: THSRouteHandler) {
73
- _handlers['DELETE'] = handler;
74
- return api;
75
- },
76
- patch(handler: THSRouteHandler) {
77
- _handlers['PATCH'] = handler;
78
- return api;
79
- },
80
66
  middleware(middleware: Array<MiddlewareHandler>) {
81
67
  _middleware = middleware;
82
68
  return api;
@@ -147,7 +133,7 @@ export function createAPIRoute(handler?: THSAPIRouteHandler): THSAPIRoute {
147
133
  }
148
134
 
149
135
  const api: THSAPIRoute = {
150
- _kind: 'hsRoute',
136
+ _kind: 'hsAPIRoute',
151
137
  get(handler: THSAPIRouteHandler) {
152
138
  _handlers['GET'] = handler;
153
139
  return api;
@@ -256,9 +242,18 @@ export function getRunnableRoute(route: unknown): THSRoute {
256
242
  );
257
243
  }
258
244
 
245
+ /**
246
+ * Check if a route is runnable by Hyperspan
247
+ */
259
248
  export function isRunnableRoute(route: unknown): boolean {
260
- // @ts-ignore
261
- return typeof route === 'object' && '_getRouteHandlers' in route;
249
+ if (typeof route !== 'object') {
250
+ return false;
251
+ }
252
+
253
+ const obj = route as { _kind: string; _getRouteHandlers: any };
254
+ const runnableKind = ['hsRoute', 'hsAPIRoute', 'hsAction'].includes(obj?._kind);
255
+
256
+ return runnableKind && '_getRouteHandlers' in obj;
262
257
  }
263
258
 
264
259
  /**
@@ -266,17 +261,35 @@ export function isRunnableRoute(route: unknown): boolean {
266
261
  * @TODO: Should check for and load user-customizeable template with special name (app/__error.ts ?)
267
262
  */
268
263
  async function showErrorReponse(context: Context, err: Error) {
264
+ let status: ContentfulStatusCode = 500;
265
+ const message = err.message || 'Internal Server Error';
266
+
267
+ // Send correct status code if HTTPException
268
+ if (err instanceof HTTPException) {
269
+ status = err.status as ContentfulStatusCode;
270
+ }
271
+
272
+ const stack = !IS_PROD && err.stack ? err.stack.split('\n').slice(1).join('\n') : '';
273
+
269
274
  const output = render(html`
270
- <main>
271
- <h1>Error</h1>
272
- <pre>${err.message}</pre>
273
- <pre>${!IS_PROD && err.stack ? err.stack.split('\n').slice(1).join('\n') : ''}</pre>
274
- </main>
275
+ <!DOCTYPE html>
276
+ <html lang="en">
277
+ <head>
278
+ <meta charset="UTF-8" />
279
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
280
+ <title>Application Error</title>
281
+ </head>
282
+ <body>
283
+ <main>
284
+ <h1>Application Error</h1>
285
+ <strong>${message}</strong>
286
+ ${stack ? html`<pre>${stack}</pre>` : ''}
287
+ </main>
288
+ </body>
289
+ </html>
275
290
  `);
276
291
 
277
- return context.html(output, {
278
- status: 500,
279
- });
292
+ return context.html(output, { status });
280
293
  }
281
294
 
282
295
  export type THSServerConfig = {
@@ -1,176 +0,0 @@
1
- /**
2
- * Fast browser md5 function (used for static asset hashing)
3
- * @link https://www.myersdaily.org/joseph/javascript/md5.js
4
- */
5
- function md5cycle(x, k) {
6
- var a = x[0],
7
- b = x[1],
8
- c = x[2],
9
- d = x[3];
10
-
11
- a = ff(a, b, c, d, k[0], 7, -680876936);
12
- d = ff(d, a, b, c, k[1], 12, -389564586);
13
- c = ff(c, d, a, b, k[2], 17, 606105819);
14
- b = ff(b, c, d, a, k[3], 22, -1044525330);
15
- a = ff(a, b, c, d, k[4], 7, -176418897);
16
- d = ff(d, a, b, c, k[5], 12, 1200080426);
17
- c = ff(c, d, a, b, k[6], 17, -1473231341);
18
- b = ff(b, c, d, a, k[7], 22, -45705983);
19
- a = ff(a, b, c, d, k[8], 7, 1770035416);
20
- d = ff(d, a, b, c, k[9], 12, -1958414417);
21
- c = ff(c, d, a, b, k[10], 17, -42063);
22
- b = ff(b, c, d, a, k[11], 22, -1990404162);
23
- a = ff(a, b, c, d, k[12], 7, 1804603682);
24
- d = ff(d, a, b, c, k[13], 12, -40341101);
25
- c = ff(c, d, a, b, k[14], 17, -1502002290);
26
- b = ff(b, c, d, a, k[15], 22, 1236535329);
27
-
28
- a = gg(a, b, c, d, k[1], 5, -165796510);
29
- d = gg(d, a, b, c, k[6], 9, -1069501632);
30
- c = gg(c, d, a, b, k[11], 14, 643717713);
31
- b = gg(b, c, d, a, k[0], 20, -373897302);
32
- a = gg(a, b, c, d, k[5], 5, -701558691);
33
- d = gg(d, a, b, c, k[10], 9, 38016083);
34
- c = gg(c, d, a, b, k[15], 14, -660478335);
35
- b = gg(b, c, d, a, k[4], 20, -405537848);
36
- a = gg(a, b, c, d, k[9], 5, 568446438);
37
- d = gg(d, a, b, c, k[14], 9, -1019803690);
38
- c = gg(c, d, a, b, k[3], 14, -187363961);
39
- b = gg(b, c, d, a, k[8], 20, 1163531501);
40
- a = gg(a, b, c, d, k[13], 5, -1444681467);
41
- d = gg(d, a, b, c, k[2], 9, -51403784);
42
- c = gg(c, d, a, b, k[7], 14, 1735328473);
43
- b = gg(b, c, d, a, k[12], 20, -1926607734);
44
-
45
- a = hh(a, b, c, d, k[5], 4, -378558);
46
- d = hh(d, a, b, c, k[8], 11, -2022574463);
47
- c = hh(c, d, a, b, k[11], 16, 1839030562);
48
- b = hh(b, c, d, a, k[14], 23, -35309556);
49
- a = hh(a, b, c, d, k[1], 4, -1530992060);
50
- d = hh(d, a, b, c, k[4], 11, 1272893353);
51
- c = hh(c, d, a, b, k[7], 16, -155497632);
52
- b = hh(b, c, d, a, k[10], 23, -1094730640);
53
- a = hh(a, b, c, d, k[13], 4, 681279174);
54
- d = hh(d, a, b, c, k[0], 11, -358537222);
55
- c = hh(c, d, a, b, k[3], 16, -722521979);
56
- b = hh(b, c, d, a, k[6], 23, 76029189);
57
- a = hh(a, b, c, d, k[9], 4, -640364487);
58
- d = hh(d, a, b, c, k[12], 11, -421815835);
59
- c = hh(c, d, a, b, k[15], 16, 530742520);
60
- b = hh(b, c, d, a, k[2], 23, -995338651);
61
-
62
- a = ii(a, b, c, d, k[0], 6, -198630844);
63
- d = ii(d, a, b, c, k[7], 10, 1126891415);
64
- c = ii(c, d, a, b, k[14], 15, -1416354905);
65
- b = ii(b, c, d, a, k[5], 21, -57434055);
66
- a = ii(a, b, c, d, k[12], 6, 1700485571);
67
- d = ii(d, a, b, c, k[3], 10, -1894986606);
68
- c = ii(c, d, a, b, k[10], 15, -1051523);
69
- b = ii(b, c, d, a, k[1], 21, -2054922799);
70
- a = ii(a, b, c, d, k[8], 6, 1873313359);
71
- d = ii(d, a, b, c, k[15], 10, -30611744);
72
- c = ii(c, d, a, b, k[6], 15, -1560198380);
73
- b = ii(b, c, d, a, k[13], 21, 1309151649);
74
- a = ii(a, b, c, d, k[4], 6, -145523070);
75
- d = ii(d, a, b, c, k[11], 10, -1120210379);
76
- c = ii(c, d, a, b, k[2], 15, 718787259);
77
- b = ii(b, c, d, a, k[9], 21, -343485551);
78
-
79
- x[0] = add32(a, x[0]);
80
- x[1] = add32(b, x[1]);
81
- x[2] = add32(c, x[2]);
82
- x[3] = add32(d, x[3]);
83
- }
84
-
85
- function cmn(q, a, b, x, s, t) {
86
- a = add32(add32(a, q), add32(x, t));
87
- return add32((a << s) | (a >>> (32 - s)), b);
88
- }
89
-
90
- function ff(a, b, c, d, x, s, t) {
91
- return cmn((b & c) | (~b & d), a, b, x, s, t);
92
- }
93
-
94
- function gg(a, b, c, d, x, s, t) {
95
- return cmn((b & d) | (c & ~d), a, b, x, s, t);
96
- }
97
-
98
- function hh(a, b, c, d, x, s, t) {
99
- return cmn(b ^ c ^ d, a, b, x, s, t);
100
- }
101
-
102
- function ii(a, b, c, d, x, s, t) {
103
- return cmn(c ^ (b | ~d), a, b, x, s, t);
104
- }
105
-
106
- function md51(s) {
107
- var txt = '';
108
- var n = s.length,
109
- state = [1732584193, -271733879, -1732584194, 271733878],
110
- i;
111
- for (i = 64; i <= s.length; i += 64) {
112
- md5cycle(state, md5blk(s.substring(i - 64, i)));
113
- }
114
- s = s.substring(i - 64);
115
- var tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
116
- for (i = 0; i < s.length; i++) tail[i >> 2] |= s.charCodeAt(i) << (i % 4 << 3);
117
- tail[i >> 2] |= 0x80 << (i % 4 << 3);
118
- if (i > 55) {
119
- md5cycle(state, tail);
120
- for (i = 0; i < 16; i++) tail[i] = 0;
121
- }
122
- tail[14] = n * 8;
123
- md5cycle(state, tail);
124
- return state;
125
- }
126
-
127
- /* there needs to be support for Unicode here,
128
- * unless we pretend that we can redefine the MD-5
129
- * algorithm for multi-byte characters (perhaps
130
- * by adding every four 16-bit characters and
131
- * shortening the sum to 32 bits). Otherwise
132
- * I suggest performing MD-5 as if every character
133
- * was two bytes--e.g., 0040 0025 = @%--but then
134
- * how will an ordinary MD-5 sum be matched?
135
- * There is no way to standardize text to something
136
- * like UTF-8 before transformation; speed cost is
137
- * utterly prohibitive. The JavaScript standard
138
- * itself needs to look at this: it should start
139
- * providing access to strings as preformed UTF-8
140
- * 8-bit unsigned value arrays.
141
- */
142
- function md5blk(s) {
143
- /* I figured global was faster. */
144
- var md5blks = [],
145
- i; /* Andy King said do it this way. */
146
- for (i = 0; i < 64; i += 4) {
147
- md5blks[i >> 2] =
148
- s.charCodeAt(i) +
149
- (s.charCodeAt(i + 1) << 8) +
150
- (s.charCodeAt(i + 2) << 16) +
151
- (s.charCodeAt(i + 3) << 24);
152
- }
153
- return md5blks;
154
- }
155
-
156
- var hex_chr = '0123456789abcdef'.split('');
157
-
158
- function rhex(n) {
159
- var s = '',
160
- j = 0;
161
- for (; j < 4; j++) s += hex_chr[(n >> (j * 8 + 4)) & 0x0f] + hex_chr[(n >> (j * 8)) & 0x0f];
162
- return s;
163
- }
164
-
165
- function hex(x) {
166
- for (var i = 0; i < x.length; i++) x[i] = rhex(x[i]);
167
- return x.join('');
168
- }
169
-
170
- function add32(a, b) {
171
- return (a + b) & 0xffffffff;
172
- }
173
-
174
- export function md5(s) {
175
- return hex(md51(s));
176
- }
@@ -1,3 +0,0 @@
1
- export * from 'preact/compat';
2
- export * from 'preact/hooks';
3
- export { h, render, hydrate } from 'preact';