evolit 0.3.0 → 0.4.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.
package/README.md CHANGED
@@ -321,6 +321,68 @@ server components and helpers that do not receive route props directly. It retur
321
321
  Reading `getRouteState().url` has the same dynamic-rendering semantics as `requestUrl()`; reading
322
322
  `params` and `searchParams` participates in the normal segment-cache key tracking.
323
323
 
324
+ ## LitSX compiler integrations
325
+
326
+ `evolit.config.js` can configure the native LitSX compiler and register build-tool-neutral LitSX
327
+ integrations. The same declaration is used by development, SSR, hydration, production builds and
328
+ the standalone runtime:
329
+
330
+ ```js
331
+ import { litsxUnoCss } from "@litsx/unocss";
332
+ import { defineEvolitConfig } from "evolit/litsx";
333
+
334
+ export default defineEvolitConfig({
335
+ litsx: {
336
+ compiler: {
337
+ sourceMaps: true,
338
+ },
339
+ integrations: [litsxUnoCss()],
340
+ },
341
+ });
342
+ ```
343
+
344
+ This is the complete Evolit + UnoCSS setup. The application does not coordinate compiler plugins,
345
+ virtual modules, generation, finalization or global CSS links, and it does not need Vite or
346
+ PostCSS. `@litsx/unocss` remains the owner of candidate extraction and CSS generation; Evolit only
347
+ executes its neutral LitSX lifecycle.
348
+
349
+ `compiler` accepts the public `TransformLitsxOptions` surface except the values Evolit must own for
350
+ correct server and browser graphs. Evolit always supplies the current `filename`, selects `ssr` per
351
+ target and forces native lowering with `reactCompat: false`; `compiler.sourceMaps` can override the
352
+ existing per-pipeline default without changing applications that omit `litsx`.
353
+ Authoring and output plugins contributed by the application and integrations are composed in
354
+ declaration order rather than replacing framework invariants.
355
+
356
+ An integration is a descriptor with a unique `name` and `create(context)` method. Each call to
357
+ `create` belongs to exactly one development server, build or standalone runtime, so mutable caches
358
+ must live on the returned instance, never on the descriptor. An instance can contribute:
359
+
360
+ - `compiler`: native compiler options, including authoring and output plugins.
361
+ - `resolveModule`: sources for integration-owned virtual modules in both server and browser graphs.
362
+ - `processModule`: post-processing, observable dependencies and preliminary outputs for a compiled
363
+ module.
364
+ - `finalize`: graph-wide assets, styles or modules after every reachable module is known.
365
+ - `invalidate` and `forget`: configuration/dependency refresh and removal of stale module state.
366
+ - `dispose`: idempotent cleanup when the owning server, build or runtime closes.
367
+
368
+ Declared outputs use stable IDs unique within the pipeline and one of `asset`, `module` or `style`. A module
369
+ may expose a safe `virtual:` specifier; a style with `document: true` is content-hashed, included in
370
+ the client asset manifest and linked once in the document. Evolit publishes a generation only after
371
+ every integration finalizes successfully, so a failed generation cannot expose a partial mix of
372
+ old and new assets.
373
+
374
+ In development, dependencies reported by compiler plugins and lifecycle hooks are watched together
375
+ with application modules. Invalidating a dependency advances the integration generation, evicts
376
+ affected compiled modules and calls `forget` for modules that leave the graph. Configuration reload
377
+ therefore removes obsolete results instead of accumulating them. Hook failures identify the
378
+ integration, lifecycle phase and relevant module in development; production errors keep the same
379
+ context while redacting project-local absolute paths.
380
+
381
+ For native Shadow DOM, `@litsx/unocss` serializes component preflight inside each Declarative Shadow
382
+ Root, preserves authored `Component.styles` before generated utilities, and emits the document
383
+ theme/custom-property layers once. Those document variables inherit through Shadow Roots and the
384
+ browser hydrates the server result without switching to `react-compat`.
385
+
324
386
  ## Server Setup
325
387
 
326
388
  Server integrations that need one-time application setup can be registered with
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "evolit",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "A convention-driven application framework for LitSX and web components.",
5
5
  "type": "module",
6
6
  "packageManager": "yarn@4.10.3",
@@ -34,6 +34,10 @@
34
34
  "browser": "./src/extensions-client.js",
35
35
  "default": "./src/extensions.js"
36
36
  },
37
+ "./litsx": {
38
+ "types": "./src/litsx-pipeline.d.ts",
39
+ "default": "./src/litsx-pipeline.js"
40
+ },
37
41
  "./internal/development-hot": "./src/development-hot-client.js"
38
42
  },
39
43
  "scripts": {
@@ -79,8 +83,10 @@
79
83
  }
80
84
  },
81
85
  "devDependencies": {
86
+ "@litsx/unocss": "1.0.0-next.8",
82
87
  "@playwright/test": "^1.62.0",
83
88
  "@webcomponents/scoped-custom-element-registry": "^0.0.10",
89
+ "unocss": "66.8.1",
84
90
  "vite": "^8.1.5"
85
91
  }
86
92
  }
package/src/build.js CHANGED
@@ -58,6 +58,7 @@ import {
58
58
  runWithOptionalSsrUrqlScope,
59
59
  } from "./urql-ssr.js";
60
60
  import { runServerSetup } from "./server-setup.js";
61
+ import { createLitsxPipeline, mergeLitsxAssetsIntoManifest } from "./litsx-pipeline.js";
61
62
 
62
63
  const CONTENT_TYPE_BY_EXTENSION = new Map([
63
64
  [".css", "text/css; charset=utf-8"],
@@ -163,11 +164,20 @@ async function writeDeploymentRuntimeEntry(buildRoot) {
163
164
 
164
165
  export async function buildProject(projectRoot, options = {}) {
165
166
  const evolitConfig = await loadEvolitConfig(projectRoot);
166
- const serverSetupCleanup = await runServerSetup({
167
+ const litsxPipeline = await createLitsxPipeline({
167
168
  projectRoot,
168
169
  mode: "production",
169
- evolitConfig,
170
+ config: evolitConfig,
170
171
  });
172
+ let serverSetupCleanup = null;
173
+ let buildError = null;
174
+ try {
175
+ serverSetupCleanup = await runServerSetup({
176
+ projectRoot,
177
+ mode: "production",
178
+ evolitConfig,
179
+ litsxPipeline,
180
+ });
171
181
  const extensions = resolveEvolitExtensions(evolitConfig);
172
182
  const extensionClientDescriptors = getExtensionClientDescriptors(extensions);
173
183
  const packageClientSpecifiers = new Set(
@@ -194,6 +204,7 @@ export async function buildProject(projectRoot, options = {}) {
194
204
  sourceMaps: false,
195
205
  ssr: true,
196
206
  target: "server",
207
+ litsxPipeline,
197
208
  });
198
209
  result.packageImports.forEach((specifier) => ssrPackageImports.add(specifier));
199
210
  return result;
@@ -202,7 +213,7 @@ export async function buildProject(projectRoot, options = {}) {
202
213
  function getEntryInventory(entryPath) {
203
214
  let inventory = inventoriesBySourceEntry.get(entryPath);
204
215
  if (!inventory) {
205
- inventory = collectClientGraphInventory([entryPath], { projectRoot });
216
+ inventory = collectClientGraphInventory([entryPath], { projectRoot, litsxPipeline });
206
217
  inventoriesBySourceEntry.set(entryPath, inventory);
207
218
  }
208
219
  return inventory;
@@ -216,6 +227,7 @@ export async function buildProject(projectRoot, options = {}) {
216
227
  mode: "production",
217
228
  sourceMaps: true,
218
229
  target: "client",
230
+ litsxPipeline,
219
231
  });
220
232
  clientModule = path.relative(clientBuild.outputRoot, clientBuild.entrypoint)
221
233
  .split(path.sep)
@@ -235,6 +247,7 @@ export async function buildProject(projectRoot, options = {}) {
235
247
  mode: "production",
236
248
  ssr: true,
237
249
  target: "server",
250
+ litsxPipeline,
238
251
  });
239
252
  const methods = Object.keys(handlerModule)
240
253
  .filter((name) => /^[A-Z]+$/.test(name) && typeof handlerModule[name] === "function")
@@ -345,18 +358,20 @@ export async function buildProject(projectRoot, options = {}) {
345
358
  }
346
359
 
347
360
  sharedVendorOptions.additionalEntrySpecifiers = [...packageClientSpecifiers].sort();
348
- const clientAssets = await emitBundledClientAssets(projectRoot, {
361
+ let clientAssets = await emitBundledClientAssets(projectRoot, {
349
362
  entryClientModules,
350
363
  additionalVendorSpecifiers: sharedVendorOptions.additionalEntrySpecifiers,
351
364
  serverAssetImportsByEntry,
352
365
  clientBoundariesByEntry,
353
366
  });
367
+ const integrationOutputs = await litsxPipeline?.finalize({
368
+ routes,
369
+ routeHandlers,
370
+ });
371
+ clientAssets = mergeLitsxAssetsIntoManifest(clientAssets, integrationOutputs);
354
372
  const staticAssetPublicUrls = createStaticAssetPublicUrlMap(clientAssets);
355
373
  await rewriteServerAssetPlaceholders(projectRoot, clientAssets);
356
374
 
357
- const routeResolver = await createRouteResolver(projectRoot, "production", {
358
- staticAssetPublicUrls,
359
- });
360
375
  const sharedRuntime = await buildSharedVendorRuntime(projectRoot, "production", sharedVendorOptions);
361
376
  clientAssets.sharedImports = { ...sharedRuntime.imports };
362
377
  const packageImports = sharedRuntime.imports;
@@ -364,6 +379,11 @@ export async function buildProject(projectRoot, options = {}) {
364
379
  assetManifest: clientAssets,
365
380
  packageImports,
366
381
  });
382
+ const routeResolver = await createRouteResolver(projectRoot, "production", {
383
+ staticAssetPublicUrls,
384
+ assetResolver,
385
+ litsxPipeline,
386
+ });
367
387
  const hydrationModuleUrl = await resolveSharedVendorModuleUrl(
368
388
  projectRoot,
369
389
  "production",
@@ -423,7 +443,21 @@ export async function buildProject(projectRoot, options = {}) {
423
443
  }
424
444
  continue;
425
445
  }
426
- if (assetResolver(moduleId) || clientAssets.byPublicPath?.[moduleId]) continue;
446
+ const directPublicUrl = assetResolver(moduleId)
447
+ ?? (clientAssets.byPublicPath?.[moduleId] ? moduleId : null);
448
+ if (directPublicUrl) {
449
+ if (Array.isArray(result.clientImports)) {
450
+ result.clientImports = result.clientImports.map((value) => (
451
+ value === moduleId ? directPublicUrl : value
452
+ ));
453
+ }
454
+ if (Array.isArray(result.hydrationData?.clientImports)) {
455
+ result.hydrationData.clientImports = result.hydrationData.clientImports.map((value) => (
456
+ value === moduleId ? directPublicUrl : value
457
+ ));
458
+ }
459
+ continue;
460
+ }
427
461
  const importerPath = routeResult.boundaryModule
428
462
  ?? routeResult.route?.page
429
463
  ?? path.join(projectRoot, "app", "page.jsx");
@@ -473,6 +507,7 @@ export async function buildProject(projectRoot, options = {}) {
473
507
  ])]
474
508
  : [];
475
509
  const styleUrls = [...new Set([
510
+ ...(clientAssets.documentStyles ?? []),
476
511
  ...collectTransitiveStyleUrls(clientImports, clientAssets),
477
512
  ...resolveServerStyleUrls(routeResult, projectRoot, clientAssets),
478
513
  ])];
@@ -650,6 +685,11 @@ export async function buildProject(projectRoot, options = {}) {
650
685
  cache: sortedRouteCache.find((entry) => entry.pathname === artifact.routePathname)?.cache
651
686
  ?? "static",
652
687
  })),
688
+ ...(integrationOutputs?.assets ?? []).map((asset) => ({
689
+ kind: asset.kind,
690
+ outputPath: toBuildRelativePath(projectRoot, asset.outputPath),
691
+ integration: asset.integration,
692
+ })),
653
693
  ],
654
694
  };
655
695
  const manifestPath = path.join(buildRoot, MANIFEST_FILENAME);
@@ -676,7 +716,22 @@ export async function buildProject(projectRoot, options = {}) {
676
716
  await writeJson(path.join(buildRoot, DEPLOY_ASSETS_MANIFEST_FILENAME), deployAssets);
677
717
  await writeJson(path.join(buildRoot, DEPLOY_SERVER_MANIFEST_FILENAME), deployServer);
678
718
 
679
- await serverSetupCleanup?.();
680
-
681
719
  return manifestPath;
720
+ } catch (error) {
721
+ buildError = error;
722
+ throw error;
723
+ } finally {
724
+ let cleanupError = null;
725
+ try {
726
+ await serverSetupCleanup?.();
727
+ } catch (error) {
728
+ cleanupError = error;
729
+ }
730
+ try {
731
+ await litsxPipeline?.dispose();
732
+ } catch (error) {
733
+ cleanupError ??= error;
734
+ }
735
+ if (!buildError && cleanupError) throw cleanupError;
736
+ }
682
737
  }
@@ -2483,6 +2483,7 @@ export function normalizeClientAssetManifest(manifest) {
2483
2483
  manifest.clientBoundariesByEntry && typeof manifest.clientBoundariesByEntry === "object"
2484
2484
  ? manifest.clientBoundariesByEntry
2485
2485
  : {},
2486
+ documentStyles: Array.isArray(manifest.documentStyles) ? manifest.documentStyles : [],
2486
2487
  assets: manifest.assets,
2487
2488
  };
2488
2489
  }
package/src/compiler.js CHANGED
@@ -182,14 +182,28 @@ async function transformModuleSource(source, {
182
182
  sourcePath,
183
183
  sourceMaps,
184
184
  ssr = false,
185
+ target = ssr ? "server" : "client",
186
+ litsxPipeline = null,
185
187
  }) {
186
188
  if (isLitsxAuthoredModule(sourcePath, source)) {
187
- const transformed = await transformLitsx(source, {
188
- filename: sourcePath,
189
- sourceMaps,
190
- ssr,
191
- });
192
- return restoreSideEffectImports(source, sourcePath, transformed, sourceMaps);
189
+ const compilerOptions = litsxPipeline
190
+ ? litsxPipeline.getCompilerOptions({ filename: sourcePath, sourceMaps, ssr })
191
+ : { filename: sourcePath, sourceMaps, ssr };
192
+ const transformed = restoreSideEffectImports(
193
+ source,
194
+ sourcePath,
195
+ await transformLitsx(source, compilerOptions),
196
+ compilerOptions.sourceMaps === true,
197
+ );
198
+ return litsxPipeline
199
+ ? litsxPipeline.processModule(transformed, {
200
+ source,
201
+ sourcePath,
202
+ sourceMaps: compilerOptions.sourceMaps === true,
203
+ ssr: ssr === true,
204
+ target,
205
+ })
206
+ : transformed;
193
207
  }
194
208
 
195
209
  if (sourcePath.endsWith(".ts")) {
@@ -963,6 +977,7 @@ async function rewriteRelativeSpecifiers({
963
977
  serverExportsByModule,
964
978
  packageImports,
965
979
  dynamicImportDirectories,
980
+ litsxPipeline,
966
981
  }) {
967
982
  const magicSource = new MagicString(code);
968
983
  let didRewrite = false;
@@ -974,10 +989,19 @@ async function rewriteRelativeSpecifiers({
974
989
  for (const reference of moduleReferences.staticReferences) {
975
990
  const { specifier } = reference;
976
991
 
977
- const aliasedImportPath = isBareSpecifier(specifier)
992
+ const virtualImportPath = litsxPipeline
993
+ ? await litsxPipeline.resolveModule(specifier, {
994
+ importer: sourcePath,
995
+ target,
996
+ ssr: target === "server",
997
+ sourceMaps,
998
+ })
999
+ : null;
1000
+
1001
+ const aliasedImportPath = !virtualImportPath && isBareSpecifier(specifier)
978
1002
  ? await resolveProjectMappedImport(projectRoot, sourcePath, specifier)
979
1003
  : null;
980
- const packageImportPath = isBareSpecifier(specifier) && !aliasedImportPath
1004
+ const packageImportPath = !virtualImportPath && isBareSpecifier(specifier) && !aliasedImportPath
981
1005
  ? await resolveProjectPackageImport(projectRoot, sourcePath, specifier)
982
1006
  : null;
983
1007
  const packageAssetPath = packageImportPath && isStaticAssetPath(packageImportPath)
@@ -987,6 +1011,7 @@ async function rewriteRelativeSpecifiers({
987
1011
  if (
988
1012
  target === "server"
989
1013
  && isBareSpecifier(specifier)
1014
+ && !virtualImportPath
990
1015
  && !aliasedImportPath
991
1016
  && !packageAssetPath
992
1017
  ) {
@@ -996,6 +1021,7 @@ async function rewriteRelativeSpecifiers({
996
1021
  if (
997
1022
  target === "client"
998
1023
  && isBareSpecifier(specifier)
1024
+ && !virtualImportPath
999
1025
  && !aliasedImportPath
1000
1026
  && !packageAssetPath
1001
1027
  ) {
@@ -1010,11 +1036,12 @@ async function rewriteRelativeSpecifiers({
1010
1036
  continue;
1011
1037
  }
1012
1038
 
1013
- if (!isRelativeSpecifier(specifier) && !aliasedImportPath && !packageAssetPath) {
1039
+ if (!virtualImportPath && !isRelativeSpecifier(specifier) && !aliasedImportPath && !packageAssetPath) {
1014
1040
  continue;
1015
1041
  }
1016
1042
 
1017
- const resolvedImportPath = packageAssetPath
1043
+ const resolvedImportPath = virtualImportPath
1044
+ ?? packageAssetPath
1018
1045
  ?? aliasedImportPath
1019
1046
  ?? await resolveImportPath(sourcePath, specifier);
1020
1047
  if (!resolvedImportPath) {
@@ -1033,6 +1060,9 @@ async function rewriteRelativeSpecifiers({
1033
1060
 
1034
1061
  if (shouldCompileModule(resolvedImportPath)) {
1035
1062
  compiledImportPath = await compileModule(resolvedImportPath);
1063
+ if (virtualImportPath) {
1064
+ litsxPipeline?.registerResolvedModule(specifier, resolvedImportPath, compiledImportPath);
1065
+ }
1036
1066
  const serverExports = serverExportsByModule?.get(resolvedImportPath) ?? new Set();
1037
1067
  const importedNames = importedNamesBySpecifier.get(specifier) ?? new Set();
1038
1068
  if (
@@ -1245,6 +1275,7 @@ function getDevelopmentGraphCacheKey(entryPath, options) {
1245
1275
  options.target ?? "server",
1246
1276
  options.ssr === true ? "ssr" : "client",
1247
1277
  options.sourceMaps === false ? "without-maps" : "with-maps",
1278
+ options.litsxPipeline?.identity?.id ?? "default-litsx",
1248
1279
  ].join("::");
1249
1280
  }
1250
1281
 
@@ -1358,6 +1389,7 @@ async function compileModuleGraphUncached(entryPath, options = {}) {
1358
1389
  staticAssetPublicUrls = null,
1359
1390
  onDevelopmentEvent,
1360
1391
  managedSourceRoots,
1392
+ litsxPipeline,
1361
1393
  } = options;
1362
1394
 
1363
1395
  const outputRoot = getTypedOutputRoot(projectRoot, mode, target);
@@ -1394,6 +1426,8 @@ async function compileModuleGraphUncached(entryPath, options = {}) {
1394
1426
  sourcePath,
1395
1427
  sourceMaps,
1396
1428
  ssr,
1429
+ target,
1430
+ litsxPipeline,
1397
1431
  });
1398
1432
  const isServerComponentModule = isCompiledServerComponentModule(transformed.code);
1399
1433
  const isMixedComponentModule = isServerComponentModule
@@ -1439,6 +1473,7 @@ async function compileModuleGraphUncached(entryPath, options = {}) {
1439
1473
  serverExportsByModule,
1440
1474
  packageImports,
1441
1475
  dynamicImportDirectories,
1476
+ litsxPipeline,
1442
1477
  });
1443
1478
 
1444
1479
  await fs.writeFile(outputPath, rewritten.code, "utf8");
@@ -1467,7 +1502,12 @@ async function compileModuleGraphUncached(entryPath, options = {}) {
1467
1502
  return {
1468
1503
  entrypoint: await compileModule(entryPath),
1469
1504
  outputRoot,
1470
- sourceFiles: [...new Set([...visited.keys(), ...staticAssetFiles])],
1505
+ sourceFiles: [...new Set([
1506
+ ...visited.keys(),
1507
+ ...staticAssetFiles,
1508
+ ...(litsxPipeline?.dependencies ?? []),
1509
+ ])],
1510
+ integrationDependencies: litsxPipeline?.dependencies ?? [],
1471
1511
  watchedDirectories: [...dynamicImportDirectories],
1472
1512
  packageImports: [...packageImports].sort(),
1473
1513
  };
@@ -1625,6 +1665,8 @@ export async function collectClientGraphInventory(entryPaths, options = {}) {
1625
1665
  projectRoot,
1626
1666
  sourcePath,
1627
1667
  sourceMaps: false,
1668
+ target: "client",
1669
+ litsxPipeline: options.litsxPipeline,
1628
1670
  });
1629
1671
  const isServer = isCompiledServerComponentModule(transformed.code);
1630
1672
  const isComponent = isCompiledClientBoundaryModule(transformed.code);
@@ -53,6 +53,7 @@ import {
53
53
  runRequestExtensions,
54
54
  } from "./extensions.js";
55
55
  import { runServerSetup } from "./server-setup.js";
56
+ import { createLitsxPipeline, mergeLitsxAssetsIntoManifest } from "./litsx-pipeline.js";
56
57
 
57
58
  function isBarePackageModuleId(value) {
58
59
  return typeof value === "string"
@@ -259,6 +260,7 @@ export async function createRequestRenderer({
259
260
  onDevelopmentEvent,
260
261
  managedSourceRoots,
261
262
  extensions = [],
263
+ litsxPipeline = null,
262
264
  }) {
263
265
  let currentAssetManifest = normalizeClientAssetManifest(assetManifest);
264
266
  const extensionClientDescriptors = getExtensionClientDescriptors(extensions);
@@ -325,7 +327,11 @@ export async function createRequestRenderer({
325
327
  function getDevelopmentEntryInventory(entryPath) {
326
328
  let inventory = devInventoryByEntry.get(entryPath);
327
329
  if (!inventory) {
328
- inventory = collectClientGraphInventory([entryPath], { projectRoot, managedSourceRoots });
330
+ inventory = collectClientGraphInventory([entryPath], {
331
+ projectRoot,
332
+ managedSourceRoots,
333
+ litsxPipeline,
334
+ });
329
335
  devInventoryByEntry.set(entryPath, inventory);
330
336
  inventory.catch(() => {
331
337
  if (devInventoryByEntry.get(entryPath) === inventory) devInventoryByEntry.delete(entryPath);
@@ -337,6 +343,7 @@ export async function createRequestRenderer({
337
343
  const resolver = await createRouteResolver(projectRoot, mode, {
338
344
  onDevelopmentEvent,
339
345
  managedSourceRoots,
346
+ litsxPipeline,
340
347
  segmentRenderCache: sharedSegmentRenderCache,
341
348
  getStaticAssetPublicUrls() {
342
349
  return createStaticAssetPublicUrlMap(currentAssetManifest);
@@ -359,6 +366,7 @@ export async function createRequestRenderer({
359
366
  target: "client",
360
367
  onDevelopmentEvent,
361
368
  managedSourceRoots,
369
+ litsxPipeline,
362
370
  });
363
371
  devPreparedClientModules.add(clientModuleKey);
364
372
  devClientModuleDependencies.set(
@@ -387,7 +395,13 @@ export async function createRequestRenderer({
387
395
  clientBoundariesByEntry: devClientBoundariesByEntry,
388
396
  additionalVendorSpecifiers: [...packageClientSpecifiers],
389
397
  });
390
- currentAssetManifest = bundledClientAssets.manifest;
398
+ const integrationOutputs = await litsxPipeline?.finalize({
399
+ assetManifest: bundledClientAssets.manifest,
400
+ });
401
+ currentAssetManifest = mergeLitsxAssetsIntoManifest(
402
+ bundledClientAssets.manifest,
403
+ integrationOutputs,
404
+ );
391
405
  devPreviousAssetOutputPaths = new Set();
392
406
  currentAssetResolver = createAssetResolver(projectRoot, {
393
407
  assetManifest: currentAssetManifest,
@@ -625,6 +639,7 @@ export async function createRequestRenderer({
625
639
  : [];
626
640
  const styleUrls = currentAssetManifest
627
641
  ? [...new Set([
642
+ ...(currentAssetManifest.documentStyles ?? []),
628
643
  ...collectTransitiveStyleUrls(clientImports, currentAssetManifest),
629
644
  ...resolveServerStyleUrls(routeResult, projectRoot, currentAssetManifest),
630
645
  ])]
@@ -857,6 +872,7 @@ export async function createRequestRenderer({
857
872
  target: "client",
858
873
  onDevelopmentEvent,
859
874
  managedSourceRoots,
875
+ litsxPipeline,
860
876
  });
861
877
  }
862
878
  return;
@@ -936,7 +952,10 @@ export async function createDeploymentRuntime({
936
952
  evolitConfig: configuredEvolitConfig,
937
953
  } = {}) {
938
954
  const evolitConfig = configuredEvolitConfig ?? await loadEvolitConfig(projectRoot);
939
- const serverSetupCleanup = await runServerSetup({ projectRoot, mode, evolitConfig });
955
+ const litsxPipeline = await createLitsxPipeline({ projectRoot, mode, config: evolitConfig });
956
+ let serverSetupCleanup = null;
957
+ try {
958
+ serverSetupCleanup = await runServerSetup({ projectRoot, mode, evolitConfig, litsxPipeline });
940
959
  const extensions = resolveEvolitExtensions(evolitConfig);
941
960
  const extensionClientSpecifiers = getExtensionClientDescriptors(extensions).map((descriptor) => descriptor.module);
942
961
  const effectiveManagedSourceRoots = managedSourceRoots
@@ -994,18 +1013,26 @@ export async function createDeploymentRuntime({
994
1013
  onDevelopmentEvent,
995
1014
  managedSourceRoots: effectiveManagedSourceRoots,
996
1015
  extensions,
1016
+ litsxPipeline,
997
1017
  });
1018
+ let closed = false;
998
1019
 
999
1020
  return {
1000
1021
  assets,
1001
1022
  cache: renderer.responseCacheController,
1002
1023
  renderer,
1024
+ get litsxDependencies() {
1025
+ return litsxPipeline?.dependencies ?? [];
1026
+ },
1003
1027
  async invalidateDevelopmentState(changedPaths = null) {
1004
1028
  if (mode !== "development") {
1005
1029
  return;
1006
1030
  }
1007
1031
 
1008
- const invalidationResult = await renderer.invalidateDevelopmentState(changedPaths);
1032
+ const litsxInvalidated = await litsxPipeline?.invalidate(changedPaths);
1033
+ const invalidationResult = await renderer.invalidateDevelopmentState(
1034
+ litsxInvalidated ? null : changedPaths,
1035
+ );
1009
1036
  if (typeof effectiveResponseCacheRuntime.store.clear === "function") {
1010
1037
  await effectiveResponseCacheRuntime.store.clear();
1011
1038
  }
@@ -1075,6 +1102,7 @@ export async function createDeploymentRuntime({
1075
1102
  const { routeResult, response } = await renderer.renderRoute(resolvedRequest, routePolicyResult, {
1076
1103
  extensionState: extensionResult,
1077
1104
  });
1105
+ await litsxPipeline?.finalize({ routeResult, assetManifest: renderer.assetManifest });
1078
1106
  runtimeState.assetManifest = normalizeClientAssetManifest(renderer.assetManifest);
1079
1107
  return formatResponseForRequest(
1080
1108
  request,
@@ -1082,13 +1110,39 @@ export async function createDeploymentRuntime({
1082
1110
  );
1083
1111
  },
1084
1112
  async close() {
1113
+ if (closed) return;
1114
+ closed = true;
1115
+ let cleanupError = null;
1085
1116
  const revalidationTasks = this.revalidationTasks;
1086
1117
  if (revalidationTasks) {
1087
- await Promise.all(revalidationTasks.values());
1118
+ try {
1119
+ await Promise.all(revalidationTasks.values());
1120
+ } catch (error) {
1121
+ cleanupError = error;
1122
+ }
1088
1123
  }
1089
- await serverSetupCleanup?.();
1124
+ try {
1125
+ await serverSetupCleanup?.();
1126
+ } catch (error) {
1127
+ cleanupError = error;
1128
+ }
1129
+ try {
1130
+ await litsxPipeline?.dispose();
1131
+ } catch (error) {
1132
+ cleanupError ??= error;
1133
+ }
1134
+ if (cleanupError) throw cleanupError;
1090
1135
  },
1091
1136
  };
1137
+ } catch (error) {
1138
+ try {
1139
+ await serverSetupCleanup?.();
1140
+ } catch {}
1141
+ try {
1142
+ await litsxPipeline?.dispose();
1143
+ } catch {}
1144
+ throw error;
1145
+ }
1092
1146
  }
1093
1147
 
1094
1148
  function addNavigationVaryHeader(response) {
package/src/index.js CHANGED
@@ -124,6 +124,9 @@ export { getRequestContext } from "./request-context.js";
124
124
  /** Defines an Evolit extension for use in evolit.config.js. */
125
125
  export { defineEvolitPlugin } from "./extensions.js";
126
126
 
127
+ /** Defines a typed Evolit configuration, including the native LitSX pipeline. */
128
+ export { defineEvolitConfig, defineLitsxIntegration } from "./litsx-pipeline.js";
129
+
127
130
  /**
128
131
  * Returns the incoming request URL. Reading it makes the current route dynamic.
129
132
  *