evolit 0.1.0-alpha.0

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.
@@ -0,0 +1,1881 @@
1
+ import fs from "node:fs/promises";
2
+ import { existsSync, realpathSync } from "node:fs";
3
+ import crypto from "node:crypto";
4
+ import path from "node:path";
5
+ import { createRequire } from "node:module";
6
+ import MagicString from "magic-string";
7
+ import { rollup } from "rollup";
8
+ import { nodeResolve } from "@rollup/plugin-node-resolve";
9
+ import {
10
+ BUILD_DIRECTORY,
11
+ CLIENT_DIRECTORY,
12
+ CLIENT_ASSET_MANIFEST_VERSION,
13
+ DEV_DIRECTORY,
14
+ INTERNAL_DIRECTORY,
15
+ MODULE_EXTENSIONS,
16
+ SERVER_DIRECTORY,
17
+ SHARED_DIRECTORY,
18
+ STATIC_ASSET_EXTENSIONS,
19
+ STATIC_DIRECTORY,
20
+ } from "./constants.js";
21
+ import { ensureDirectory, walkFiles, writeJson } from "./fs-utils.js";
22
+
23
+ const MODULE_SPECIFIER_PATTERN =
24
+ /\b(?:import|export)\s*[^"']*?from\s*["']([^"']+)["']|\bimport\s*["']([^"']+)["']|\bimport\s*\(\s*["']([^"']+)["']\s*\)/g;
25
+
26
+ const requireFromHere = createRequire(import.meta.url);
27
+ const SHARED_VENDOR_SPECIFIERS = [
28
+ "@litsx/core",
29
+ "@litsx/core/elements",
30
+ "@litsx/ssr/hydration",
31
+ "lit",
32
+ ];
33
+ const browserSpecifierFilePathCache = new Map();
34
+ const packageRootCache = new Map();
35
+ const sharedRuntimeBuildCache = new Map();
36
+ const clientVendorSpecifierCache = new Map();
37
+ const clientModuleMetadataCache = new Map();
38
+
39
+ function isBareSpecifier(specifier) {
40
+ return (
41
+ typeof specifier === "string" &&
42
+ specifier.length > 0 &&
43
+ !specifier.startsWith(".") &&
44
+ !specifier.startsWith("/") &&
45
+ !specifier.startsWith("file:")
46
+ && !specifier.startsWith("node:")
47
+ && !specifier.startsWith("data:")
48
+ && !specifier.startsWith("http:")
49
+ && !specifier.startsWith("https:")
50
+ );
51
+ }
52
+
53
+ function isRelativeSpecifier(specifier) {
54
+ return (
55
+ typeof specifier === "string" &&
56
+ (specifier.startsWith("./") || specifier.startsWith("../"))
57
+ );
58
+ }
59
+
60
+ function parsePackageSpecifier(specifier) {
61
+ if (!isBareSpecifier(specifier)) {
62
+ return null;
63
+ }
64
+
65
+ const segments = specifier.split("/");
66
+ if (specifier.startsWith("@")) {
67
+ if (segments.length < 2) {
68
+ return null;
69
+ }
70
+
71
+ return {
72
+ packageName: `${segments[0]}/${segments[1]}`,
73
+ subpath: segments.slice(2).join("/"),
74
+ };
75
+ }
76
+
77
+ return {
78
+ packageName: segments[0],
79
+ subpath: segments.slice(1).join("/"),
80
+ };
81
+ }
82
+
83
+ export async function resolvePackageRoot(packageName) {
84
+ if (packageRootCache.has(packageName)) {
85
+ return packageRootCache.get(packageName);
86
+ }
87
+
88
+ const pendingResolution = (async () => {
89
+ const packageEntryPath = requireFromHere.resolve(packageName);
90
+ let currentPath = path.dirname(packageEntryPath);
91
+
92
+ while (true) {
93
+ const packageJsonPath = path.join(currentPath, "package.json");
94
+
95
+ try {
96
+ const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf8"));
97
+ if (packageJson?.name === packageName) {
98
+ return { packageRoot: currentPath, packageJson };
99
+ }
100
+ } catch {
101
+ // Keep walking up until we find the owning package root.
102
+ }
103
+
104
+ const parentPath = path.dirname(currentPath);
105
+ if (parentPath === currentPath) {
106
+ break;
107
+ }
108
+
109
+ currentPath = parentPath;
110
+ }
111
+
112
+ throw new Error(`Unable to resolve package root for ${packageName}`);
113
+ })();
114
+
115
+ packageRootCache.set(packageName, pendingResolution);
116
+
117
+ try {
118
+ return await pendingResolution;
119
+ } catch (error) {
120
+ packageRootCache.delete(packageName);
121
+ throw error;
122
+ }
123
+ }
124
+
125
+ function pickBrowserExportTarget(target) {
126
+ if (typeof target === "string") {
127
+ return target;
128
+ }
129
+
130
+ if (!target || typeof target !== "object") {
131
+ return null;
132
+ }
133
+
134
+ return pickBrowserExportTarget(target.browser)
135
+ ?? pickBrowserExportTarget(target.import)
136
+ ?? pickBrowserExportTarget(target.default)
137
+ ?? pickBrowserExportTarget(target.module)
138
+ ?? pickBrowserExportTarget(target.require)
139
+ ?? null;
140
+ }
141
+
142
+ export async function resolveBrowserSpecifierFilePath(specifier) {
143
+ if (!isBareSpecifier(specifier)) {
144
+ return null;
145
+ }
146
+
147
+ if (browserSpecifierFilePathCache.has(specifier)) {
148
+ return browserSpecifierFilePathCache.get(specifier);
149
+ }
150
+
151
+ const pendingResolution = (async () => {
152
+ const parsedSpecifier = parsePackageSpecifier(specifier);
153
+ if (!parsedSpecifier) {
154
+ throw new Error(`Unsupported specifier: ${specifier}`);
155
+ }
156
+
157
+ const { packageName, subpath } = parsedSpecifier;
158
+ const { packageRoot, packageJson } = await resolvePackageRoot(packageName);
159
+ const exportKey = subpath.length > 0 ? `./${subpath}` : ".";
160
+ const exportTarget = pickBrowserExportTarget(packageJson.exports?.[exportKey]);
161
+ const fallbackTarget = subpath.length > 0
162
+ ? `./${subpath}`
163
+ : packageJson.module ?? packageJson.main ?? null;
164
+ const resolvedTarget = exportTarget ?? fallbackTarget;
165
+
166
+ if (typeof resolvedTarget !== "string" || resolvedTarget.length === 0) {
167
+ throw new Error(`Unable to resolve browser entry for ${specifier}`);
168
+ }
169
+
170
+ return path.resolve(packageRoot, resolvedTarget);
171
+ })();
172
+
173
+ browserSpecifierFilePathCache.set(specifier, pendingResolution);
174
+
175
+ try {
176
+ return await pendingResolution;
177
+ } catch (error) {
178
+ browserSpecifierFilePathCache.delete(specifier);
179
+ throw error;
180
+ }
181
+ }
182
+
183
+ export function getSharedOutputRoot(projectRoot, mode) {
184
+ return path.join(
185
+ projectRoot,
186
+ INTERNAL_DIRECTORY,
187
+ mode === "development" ? DEV_DIRECTORY : BUILD_DIRECTORY,
188
+ SHARED_DIRECTORY,
189
+ );
190
+ }
191
+
192
+ function createSharedEntryName(specifier) {
193
+ return specifier
194
+ .replaceAll("@", "")
195
+ .replaceAll("/", "__")
196
+ .replaceAll(".", "_");
197
+ }
198
+
199
+ function createClientEntryName(clientModule) {
200
+ return clientModule.endsWith(".mjs")
201
+ ? clientModule.slice(0, -".mjs".length)
202
+ : clientModule;
203
+ }
204
+
205
+ function createSharedEntrySource(specifier) {
206
+ if (specifier === "@litsx/ssr/hydration") {
207
+ return [
208
+ 'import "@lit-labs/ssr-client/lit-element-hydrate-support.js";',
209
+ `export * from ${JSON.stringify(specifier)};`,
210
+ "",
211
+ ].join("\n");
212
+ }
213
+
214
+ return [
215
+ `export * from ${JSON.stringify(specifier)};`,
216
+ "",
217
+ ].join("\n");
218
+ }
219
+
220
+ export async function collectSharedVendorSpecifiers(additionalEntrySpecifiers = []) {
221
+ return [...new Set([
222
+ ...SHARED_VENDOR_SPECIFIERS,
223
+ ...additionalEntrySpecifiers.filter((specifier) => isBareSpecifier(specifier)),
224
+ ])].sort();
225
+ }
226
+
227
+ async function writeSharedRuntimeEntrySources(entriesRoot, specifiers) {
228
+ await fs.rm(entriesRoot, { recursive: true, force: true });
229
+ await ensureDirectory(entriesRoot);
230
+
231
+ const inputEntries = {};
232
+ for (const specifier of specifiers) {
233
+ const entryName = createSharedEntryName(specifier);
234
+ const entryPath = path.join(entriesRoot, `${entryName}.mjs`);
235
+ await fs.writeFile(entryPath, createSharedEntrySource(specifier), "utf8");
236
+ inputEntries[entryName] = entryPath;
237
+ }
238
+
239
+ return inputEntries;
240
+ }
241
+
242
+ function createSharedChunkGroup(id) {
243
+ const normalizedId = String(id).split(path.sep).join("/");
244
+
245
+ if (!normalizedId.includes("/node_modules/")) {
246
+ return undefined;
247
+ }
248
+
249
+ if (normalizedId.includes("/node_modules/@lit-labs/ssr-client/")) {
250
+ return "vendor-hydration-support";
251
+ }
252
+
253
+ if (
254
+ normalizedId.includes("/node_modules/lit-html/")
255
+ || normalizedId.includes("/node_modules/lit/directive")
256
+ || normalizedId.includes("/node_modules/lit/html.js")
257
+ ) {
258
+ return "vendor-lit-html";
259
+ }
260
+
261
+ if (
262
+ normalizedId.includes("/node_modules/lit/index.js")
263
+ || normalizedId.includes("/node_modules/lit-element/")
264
+ || normalizedId.includes("/node_modules/@lit/")
265
+ ) {
266
+ return "vendor-lit";
267
+ }
268
+
269
+ if (
270
+ normalizedId.includes("/node_modules/@litsx/")
271
+ ) {
272
+ return "vendor-litsx";
273
+ }
274
+
275
+ return "vendor-misc";
276
+ }
277
+
278
+ function isClientAssetStubModule(relativePath) {
279
+ if (!relativePath.endsWith(".mjs")) {
280
+ return false;
281
+ }
282
+
283
+ const originalPath = relativePath.slice(0, -".mjs".length);
284
+ return STATIC_ASSET_EXTENSIONS.some((extension) => originalPath.endsWith(extension));
285
+ }
286
+
287
+ function resolveOriginalSourcePath(projectRoot, compiledClientRelativePath) {
288
+ if (typeof compiledClientRelativePath !== "string" || !compiledClientRelativePath.endsWith(".mjs")) {
289
+ return null;
290
+ }
291
+
292
+ const sourceBaseRelativePath = compiledClientRelativePath.slice(0, -".mjs".length);
293
+ for (const extension of [...MODULE_EXTENSIONS, ...STATIC_ASSET_EXTENSIONS]) {
294
+ const candidateRelativePath = `${sourceBaseRelativePath}${extension}`;
295
+ const candidatePath = path.join(projectRoot, candidateRelativePath);
296
+ if (existsSync(candidatePath)) {
297
+ return `/${candidateRelativePath.split(path.sep).join("/")}`;
298
+ }
299
+ }
300
+
301
+ return `/${compiledClientRelativePath.split(path.sep).join("/")}`;
302
+ }
303
+
304
+ function createStaticSourceMapPathTransform(projectRoot, clientRoot) {
305
+ const normalizedProjectRoot = existsSync(projectRoot)
306
+ ? realpathSync(projectRoot)
307
+ : projectRoot;
308
+ const normalizedClientRoot = existsSync(clientRoot)
309
+ ? realpathSync(clientRoot)
310
+ : clientRoot;
311
+
312
+ return function sourcemapPathTransform(relativeSourcePath, sourcemapPath) {
313
+ const absoluteSourcePath = path.resolve(path.dirname(sourcemapPath), relativeSourcePath);
314
+ const normalizedAbsoluteSourcePath = existsSync(absoluteSourcePath)
315
+ ? realpathSync(absoluteSourcePath)
316
+ : absoluteSourcePath;
317
+ const clientRelativePath = path.relative(normalizedClientRoot, normalizedAbsoluteSourcePath);
318
+ const isCompiledClientModule = clientRelativePath.length === 0 || (
319
+ !clientRelativePath.startsWith(`..${path.sep}`)
320
+ && clientRelativePath !== ".."
321
+ && !path.isAbsolute(clientRelativePath)
322
+ );
323
+ if (isCompiledClientModule) {
324
+ const compiledClientRelativePath = clientRelativePath.split(path.sep).join("/");
325
+ if (isClientAssetStubModule(compiledClientRelativePath)) {
326
+ return `/${compiledClientRelativePath.slice(0, -".mjs".length)}`;
327
+ }
328
+
329
+ return resolveOriginalSourcePath(projectRoot, compiledClientRelativePath) ?? relativeSourcePath;
330
+ }
331
+
332
+ const projectRelativePath = path.relative(normalizedProjectRoot, normalizedAbsoluteSourcePath);
333
+ const isProjectSource = projectRelativePath.length > 0
334
+ && !projectRelativePath.startsWith(`..${path.sep}`)
335
+ && projectRelativePath !== ".."
336
+ && !path.isAbsolute(projectRelativePath);
337
+ if (isProjectSource) {
338
+ return `/${projectRelativePath.split(path.sep).join("/")}`;
339
+ }
340
+
341
+ return relativeSourcePath;
342
+ };
343
+ }
344
+
345
+ function resolveProjectSourcePath(projectRoot, sourcePath) {
346
+ if (typeof sourcePath !== "string" || !sourcePath.startsWith("/")) {
347
+ return null;
348
+ }
349
+
350
+ const relativeSourcePath = sourcePath.slice(1).split("/").join(path.sep);
351
+ const absoluteSourcePath = path.join(projectRoot, relativeSourcePath);
352
+
353
+ return existsSync(absoluteSourcePath) ? absoluteSourcePath : null;
354
+ }
355
+
356
+ async function rewriteEmittedStaticSourceMaps(projectRoot, clientRoot, staticRoot) {
357
+ const transformSourcePath = createStaticSourceMapPathTransform(projectRoot, clientRoot);
358
+ const staticFiles = await walkFiles(staticRoot);
359
+
360
+ for (const filePath of staticFiles) {
361
+ if (!filePath.endsWith(".map")) {
362
+ continue;
363
+ }
364
+
365
+ let sourceMap;
366
+ try {
367
+ sourceMap = JSON.parse(await fs.readFile(filePath, "utf8"));
368
+ } catch {
369
+ continue;
370
+ }
371
+
372
+ if (!Array.isArray(sourceMap.sources) || sourceMap.sources.length === 0) {
373
+ continue;
374
+ }
375
+
376
+ const rewrittenSources = sourceMap.sources.map((sourcePath) => transformSourcePath(sourcePath, filePath));
377
+ const rewrittenSourcesContent = await Promise.all(
378
+ rewrittenSources.map(async (sourcePath, index) => {
379
+ const resolvedSourcePath = resolveProjectSourcePath(projectRoot, sourcePath);
380
+ if (!resolvedSourcePath) {
381
+ return Array.isArray(sourceMap.sourcesContent) ? sourceMap.sourcesContent[index] ?? null : null;
382
+ }
383
+
384
+ try {
385
+ return await fs.readFile(resolvedSourcePath, "utf8");
386
+ } catch {
387
+ return Array.isArray(sourceMap.sourcesContent) ? sourceMap.sourcesContent[index] ?? null : null;
388
+ }
389
+ }),
390
+ );
391
+ const didChangeSources = rewrittenSources.some((sourcePath, index) => sourcePath !== sourceMap.sources[index]);
392
+ const existingSourcesContent = Array.isArray(sourceMap.sourcesContent) ? sourceMap.sourcesContent : [];
393
+ const didChangeSourcesContent = rewrittenSourcesContent.some(
394
+ (sourceContent, index) => sourceContent !== (existingSourcesContent[index] ?? null),
395
+ );
396
+
397
+ if (!didChangeSources && !didChangeSourcesContent) {
398
+ continue;
399
+ }
400
+
401
+ sourceMap.sources = rewrittenSources;
402
+ sourceMap.sourcesContent = rewrittenSourcesContent;
403
+ await fs.writeFile(filePath, `${JSON.stringify(sourceMap)}\n`, "utf8");
404
+ }
405
+ }
406
+
407
+ export async function buildSharedVendorRuntime(
408
+ projectRoot = process.cwd(),
409
+ mode = "development",
410
+ options = {},
411
+ ) {
412
+ const additionalEntrySpecifiers = [...new Set(
413
+ Array.isArray(options.additionalEntrySpecifiers)
414
+ ? options.additionalEntrySpecifiers.filter((specifier) => isBareSpecifier(specifier))
415
+ : [],
416
+ )].sort();
417
+ const cacheKey = `${projectRoot}::${mode}::${additionalEntrySpecifiers.join("|")}`;
418
+ if (sharedRuntimeBuildCache.has(cacheKey)) {
419
+ return sharedRuntimeBuildCache.get(cacheKey);
420
+ }
421
+
422
+ const pendingBuild = (async () => {
423
+ const sharedRoot = getSharedOutputRoot(projectRoot, mode);
424
+ const entriesRoot = path.join(
425
+ projectRoot,
426
+ INTERNAL_DIRECTORY,
427
+ mode === "development" ? DEV_DIRECTORY : BUILD_DIRECTORY,
428
+ "__shared_entries__",
429
+ );
430
+ const specifiers = await collectSharedVendorSpecifiers(additionalEntrySpecifiers);
431
+ const inputEntries = await writeSharedRuntimeEntrySources(entriesRoot, specifiers);
432
+
433
+ await fs.rm(sharedRoot, { recursive: true, force: true });
434
+ await ensureDirectory(sharedRoot);
435
+
436
+ const specifierByEntryName = new Map(
437
+ specifiers.map((specifier) => [createSharedEntryName(specifier), specifier]),
438
+ );
439
+ const bundle = await rollup({
440
+ input: inputEntries,
441
+ plugins: [
442
+ nodeResolve({
443
+ browser: true,
444
+ exportConditions: ["browser", "import", "default"],
445
+ extensions: [".mjs", ".js", ".json"],
446
+ preferBuiltins: false,
447
+ }),
448
+ ],
449
+ onwarn(warning, warn) {
450
+ if (warning.code === "CIRCULAR_DEPENDENCY") {
451
+ return;
452
+ }
453
+
454
+ warn(warning);
455
+ },
456
+ });
457
+
458
+ try {
459
+ const output = await bundle.write({
460
+ dir: sharedRoot,
461
+ format: "esm",
462
+ sourcemap: mode === "development",
463
+ entryFileNames: mode === "development"
464
+ ? "[name].mjs"
465
+ : "[name]-[hash].mjs",
466
+ chunkFileNames: mode === "development"
467
+ ? "chunks/[name].mjs"
468
+ : "chunks/[name]-[hash].mjs",
469
+ manualChunks(id) {
470
+ return createSharedChunkGroup(id);
471
+ },
472
+ });
473
+
474
+ const imports = {};
475
+ for (const chunk of output.output) {
476
+ if (chunk.type !== "chunk" || !chunk.isEntry) {
477
+ continue;
478
+ }
479
+
480
+ const specifier = specifierByEntryName.get(chunk.name);
481
+ if (!specifier) {
482
+ continue;
483
+ }
484
+
485
+ imports[specifier] = `/_evolit/shared/${chunk.fileName}`;
486
+ }
487
+
488
+ return {
489
+ imports,
490
+ outputRoot: sharedRoot,
491
+ };
492
+ } finally {
493
+ await bundle.close();
494
+ }
495
+ })();
496
+
497
+ sharedRuntimeBuildCache.set(cacheKey, pendingBuild);
498
+
499
+ try {
500
+ return await pendingBuild;
501
+ } catch (error) {
502
+ sharedRuntimeBuildCache.delete(cacheKey);
503
+ throw error;
504
+ }
505
+ }
506
+
507
+ export async function createBrowserSpecifierPublicUrl(specifier) {
508
+ const parsedSpecifier = parsePackageSpecifier(specifier);
509
+ if (!parsedSpecifier) {
510
+ return null;
511
+ }
512
+
513
+ const [{ packageRoot }, filePath] = await Promise.all([
514
+ resolvePackageRoot(parsedSpecifier.packageName),
515
+ resolveBrowserSpecifierFilePath(specifier),
516
+ ]);
517
+ const relativeFilePath = path.relative(packageRoot, filePath).split(path.sep).join("/");
518
+
519
+ return `/_evolit/pkg/${encodePathForUrl(parsedSpecifier.packageName)}/${encodePathForUrl(relativeFilePath)}`;
520
+ }
521
+
522
+ export function createBrowserPackageBaseUrl(packageName) {
523
+ return `/_evolit/pkg/${encodePathForUrl(packageName)}/`;
524
+ }
525
+
526
+ export async function resolveBrowserPackageAssetFilePath(pathname) {
527
+ if (!pathname.startsWith("/_evolit/pkg/")) {
528
+ return null;
529
+ }
530
+
531
+ const decodedPath = decodeURIComponent(pathname.slice("/_evolit/pkg/".length));
532
+ const segments = decodedPath.split("/").filter(Boolean);
533
+ if (segments.length < 2) {
534
+ return null;
535
+ }
536
+
537
+ const packageName = decodedPath.startsWith("@")
538
+ ? `${segments[0]}/${segments[1]}`
539
+ : segments[0];
540
+ const relativeFilePath = decodedPath.startsWith("@")
541
+ ? segments.slice(2).join("/")
542
+ : segments.slice(1).join("/");
543
+
544
+ if (relativeFilePath.length === 0) {
545
+ return resolveBrowserSpecifierFilePath(packageName);
546
+ }
547
+
548
+ const packageSubpathSpecifier = `${packageName}/${relativeFilePath}`;
549
+ try {
550
+ return await resolveBrowserSpecifierFilePath(packageSubpathSpecifier);
551
+ } catch {
552
+ // Fall back to direct package-relative files for explicit asset paths.
553
+ }
554
+
555
+ const { packageRoot } = await resolvePackageRoot(packageName);
556
+ return path.resolve(packageRoot, relativeFilePath);
557
+ }
558
+
559
+ async function resolveRelativeImportFilePath(fromFilePath, specifier) {
560
+ if (!isRelativeSpecifier(specifier)) {
561
+ return null;
562
+ }
563
+
564
+ const candidatePath = path.resolve(path.dirname(fromFilePath), specifier);
565
+ const candidatePaths = [
566
+ candidatePath,
567
+ `${candidatePath}.js`,
568
+ `${candidatePath}.mjs`,
569
+ path.join(candidatePath, "index.js"),
570
+ path.join(candidatePath, "index.mjs"),
571
+ ];
572
+
573
+ for (const nextPath of candidatePaths) {
574
+ try {
575
+ const stats = await fs.stat(nextPath);
576
+ if (stats.isFile()) {
577
+ return nextPath;
578
+ }
579
+ } catch {
580
+ // Keep probing plausible ESM file variants.
581
+ }
582
+ }
583
+
584
+ return null;
585
+ }
586
+
587
+ async function collectImportsFromFile(filePath, specifiers, visitedFiles) {
588
+ if (visitedFiles.has(filePath)) {
589
+ return;
590
+ }
591
+
592
+ visitedFiles.add(filePath);
593
+
594
+ let source = "";
595
+ try {
596
+ source = await fs.readFile(filePath, "utf8");
597
+ } catch {
598
+ return;
599
+ }
600
+
601
+ for (const match of source.matchAll(MODULE_SPECIFIER_PATTERN)) {
602
+ const candidate = match[1] ?? match[2] ?? match[3] ?? null;
603
+ if (isBareSpecifier(candidate)) {
604
+ await collectBareImports(candidate, specifiers, visitedFiles);
605
+ continue;
606
+ }
607
+
608
+ if (isRelativeSpecifier(candidate)) {
609
+ const relativeImportFilePath = await resolveRelativeImportFilePath(filePath, candidate);
610
+ if (relativeImportFilePath) {
611
+ await collectImportsFromFile(relativeImportFilePath, specifiers, visitedFiles);
612
+ }
613
+ }
614
+ }
615
+ }
616
+
617
+ async function collectBareImports(entrySpecifier, specifiers, visitedFiles) {
618
+ if (!isBareSpecifier(entrySpecifier)) {
619
+ return;
620
+ }
621
+
622
+ if (specifiers.has(entrySpecifier)) {
623
+ return;
624
+ }
625
+
626
+ specifiers.set(
627
+ entrySpecifier,
628
+ await resolveBrowserSpecifierFilePath(entrySpecifier),
629
+ );
630
+
631
+ const resolvedFile = specifiers.get(entrySpecifier);
632
+ await collectImportsFromFile(resolvedFile, specifiers, visitedFiles);
633
+ }
634
+
635
+ export function getClientModuleFilePath(projectRoot, mode, clientModule) {
636
+ return path.join(
637
+ getClientOutputRoot(projectRoot, mode),
638
+ clientModule.split("/").join(path.sep),
639
+ );
640
+ }
641
+
642
+ async function readClientModuleMetadata(projectRoot, mode, clientModule) {
643
+ const cacheKey = `${projectRoot}::${mode}::${clientModule}`;
644
+ if (clientModuleMetadataCache.has(cacheKey)) {
645
+ return clientModuleMetadataCache.get(cacheKey);
646
+ }
647
+
648
+ const pendingRead = (async () => {
649
+ const filePath = getClientModuleFilePath(projectRoot, mode, clientModule);
650
+ const metadata = JSON.parse(await fs.readFile(`${filePath}.meta.json`, "utf8"));
651
+ return {
652
+ moduleImports: Array.isArray(metadata?.moduleImports) ? metadata.moduleImports : [],
653
+ vendorImports: Array.isArray(metadata?.vendorImports) ? metadata.vendorImports : [],
654
+ styleImports: Array.isArray(metadata?.styleImports) ? metadata.styleImports : [],
655
+ assetImports: Array.isArray(metadata?.assetImports) ? metadata.assetImports : [],
656
+ };
657
+ })();
658
+
659
+ clientModuleMetadataCache.set(cacheKey, pendingRead);
660
+
661
+ try {
662
+ return await pendingRead;
663
+ } catch (error) {
664
+ clientModuleMetadataCache.delete(cacheKey);
665
+ throw error;
666
+ }
667
+ }
668
+
669
+ async function collectTransitiveClientModuleMetadata(projectRoot, mode, clientModule) {
670
+ const visitedModules = new Set();
671
+ const pendingModules = [clientModule];
672
+ const styleImports = new Set();
673
+ const assetImports = new Set();
674
+
675
+ while (pendingModules.length > 0) {
676
+ const currentModule = pendingModules.shift();
677
+ if (!currentModule || visitedModules.has(currentModule)) {
678
+ continue;
679
+ }
680
+
681
+ visitedModules.add(currentModule);
682
+
683
+ let metadata;
684
+ try {
685
+ metadata = await readClientModuleMetadata(projectRoot, mode, currentModule);
686
+ } catch {
687
+ continue;
688
+ }
689
+
690
+ for (const styleImport of metadata.styleImports) {
691
+ styleImports.add(styleImport);
692
+ }
693
+ for (const assetImport of metadata.assetImports) {
694
+ assetImports.add(assetImport);
695
+ }
696
+ for (const importedModule of metadata.moduleImports) {
697
+ pendingModules.push(importedModule);
698
+ }
699
+ }
700
+
701
+ return {
702
+ styleImports: [...styleImports].sort(),
703
+ assetImports: [...assetImports].sort(),
704
+ };
705
+ }
706
+
707
+ function getClientModuleRelativePathFromPublicUrl(publicUrl, assetManifest) {
708
+ if (typeof publicUrl !== "string") {
709
+ return null;
710
+ }
711
+
712
+ const manifestAsset = getAssetByPublicUrl(assetManifest, publicUrl);
713
+ return manifestAsset?.clientModule ?? null;
714
+ }
715
+
716
+ export function resolveClientEntryModules(clientPublicUrls, assetManifest) {
717
+ return [...new Set(
718
+ (Array.isArray(clientPublicUrls) ? clientPublicUrls : [])
719
+ .map((publicUrl) => getClientModuleRelativePathFromPublicUrl(publicUrl, assetManifest))
720
+ .filter((clientModule) => typeof clientModule === "string" && clientModule.length > 0),
721
+ )].sort();
722
+ }
723
+
724
+ export async function collectClientVendorSpecifiers(
725
+ projectRoot,
726
+ clientPublicUrls,
727
+ options = {},
728
+ ) {
729
+ const mode = options.mode ?? "development";
730
+ const assetManifest = normalizeClientAssetManifest(options.assetManifest);
731
+ const entryClientModules = Array.isArray(options.entryClientModules)
732
+ ? [...new Set(options.entryClientModules)].sort()
733
+ : resolveClientEntryModules(clientPublicUrls, assetManifest);
734
+ const cacheKey = `${projectRoot}::${mode}::${entryClientModules.join("|")}`;
735
+ if (clientVendorSpecifierCache.has(cacheKey)) {
736
+ return clientVendorSpecifierCache.get(cacheKey);
737
+ }
738
+
739
+ const pendingCollection = (async () => {
740
+ const visitedClientModules = new Set();
741
+ const pendingClientModules = [...entryClientModules];
742
+ const vendorSpecifiers = new Set();
743
+
744
+ while (pendingClientModules.length > 0) {
745
+ const currentClientModule = pendingClientModules.shift();
746
+ if (!currentClientModule || visitedClientModules.has(currentClientModule)) {
747
+ continue;
748
+ }
749
+
750
+ visitedClientModules.add(currentClientModule);
751
+ const filePath = getClientModuleFilePath(projectRoot, mode, currentClientModule);
752
+
753
+ try {
754
+ const metadata = JSON.parse(await fs.readFile(`${filePath}.meta.json`, "utf8"));
755
+ for (const vendorImport of Array.isArray(metadata?.vendorImports) ? metadata.vendorImports : []) {
756
+ if (isBareSpecifier(vendorImport)) {
757
+ vendorSpecifiers.add(vendorImport);
758
+ }
759
+ }
760
+ for (const importedModule of Array.isArray(metadata?.moduleImports) ? metadata.moduleImports : []) {
761
+ if (typeof importedModule === "string" && importedModule.length > 0) {
762
+ pendingClientModules.push(importedModule);
763
+ }
764
+ }
765
+ continue;
766
+ } catch {
767
+ // Fall back to parsing the compiled module when metadata is missing.
768
+ }
769
+
770
+ let source = "";
771
+ try {
772
+ source = await fs.readFile(filePath, "utf8");
773
+ } catch {
774
+ continue;
775
+ }
776
+
777
+ for (const match of source.matchAll(MODULE_SPECIFIER_PATTERN)) {
778
+ const specifier = match[1] ?? match[2] ?? match[3] ?? null;
779
+ if (isBareSpecifier(specifier)) {
780
+ vendorSpecifiers.add(specifier);
781
+ continue;
782
+ }
783
+
784
+ if (!isRelativeSpecifier(specifier)) {
785
+ continue;
786
+ }
787
+
788
+ const resolvedRelativePath = resolveRelativeClientImportPath(currentClientModule, specifier);
789
+ pendingClientModules.push(resolvedRelativePath);
790
+ }
791
+ }
792
+
793
+ return [...vendorSpecifiers].sort();
794
+ })();
795
+
796
+ clientVendorSpecifierCache.set(cacheKey, pendingCollection);
797
+
798
+ try {
799
+ return await pendingCollection;
800
+ } catch (error) {
801
+ clientVendorSpecifierCache.delete(cacheKey);
802
+ throw error;
803
+ }
804
+ }
805
+
806
+ export async function resolveSharedVendorModuleUrl(
807
+ projectRoot = process.cwd(),
808
+ mode = "development",
809
+ specifier,
810
+ options = {},
811
+ ) {
812
+ const assetManifest = normalizeClientAssetManifest(options.assetManifest);
813
+ const entryClientModules = Array.isArray(options.entryClientModules)
814
+ ? [...new Set(options.entryClientModules)].sort()
815
+ : resolveClientEntryModules(options.clientPublicUrls, assetManifest);
816
+ const additionalEntrySpecifiers = [
817
+ ...(Array.isArray(options.additionalEntrySpecifiers) ? options.additionalEntrySpecifiers : []),
818
+ ...await collectClientVendorSpecifiers(projectRoot, options.clientPublicUrls, {
819
+ mode,
820
+ assetManifest,
821
+ entryClientModules,
822
+ }),
823
+ ];
824
+ const sharedRuntime = await buildSharedVendorRuntime(projectRoot, mode, {
825
+ additionalEntrySpecifiers,
826
+ });
827
+ return sharedRuntime.imports[specifier] ?? null;
828
+ }
829
+
830
+ export function getClientOutputRoot(projectRoot, mode) {
831
+ return path.join(
832
+ projectRoot,
833
+ INTERNAL_DIRECTORY,
834
+ mode === "development" ? DEV_DIRECTORY : BUILD_DIRECTORY,
835
+ CLIENT_DIRECTORY,
836
+ );
837
+ }
838
+
839
+ export function toClientModuleRelativePath(projectRoot, sourcePath) {
840
+ const relativePath = path.relative(projectRoot, sourcePath);
841
+ return toCompiledClientModuleRelativePath(relativePath);
842
+ }
843
+
844
+ function toCompiledClientModuleRelativePath(relativePath) {
845
+ const extension = path.extname(relativePath);
846
+ return extension
847
+ ? `${relativePath.slice(0, -extension.length)}.mjs`
848
+ : `${relativePath}.mjs`;
849
+ }
850
+
851
+ function toPublicHydrationModuleId(projectRoot, moduleId) {
852
+ if (typeof moduleId !== "string" || moduleId.length === 0) {
853
+ return null;
854
+ }
855
+
856
+ if (path.isAbsolute(moduleId)) {
857
+ if (!moduleId.startsWith(projectRoot)) {
858
+ return null;
859
+ }
860
+
861
+ const relativePath = path.relative(projectRoot, moduleId).split(path.sep).join("/");
862
+ return `/${relativePath}`;
863
+ }
864
+
865
+ if (moduleId.startsWith("/")) {
866
+ return moduleId;
867
+ }
868
+
869
+ return `/${moduleId}`;
870
+ }
871
+
872
+ export function getStaticOutputRoot(projectRoot) {
873
+ return getModeStaticOutputRoot(projectRoot, "production");
874
+ }
875
+
876
+ export function getModeStaticOutputRoot(projectRoot, mode = "production") {
877
+ return path.join(
878
+ projectRoot,
879
+ INTERNAL_DIRECTORY,
880
+ mode === "development" ? DEV_DIRECTORY : BUILD_DIRECTORY,
881
+ STATIC_DIRECTORY,
882
+ );
883
+ }
884
+
885
+ function getServerOutputRoot(projectRoot) {
886
+ return path.join(
887
+ projectRoot,
888
+ INTERNAL_DIRECTORY,
889
+ BUILD_DIRECTORY,
890
+ SERVER_DIRECTORY,
891
+ );
892
+ }
893
+
894
+ export function createAssetResolver(projectRoot, options = {}) {
895
+ const assetManifest = normalizeClientAssetManifest(options.assetManifest);
896
+
897
+ return function assetResolver(moduleId) {
898
+ if (typeof moduleId !== "string" || moduleId.length === 0) {
899
+ return null;
900
+ }
901
+
902
+ let relativeClientModule = null;
903
+ if (moduleId.startsWith("/") && !moduleId.startsWith(projectRoot)) {
904
+ relativeClientModule = toCompiledClientModuleRelativePath(moduleId.slice(1));
905
+ } else if (path.isAbsolute(moduleId)) {
906
+ if (!moduleId.startsWith(projectRoot)) {
907
+ return null;
908
+ }
909
+
910
+ relativeClientModule = toClientModuleRelativePath(projectRoot, moduleId);
911
+ }
912
+
913
+ if (!relativeClientModule) {
914
+ return null;
915
+ }
916
+
917
+ if (assetManifest) {
918
+ return getAssetByClientModule(
919
+ assetManifest,
920
+ relativeClientModule,
921
+ )?.publicUrl ?? null;
922
+ }
923
+
924
+ return null;
925
+ };
926
+ }
927
+
928
+ function escapeInlineScriptText(value) {
929
+ return String(value)
930
+ .replaceAll("<", "\\u003C");
931
+ }
932
+
933
+ function escapeJsonScriptText(value) {
934
+ return JSON.stringify(value)
935
+ .replaceAll("<", "\\u003C")
936
+ .replaceAll(">", "\\u003E")
937
+ .replaceAll("&", "\\u0026");
938
+ }
939
+
940
+ export function createHydrationBootstrap({
941
+ hydrationData,
942
+ assetResolver,
943
+ hydrationModuleUrl = "@litsx/ssr/hydration",
944
+ }) {
945
+ const normalizedHydrationData = normalizeHydrationDataForClient(hydrationData);
946
+ const moduleUrls = [...new Set(
947
+ (normalizedHydrationData?.roots ?? [])
948
+ .map((root) => {
949
+ if (typeof root?.moduleId !== "string" || root.moduleId.length === 0) {
950
+ return null;
951
+ }
952
+
953
+ return assetResolver?.(root.moduleId) ?? null;
954
+ })
955
+ .filter((moduleUrl) => typeof moduleUrl === "string" && moduleUrl.length > 0),
956
+ )];
957
+
958
+ if (moduleUrls.length === 0) {
959
+ return "";
960
+ }
961
+
962
+ const moduleLoaders = moduleUrls
963
+ .map((moduleUrl) => ` () => import(${JSON.stringify(moduleUrl)})`)
964
+ .join(",\n");
965
+
966
+ const source = `
967
+ import { hydratePage, registerHydrationModules } from ${JSON.stringify(hydrationModuleUrl)};
968
+
969
+ await hydratePage({
970
+ clientImports: [],
971
+ register: () => registerHydrationModules([
972
+ ${moduleLoaders}
973
+ ])
974
+ });
975
+ `;
976
+
977
+ return escapeInlineScriptText(source);
978
+ }
979
+
980
+ export function normalizeHydrationDataForClient(hydrationData, projectRoot = null) {
981
+ if (!hydrationData || typeof hydrationData !== "object") {
982
+ return hydrationData ?? null;
983
+ }
984
+
985
+ const roots = Array.isArray(hydrationData.roots)
986
+ ? hydrationData.roots.map((root) => {
987
+ if (!root || typeof root !== "object") {
988
+ return root;
989
+ }
990
+
991
+ const nextRoot = { ...root };
992
+ if (typeof nextRoot.moduleId === "string") {
993
+ const normalizedModuleId = projectRoot
994
+ ? toPublicHydrationModuleId(projectRoot, nextRoot.moduleId)
995
+ : nextRoot.moduleId;
996
+
997
+ if (typeof normalizedModuleId === "string" && normalizedModuleId.length > 0) {
998
+ nextRoot.moduleId = normalizedModuleId;
999
+ } else {
1000
+ delete nextRoot.moduleId;
1001
+ }
1002
+ }
1003
+
1004
+ return nextRoot;
1005
+ })
1006
+ : hydrationData.roots;
1007
+
1008
+ const normalizedHydrationData = {
1009
+ version: hydrationData.version,
1010
+ roots,
1011
+ };
1012
+ const payload = hydrationData.payload;
1013
+ const clientImports = hydrationData.clientImports;
1014
+
1015
+ Object.defineProperties(normalizedHydrationData, {
1016
+ payload: {
1017
+ enumerable: false,
1018
+ value: payload,
1019
+ },
1020
+ clientImports: {
1021
+ enumerable: false,
1022
+ value: clientImports,
1023
+ },
1024
+ toJSON: {
1025
+ enumerable: false,
1026
+ value() {
1027
+ return {
1028
+ version: normalizedHydrationData.version,
1029
+ roots: normalizedHydrationData.roots,
1030
+ ...(payload !== undefined ? { payload } : {}),
1031
+ ...(clientImports !== undefined ? { clientImports } : {}),
1032
+ };
1033
+ },
1034
+ },
1035
+ });
1036
+
1037
+ return normalizedHydrationData;
1038
+ }
1039
+
1040
+ export function rewriteHydrationDataScript(documentHtml, hydrationData) {
1041
+ if (typeof documentHtml !== "string" || documentHtml.length === 0 || !hydrationData) {
1042
+ return documentHtml;
1043
+ }
1044
+
1045
+ return documentHtml.replace(
1046
+ /(<script type="application\/json" id="__LITSX_HYDRATION__">)([\s\S]*?)(<\/script>)/,
1047
+ (_, prefix, __content, suffix) => `${prefix}${escapeJsonScriptText(hydrationData)}${suffix}`,
1048
+ );
1049
+ }
1050
+
1051
+ function createHashedModulePath(relativePath, hash) {
1052
+ const extension = path.extname(relativePath);
1053
+ if (!extension) {
1054
+ return `${relativePath}.${hash}`;
1055
+ }
1056
+
1057
+ return path.join(
1058
+ path.dirname(relativePath),
1059
+ `${path.basename(relativePath, extension)}.${hash}${extension}`,
1060
+ );
1061
+ }
1062
+
1063
+ function getAssetType(relativePath) {
1064
+ if (relativePath.endsWith(".css")) {
1065
+ return "style";
1066
+ }
1067
+
1068
+ if (relativePath.endsWith(".mjs")) {
1069
+ return "script";
1070
+ }
1071
+
1072
+ return "asset";
1073
+ }
1074
+
1075
+ function replaceStaticAssetPlaceholders(source, publicPathByRelativePath) {
1076
+ return source.replaceAll(/__EVOLIT_ASSET_URL__:([^"]+)/g, (match, encodedRelativeAssetPath) => {
1077
+ const relativeAssetPath = encodedRelativeAssetPath
1078
+ .replaceAll("\\\\", "\\")
1079
+ .replaceAll('\\"', '"');
1080
+ const rewrittenTarget = publicPathByRelativePath.get(relativeAssetPath);
1081
+ if (!rewrittenTarget) {
1082
+ return match;
1083
+ }
1084
+
1085
+ return toStaticPublicUrl(rewrittenTarget);
1086
+ });
1087
+ }
1088
+
1089
+ function rewriteStaticAssetPlaceholdersWithMap(source, publicPathByRelativePath) {
1090
+ const magicSource = new MagicString(source);
1091
+ let didRewrite = false;
1092
+
1093
+ for (const match of source.matchAll(/__EVOLIT_ASSET_URL__:([^"]+)/g)) {
1094
+ const encodedRelativeAssetPath = match[1];
1095
+ const relativeAssetPath = encodedRelativeAssetPath
1096
+ .replaceAll("\\\\", "\\")
1097
+ .replaceAll('\\"', '"');
1098
+ const rewrittenTarget = publicPathByRelativePath.get(relativeAssetPath);
1099
+ if (!rewrittenTarget || typeof match.index !== "number") {
1100
+ continue;
1101
+ }
1102
+
1103
+ didRewrite = true;
1104
+ magicSource.update(
1105
+ match.index,
1106
+ match.index + match[0].length,
1107
+ toStaticPublicUrl(rewrittenTarget),
1108
+ );
1109
+ }
1110
+
1111
+ if (!didRewrite) {
1112
+ return null;
1113
+ }
1114
+
1115
+ return {
1116
+ code: magicSource.toString(),
1117
+ map: magicSource.generateMap({ hires: true }),
1118
+ };
1119
+ }
1120
+
1121
+ function rewriteRelativeCssAssetUrls(source, fileRelativePath, publicPathByRelativePath) {
1122
+ return source.replaceAll(
1123
+ /url\(\s*(['"]?)([^"'()]+)\1\s*\)/g,
1124
+ (match, quote = "", rawSpecifier) => {
1125
+ const specifier = String(rawSpecifier).trim();
1126
+ if (
1127
+ specifier.length === 0 ||
1128
+ specifier.startsWith("/") ||
1129
+ specifier.startsWith("data:") ||
1130
+ specifier.startsWith("http:") ||
1131
+ specifier.startsWith("https:")
1132
+ ) {
1133
+ return match;
1134
+ }
1135
+
1136
+ const [assetPath, hashFragment] = specifier.split("#", 2);
1137
+ const [cleanAssetPath, searchFragment] = assetPath.split("?", 2);
1138
+ const resolvedRelativePath = path.normalize(
1139
+ path.join(path.dirname(fileRelativePath), cleanAssetPath),
1140
+ );
1141
+ const rewrittenTarget = publicPathByRelativePath.get(resolvedRelativePath);
1142
+ if (!rewrittenTarget) {
1143
+ return match;
1144
+ }
1145
+
1146
+ let rewrittenSpecifier = normalizeRelativeImportPath(
1147
+ path.relative(
1148
+ path.dirname(publicPathByRelativePath.get(fileRelativePath)),
1149
+ rewrittenTarget,
1150
+ ),
1151
+ );
1152
+ if (typeof searchFragment === "string") {
1153
+ rewrittenSpecifier += `?${searchFragment}`;
1154
+ }
1155
+ if (typeof hashFragment === "string") {
1156
+ rewrittenSpecifier += `#${hashFragment}`;
1157
+ }
1158
+
1159
+ return `url(${quote}${rewrittenSpecifier}${quote})`;
1160
+ },
1161
+ );
1162
+ }
1163
+
1164
+ function normalizeRelativeImportPath(value) {
1165
+ const normalized = value.split(path.sep).join("/");
1166
+ return normalized.startsWith(".") ? normalized : `./${normalized}`;
1167
+ }
1168
+
1169
+ function encodePathForUrl(value) {
1170
+ return value
1171
+ .split(path.sep)
1172
+ .join("/")
1173
+ .split("/")
1174
+ .map((segment) => encodeURIComponent(segment))
1175
+ .join("/");
1176
+ }
1177
+
1178
+ function toStaticPublicUrl(relativePath) {
1179
+ return `/_evolit/static/${encodePathForUrl(relativePath)}`;
1180
+ }
1181
+
1182
+ function collectRelativeClientImportPaths(source, fileRelativePath, publicPathByRelativePath) {
1183
+ const imports = [];
1184
+
1185
+ for (const match of source.matchAll(MODULE_SPECIFIER_PATTERN)) {
1186
+ const specifier = match[1] ?? match[2] ?? match[3] ?? null;
1187
+ if (
1188
+ !specifier ||
1189
+ (!specifier.startsWith("./") && !specifier.startsWith("../"))
1190
+ ) {
1191
+ continue;
1192
+ }
1193
+
1194
+ const resolvedRelativePath = path.normalize(
1195
+ path.join(path.dirname(fileRelativePath), specifier),
1196
+ );
1197
+ if (!publicPathByRelativePath.has(resolvedRelativePath)) {
1198
+ continue;
1199
+ }
1200
+
1201
+ imports.push(resolvedRelativePath.split(path.sep).join("/"));
1202
+ }
1203
+
1204
+ return [...new Set(imports)].sort();
1205
+ }
1206
+
1207
+ function rewriteRelativeClientImports(source, fileRelativePath, publicPathByRelativePath) {
1208
+ let rewrittenCode = "";
1209
+ let previousIndex = 0;
1210
+
1211
+ for (const match of source.matchAll(MODULE_SPECIFIER_PATTERN)) {
1212
+ const specifier = match[1] ?? match[2] ?? match[3] ?? null;
1213
+ if (
1214
+ !specifier ||
1215
+ (!specifier.startsWith("./") && !specifier.startsWith("../"))
1216
+ ) {
1217
+ continue;
1218
+ }
1219
+
1220
+ const resolvedRelativePath = path.normalize(
1221
+ path.join(path.dirname(fileRelativePath), specifier),
1222
+ );
1223
+ const rewrittenTarget = publicPathByRelativePath.get(resolvedRelativePath);
1224
+ if (!rewrittenTarget) {
1225
+ continue;
1226
+ }
1227
+
1228
+ const replacementPath = path.relative(
1229
+ path.dirname(publicPathByRelativePath.get(fileRelativePath)),
1230
+ rewrittenTarget,
1231
+ );
1232
+ const quotedSpecifierIndex = match.index + match[0].indexOf(specifier);
1233
+
1234
+ rewrittenCode += source.slice(previousIndex, quotedSpecifierIndex);
1235
+ rewrittenCode += normalizeRelativeImportPath(replacementPath);
1236
+ previousIndex = quotedSpecifierIndex + specifier.length;
1237
+ }
1238
+
1239
+ rewrittenCode += source.slice(previousIndex);
1240
+ return rewrittenCode;
1241
+ }
1242
+
1243
+ export async function emitHashedClientAssets(projectRoot, options = {}) {
1244
+ const clientRoot = getClientOutputRoot(projectRoot, "production");
1245
+ const staticRoot = getStaticOutputRoot(projectRoot);
1246
+ const files = await walkFiles(clientRoot);
1247
+ const entryClientModules = new Set(options.entryClientModules ?? []);
1248
+ const assetEntries = [];
1249
+ const publicPathByRelativePath = new Map();
1250
+
1251
+ for (const filePath of files) {
1252
+ if (filePath.endsWith(".meta.json") || filePath.endsWith(".map")) {
1253
+ continue;
1254
+ }
1255
+ const relativePath = path.relative(clientRoot, filePath);
1256
+ const buffer = await fs.readFile(filePath);
1257
+ const type = getAssetType(relativePath);
1258
+ assetEntries.push({
1259
+ filePath,
1260
+ relativePath,
1261
+ buffer,
1262
+ type,
1263
+ source: type === "script" ? buffer.toString("utf8") : null,
1264
+ });
1265
+ }
1266
+
1267
+ for (const entry of assetEntries) {
1268
+ const hash = crypto
1269
+ .createHash("sha1")
1270
+ .update(entry.buffer)
1271
+ .digest("hex")
1272
+ .slice(0, 8);
1273
+ const hashedRelativePath = createHashedModulePath(entry.relativePath, hash);
1274
+ publicPathByRelativePath.set(entry.relativePath, hashedRelativePath);
1275
+ }
1276
+
1277
+ const byClientModule = {};
1278
+ const byPublicPath = {};
1279
+ const assets = [];
1280
+
1281
+ for (const entry of assetEntries) {
1282
+ const rewrittenSource = entry.type === "script"
1283
+ ? replaceStaticAssetPlaceholders(
1284
+ rewriteRelativeClientImports(
1285
+ entry.source,
1286
+ entry.relativePath,
1287
+ publicPathByRelativePath,
1288
+ ),
1289
+ publicPathByRelativePath,
1290
+ )
1291
+ : entry.type === "style"
1292
+ ? rewriteRelativeCssAssetUrls(
1293
+ entry.buffer.toString("utf8"),
1294
+ entry.relativePath,
1295
+ publicPathByRelativePath,
1296
+ )
1297
+ : entry.source;
1298
+ const hashedRelativePath = publicPathByRelativePath.get(entry.relativePath);
1299
+ const outputPath = path.join(staticRoot, hashedRelativePath);
1300
+ const clientModule = entry.relativePath.split(path.sep).join("/");
1301
+ const importedClientModules = entry.type === "script"
1302
+ ? collectRelativeClientImportPaths(
1303
+ entry.source,
1304
+ entry.relativePath,
1305
+ publicPathByRelativePath,
1306
+ )
1307
+ : [];
1308
+ const importedPublicUrls = importedClientModules
1309
+ .map((importedModule) => byClientModule[importedModule] ?? publicPathByRelativePath.get(importedModule))
1310
+ .map((value) => {
1311
+ if (typeof value === "string" && value.startsWith("/_evolit/")) {
1312
+ return value;
1313
+ }
1314
+ if (typeof value !== "string") {
1315
+ return null;
1316
+ }
1317
+ return toStaticPublicUrl(value);
1318
+ })
1319
+ .filter((value) => typeof value === "string");
1320
+ let styleImports = [];
1321
+ let styleUrls = [];
1322
+ let assetImports = [];
1323
+ let assetUrls = [];
1324
+ if (entry.type === "script") {
1325
+ try {
1326
+ const metadata = JSON.parse(await fs.readFile(`${entry.filePath}.meta.json`, "utf8"));
1327
+ styleImports = Array.isArray(metadata?.styleImports) ? metadata.styleImports : [];
1328
+ assetImports = Array.isArray(metadata?.assetImports) ? metadata.assetImports : [];
1329
+ } catch {
1330
+ styleImports = [];
1331
+ assetImports = [];
1332
+ }
1333
+ styleUrls = styleImports
1334
+ .map((importedStyle) => publicPathByRelativePath.get(importedStyle))
1335
+ .filter((value) => typeof value === "string")
1336
+ .map((value) => toStaticPublicUrl(value));
1337
+ assetUrls = assetImports
1338
+ .map((importedAsset) => publicPathByRelativePath.get(importedAsset))
1339
+ .filter((value) => typeof value === "string")
1340
+ .map((value) => toStaticPublicUrl(value));
1341
+ }
1342
+ await ensureDirectory(path.dirname(outputPath));
1343
+ await fs.writeFile(
1344
+ outputPath,
1345
+ entry.type === "asset" ? entry.buffer : rewrittenSource,
1346
+ entry.type === "asset" ? undefined : "utf8",
1347
+ );
1348
+
1349
+ const publicUrl = toStaticPublicUrl(hashedRelativePath);
1350
+ const hash = path.basename(hashedRelativePath).match(/\.([a-f0-9]{8})\.[^.]+$/)?.[1] ?? null;
1351
+ const kind = entry.type === "style"
1352
+ ? "style"
1353
+ : entry.type === "asset"
1354
+ ? "asset"
1355
+ : entryClientModules.has(clientModule) ? "entry" : "chunk";
1356
+ const size = entry.type === "asset"
1357
+ ? entry.buffer.byteLength
1358
+ : Buffer.byteLength(rewrittenSource, "utf8");
1359
+
1360
+ byClientModule[clientModule] = publicUrl;
1361
+ byPublicPath[publicUrl] = outputPath;
1362
+ assets.push({
1363
+ clientModule,
1364
+ type: entry.type,
1365
+ kind,
1366
+ publicUrl,
1367
+ outputPath,
1368
+ hash,
1369
+ size,
1370
+ imports: importedClientModules,
1371
+ importUrls: importedPublicUrls,
1372
+ styleImports,
1373
+ styleUrls,
1374
+ assetImports,
1375
+ assetUrls,
1376
+ });
1377
+ }
1378
+
1379
+ const entries = assets
1380
+ .filter((asset) => asset.kind === "entry")
1381
+ .map((asset) => asset.clientModule)
1382
+ .sort();
1383
+ const chunks = assets
1384
+ .filter((asset) => asset.kind === "chunk")
1385
+ .map((asset) => asset.clientModule)
1386
+ .sort();
1387
+ const styles = assets
1388
+ .filter((asset) => asset.type === "style")
1389
+ .map((asset) => asset.clientModule)
1390
+ .sort();
1391
+ const resources = assets
1392
+ .filter((asset) => asset.type === "asset")
1393
+ .map((asset) => asset.clientModule)
1394
+ .sort();
1395
+
1396
+ const manifest = {
1397
+ version: CLIENT_ASSET_MANIFEST_VERSION,
1398
+ publicPathPrefix: "/_evolit/static/",
1399
+ byClientModule,
1400
+ byPublicPath,
1401
+ entries,
1402
+ chunks,
1403
+ styles,
1404
+ resources,
1405
+ assets: assets.sort((left, right) => left.clientModule.localeCompare(right.clientModule)),
1406
+ };
1407
+
1408
+ await writeJson(path.join(projectRoot, INTERNAL_DIRECTORY, BUILD_DIRECTORY, "client-assets.json"), manifest);
1409
+ return manifest;
1410
+ }
1411
+
1412
+ async function bundleClientAssets(projectRoot, options = {}) {
1413
+ const mode = options.mode ?? "production";
1414
+ const clientRoot = getClientOutputRoot(projectRoot, mode);
1415
+ const staticRoot = getModeStaticOutputRoot(projectRoot, mode);
1416
+ const files = await walkFiles(clientRoot);
1417
+ const entryClientModules = new Set(options.entryClientModules ?? []);
1418
+ const assetEntries = [];
1419
+ const publicPathByRelativePath = new Map();
1420
+
1421
+ for (const filePath of files) {
1422
+ if (filePath.endsWith(".meta.json") || filePath.endsWith(".map")) {
1423
+ continue;
1424
+ }
1425
+
1426
+ const relativePath = path.relative(clientRoot, filePath);
1427
+ if (isClientAssetStubModule(relativePath)) {
1428
+ continue;
1429
+ }
1430
+
1431
+ const buffer = await fs.readFile(filePath);
1432
+ const type = getAssetType(relativePath);
1433
+ assetEntries.push({
1434
+ filePath,
1435
+ relativePath,
1436
+ buffer,
1437
+ type,
1438
+ source: type === "script" ? buffer.toString("utf8") : null,
1439
+ });
1440
+ }
1441
+
1442
+ for (const entry of assetEntries.filter((asset) => asset.type !== "script")) {
1443
+ const hash = crypto
1444
+ .createHash("sha1")
1445
+ .update(entry.buffer)
1446
+ .digest("hex")
1447
+ .slice(0, 8);
1448
+ const hashedRelativePath = createHashedModulePath(entry.relativePath, hash);
1449
+ publicPathByRelativePath.set(entry.relativePath, hashedRelativePath);
1450
+ }
1451
+
1452
+ await fs.rm(staticRoot, { recursive: true, force: true });
1453
+ await ensureDirectory(staticRoot);
1454
+
1455
+ const nonScriptAssets = [];
1456
+ const byClientModule = {};
1457
+ const byPublicPath = {};
1458
+
1459
+ for (const entry of assetEntries.filter((asset) => asset.type !== "script")) {
1460
+ const rewrittenSource = entry.type === "style"
1461
+ ? rewriteRelativeCssAssetUrls(
1462
+ entry.buffer.toString("utf8"),
1463
+ entry.relativePath,
1464
+ publicPathByRelativePath,
1465
+ )
1466
+ : entry.buffer;
1467
+ const hashedRelativePath = publicPathByRelativePath.get(entry.relativePath);
1468
+ const outputPath = path.join(staticRoot, hashedRelativePath);
1469
+ await ensureDirectory(path.dirname(outputPath));
1470
+ await fs.writeFile(
1471
+ outputPath,
1472
+ entry.type === "asset" ? rewrittenSource : rewrittenSource,
1473
+ entry.type === "asset" ? undefined : "utf8",
1474
+ );
1475
+
1476
+ const publicUrl = toStaticPublicUrl(hashedRelativePath);
1477
+ const clientModule = entry.relativePath.split(path.sep).join("/");
1478
+ const hash = path.basename(hashedRelativePath).match(/\.([a-f0-9]{8})\.[^.]+$/)?.[1] ?? null;
1479
+ const size = entry.type === "asset"
1480
+ ? entry.buffer.byteLength
1481
+ : Buffer.byteLength(rewrittenSource, "utf8");
1482
+
1483
+ byClientModule[clientModule] = publicUrl;
1484
+ byPublicPath[publicUrl] = outputPath;
1485
+ nonScriptAssets.push({
1486
+ clientModule,
1487
+ type: entry.type,
1488
+ kind: entry.type === "style" ? "style" : "asset",
1489
+ publicUrl,
1490
+ outputPath,
1491
+ hash,
1492
+ size,
1493
+ imports: [],
1494
+ importUrls: [],
1495
+ styleImports: [],
1496
+ styleUrls: [],
1497
+ assetImports: [],
1498
+ assetUrls: [],
1499
+ });
1500
+ }
1501
+
1502
+ const inputEntries = {};
1503
+ const clientModuleByEntryName = new Map();
1504
+ for (const entry of assetEntries.filter((asset) => asset.type === "script")) {
1505
+ const clientModule = entry.relativePath.split(path.sep).join("/");
1506
+ const entryName = createClientEntryName(clientModule);
1507
+ inputEntries[entryName] = entry.filePath;
1508
+ clientModuleByEntryName.set(entryName, clientModule);
1509
+ }
1510
+
1511
+ const scriptAssetRecords = [];
1512
+ let nextRollupCache = options.rollupCache ?? null;
1513
+ if (Object.keys(inputEntries).length > 0) {
1514
+ const sharedVendorSpecifiers = await collectClientVendorSpecifiers(projectRoot, [], {
1515
+ mode,
1516
+ entryClientModules: [...entryClientModules],
1517
+ });
1518
+ const sharedRuntime = await buildSharedVendorRuntime(projectRoot, mode, {
1519
+ additionalEntrySpecifiers: sharedVendorSpecifiers,
1520
+ });
1521
+ const sharedImportPaths = Object.fromEntries(
1522
+ Object.entries(sharedRuntime.imports).map(([specifier, publicUrl]) => [specifier, publicUrl]),
1523
+ );
1524
+ const bundle = await rollup({
1525
+ input: inputEntries,
1526
+ cache: options.rollupCache ?? undefined,
1527
+ external(id) {
1528
+ return isBareSpecifier(id);
1529
+ },
1530
+ plugins: [
1531
+ {
1532
+ name: "evolit-client-input-sourcemaps",
1533
+ async load(id) {
1534
+ if (!id.startsWith(clientRoot) || !id.endsWith(".mjs")) {
1535
+ return null;
1536
+ }
1537
+
1538
+ try {
1539
+ const [code, map] = await Promise.all([
1540
+ fs.readFile(id, "utf8"),
1541
+ fs.readFile(`${id}.map`, "utf8"),
1542
+ ]);
1543
+ return { code, map: JSON.parse(map) };
1544
+ } catch {
1545
+ return null;
1546
+ }
1547
+ },
1548
+ },
1549
+ nodeResolve({
1550
+ browser: true,
1551
+ exportConditions: ["browser", "import", "default"],
1552
+ extensions: [".mjs", ".js", ".json"],
1553
+ preferBuiltins: false,
1554
+ }),
1555
+ {
1556
+ name: "evolit-client-asset-placeholders",
1557
+ renderChunk(code) {
1558
+ return rewriteStaticAssetPlaceholdersWithMap(code, publicPathByRelativePath);
1559
+ },
1560
+ },
1561
+ ],
1562
+ onwarn(warning, warn) {
1563
+ if (warning.code === "CIRCULAR_DEPENDENCY") {
1564
+ return;
1565
+ }
1566
+
1567
+ warn(warning);
1568
+ },
1569
+ });
1570
+
1571
+ try {
1572
+ const output = await bundle.write({
1573
+ dir: staticRoot,
1574
+ format: "esm",
1575
+ sourcemap: true,
1576
+ entryFileNames: mode === "development" ? "[name].mjs" : "[name]-[hash].mjs",
1577
+ chunkFileNames: mode === "development" ? "chunks/[name].mjs" : "chunks/[name]-[hash].mjs",
1578
+ paths(id) {
1579
+ return sharedImportPaths[id] ?? id;
1580
+ },
1581
+ });
1582
+ await rewriteEmittedStaticSourceMaps(projectRoot, clientRoot, staticRoot);
1583
+
1584
+ const publicUrlByFileName = new Map();
1585
+ for (const chunk of output.output) {
1586
+ if (chunk.type !== "chunk") {
1587
+ continue;
1588
+ }
1589
+ publicUrlByFileName.set(chunk.fileName, toStaticPublicUrl(chunk.fileName));
1590
+ }
1591
+
1592
+ for (const chunk of output.output) {
1593
+ if (chunk.type !== "chunk") {
1594
+ continue;
1595
+ }
1596
+
1597
+ const outputPath = path.join(staticRoot, chunk.fileName);
1598
+ const publicUrl = toStaticPublicUrl(chunk.fileName);
1599
+ const isNamedEntry = chunk.isEntry && clientModuleByEntryName.has(chunk.name);
1600
+ const clientModule = isNamedEntry
1601
+ ? clientModuleByEntryName.get(chunk.name)
1602
+ : chunk.fileName;
1603
+ const transitiveMetadata = isNamedEntry
1604
+ ? await collectTransitiveClientModuleMetadata(projectRoot, mode, clientModule)
1605
+ : { styleImports: [], assetImports: [] };
1606
+ const styleUrls = transitiveMetadata.styleImports
1607
+ .map((importedStyle) => byClientModule[importedStyle])
1608
+ .filter((value) => typeof value === "string");
1609
+ const assetUrls = transitiveMetadata.assetImports
1610
+ .map((importedAsset) => byClientModule[importedAsset])
1611
+ .filter((value) => typeof value === "string");
1612
+ const hash = path.basename(chunk.fileName).match(/-([A-Za-z0-9_-]+)\.mjs$/)?.[1] ?? null;
1613
+
1614
+ if (isNamedEntry) {
1615
+ byClientModule[clientModule] = publicUrl;
1616
+ }
1617
+ byPublicPath[publicUrl] = outputPath;
1618
+ scriptAssetRecords.push({
1619
+ clientModule,
1620
+ type: "script",
1621
+ kind: entryClientModules.has(clientModule) ? "entry" : "chunk",
1622
+ publicUrl,
1623
+ outputPath,
1624
+ hash,
1625
+ size: Buffer.byteLength(chunk.code, "utf8"),
1626
+ imports: chunk.imports,
1627
+ importUrls: chunk.imports
1628
+ .map((fileName) => publicUrlByFileName.get(fileName))
1629
+ .filter((value) => typeof value === "string"),
1630
+ styleImports: transitiveMetadata.styleImports,
1631
+ styleUrls,
1632
+ assetImports: transitiveMetadata.assetImports,
1633
+ assetUrls,
1634
+ });
1635
+ }
1636
+ nextRollupCache = bundle.cache;
1637
+ } finally {
1638
+ await bundle.close();
1639
+ }
1640
+ }
1641
+
1642
+ const mapAssets = [];
1643
+ const staticFiles = await walkFiles(staticRoot);
1644
+ for (const filePath of staticFiles) {
1645
+ if (!filePath.endsWith(".map")) {
1646
+ continue;
1647
+ }
1648
+
1649
+ const relativePath = path.relative(staticRoot, filePath).split(path.sep).join("/");
1650
+ const publicUrl = toStaticPublicUrl(relativePath);
1651
+ byPublicPath[publicUrl] = filePath;
1652
+ mapAssets.push({
1653
+ clientModule: relativePath,
1654
+ type: "asset",
1655
+ kind: "asset",
1656
+ publicUrl,
1657
+ outputPath: filePath,
1658
+ hash: null,
1659
+ size: (await fs.stat(filePath)).size,
1660
+ imports: [],
1661
+ importUrls: [],
1662
+ styleImports: [],
1663
+ styleUrls: [],
1664
+ assetImports: [],
1665
+ assetUrls: [],
1666
+ });
1667
+ }
1668
+
1669
+ const assets = [...scriptAssetRecords, ...nonScriptAssets, ...mapAssets]
1670
+ .sort((left, right) => left.clientModule.localeCompare(right.clientModule));
1671
+ const manifest = {
1672
+ version: CLIENT_ASSET_MANIFEST_VERSION,
1673
+ publicPathPrefix: "/_evolit/static/",
1674
+ byClientModule,
1675
+ byPublicPath,
1676
+ entries: assets.filter((asset) => asset.kind === "entry").map((asset) => asset.clientModule).sort(),
1677
+ chunks: assets.filter((asset) => asset.kind === "chunk").map((asset) => asset.clientModule).sort(),
1678
+ styles: assets.filter((asset) => asset.type === "style").map((asset) => asset.clientModule).sort(),
1679
+ resources: assets
1680
+ .filter((asset) => asset.kind === "asset" && !asset.clientModule.endsWith(".map"))
1681
+ .map((asset) => asset.clientModule)
1682
+ .sort(),
1683
+ assets,
1684
+ };
1685
+
1686
+ await writeJson(
1687
+ path.join(
1688
+ projectRoot,
1689
+ INTERNAL_DIRECTORY,
1690
+ mode === "development" ? DEV_DIRECTORY : BUILD_DIRECTORY,
1691
+ "client-assets.json",
1692
+ ),
1693
+ manifest,
1694
+ );
1695
+ return {
1696
+ manifest,
1697
+ rollupCache: nextRollupCache,
1698
+ };
1699
+ }
1700
+
1701
+ export async function emitBundledClientAssets(projectRoot, options = {}) {
1702
+ const result = await bundleClientAssets(projectRoot, options);
1703
+ return result.manifest;
1704
+ }
1705
+
1706
+ export async function emitBundledClientAssetsWithState(projectRoot, options = {}) {
1707
+ return bundleClientAssets(projectRoot, options);
1708
+ }
1709
+
1710
+ export async function rewriteServerAssetPlaceholders(projectRoot, assetManifest) {
1711
+ const normalizedManifest = normalizeClientAssetManifest(assetManifest);
1712
+ if (!normalizedManifest) {
1713
+ return;
1714
+ }
1715
+
1716
+ const serverRoot = getServerOutputRoot(projectRoot);
1717
+ const serverFiles = await walkFiles(serverRoot);
1718
+ const assetUrlByClientModule = new Map(
1719
+ normalizedManifest.assets.map((asset) => [asset.clientModule, asset.publicUrl]),
1720
+ );
1721
+
1722
+ for (const filePath of serverFiles) {
1723
+ if (!filePath.endsWith(".mjs")) {
1724
+ continue;
1725
+ }
1726
+
1727
+ const source = await fs.readFile(filePath, "utf8");
1728
+ const rewritten = source.replaceAll(/__EVOLIT_ASSET_URL__:([A-Za-z0-9._/-]+)/g, (match, relativeAssetPath) => {
1729
+ return assetUrlByClientModule.get(relativeAssetPath) ?? match;
1730
+ }).replaceAll(/__EVOLIT_ASSET_URL__:([^"]+)/g, (match, encodedRelativeAssetPath) => {
1731
+ const relativeAssetPath = encodedRelativeAssetPath
1732
+ .replaceAll("\\\\", "\\")
1733
+ .replaceAll('\\"', '"');
1734
+ return assetUrlByClientModule.get(relativeAssetPath) ?? match;
1735
+ });
1736
+
1737
+ if (rewritten !== source) {
1738
+ await fs.writeFile(filePath, rewritten, "utf8");
1739
+ }
1740
+ }
1741
+ }
1742
+
1743
+ export function collectTransitiveAssetPreloads(publicUrls, assetManifest) {
1744
+ const normalizedManifest = normalizeClientAssetManifest(assetManifest);
1745
+ if (!normalizedManifest) {
1746
+ return [];
1747
+ }
1748
+
1749
+ const byPublicUrl = new Map(
1750
+ normalizedManifest.assets
1751
+ .filter((asset) => typeof asset?.publicUrl === "string")
1752
+ .map((asset) => [asset.publicUrl, asset]),
1753
+ );
1754
+ const visited = new Set(publicUrls);
1755
+ const queue = [...publicUrls];
1756
+ const discovered = [];
1757
+
1758
+ while (queue.length > 0) {
1759
+ const currentUrl = queue.shift();
1760
+ const asset = byPublicUrl.get(currentUrl);
1761
+ if (!asset || !Array.isArray(asset.importUrls)) {
1762
+ continue;
1763
+ }
1764
+
1765
+ for (const importedUrl of asset.importUrls) {
1766
+ if (typeof importedUrl !== "string" || visited.has(importedUrl)) {
1767
+ continue;
1768
+ }
1769
+
1770
+ visited.add(importedUrl);
1771
+ discovered.push(importedUrl);
1772
+ queue.push(importedUrl);
1773
+ }
1774
+ }
1775
+
1776
+ return discovered;
1777
+ }
1778
+
1779
+ export function collectTransitiveStyleUrls(publicUrls, assetManifest) {
1780
+ const normalizedManifest = normalizeClientAssetManifest(assetManifest);
1781
+ if (!normalizedManifest) {
1782
+ return [];
1783
+ }
1784
+
1785
+ const byPublicUrl = new Map(
1786
+ normalizedManifest.assets
1787
+ .filter((asset) => typeof asset?.publicUrl === "string")
1788
+ .map((asset) => [asset.publicUrl, asset]),
1789
+ );
1790
+ const visitedAssets = new Set(publicUrls);
1791
+ const queue = [...publicUrls];
1792
+ const styles = new Set();
1793
+
1794
+ while (queue.length > 0) {
1795
+ const currentUrl = queue.shift();
1796
+ const asset = byPublicUrl.get(currentUrl);
1797
+ if (!asset) {
1798
+ continue;
1799
+ }
1800
+
1801
+ for (const styleUrl of Array.isArray(asset.styleUrls) ? asset.styleUrls : []) {
1802
+ if (typeof styleUrl === "string") {
1803
+ styles.add(styleUrl);
1804
+ }
1805
+ }
1806
+
1807
+ for (const importedUrl of Array.isArray(asset.importUrls) ? asset.importUrls : []) {
1808
+ if (typeof importedUrl !== "string" || visitedAssets.has(importedUrl)) {
1809
+ continue;
1810
+ }
1811
+
1812
+ visitedAssets.add(importedUrl);
1813
+ queue.push(importedUrl);
1814
+ }
1815
+ }
1816
+
1817
+ return [...styles].sort();
1818
+ }
1819
+
1820
+ export function normalizeClientAssetManifest(manifest) {
1821
+ if (!manifest || typeof manifest !== "object" || !Array.isArray(manifest.assets)) {
1822
+ return null;
1823
+ }
1824
+
1825
+ return {
1826
+ version: manifest.version ?? CLIENT_ASSET_MANIFEST_VERSION,
1827
+ publicPathPrefix: manifest.publicPathPrefix ?? "/_evolit/static/",
1828
+ byClientModule: manifest.byClientModule ?? {},
1829
+ byPublicPath: manifest.byPublicPath ?? {},
1830
+ entries: Array.isArray(manifest.entries) ? manifest.entries : [],
1831
+ chunks: Array.isArray(manifest.chunks) ? manifest.chunks : [],
1832
+ styles: Array.isArray(manifest.styles) ? manifest.styles : [],
1833
+ resources: Array.isArray(manifest.resources) ? manifest.resources : [],
1834
+ assets: manifest.assets,
1835
+ };
1836
+ }
1837
+
1838
+ export function createStaticAssetPublicUrlMap(manifest) {
1839
+ const normalizedManifest = normalizeClientAssetManifest(manifest);
1840
+ if (!normalizedManifest) {
1841
+ return null;
1842
+ }
1843
+
1844
+ const entries = normalizedManifest.assets
1845
+ .filter(
1846
+ (asset) =>
1847
+ typeof asset?.clientModule === "string"
1848
+ && typeof asset?.publicUrl === "string"
1849
+ && asset.type === "asset",
1850
+ )
1851
+ .map((asset) => [asset.clientModule, asset.publicUrl]);
1852
+
1853
+ return new Map(entries);
1854
+ }
1855
+
1856
+ export function getAssetByClientModule(manifest, clientModule) {
1857
+ const normalizedManifest = normalizeClientAssetManifest(manifest);
1858
+ if (!normalizedManifest || typeof clientModule !== "string") {
1859
+ return null;
1860
+ }
1861
+
1862
+ return normalizedManifest.assets.find((asset) => asset.clientModule === clientModule) ?? null;
1863
+ }
1864
+
1865
+ export function getAssetByPublicUrl(manifest, publicUrl) {
1866
+ const normalizedManifest = normalizeClientAssetManifest(manifest);
1867
+ if (!normalizedManifest || typeof publicUrl !== "string") {
1868
+ return null;
1869
+ }
1870
+
1871
+ return normalizedManifest.assets.find((asset) => asset.publicUrl === publicUrl) ?? null;
1872
+ }
1873
+
1874
+ export function getAssetsByKind(manifest, kind) {
1875
+ const normalizedManifest = normalizeClientAssetManifest(manifest);
1876
+ if (!normalizedManifest || typeof kind !== "string") {
1877
+ return [];
1878
+ }
1879
+
1880
+ return normalizedManifest.assets.filter((asset) => asset.kind === kind);
1881
+ }