@teamscala/service-boot 0.23.555 → 0.23.558

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 CHANGED
@@ -27,7 +27,7 @@
27
27
  "repository": "https://github.com/teamgbg/service-boot",
28
28
  "sideEffects": false,
29
29
  "type": "module",
30
- "version": "0.23.555",
30
+ "version": "0.23.558",
31
31
  "dependencies": {
32
32
  "@orpc/openapi": "*",
33
33
  "@orpc/server": "*",
@@ -18,6 +18,8 @@ const HYDRATE_BASE = "hydrate";
18
18
  export interface HydrateBundleResult {
19
19
  /** Served entry path (`/assets/hydrate-<hash>.js`), or null when no entry exists. */
20
20
  hydratePath: string | null;
21
+ /** Chunk URLs the entry itself can load, for modulepreload emission. */
22
+ preloads: string[];
21
23
  /** The VERIFIED source-map upload outcome for this build's release. */
22
24
  sourceMaps: SourceMapUploadResult;
23
25
  }
@@ -195,8 +197,28 @@ export async function buildHydrateBundle(
195
197
  .filter((o) => o.kind === "chunk")
196
198
  .map((o) => o.path.split("/").pop() as string),
197
199
  );
200
+ const entryText = await Bun.file(hashedPath).text();
201
+ const preloads = [
202
+ ...new Set(
203
+ [...entryText.matchAll(/["'](\/assets\/[A-Za-z0-9-]+\.js)["']/g)].map(
204
+ (m) => m[1],
205
+ ),
206
+ ),
207
+ ].sort();
208
+ const unknownPreloads = preloads.filter(
209
+ (url) => !currentChunks.has(url.replace(/^\/assets\//, "")),
210
+ );
211
+ if (unknownPreloads.length > 0) {
212
+ throw new Error(
213
+ `[build-client-assets] entry references chunk URL(s) Bun did not emit: ${unknownPreloads.join(", ")} — refusing a manifest whose preloads name unbuilt assets`,
214
+ );
215
+ }
198
216
  const sourceMaps = await uploadSourceMaps(cwd, outDir, hash, log, currentOutputs);
199
- return { hydratePath: `${ASSET_PUBLIC_PREFIX}${hashedName}`, sourceMaps };
217
+ return {
218
+ hydratePath: `${ASSET_PUBLIC_PREFIX}${hashedName}`,
219
+ preloads,
220
+ sourceMaps,
221
+ };
200
222
  }
201
223
 
202
224
  async function removeStaleChunks(
@@ -12,6 +12,7 @@ export const MANIFEST_FILE = "asset-manifest.json";
12
12
  export interface AssetManifest {
13
13
  stylesPath?: string;
14
14
  hydratePath?: string;
15
+ preloads?: string[];
15
16
  sourceMaps?: SourceMapUploadResult;
16
17
  }
17
18
 
@@ -47,6 +48,7 @@ export async function readManifestState(outDir: string): Promise<ManifestState>
47
48
  manifest: {
48
49
  stylesPath: parsed.stylesPath,
49
50
  hydratePath: parsed.hydratePath,
51
+ preloads: parsed.preloads,
50
52
  sourceMaps: parsed.sourceMaps,
51
53
  },
52
54
  };
@@ -96,10 +98,12 @@ export async function verifyClientBuildAssets(
96
98
  const verdict = verifyManifestState(await readManifestState(outDir), outDir);
97
99
  if (!verdict.ok) return verdict;
98
100
  const manifest = verdict.manifest;
99
- for (const [label, url] of [
101
+ const named: Array<readonly [string, string]> = [
100
102
  ["stylesheet", manifest.stylesPath],
101
103
  ["hydrate bundle", manifest.hydratePath],
102
- ] as const) {
104
+ ];
105
+ for (const url of manifest.preloads ?? []) named.push(["preload chunk", url]);
106
+ for (const [label, url] of named) {
103
107
  const file = join(outDir, url.replace(/^\/assets\//, ""));
104
108
  if (!(await Bun.file(file).exists())) {
105
109
  return {
@@ -109,6 +109,7 @@ async function performBuild(
109
109
  await writeManifest(outDir, {
110
110
  stylesPath,
111
111
  hydratePath: hydrate.hydratePath,
112
+ preloads: hydrate.preloads,
112
113
  sourceMaps: hydrate.sourceMaps,
113
114
  });
114
115
 
@@ -11,6 +11,7 @@ import { join } from "node:path";
11
11
  interface TemplateManifest {
12
12
  stylesPath: string;
13
13
  hydratePath: string;
14
+ preloads: string[];
14
15
  }
15
16
 
16
17
  const PAGE_DOCUMENT = `<!doctype html>
@@ -22,6 +23,7 @@ const PAGE_DOCUMENT = `<!doctype html>
22
23
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
23
24
  <!--theme-fonts-->
24
25
  <link rel="stylesheet" href="{{stylesPath}}" />
26
+ <!--module-preloads-->
25
27
  <!--head-outlet-->
26
28
  </head>
27
29
  <body>
@@ -56,7 +58,11 @@ async function manifestMtimeMs(): Promise<number> {
56
58
  async function loadManifest(): Promise<TemplateManifest | null> {
57
59
  const manifest = await readManifest(join(process.cwd(), "dist", "client"));
58
60
  if (!manifest?.stylesPath || !manifest.hydratePath) return null;
59
- return { stylesPath: manifest.stylesPath, hydratePath: manifest.hydratePath };
61
+ return {
62
+ stylesPath: manifest.stylesPath,
63
+ hydratePath: manifest.hydratePath,
64
+ preloads: manifest.preloads ?? [],
65
+ };
60
66
  }
61
67
 
62
68
  function manifestCacheKey(): string {
@@ -78,6 +84,16 @@ export async function readHtmlTemplate(): Promise<string> {
78
84
  let html = PAGE_DOCUMENT
79
85
  .replace(/\{\{stylesPath\}\}/g, manifest.stylesPath)
80
86
  .replace(/\{\{hydratePath\}\}/g, manifest.hydratePath)
87
+ .replace(
88
+ "<!--module-preloads-->",
89
+ () =>
90
+ manifest.preloads
91
+ .map(
92
+ (url) =>
93
+ `<link rel="modulepreload" href="${url}" />`,
94
+ )
95
+ .join("\n\t\t"),
96
+ )
81
97
  .replace("<!--theme-fonts-->", () => themeFontsLink());
82
98
  htmlCache.set(manifestCacheKey(), { html, manifestMtime });
83
99
  return html;