@hyperspan/framework 1.0.12 → 1.0.14
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/package.json +1 -1
- package/src/actions.test.ts +4 -5
- package/src/client/_hs/hyperspan-streaming.client.ts +21 -2
- package/src/client/js.test.ts +27 -0
- package/src/client/js.ts +31 -15
- package/src/layout.ts +0 -9
package/package.json
CHANGED
package/src/actions.test.ts
CHANGED
|
@@ -230,15 +230,15 @@ describe('createAction', () => {
|
|
|
230
230
|
</form>
|
|
231
231
|
`;
|
|
232
232
|
}).post(async (c, { data }) => {
|
|
233
|
-
return
|
|
233
|
+
return html`
|
|
234
234
|
<p>Hello, ${data?.name}!</p>
|
|
235
235
|
<p>Your email is ${data?.email}.</p>
|
|
236
|
-
|
|
236
|
+
`;
|
|
237
237
|
}).errorHandler(async (c, { data, error }) => {
|
|
238
|
-
return
|
|
238
|
+
return html`
|
|
239
239
|
<p>Caught error in custom error handler: ${error?.message}</p>
|
|
240
240
|
<p>Data: ${JSON.stringify(data)}</p>
|
|
241
|
-
|
|
241
|
+
`;
|
|
242
242
|
});
|
|
243
243
|
|
|
244
244
|
// Test fetch method with invalid data (missing name, invalid email)
|
|
@@ -257,7 +257,6 @@ describe('createAction', () => {
|
|
|
257
257
|
const responseText = await response.text();
|
|
258
258
|
// Should render the custom error handler
|
|
259
259
|
expect(responseText).toContain('Invalid email address');
|
|
260
|
-
expect(responseText).toContain('Data: {"email":"not-an-email"}');
|
|
261
260
|
// Should NOT contain the success message from post handler
|
|
262
261
|
expect(responseText).not.toContain('Hello,');
|
|
263
262
|
});
|
|
@@ -62,5 +62,24 @@ function renderStreamChunk(chunk: { id: string }) {
|
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Define the _hsc property on the Window object for streaming content
|
|
67
|
+
* Render all current chunks in the _hsc array when the window is loaded
|
|
68
|
+
*/
|
|
69
|
+
declare global {
|
|
70
|
+
interface Window {
|
|
71
|
+
_hsc: {
|
|
72
|
+
push: (e: { id: string }) => void;
|
|
73
|
+
forEach: (callback: (e: { id: string }) => void) => void;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
window._hsc = window._hsc || [];
|
|
79
|
+
window._hsc.push = function (e: { id: string }) {
|
|
80
|
+
Array.prototype.push.call(window._hsc, e);
|
|
81
|
+
renderStreamChunk(e);
|
|
82
|
+
};
|
|
83
|
+
window._hsc.forEach((e: { id: string }) => {
|
|
84
|
+
renderStreamChunk(e);
|
|
85
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { extractExports } from './js';
|
|
3
|
+
|
|
4
|
+
describe('extractExports', () => {
|
|
5
|
+
test('extracts aliased export from bundled JS content', () => {
|
|
6
|
+
const contents = 'function d(n,t){if(t)n.classList.remove("hidden");else n.classList.add("hidden")}function p(){let n=document.querySelector("[data-google-spreadsheet-id]"),t=document.querySelector("[data-google-sheet-select]"),a=document.querySelector("[data-google-sheet-message]");if(console.log("googleSheetsPickerClient",n,t),!n||!t)return;let l=(e)=>{if(!a)return;a.textContent=e||"",d(a,Boolean(e))},h=(e)=>{t.innerHTML="",e.forEach((s)=>{let o=document.createElement("option");o.value=s,o.textContent=s,t.appendChild(o)})},r=(e)=>{d(t,e),t.disabled=!e,t.required=e},c=async()=>{let e=n.value.trim();if(l(null),!e){r(!1);return}try{let s=await fetch(`/api/google-sheets/${encodeURIComponent(e)}`),o=await s.json();if(!s.ok||o.error)throw Error(o.error||"Unable to load sheet names.");let i=(o.sheets||[]).map((u)=>u.title).filter(Boolean);if(i.length===0){l("No sheets found in that spreadsheet."),r(!1);return}h(i),t.value=i[0],r(!0)}catch(s){l(s instanceof Error?s.message:"Unable to load sheet names."),r(!1)}};n.addEventListener("input",c),n.addEventListener("change",c)}export{p as mountGoogleSheetsPicker};';
|
|
7
|
+
|
|
8
|
+
const result = extractExports(contents);
|
|
9
|
+
|
|
10
|
+
expect(result).toEqual({
|
|
11
|
+
exports: '{mountGoogleSheetsPicker}',
|
|
12
|
+
fnArgs: '{mountGoogleSheetsPicker}',
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('returns empty exports when none found', () => {
|
|
17
|
+
const contents = 'function noop(){return 1}const value=2;';
|
|
18
|
+
|
|
19
|
+
const result = extractExports(contents);
|
|
20
|
+
|
|
21
|
+
expect(result).toEqual({
|
|
22
|
+
exports: '* as _module',
|
|
23
|
+
fnArgs: '_module',
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
package/src/client/js.ts
CHANGED
|
@@ -47,21 +47,7 @@ export async function buildClientJS(modulePathResolved: string): Promise<HS.Clie
|
|
|
47
47
|
|
|
48
48
|
// Get the contents of the file to extract the exports
|
|
49
49
|
const contents = await result.outputs[0].text();
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
let exports = '{}';
|
|
53
|
-
if (exportLine) {
|
|
54
|
-
const exportName = exportLine[1];
|
|
55
|
-
exports =
|
|
56
|
-
'{' +
|
|
57
|
-
exportName
|
|
58
|
-
.split(',')
|
|
59
|
-
.map((name) => name.trim().split(' as '))
|
|
60
|
-
.map(([name, alias]) => `${alias === 'default' ? 'default as ' + name : alias}`)
|
|
61
|
-
.join(', ') +
|
|
62
|
-
'}';
|
|
63
|
-
}
|
|
64
|
-
const fnArgs = exports.replace(/(\w+)\s*as\s*(\w+)/g, '$1: $2');
|
|
50
|
+
const { exports, fnArgs } = extractExports(contents);
|
|
65
51
|
|
|
66
52
|
CLIENT_JS_CACHE.set(assetHash, { esmName, exports, fnArgs, publicPath });
|
|
67
53
|
})();
|
|
@@ -102,6 +88,36 @@ export async function buildClientJS(modulePathResolved: string): Promise<HS.Clie
|
|
|
102
88
|
}
|
|
103
89
|
}
|
|
104
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Extract the exports from a client JS module
|
|
93
|
+
*/
|
|
94
|
+
export function extractExports(contents: string): { exports: string, fnArgs: string } {
|
|
95
|
+
const exportLine = EXPORT_REGEX.exec(contents);
|
|
96
|
+
let exports = '{}';
|
|
97
|
+
let fnArgs = '{}';
|
|
98
|
+
|
|
99
|
+
if (exportLine) {
|
|
100
|
+
const exportName = exportLine[1];
|
|
101
|
+
exports =
|
|
102
|
+
'{' +
|
|
103
|
+
exportName
|
|
104
|
+
.split(',')
|
|
105
|
+
.map((name) => name.trim().split(' as '))
|
|
106
|
+
.map(([name, alias]) => `${alias === 'default' ? 'default as ' + name : alias}`)
|
|
107
|
+
.join(', ') +
|
|
108
|
+
'}';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
fnArgs = exports.replace(/(\w+)\s*as\s*(\w+)/g, '$1: $2').trim();
|
|
112
|
+
|
|
113
|
+
if (exports === '{}' && fnArgs === '{}') {
|
|
114
|
+
exports = '* as _module'
|
|
115
|
+
fnArgs = '_module'
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return { exports, fnArgs };
|
|
119
|
+
}
|
|
120
|
+
|
|
105
121
|
/**
|
|
106
122
|
* Convert a function to a string (results in loss of context!)
|
|
107
123
|
* Handles named, async, and arrow functions
|
package/src/layout.ts
CHANGED
|
@@ -16,22 +16,13 @@ export function hyperspanScriptTags() {
|
|
|
16
16
|
<script id="hyperspan-streaming-script">
|
|
17
17
|
// [Hyperspan] Streaming - Load the client streaming JS module only when the first chunk is loaded
|
|
18
18
|
window._hsc = window._hsc || [];
|
|
19
|
-
var hscc = function(e) {
|
|
20
|
-
if (window._hscc !== undefined) {
|
|
21
|
-
window._hscc(e);
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
19
|
window._hsc.push = function(e) {
|
|
25
20
|
Array.prototype.push.call(window._hsc, e);
|
|
26
21
|
if (window._hsc.length === 1) {
|
|
27
22
|
const script = document.createElement('script');
|
|
28
23
|
script.src = "${clientStreamingJS.publicPath}";
|
|
29
24
|
document.body.appendChild(script);
|
|
30
|
-
script.onload = function() {
|
|
31
|
-
hscc(e);
|
|
32
|
-
};
|
|
33
25
|
}
|
|
34
|
-
hscc(e);
|
|
35
26
|
};
|
|
36
27
|
</script>
|
|
37
28
|
`;
|