evolit 0.1.0-alpha.24 → 0.1.0-alpha.26

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/README.md CHANGED
@@ -317,6 +317,33 @@ export default {
317
317
  };
318
318
  ```
319
319
 
320
+ For monorepos, development watches only the application project by default. Additional source
321
+ trees can be opted into explicitly; generated output and dependency directories below every root
322
+ remain ignored:
323
+
324
+ ```js
325
+ export default {
326
+ development: {
327
+ managedSourceRoots: ["../packages/design-system/src"],
328
+ },
329
+ };
330
+ ```
331
+
332
+ Imports outside these roots remain unmanaged and produce the existing development warning.
333
+
334
+ Evolit normally discovers browser boundaries from the static module graph and reconciles that
335
+ inventory with the components actually emitted by SSR. A genuinely computed import cannot be
336
+ enumerated at build time, so production applications can declare only those exceptional modules:
337
+
338
+ ```js
339
+ export default {
340
+ clientBoundaries: ["./src/components/dynamic-card.litsx", "@acme/ui/product-card"],
341
+ };
342
+ ```
343
+
344
+ These modules are materialized as build artifacts, but are not automatically imported or
345
+ preloaded. They reach the browser only when an SSR hydration root references them.
346
+
320
347
  The framework also exports `ObjectStorageResponseCacheStore`, which is intended for object-store
321
348
  backends such as S3. A concrete app can wire it to the AWS SDK without pulling AWS dependencies
322
349
  into `evolit` itself.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "evolit",
3
- "version": "0.1.0-alpha.24",
3
+ "version": "0.1.0-alpha.26",
4
4
  "description": "A convention-driven application framework for LitSX and web components.",
5
5
  "type": "module",
6
6
  "packageManager": "yarn@4.10.3",
@@ -14,7 +14,9 @@
14
14
  "homepage": "https://github.com/litsxdev/nextsx#readme",
15
15
  "files": [
16
16
  "src",
17
- "templates"
17
+ "templates/default/app/**",
18
+ "templates/default/jsconfig.json",
19
+ "templates/default/yarnrc.yml.template"
18
20
  ],
19
21
  "bin": "./src/cli.js",
20
22
  "main": "./src/index.js",
@@ -52,10 +54,11 @@
52
54
  },
53
55
  "dependencies": {
54
56
  "@jridgewell/remapping": "^2.3.5",
55
- "@litsx/compiler": "0.9.5-canary-feat-ssr-20260726184312",
56
- "@litsx/core": "0.17.0-canary-feat-ssr-20260726184312",
57
- "@litsx/ssr": "0.2.0-canary-feat-ssr-20260726184312",
57
+ "@litsx/compiler": "0.10.0-canary-feat-ssr-20260731112627",
58
+ "@litsx/core": "0.17.0-canary-feat-ssr-20260731112627",
59
+ "@litsx/ssr": "0.2.0-canary-feat-ssr-20260731112627",
58
60
  "@litsx/typescript": "^0.9.0",
61
+ "@rollup/plugin-commonjs": "^29.0.0",
59
62
  "@rollup/plugin-node-resolve": "^16.0.3",
60
63
  "lit": "^3.3.3",
61
64
  "magic-string": "^1.1.0",
package/src/build.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
3
4
  import {
4
5
  discoverAppRouteHandlers,
5
6
  discoverAppRoutes,
@@ -15,13 +16,20 @@ import {
15
16
  emitBundledClientAssets,
16
17
  emitHashedClientAssets,
17
18
  normalizeHydrationDataForClient,
19
+ resolveServerStyleUrls,
18
20
  resolveHydrationRootClientImports,
19
- resolveRouteClientImports,
20
21
  resolveSharedVendorModuleUrl,
21
22
  rewriteHydrationDataScript,
22
23
  rewriteServerAssetPlaceholders,
23
24
  } from "./client-assets.js";
24
- import { compileModuleGraph, importCompiledModule } from "./compiler.js";
25
+ import {
26
+ collectClientGraphInventory,
27
+ compileModuleGraph,
28
+ emitClientStaticAssets,
29
+ getClientStaticAssetModule,
30
+ importCompiledModule,
31
+ resolveProjectModuleSpecifier,
32
+ } from "./compiler.js";
25
33
  import { loadEvolitConfig } from "./config.js";
26
34
  import {
27
35
  BUILD_DIRECTORY,
@@ -125,7 +133,37 @@ export async function buildProject(projectRoot) {
125
133
  const routeHandlers = await discoverAppRouteHandlers(projectRoot);
126
134
  const buildRoot = path.join(projectRoot, INTERNAL_DIRECTORY, BUILD_DIRECTORY);
127
135
  const entryClientModules = new Set();
136
+ const serverAssetImportsByEntry = {};
137
+ const clientBoundariesByEntry = {};
128
138
  const deployHandlers = [];
139
+ const compiledClientBoundaries = new Map();
140
+ const inventoriesBySourceEntry = new Map();
141
+
142
+ function getEntryInventory(entryPath) {
143
+ let inventory = inventoriesBySourceEntry.get(entryPath);
144
+ if (!inventory) {
145
+ inventory = collectClientGraphInventory([entryPath], { projectRoot });
146
+ inventoriesBySourceEntry.set(entryPath, inventory);
147
+ }
148
+ return inventory;
149
+ }
150
+
151
+ async function compileProductionClientBoundary(sourcePath) {
152
+ let clientModule = compiledClientBoundaries.get(sourcePath);
153
+ if (clientModule) return clientModule;
154
+ const clientBuild = await compileModuleGraph(sourcePath, {
155
+ projectRoot,
156
+ mode: "production",
157
+ sourceMaps: true,
158
+ target: "client",
159
+ });
160
+ clientModule = path.relative(clientBuild.outputRoot, clientBuild.entrypoint)
161
+ .split(path.sep)
162
+ .join("/");
163
+ compiledClientBoundaries.set(sourcePath, clientModule);
164
+ entryClientModules.add(clientModule);
165
+ return clientModule;
166
+ }
129
167
 
130
168
  await ensureDirectory(buildRoot);
131
169
 
@@ -168,16 +206,6 @@ export async function buildProject(projectRoot) {
168
206
  target: "server",
169
207
  });
170
208
 
171
- const pageClientBuild = await compileModuleGraph(route.page, {
172
- projectRoot,
173
- mode: "production",
174
- sourceMaps: true,
175
- target: "client",
176
- });
177
- entryClientModules.add(
178
- path.relative(pageClientBuild.outputRoot, pageClientBuild.entrypoint).split(path.sep).join("/"),
179
- );
180
-
181
209
  for (const layoutPath of route.layouts) {
182
210
  await compileModuleGraph(layoutPath, {
183
211
  projectRoot,
@@ -187,15 +215,6 @@ export async function buildProject(projectRoot) {
187
215
  target: "server",
188
216
  });
189
217
 
190
- const layoutClientBuild = await compileModuleGraph(layoutPath, {
191
- projectRoot,
192
- mode: "production",
193
- sourceMaps: true,
194
- target: "client",
195
- });
196
- entryClientModules.add(
197
- path.relative(layoutClientBuild.outputRoot, layoutClientBuild.entrypoint).split(path.sep).join("/"),
198
- );
199
218
  }
200
219
 
201
220
  const boundaryModules = [
@@ -210,23 +229,70 @@ export async function buildProject(projectRoot) {
210
229
  ssr: true,
211
230
  target: "server",
212
231
  });
232
+ }
213
233
 
214
- const boundaryClientBuild = await compileModuleGraph(boundaryPath, {
234
+ const toProjectRelative = (filePath) => path.relative(projectRoot, filePath).split(path.sep).join("/");
235
+ const segmentEntries = [...new Set([route.page, ...route.layouts, ...boundaryModules])];
236
+ const inventoriesByEntry = new Map(await Promise.all(segmentEntries.map(async (entryPath) => [
237
+ entryPath,
238
+ await getEntryInventory(entryPath),
239
+ ])));
240
+ const allStyles = new Set();
241
+ const allAssets = new Set();
242
+ const allClientBoundaries = new Set();
243
+ for (const [entryPath, entryInventory] of inventoriesByEntry) {
244
+ serverAssetImportsByEntry[toProjectRelative(entryPath)] = {
245
+ styles: entryInventory.styles.map((filePath) => getClientStaticAssetModule(projectRoot, filePath)),
246
+ assets: entryInventory.assets.map((filePath) => getClientStaticAssetModule(projectRoot, filePath)),
247
+ };
248
+ entryInventory.styles.forEach((filePath) => allStyles.add(filePath));
249
+ entryInventory.assets.forEach((filePath) => allAssets.add(filePath));
250
+ entryInventory.clientBoundaries.forEach((filePath) => allClientBoundaries.add(filePath));
251
+ }
252
+ await emitClientStaticAssets([...allStyles, ...allAssets], {
253
+ projectRoot,
254
+ mode: "production",
255
+ });
256
+ const compiledBoundaryModules = new Map();
257
+ for (const clientBoundary of allClientBoundaries) {
258
+ compiledBoundaryModules.set(
259
+ clientBoundary,
260
+ await compileProductionClientBoundary(clientBoundary),
261
+ );
262
+ }
263
+ for (const [entryPath, entryInventory] of inventoriesByEntry) {
264
+ clientBoundariesByEntry[toProjectRelative(entryPath)] = entryInventory.clientBoundaries
265
+ .map((clientBoundary) => compiledBoundaryModules.get(clientBoundary))
266
+ .filter(Boolean)
267
+ .sort();
268
+ }
269
+ }
270
+
271
+ const configuredClientBoundaries = evolitConfig.clientBoundaries ?? [];
272
+ if (!Array.isArray(configuredClientBoundaries)) {
273
+ throw new Error("Expected clientBoundaries in evolit.config.js to be an array of module specifiers.");
274
+ }
275
+ for (const specifier of configuredClientBoundaries) {
276
+ if (typeof specifier !== "string" || specifier.length === 0) {
277
+ throw new Error("Expected every clientBoundaries entry to be a non-empty module specifier.");
278
+ }
279
+ const sourcePath = path.isAbsolute(specifier)
280
+ ? specifier
281
+ : await resolveProjectModuleSpecifier(
215
282
  projectRoot,
216
- mode: "production",
217
- sourceMaps: true,
218
- target: "client",
219
- });
220
- entryClientModules.add(
221
- path.relative(boundaryClientBuild.outputRoot, boundaryClientBuild.entrypoint)
222
- .split(path.sep)
223
- .join("/"),
283
+ path.join(projectRoot, "evolit.config.js"),
284
+ specifier,
224
285
  );
286
+ if (!sourcePath) {
287
+ throw new Error(`Unable to resolve configured client boundary ${JSON.stringify(specifier)}.`);
225
288
  }
289
+ await compileProductionClientBoundary(sourcePath);
226
290
  }
227
291
 
228
292
  const clientAssets = await emitBundledClientAssets(projectRoot, {
229
293
  entryClientModules,
294
+ serverAssetImportsByEntry,
295
+ clientBoundariesByEntry,
230
296
  });
231
297
  const staticAssetPublicUrls = createStaticAssetPublicUrlMap(clientAssets);
232
298
  await rewriteServerAssetPlaceholders(projectRoot, clientAssets);
@@ -244,19 +310,68 @@ export async function buildProject(projectRoot) {
244
310
  );
245
311
  const ssrAdapter = createSsrAdapter({
246
312
  assetResolver,
313
+ async onSsrResult({ routeResult, result }) {
314
+ const renderedModules = [...new Set([
315
+ ...(Array.isArray(result.clientImports) ? result.clientImports : []),
316
+ ...(Array.isArray(result.hydrationData?.roots)
317
+ ? result.hydrationData.roots.map((root) => root?.moduleId)
318
+ : []),
319
+ ].filter((moduleId) => typeof moduleId === "string" && moduleId.length > 0))];
320
+ const unresolved = [];
321
+ for (const moduleId of renderedModules) {
322
+ if (assetResolver(moduleId) || clientAssets.byPublicPath?.[moduleId]) continue;
323
+ const importerPath = routeResult.boundaryModule
324
+ ?? routeResult.route?.page
325
+ ?? path.join(projectRoot, "app", "page.litsx");
326
+ let sourcePath = null;
327
+ if (moduleId.startsWith("file:")) {
328
+ try { sourcePath = fileURLToPath(moduleId); } catch {}
329
+ } else if (path.isAbsolute(moduleId) && !moduleId.startsWith("/app/") && !moduleId.startsWith("/src/")) {
330
+ sourcePath = moduleId;
331
+ } else if (moduleId.startsWith("/")) {
332
+ sourcePath = await resolveProjectModuleSpecifier(projectRoot, importerPath, `.${moduleId}`);
333
+ } else {
334
+ sourcePath = await resolveProjectModuleSpecifier(projectRoot, importerPath, moduleId);
335
+ }
336
+ const publicUrl = sourcePath ? assetResolver(sourcePath) : null;
337
+ if (!publicUrl) {
338
+ unresolved.push(moduleId);
339
+ continue;
340
+ }
341
+ for (const root of result.hydrationData?.roots ?? []) {
342
+ if (root?.moduleId === moduleId) root.moduleId = sourcePath;
343
+ }
344
+ if (Array.isArray(result.clientImports)) {
345
+ result.clientImports = result.clientImports.map((value) => value === moduleId ? publicUrl : value);
346
+ }
347
+ if (Array.isArray(result.hydrationData?.clientImports)) {
348
+ result.hydrationData.clientImports = result.hydrationData.clientImports
349
+ .map((value) => value === moduleId ? publicUrl : value);
350
+ }
351
+ }
352
+ if (unresolved.length > 0) {
353
+ throw new Error(
354
+ `SSR rendered client boundaries without build artifacts for ${routeResult.route?.pathname ?? "route"}: `
355
+ + `${unresolved.join(", ")}.`,
356
+ );
357
+ }
358
+ },
247
359
  async resolveAdditionalHead({ routeResult, result }) {
248
- const clientImports = [
360
+ const clientImports = [...new Set([
249
361
  ...(Array.isArray(result.clientImports) ? result.clientImports : []),
250
- ...resolveRouteClientImports(routeResult, projectRoot, clientAssets),
251
- ];
252
- const hydratedClientImports = Array.isArray(result.clientImports) ? result.clientImports : [];
362
+ ...resolveHydrationRootClientImports(result.hydrationData, assetResolver),
363
+ ])];
364
+ const hydratedClientImports = clientImports;
253
365
  const urls = hydratedClientImports.length > 0
254
366
  ? [...new Set([
255
367
  ...hydratedClientImports,
256
368
  ...collectTransitiveAssetPreloads(hydratedClientImports, clientAssets),
257
369
  ])]
258
370
  : [];
259
- const styleUrls = collectTransitiveStyleUrls(clientImports, clientAssets);
371
+ const styleUrls = [...new Set([
372
+ ...collectTransitiveStyleUrls(clientImports, clientAssets),
373
+ ...resolveServerStyleUrls(routeResult, projectRoot, clientAssets),
374
+ ])];
260
375
 
261
376
  return [
262
377
  ...urls.map((href) => `<link rel="modulepreload" href="${href}" data-evolit-route-asset="preload">`),
@@ -264,36 +379,23 @@ export async function buildProject(projectRoot) {
264
379
  ].join("\n");
265
380
  },
266
381
  resolveBootstrap({ routeResult, result }) {
267
- const routeClientImports = resolveRouteClientImports(
268
- routeResult,
269
- projectRoot,
270
- clientAssets,
271
- );
272
382
  return createHydrationBootstrap({
273
383
  hydrationData: normalizeHydrationDataForClient(
274
384
  result.hydrationData,
275
385
  projectRoot,
276
- routeClientImports,
386
+ resolveHydrationRootClientImports(result.hydrationData, assetResolver),
277
387
  ),
278
388
  assetResolver,
279
389
  hydrationModuleUrl,
280
390
  });
281
391
  },
282
392
  transformDocument({ routeResult, result, document }) {
283
- const routeClientImports = resolveRouteClientImports(
284
- routeResult,
285
- projectRoot,
286
- clientAssets,
287
- );
288
393
  return rewriteHydrationDataScript(
289
394
  document,
290
395
  normalizeHydrationDataForClient(
291
396
  result.hydrationData,
292
397
  projectRoot,
293
- [
294
- ...routeClientImports,
295
- ...resolveHydrationRootClientImports(result.hydrationData, assetResolver),
296
- ],
398
+ resolveHydrationRootClientImports(result.hydrationData, assetResolver),
297
399
  ),
298
400
  );
299
401
  },
@@ -6,6 +6,7 @@ import { createRequire } from "node:module";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import MagicString from "magic-string";
8
8
  import { rollup } from "rollup";
9
+ import commonjs from "@rollup/plugin-commonjs";
9
10
  import { nodeResolve } from "@rollup/plugin-node-resolve";
10
11
  import {
11
12
  BUILD_DIRECTORY,
@@ -54,6 +55,12 @@ export function resetDevelopmentAssetCaches(projectRoot) {
54
55
  }
55
56
  }
56
57
  }
58
+ const resolutionPrefix = `${normalizedProjectRoot}::`;
59
+ for (const cache of [browserSpecifierFilePathCache, packageRootCache]) {
60
+ for (const key of cache.keys()) {
61
+ if (key.startsWith(resolutionPrefix)) cache.delete(key);
62
+ }
63
+ }
57
64
  }
58
65
 
59
66
  function isBareSpecifier(specifier) {
@@ -125,13 +132,20 @@ function parsePackageSpecifier(specifier) {
125
132
  };
126
133
  }
127
134
 
128
- export async function resolvePackageRoot(packageName) {
129
- if (packageRootCache.has(packageName)) {
130
- return packageRootCache.get(packageName);
135
+ export async function resolvePackageRoot(packageName, options = {}) {
136
+ const resolveFrom = path.resolve(options.projectRoot ?? path.dirname(fileURLToPath(import.meta.url)));
137
+ const cacheKey = `${resolveFrom}::${packageName}`;
138
+ if (packageRootCache.has(cacheKey)) {
139
+ return packageRootCache.get(cacheKey);
131
140
  }
132
141
 
133
142
  const pendingResolution = (async () => {
134
- const packageEntryPath = requireFromHere.resolve(packageName);
143
+ let packageEntryPath;
144
+ try {
145
+ packageEntryPath = createRequire(path.join(resolveFrom, "package.json")).resolve(packageName);
146
+ } catch {
147
+ packageEntryPath = requireFromHere.resolve(packageName);
148
+ }
135
149
  let currentPath = path.dirname(packageEntryPath);
136
150
 
137
151
  while (true) {
@@ -157,12 +171,12 @@ export async function resolvePackageRoot(packageName) {
157
171
  throw new Error(`Unable to resolve package root for ${packageName}`);
158
172
  })();
159
173
 
160
- packageRootCache.set(packageName, pendingResolution);
174
+ packageRootCache.set(cacheKey, pendingResolution);
161
175
 
162
176
  try {
163
177
  return await pendingResolution;
164
178
  } catch (error) {
165
- packageRootCache.delete(packageName);
179
+ packageRootCache.delete(cacheKey);
166
180
  throw error;
167
181
  }
168
182
  }
@@ -184,13 +198,15 @@ function pickBrowserExportTarget(target) {
184
198
  ?? null;
185
199
  }
186
200
 
187
- export async function resolveBrowserSpecifierFilePath(specifier) {
201
+ export async function resolveBrowserSpecifierFilePath(specifier, options = {}) {
188
202
  if (!isBareSpecifier(specifier)) {
189
203
  return null;
190
204
  }
191
205
 
192
- if (browserSpecifierFilePathCache.has(specifier)) {
193
- return browserSpecifierFilePathCache.get(specifier);
206
+ const resolveFrom = path.resolve(options.projectRoot ?? path.dirname(fileURLToPath(import.meta.url)));
207
+ const cacheKey = `${resolveFrom}::${specifier}`;
208
+ if (browserSpecifierFilePathCache.has(cacheKey)) {
209
+ return browserSpecifierFilePathCache.get(cacheKey);
194
210
  }
195
211
 
196
212
  const pendingResolution = (async () => {
@@ -200,13 +216,32 @@ export async function resolveBrowserSpecifierFilePath(specifier) {
200
216
  }
201
217
 
202
218
  const { packageName, subpath } = parsedSpecifier;
203
- const { packageRoot, packageJson } = await resolvePackageRoot(packageName);
219
+ const { packageRoot, packageJson } = await resolvePackageRoot(packageName, { projectRoot: resolveFrom });
204
220
  const exportKey = subpath.length > 0 ? `./${subpath}` : ".";
205
- const exportTarget = pickBrowserExportTarget(packageJson.exports?.[exportKey]);
221
+ const exportsField = packageJson.exports;
222
+ const exportTarget = pickBrowserExportTarget(
223
+ exportsField && typeof exportsField === "object" && Object.keys(exportsField).some((key) => key.startsWith("."))
224
+ ? exportsField[exportKey]
225
+ : exportKey === "." ? exportsField : null,
226
+ );
206
227
  const fallbackTarget = subpath.length > 0
207
228
  ? `./${subpath}`
208
- : packageJson.module ?? packageJson.main ?? null;
209
- const resolvedTarget = exportTarget ?? fallbackTarget;
229
+ : typeof packageJson.browser === "string"
230
+ ? packageJson.browser
231
+ : packageJson.module ?? packageJson.main ?? null;
232
+ let resolvedTarget = exportTarget ?? fallbackTarget;
233
+ if (resolvedTarget && packageJson.browser && typeof packageJson.browser === "object") {
234
+ const normalizedTarget = resolvedTarget.startsWith("./")
235
+ ? resolvedTarget
236
+ : `./${resolvedTarget}`;
237
+ const browserTarget = packageJson.browser[normalizedTarget]
238
+ ?? packageJson.browser[resolvedTarget]
239
+ ?? packageJson.browser[exportKey];
240
+ if (browserTarget === false) {
241
+ throw new Error(`Browser entry is disabled for ${specifier}`);
242
+ }
243
+ if (typeof browserTarget === "string") resolvedTarget = browserTarget;
244
+ }
210
245
 
211
246
  if (typeof resolvedTarget !== "string" || resolvedTarget.length === 0) {
212
247
  throw new Error(`Unable to resolve browser entry for ${specifier}`);
@@ -215,12 +250,12 @@ export async function resolveBrowserSpecifierFilePath(specifier) {
215
250
  return path.resolve(packageRoot, resolvedTarget);
216
251
  })();
217
252
 
218
- browserSpecifierFilePathCache.set(specifier, pendingResolution);
253
+ browserSpecifierFilePathCache.set(cacheKey, pendingResolution);
219
254
 
220
255
  try {
221
256
  return await pendingResolution;
222
257
  } catch (error) {
223
- browserSpecifierFilePathCache.delete(specifier);
258
+ browserSpecifierFilePathCache.delete(cacheKey);
224
259
  throw error;
225
260
  }
226
261
  }
@@ -272,16 +307,22 @@ export async function collectSharedVendorSpecifiers(
272
307
  ])].sort();
273
308
  }
274
309
 
275
- async function writeSharedRuntimeEntrySources(entriesRoot, specifiers) {
310
+ async function createSharedRuntimeInputEntries(projectRoot, entriesRoot, specifiers) {
276
311
  await fs.rm(entriesRoot, { recursive: true, force: true });
277
312
  await ensureDirectory(entriesRoot);
278
313
 
279
314
  const inputEntries = {};
280
315
  for (const specifier of specifiers) {
281
316
  const entryName = createSharedEntryName(specifier);
282
- const entryPath = path.join(entriesRoot, `${entryName}.mjs`);
283
- await fs.writeFile(entryPath, createSharedEntrySource(specifier), "utf8");
284
- inputEntries[entryName] = entryPath;
317
+ if (specifier === "@litsx/ssr/hydration") {
318
+ const entryPath = path.join(entriesRoot, `${entryName}.mjs`);
319
+ await fs.writeFile(entryPath, createSharedEntrySource(specifier), "utf8");
320
+ inputEntries[entryName] = entryPath;
321
+ continue;
322
+ }
323
+ inputEntries[entryName] = specifier === "evolit/navigation"
324
+ ? EVOLIT_NAVIGATION_BROWSER_ENTRY
325
+ : await resolveBrowserSpecifierFilePath(specifier, { projectRoot });
285
326
  }
286
327
 
287
328
  return inputEntries;
@@ -508,7 +549,7 @@ export async function buildSharedVendorRuntime(
508
549
  const specifiers = await collectSharedVendorSpecifiers(additionalEntrySpecifiers, {
509
550
  includeBase: options.includeBase !== false,
510
551
  });
511
- const inputEntries = await writeSharedRuntimeEntrySources(entriesRoot, specifiers);
552
+ const inputEntries = await createSharedRuntimeInputEntries(projectRoot, entriesRoot, specifiers);
512
553
 
513
554
  await fs.rm(sharedRoot, { recursive: true, force: true });
514
555
  await ensureDirectory(sharedRoot);
@@ -534,7 +575,11 @@ export async function buildSharedVendorRuntime(
534
575
  extensions: [".mjs", ".js", ".json"],
535
576
  preferBuiltins: false,
536
577
  }),
578
+ commonjs({
579
+ transformMixedEsModules: true,
580
+ }),
537
581
  ],
582
+ preserveEntrySignatures: "strict",
538
583
  onwarn(warning, warn) {
539
584
  if (warning.code === "CIRCULAR_DEPENDENCY") {
540
585
  return;
@@ -1597,6 +1642,8 @@ export async function emitHashedClientAssets(projectRoot, options = {}) {
1597
1642
  chunks,
1598
1643
  styles,
1599
1644
  resources,
1645
+ serverAssetImportsByEntry: options.serverAssetImportsByEntry ?? {},
1646
+ clientBoundariesByEntry: options.clientBoundariesByEntry ?? {},
1600
1647
  assets: assets.sort((left, right) => left.clientModule.localeCompare(right.clientModule)),
1601
1648
  };
1602
1649
 
@@ -1893,6 +1940,13 @@ async function bundleClientAssets(projectRoot, options = {}) {
1893
1940
  [...scriptAssetRecords, ...nonScriptAssets, ...mapAssets]
1894
1941
  .map((asset) => asset.outputPath),
1895
1942
  );
1943
+ // Requests for an asset from the previous manifest can still be in flight
1944
+ // while a development rebuild publishes this one. Keep precisely that
1945
+ // generation for one rebuild so those requests do not resolve to a file
1946
+ // which this cleanup has just removed.
1947
+ for (const outputPath of options.retainOutputPaths ?? []) {
1948
+ retainedOutputPaths.add(outputPath);
1949
+ }
1896
1950
  for (const filePath of await walkFiles(staticRoot)) {
1897
1951
  if (!retainedOutputPaths.has(filePath)) {
1898
1952
  await fs.rm(filePath, { force: true });
@@ -1914,6 +1968,8 @@ async function bundleClientAssets(projectRoot, options = {}) {
1914
1968
  .filter((asset) => asset.kind === "asset" && !asset.clientModule.endsWith(".map"))
1915
1969
  .map((asset) => asset.clientModule)
1916
1970
  .sort(),
1971
+ serverAssetImportsByEntry: options.serverAssetImportsByEntry ?? {},
1972
+ clientBoundariesByEntry: options.clientBoundariesByEntry ?? {},
1917
1973
  assets,
1918
1974
  };
1919
1975
 
@@ -2092,6 +2148,40 @@ export function collectTransitiveStyleUrls(publicUrls, assetManifest) {
2092
2148
  return [...styles].sort();
2093
2149
  }
2094
2150
 
2151
+ function getServerAssetImports(routeResult, projectRoot, assetManifest) {
2152
+ if (routeResult?.serverAssetImports && typeof routeResult.serverAssetImports === "object") {
2153
+ return routeResult.serverAssetImports;
2154
+ }
2155
+
2156
+ const entryPaths = routeResult?.boundaryModule
2157
+ ? [routeResult.boundaryModule]
2158
+ : [
2159
+ ...(routeResult?.route?.layouts ?? []),
2160
+ routeResult?.route?.page,
2161
+ ].filter((entryPath) => typeof entryPath === "string");
2162
+ if (entryPaths.length === 0 || !assetManifest) {
2163
+ return { styles: [], assets: [] };
2164
+ }
2165
+ const styles = new Set();
2166
+ const assets = new Set();
2167
+ for (const entryPath of entryPaths) {
2168
+ const entry = path.relative(projectRoot, entryPath).split(path.sep).join("/");
2169
+ const imports = assetManifest.serverAssetImportsByEntry?.[entry];
2170
+ for (const style of imports?.styles ?? []) styles.add(style);
2171
+ for (const asset of imports?.assets ?? []) assets.add(asset);
2172
+ }
2173
+ return { styles: [...styles], assets: [...assets] };
2174
+ }
2175
+
2176
+ export function resolveServerStyleUrls(routeResult, projectRoot, assetManifest) {
2177
+ const normalizedManifest = normalizeClientAssetManifest(assetManifest);
2178
+ if (!normalizedManifest) return [];
2179
+ const imports = getServerAssetImports(routeResult, projectRoot, normalizedManifest);
2180
+ return [...new Set((imports.styles ?? [])
2181
+ .map((style) => normalizedManifest.byClientModule[style])
2182
+ .filter((url) => typeof url === "string"))].sort();
2183
+ }
2184
+
2095
2185
  export function normalizeClientAssetManifest(manifest) {
2096
2186
  if (!manifest || typeof manifest !== "object" || !Array.isArray(manifest.assets)) {
2097
2187
  return null;
@@ -2106,6 +2196,14 @@ export function normalizeClientAssetManifest(manifest) {
2106
2196
  chunks: Array.isArray(manifest.chunks) ? manifest.chunks : [],
2107
2197
  styles: Array.isArray(manifest.styles) ? manifest.styles : [],
2108
2198
  resources: Array.isArray(manifest.resources) ? manifest.resources : [],
2199
+ serverAssetImportsByEntry:
2200
+ manifest.serverAssetImportsByEntry && typeof manifest.serverAssetImportsByEntry === "object"
2201
+ ? manifest.serverAssetImportsByEntry
2202
+ : {},
2203
+ clientBoundariesByEntry:
2204
+ manifest.clientBoundariesByEntry && typeof manifest.clientBoundariesByEntry === "object"
2205
+ ? manifest.clientBoundariesByEntry
2206
+ : {},
2109
2207
  assets: manifest.assets,
2110
2208
  };
2111
2209
  }