@hyperspan/framework 2.0.0-alpha.14 → 2.0.0-alpha.15
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 +2 -2
- package/src/client/js.test.ts +59 -11
- package/src/client/js.ts +128 -41
- package/src/client/manifest.ts +9 -1
- package/src/types.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperspan/framework",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.15",
|
|
4
4
|
"description": "Hyperspan Web Framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/server.ts",
|
|
@@ -104,7 +104,7 @@
|
|
|
104
104
|
"vitest": "^4.1.10"
|
|
105
105
|
},
|
|
106
106
|
"dependencies": {
|
|
107
|
-
"@hyperspan/html": "^2.0.0-alpha.
|
|
107
|
+
"@hyperspan/html": "^2.0.0-alpha.15",
|
|
108
108
|
"debug": "^4.4.3",
|
|
109
109
|
"isbot": "^5.1.32",
|
|
110
110
|
"zod": "^4.4.3"
|
package/src/client/js.test.ts
CHANGED
|
@@ -2,7 +2,9 @@ import { describe, expect, test, beforeEach } from 'vitest';
|
|
|
2
2
|
import { mkdtempSync, writeFileSync, existsSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
|
+
import { pathToFileURL } from 'node:url';
|
|
5
6
|
import { render } from '@hyperspan/html';
|
|
7
|
+
import { setAssetManifest } from './manifest';
|
|
6
8
|
import {
|
|
7
9
|
buildClientJS,
|
|
8
10
|
discoverClientExports,
|
|
@@ -10,6 +12,8 @@ import {
|
|
|
10
12
|
getClientJSEntries,
|
|
11
13
|
registerPathAliases,
|
|
12
14
|
resetClientJSEntriesForTests,
|
|
15
|
+
stableClientSourceKey,
|
|
16
|
+
streamingClient,
|
|
13
17
|
} from './js';
|
|
14
18
|
|
|
15
19
|
describe('buildClientJS', () => {
|
|
@@ -17,6 +21,7 @@ describe('buildClientJS', () => {
|
|
|
17
21
|
|
|
18
22
|
beforeEach(() => {
|
|
19
23
|
resetClientJSEntriesForTests();
|
|
24
|
+
setAssetManifest({ imports: {}, css: {}, clients: {}, clientSources: {} });
|
|
20
25
|
const dir = mkdtempSync(join(tmpdir(), 'hs-client-'));
|
|
21
26
|
clientFile = join(dir, 'picker.ts');
|
|
22
27
|
writeFileSync(
|
|
@@ -28,7 +33,7 @@ describe('buildClientJS', () => {
|
|
|
28
33
|
);
|
|
29
34
|
});
|
|
30
35
|
|
|
31
|
-
test('registers entry
|
|
36
|
+
test('registers entry with a stable path identity', async () => {
|
|
32
37
|
const result = await buildClientJS(clientFile);
|
|
33
38
|
|
|
34
39
|
expect(result.esmName).toMatch(/^client-[0-9a-f]{16}$/);
|
|
@@ -37,11 +42,38 @@ describe('buildClientJS', () => {
|
|
|
37
42
|
expect(getClientJSEntries()[0].absPath).toBe(clientFile);
|
|
38
43
|
});
|
|
39
44
|
|
|
45
|
+
test('changing file contents does not change the import-map key', async () => {
|
|
46
|
+
const first = await buildClientJS(clientFile);
|
|
47
|
+
writeFileSync(clientFile, `export function mountPicker() { return 2; }\n`);
|
|
48
|
+
resetClientJSEntriesForTests();
|
|
49
|
+
const second = await buildClientJS(clientFile);
|
|
50
|
+
expect(second.esmName).toBe(first.esmName);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('publicPath uses the Vite-hashed URL from the import map', async () => {
|
|
54
|
+
const result = await buildClientJS(clientFile);
|
|
55
|
+
const hashed = `/_hs/js/${result.esmName}-a1b2c3d4.js`;
|
|
56
|
+
setAssetManifest({
|
|
57
|
+
imports: { [result.esmName]: hashed },
|
|
58
|
+
css: {},
|
|
59
|
+
clients: {},
|
|
60
|
+
clientSources: {},
|
|
61
|
+
});
|
|
62
|
+
expect(result.publicPath).toBe(hashed);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('absolute path and file URL of the same file share one hash', async () => {
|
|
66
|
+
const viaAbs = await buildClientJS(clientFile);
|
|
67
|
+
resetClientJSEntriesForTests();
|
|
68
|
+
const viaUrl = await buildClientJS(pathToFileURL(clientFile).href);
|
|
69
|
+
expect(viaUrl.esmName).toBe(viaAbs.esmName);
|
|
70
|
+
});
|
|
71
|
+
|
|
40
72
|
test('renderScriptTag with no loader uses import map key', async () => {
|
|
41
73
|
const result = await buildClientJS(clientFile);
|
|
42
74
|
const tag = render(result.renderScriptTag());
|
|
43
75
|
|
|
44
|
-
expect(tag).toContain(`import
|
|
76
|
+
expect(tag).toContain(`import '${result.esmName}'`);
|
|
45
77
|
expect(tag).toContain(`data-source-id="${result.assetHash}"`);
|
|
46
78
|
});
|
|
47
79
|
|
|
@@ -53,7 +85,7 @@ describe('buildClientJS', () => {
|
|
|
53
85
|
})
|
|
54
86
|
);
|
|
55
87
|
|
|
56
|
-
expect(tag).toContain(`import {mountPicker} from
|
|
88
|
+
expect(tag).toContain(`import {mountPicker} from '${result.esmName}'`);
|
|
57
89
|
expect(tag).toContain('mountPicker()');
|
|
58
90
|
});
|
|
59
91
|
|
|
@@ -64,11 +96,22 @@ describe('buildClientJS', () => {
|
|
|
64
96
|
expect(tag).toContain('({ mountPicker }) => mountPicker()');
|
|
65
97
|
});
|
|
66
98
|
|
|
99
|
+
test('package specifiers and import.meta.resolve of the same file share one hash', async () => {
|
|
100
|
+
const spec = '@hyperspan/framework/client/_hs/hyperspan-streaming.client.ts';
|
|
101
|
+
const first = await buildClientJS(spec, { type: 'iife' });
|
|
102
|
+
resetClientJSEntriesForTests();
|
|
103
|
+
const second = await buildClientJS(spec, { type: 'iife' });
|
|
104
|
+
expect(first.esmName).toBe(second.esmName);
|
|
105
|
+
expect(first.esmName).toBe(streamingClient.esmName);
|
|
106
|
+
expect(existsSync(getClientJSEntries()[0].absPath)).toBe(true);
|
|
107
|
+
});
|
|
108
|
+
|
|
67
109
|
test('import.meta.resolve at the call site registers a real file', async () => {
|
|
68
110
|
const result = await buildClientJS(import.meta.resolve('./_hs/hyperspan-streaming.client.ts'), {
|
|
69
111
|
type: 'iife',
|
|
70
112
|
});
|
|
71
113
|
|
|
114
|
+
expect(result.esmName).toBe(streamingClient.esmName);
|
|
72
115
|
expect(result.publicPath).toMatch(/^\/_hs\/js\/client-[0-9a-f]{16}\.js$/);
|
|
73
116
|
expect(getClientJSEntries()[0].type).toBe('iife');
|
|
74
117
|
expect(existsSync(getClientJSEntries()[0].absPath)).toBe(true);
|
|
@@ -132,14 +175,19 @@ describe('buildClientJS', () => {
|
|
|
132
175
|
expect(second.esmName).toBe(result.esmName);
|
|
133
176
|
});
|
|
134
177
|
|
|
135
|
-
test('
|
|
136
|
-
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
178
|
+
test('missing files use a build-time hash from clientSources', async () => {
|
|
179
|
+
const ghost = '/definitely-missing-hyperspan-root/app/client/gone.ts';
|
|
180
|
+
const builtName = 'client-from-build0123';
|
|
181
|
+
setAssetManifest({
|
|
182
|
+
imports: { [builtName]: `/_hs/js/${builtName}.js` },
|
|
183
|
+
css: {},
|
|
184
|
+
clients: {},
|
|
185
|
+
clientSources: { [stableClientSourceKey(ghost)]: builtName },
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const result = await buildClientJS(ghost);
|
|
189
|
+
expect(result.esmName).toBe(builtName);
|
|
190
|
+
expect(result.publicPath).toBe(`/_hs/js/${builtName}.js`);
|
|
143
191
|
});
|
|
144
192
|
});
|
|
145
193
|
|
package/src/client/js.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { isAbsolute, join, relative } from 'node:path';
|
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import { html } from '@hyperspan/html';
|
|
5
5
|
import { assetHash as assetHashFn } from '../utils';
|
|
6
|
-
import { getImportMap, registerImport, resolveImport } from './manifest';
|
|
6
|
+
import { getAssetManifest, getImportMap, registerImport, resolveImport } from './manifest';
|
|
7
7
|
import type { Hyperspan as HS } from '../types';
|
|
8
8
|
|
|
9
9
|
export { registerImport, resolveImport, getImportMap } from './manifest';
|
|
@@ -22,6 +22,9 @@ export type ClientJSEntry = {
|
|
|
22
22
|
/** Original path/specifier passed to `buildClientJS`. */
|
|
23
23
|
modulePath: string;
|
|
24
24
|
absPath: string;
|
|
25
|
+
/** Identities used to look up a stable import-map key when the source file is gone (Workers). */
|
|
26
|
+
sourceKeys: string[];
|
|
27
|
+
/** Identity hash of the resolved path (not file contents). */
|
|
25
28
|
assetHash: string;
|
|
26
29
|
esmName: string;
|
|
27
30
|
publicPath: string;
|
|
@@ -99,22 +102,23 @@ export const JS_IMPORT_MAP = {
|
|
|
99
102
|
};
|
|
100
103
|
|
|
101
104
|
/**
|
|
102
|
-
*
|
|
103
|
-
*
|
|
105
|
+
* File URLs from `import.meta.resolve` must not use a cwd-relative pnpm path —
|
|
106
|
+
* that changes between install layouts and Workers. Prefer the path after the
|
|
107
|
+
* last `node_modules/` so published packages stay stable.
|
|
104
108
|
*/
|
|
105
|
-
function
|
|
109
|
+
export function stableClientSourceKey(absPath: string): string {
|
|
106
110
|
const normalized = absPath.replace(/\\/g, '/');
|
|
111
|
+
const marker = '/node_modules/';
|
|
112
|
+
const idx = normalized.lastIndexOf(marker);
|
|
113
|
+
if (idx !== -1) {
|
|
114
|
+
return normalized.slice(idx + marker.length);
|
|
115
|
+
}
|
|
107
116
|
if (typeof process !== 'undefined' && typeof process.cwd === 'function') {
|
|
108
117
|
const rel = relative(process.cwd(), absPath).replace(/\\/g, '/');
|
|
109
118
|
if (rel && !rel.startsWith('..') && !isAbsolute(rel)) {
|
|
110
119
|
return rel;
|
|
111
120
|
}
|
|
112
121
|
}
|
|
113
|
-
const marker = '/node_modules/';
|
|
114
|
-
const idx = normalized.lastIndexOf(marker);
|
|
115
|
-
if (idx !== -1) {
|
|
116
|
-
return normalized.slice(idx + marker.length);
|
|
117
|
-
}
|
|
118
122
|
return normalized;
|
|
119
123
|
}
|
|
120
124
|
|
|
@@ -122,16 +126,35 @@ function toFilePath(modulePath: string): string {
|
|
|
122
126
|
return modulePath.startsWith('file://') ? fileURLToPath(modulePath) : modulePath;
|
|
123
127
|
}
|
|
124
128
|
|
|
129
|
+
function tryResolveToFile(specifier: string): string | undefined {
|
|
130
|
+
try {
|
|
131
|
+
if (typeof import.meta.resolve !== 'function') {
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
const resolved = import.meta.resolve(specifier);
|
|
135
|
+
if (typeof resolved === 'string' && (resolved.startsWith('file://') || isAbsolute(resolved))) {
|
|
136
|
+
return toFilePath(resolved);
|
|
137
|
+
}
|
|
138
|
+
} catch {
|
|
139
|
+
// Workers and unresolved package specifiers — Vite already emitted the asset.
|
|
140
|
+
}
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function clientPublicPath(esmName: string): string {
|
|
145
|
+
return resolveImport(esmName) ?? `${JS_PUBLIC_PATH}/${esmName}.js`;
|
|
146
|
+
}
|
|
147
|
+
|
|
125
148
|
/**
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
* Relative `./` / `../` paths error — resolve
|
|
149
|
+
* Resolve to a real file path. The import-map key hashes `stableClientSourceKey(absPath)`
|
|
150
|
+
* so `import.meta.resolve()`, aliases, and package specifiers of the same file share
|
|
151
|
+
* one key. Vite/esbuild attach a content hash to the emitted filename; `publicPath`
|
|
152
|
+
* reads that from the asset manifest. Relative `./` / `../` paths error — resolve
|
|
153
|
+
* them at the call site.
|
|
130
154
|
*/
|
|
131
|
-
function
|
|
155
|
+
function resolveClientAbsPath(modulePath: string): string {
|
|
132
156
|
if (modulePath.startsWith('file://') || isAbsolute(modulePath)) {
|
|
133
|
-
|
|
134
|
-
return { hashKey: stableHashKey(absPath), absPath };
|
|
157
|
+
return toFilePath(modulePath);
|
|
135
158
|
}
|
|
136
159
|
|
|
137
160
|
const specifier = modulePath.replace(/\\/g, '/');
|
|
@@ -145,10 +168,41 @@ function resolveClientModule(modulePath: string): { hashKey: string; absPath: st
|
|
|
145
168
|
|
|
146
169
|
const aliased = resolveWithAliases(specifier);
|
|
147
170
|
if (aliased) {
|
|
148
|
-
return
|
|
171
|
+
return aliased;
|
|
149
172
|
}
|
|
150
173
|
|
|
151
|
-
return
|
|
174
|
+
return tryResolveToFile(specifier) ?? join(process.cwd(), specifier);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function readClientSource(absPath: string): string | undefined {
|
|
178
|
+
try {
|
|
179
|
+
if (existsSync(absPath)) {
|
|
180
|
+
return readFileSync(absPath, 'utf-8');
|
|
181
|
+
}
|
|
182
|
+
} catch {
|
|
183
|
+
// Workers have no filesystem; Vite already bundled from this entry.
|
|
184
|
+
}
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function clientSourceKeys(modulePath: string, absPath: string): string[] {
|
|
189
|
+
const keys = new Set<string>([stableClientSourceKey(absPath), modulePath.replace(/\\/g, '/')]);
|
|
190
|
+
if (modulePath.startsWith('file://')) {
|
|
191
|
+
keys.add(toFilePath(modulePath));
|
|
192
|
+
}
|
|
193
|
+
return [...keys];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function resolveEsmName(sourceKeys: string[], fallbackHash: string): string {
|
|
197
|
+
const sources = getAssetManifest().clientSources;
|
|
198
|
+
if (sources) {
|
|
199
|
+
for (const key of sourceKeys) {
|
|
200
|
+
if (sources[key]) {
|
|
201
|
+
return sources[key];
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return `client-${fallbackHash}`;
|
|
152
206
|
}
|
|
153
207
|
|
|
154
208
|
export function getClientJSEntries(): ClientJSEntry[] {
|
|
@@ -156,7 +210,23 @@ export function getClientJSEntries(): ClientJSEntry[] {
|
|
|
156
210
|
}
|
|
157
211
|
|
|
158
212
|
export function getClientJSEntryByEsmName(esmName: string): ClientJSEntry | undefined {
|
|
159
|
-
|
|
213
|
+
for (const entry of getClientJSRegistry().values()) {
|
|
214
|
+
if (currentEsmName(entry) === esmName) {
|
|
215
|
+
return entry;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return undefined;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function getClientJSSourceMap(): Record<string, string> {
|
|
222
|
+
const map: Record<string, string> = {};
|
|
223
|
+
for (const entry of getClientJSEntries()) {
|
|
224
|
+
const esmName = currentEsmName(entry);
|
|
225
|
+
for (const key of entry.sourceKeys) {
|
|
226
|
+
map[key] = esmName;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return map;
|
|
160
230
|
}
|
|
161
231
|
|
|
162
232
|
export function resetClientJSEntriesForTests(): void {
|
|
@@ -167,52 +237,69 @@ export function resetClientJSEntriesForTests(): void {
|
|
|
167
237
|
}
|
|
168
238
|
}
|
|
169
239
|
|
|
240
|
+
function currentEsmName(entry: ClientJSEntry): string {
|
|
241
|
+
return resolveEsmName(entry.sourceKeys, entry.assetHash);
|
|
242
|
+
}
|
|
243
|
+
|
|
170
244
|
function registerClientJS(
|
|
171
245
|
modulePathResolved: string,
|
|
172
246
|
options: BuildClientJSOptions = {}
|
|
173
247
|
): HS.ClientJSBuildResult {
|
|
174
248
|
const type = options.type ?? 'module';
|
|
175
|
-
const
|
|
176
|
-
const
|
|
177
|
-
const
|
|
178
|
-
const
|
|
249
|
+
const absPath = resolveClientAbsPath(modulePathResolved);
|
|
250
|
+
const sourceKeys = clientSourceKeys(modulePathResolved, absPath);
|
|
251
|
+
const identityKey = stableClientSourceKey(absPath);
|
|
252
|
+
const hash = assetHashFn(identityKey);
|
|
253
|
+
const source = readClientSource(absPath);
|
|
179
254
|
|
|
180
255
|
let exports = '* as _module';
|
|
181
256
|
let fnArgs = '_module';
|
|
182
|
-
|
|
183
|
-
if (sourcePath) {
|
|
184
|
-
const source = readFileSync(sourcePath, 'utf-8');
|
|
257
|
+
if (source) {
|
|
185
258
|
const discovered = discoverClientExports(source);
|
|
186
259
|
exports = discovered.exports;
|
|
187
260
|
fnArgs = discovered.fnArgs;
|
|
188
261
|
}
|
|
189
262
|
|
|
190
|
-
|
|
263
|
+
const entry: ClientJSEntry = {
|
|
191
264
|
modulePath: modulePathResolved,
|
|
192
265
|
absPath,
|
|
266
|
+
sourceKeys,
|
|
193
267
|
assetHash: hash,
|
|
194
|
-
esmName
|
|
195
|
-
|
|
268
|
+
get esmName() {
|
|
269
|
+
return currentEsmName(entry);
|
|
270
|
+
},
|
|
271
|
+
get publicPath() {
|
|
272
|
+
return clientPublicPath(currentEsmName(entry));
|
|
273
|
+
},
|
|
196
274
|
exports,
|
|
197
275
|
fnArgs,
|
|
198
276
|
type,
|
|
199
|
-
}
|
|
200
|
-
|
|
277
|
+
};
|
|
278
|
+
getClientJSRegistry().set(identityKey, entry);
|
|
279
|
+
registerImport(entry.esmName, entry.publicPath);
|
|
201
280
|
|
|
202
281
|
return {
|
|
203
|
-
assetHash
|
|
204
|
-
|
|
205
|
-
|
|
282
|
+
get assetHash() {
|
|
283
|
+
return currentEsmName(entry).replace(/^client-/, '');
|
|
284
|
+
},
|
|
285
|
+
get esmName() {
|
|
286
|
+
return currentEsmName(entry);
|
|
287
|
+
},
|
|
288
|
+
get publicPath() {
|
|
289
|
+
return clientPublicPath(currentEsmName(entry));
|
|
290
|
+
},
|
|
206
291
|
renderScriptTag: (loadScript) => {
|
|
292
|
+
const esmName = currentEsmName(entry);
|
|
293
|
+
const tagHash = esmName.replace(/^client-/, '');
|
|
207
294
|
if (type === 'iife') {
|
|
208
|
-
return html`<script src="${
|
|
295
|
+
return html`<script src="${clientPublicPath(esmName)}"></script>`;
|
|
209
296
|
}
|
|
210
297
|
|
|
211
298
|
const t = typeof loadScript;
|
|
212
299
|
|
|
213
300
|
if (t === 'string') {
|
|
214
301
|
return html`
|
|
215
|
-
<script type="module" data-source-id="${
|
|
302
|
+
<script type="module" data-source-id="${tagHash}">
|
|
216
303
|
import ${exports} from '${esmName}';
|
|
217
304
|
(${html.raw(loadScript as string)})(${fnArgs});
|
|
218
305
|
</script>
|
|
@@ -220,7 +307,7 @@ function registerClientJS(
|
|
|
220
307
|
}
|
|
221
308
|
if (t === 'function') {
|
|
222
309
|
return html`
|
|
223
|
-
<script type="module" data-source-id="${
|
|
310
|
+
<script type="module" data-source-id="${tagHash}">
|
|
224
311
|
import ${exports} from '${esmName}';
|
|
225
312
|
(${html.raw(functionToString(loadScript))})(${fnArgs});
|
|
226
313
|
</script>
|
|
@@ -228,7 +315,7 @@ function registerClientJS(
|
|
|
228
315
|
}
|
|
229
316
|
|
|
230
317
|
return html`
|
|
231
|
-
<script type="module" data-source-id="${
|
|
318
|
+
<script type="module" data-source-id="${tagHash}">
|
|
232
319
|
import '${esmName}';
|
|
233
320
|
</script>
|
|
234
321
|
`;
|
|
@@ -238,9 +325,9 @@ function registerClientJS(
|
|
|
238
325
|
|
|
239
326
|
/**
|
|
240
327
|
* Register a client JS module for Vite to bundle.
|
|
241
|
-
* Prefer a tsconfig alias or project-root path
|
|
242
|
-
*
|
|
243
|
-
*
|
|
328
|
+
* Prefer `import.meta.resolve('./file.ts')`, a tsconfig alias, or a project-root path.
|
|
329
|
+
* The import-map key is a stable identity of the resolved file. Vite/esbuild content-hash
|
|
330
|
+
* the emitted filename; `publicPath` uses that URL from the asset manifest when present.
|
|
244
331
|
*/
|
|
245
332
|
export async function buildClientJS(
|
|
246
333
|
modulePathResolved: string,
|
package/src/client/manifest.ts
CHANGED
|
@@ -4,19 +4,27 @@ export type AssetManifest = {
|
|
|
4
4
|
imports: Record<string, string>;
|
|
5
5
|
css: Record<string, string[]>;
|
|
6
6
|
clients: Record<string, string>;
|
|
7
|
+
/** Resolved path / original specifier → import-map key (`client-{id}`). */
|
|
8
|
+
clientSources?: Record<string, string>;
|
|
7
9
|
};
|
|
8
10
|
|
|
9
11
|
let _manifest: AssetManifest = {
|
|
10
12
|
imports: {},
|
|
11
13
|
css: {},
|
|
12
14
|
clients: {},
|
|
15
|
+
clientSources: {},
|
|
13
16
|
};
|
|
14
17
|
|
|
15
18
|
/**
|
|
16
19
|
* Set the build-time asset manifest (called by Vite plugin or build step).
|
|
17
20
|
*/
|
|
18
21
|
export function setAssetManifest(manifest: AssetManifest): void {
|
|
19
|
-
_manifest =
|
|
22
|
+
_manifest = {
|
|
23
|
+
imports: manifest.imports ?? {},
|
|
24
|
+
css: manifest.css ?? {},
|
|
25
|
+
clients: manifest.clients ?? {},
|
|
26
|
+
clientSources: { ...(manifest.clientSources ?? {}) },
|
|
27
|
+
};
|
|
20
28
|
}
|
|
21
29
|
|
|
22
30
|
/**
|
package/src/types.ts
CHANGED
|
@@ -430,7 +430,7 @@ export namespace Hyperspan {
|
|
|
430
430
|
* Client JS Module = ESM Module + Public Path + Render Script Tag
|
|
431
431
|
*/
|
|
432
432
|
export type ClientJSBuildResult = {
|
|
433
|
-
assetHash: string; //
|
|
433
|
+
assetHash: string; // Identity of the resolved path; production file hash comes from Vite
|
|
434
434
|
esmName: string; // Filename of the built JavaScript file without the extension
|
|
435
435
|
publicPath: string; // Full public path of the built JavaScript file
|
|
436
436
|
/**
|