evolit 0.4.4 → 0.4.5

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
@@ -444,12 +444,17 @@ configuration therefore stays isolated per render, extracted SSR data is read
444
444
  before the scope closes, and integration response headers are included before
445
445
  the response is cached.
446
446
 
447
- The supported SSR combination is `evolit@0.4.4` or newer,
447
+ The supported SSR combination is `evolit@0.4.5` or newer,
448
448
  `@litsx/core@1.0.0-next.8` or newer, and `@litsx/urql@0.4.2` or newer. Evolit
449
449
  resolves the server-conditioned URQL adapter from the application project and
450
450
  uses that same module instance for setup, request resources, route rendering,
451
451
  SSR extraction, and cleanup.
452
452
 
453
+ The concurrent UnoCSS + URQL regression gate is validated with
454
+ `evolit@0.4.5`, `@litsx/compiler@1.0.0-next.13`,
455
+ `@litsx/ssr@1.0.0-next.5`, `@litsx/unocss@1.0.0-next.8`,
456
+ `@litsx/urql@0.4.2`, and `unocss@66.8.1`.
457
+
453
458
  ## Extensions
454
459
 
455
460
  Optional integrations are configured explicitly in `evolit.config.js`. Core only coordinates their
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "evolit",
3
- "version": "0.4.4",
3
+ "version": "0.4.5",
4
4
  "description": "A convention-driven application framework for LitSX and web components.",
5
5
  "type": "module",
6
6
  "packageManager": "yarn@4.10.3",
package/src/compiler.js CHANGED
@@ -28,6 +28,7 @@ const RESOLVABLE_IMPORT_EXTENSIONS = [
28
28
  const developmentGraphCache = new Map();
29
29
  const developmentGraphDependencies = new Map();
30
30
  const developmentModuleNamespaceCache = new Map();
31
+ const productionModuleNamespaceCache = new Map();
31
32
  const developmentGraphVersions = new Map();
32
33
  const developmentUnmanagedImportWarnings = new Set();
33
34
  const projectPathAliasesCache = new Map();
@@ -1785,34 +1786,34 @@ export async function emitClientStaticAssets(assetPaths, options = {}) {
1785
1786
 
1786
1787
  export async function importCompiledModule(entryPath, options = {}) {
1787
1788
  const mode = options.mode ?? "development";
1788
- const normalizedOptions = mode === "development"
1789
- ? { ...options, projectRoot: path.resolve(options.projectRoot ?? process.cwd()) }
1790
- : options;
1791
- const cacheKey = mode === "development"
1792
- ? getDevelopmentGraphCacheKey(path.resolve(entryPath), normalizedOptions)
1793
- : null;
1794
- const cachedModule = cacheKey ? developmentModuleNamespaceCache.get(cacheKey) : null;
1789
+ const normalizedOptions = {
1790
+ ...options,
1791
+ projectRoot: path.resolve(options.projectRoot ?? process.cwd()),
1792
+ };
1793
+ const cacheKey = getDevelopmentGraphCacheKey(path.resolve(entryPath), normalizedOptions);
1794
+ const moduleCache = mode === "development"
1795
+ ? developmentModuleNamespaceCache
1796
+ : productionModuleNamespaceCache;
1797
+ const cachedModule = moduleCache.get(cacheKey);
1795
1798
  if (cachedModule) {
1796
1799
  return cachedModule;
1797
1800
  }
1798
1801
 
1799
- const { entrypoint } = await compileModuleGraph(entryPath, normalizedOptions);
1800
- const moduleUrl = new URL(pathToFileURL(entrypoint).href);
1801
-
1802
- if (mode === "development") {
1803
- moduleUrl.searchParams.set("t", String(developmentGraphVersions.get(cacheKey) ?? 0));
1804
- }
1805
-
1806
- const pendingModule = import(moduleUrl.href);
1807
- if (cacheKey) {
1808
- developmentModuleNamespaceCache.set(cacheKey, pendingModule);
1809
- }
1802
+ const pendingModule = (async () => {
1803
+ const { entrypoint } = await compileModuleGraph(entryPath, normalizedOptions);
1804
+ const moduleUrl = new URL(pathToFileURL(entrypoint).href);
1805
+ if (mode === "development") {
1806
+ moduleUrl.searchParams.set("t", String(developmentGraphVersions.get(cacheKey) ?? 0));
1807
+ }
1808
+ return import(moduleUrl.href);
1809
+ })();
1810
+ moduleCache.set(cacheKey, pendingModule);
1810
1811
 
1811
1812
  try {
1812
1813
  return await pendingModule;
1813
1814
  } catch (error) {
1814
- if (developmentModuleNamespaceCache.get(cacheKey) === pendingModule) {
1815
- developmentModuleNamespaceCache.delete(cacheKey);
1815
+ if (moduleCache.get(cacheKey) === pendingModule) {
1816
+ moduleCache.delete(cacheKey);
1816
1817
  }
1817
1818
  throw error;
1818
1819
  }
@@ -206,8 +206,13 @@ export async function createLitsxPipeline({ projectRoot, mode, config } = {}) {
206
206
  const instances = [];
207
207
  const names = new Set();
208
208
  let disposed = false;
209
+ let closing = false;
209
210
  let generation = 0;
210
211
  let finalizedGeneration = -1;
212
+ let finalizationFlight = null;
213
+ let invalidationFlight = null;
214
+ let queuedFinalization = null;
215
+ let disposalPromise = null;
211
216
  const dependencies = new Set();
212
217
  const processedModules = new Set();
213
218
  const moduleDependencies = new Map();
@@ -288,7 +293,7 @@ export async function createLitsxPipeline({ projectRoot, mode, config } = {}) {
288
293
  }
289
294
 
290
295
  function assertActive() {
291
- if (disposed) throw new Error("This LitSX pipeline has already been disposed.");
296
+ if (closing || disposed) throw new Error("This LitSX pipeline has already been disposed.");
292
297
  }
293
298
 
294
299
  function recordContribution(integrationName, moduleId, contribution) {
@@ -496,16 +501,43 @@ export async function createLitsxPipeline({ projectRoot, mode, config } = {}) {
496
501
  evolitDependencies: [...(moduleDependencies.get(moduleId) ?? [])].sort(),
497
502
  };
498
503
  },
499
- async finalize(context = {}) {
504
+ finalize(context = {}) {
500
505
  assertActive();
501
- if (finalizedGeneration === generation) return null;
506
+ if (invalidationFlight) {
507
+ if (queuedFinalization) return queuedFinalization.promise;
508
+ const awaitedInvalidation = invalidationFlight.promise;
509
+ const promise = awaitedInvalidation.then(() => {
510
+ if (queuedFinalization?.promise === promise) queuedFinalization = null;
511
+ return pipeline.finalize(context);
512
+ });
513
+ queuedFinalization = { promise };
514
+ promise.then(
515
+ () => {
516
+ if (queuedFinalization?.promise === promise) queuedFinalization = null;
517
+ },
518
+ () => {
519
+ if (queuedFinalization?.promise === promise) queuedFinalization = null;
520
+ },
521
+ );
522
+ return promise;
523
+ }
524
+ if (finalizedGeneration === generation) return Promise.resolve(null);
525
+ if (finalizationFlight?.generation === generation) return finalizationFlight.promise;
526
+ if (finalizationFlight) {
527
+ return finalizationFlight.promise.then(
528
+ () => pipeline.finalize(context),
529
+ () => pipeline.finalize(context),
530
+ );
531
+ }
532
+ const targetGeneration = generation;
533
+ const publishGeneration = async () => {
502
534
  if (moduleDependencies.delete("<graph>")) rebuildDependencies();
503
535
  removeOwnedContributions("<graph>");
504
536
  for (const { descriptor, instance } of instances) {
505
537
  if (typeof instance.finalize !== "function") continue;
506
538
  try {
507
539
  const contribution = normalizeContribution(
508
- await instance.finalize(Object.freeze({ ...context, generation, mode })),
540
+ await instance.finalize(Object.freeze({ ...context, generation: targetGeneration, mode })),
509
541
  descriptor.name,
510
542
  resolvedProjectRoot,
511
543
  );
@@ -539,6 +571,7 @@ export async function createLitsxPipeline({ projectRoot, mode, config } = {}) {
539
571
  hadPreviousRoot: false,
540
572
  installed: false,
541
573
  };
574
+ rootOperations.push(operation);
542
575
  if (outputs.length > 0) {
543
576
  operation.stagingRoot = `${root}.staging-${randomUUID()}`;
544
577
  await ensureDirectory(operation.stagingRoot);
@@ -558,7 +591,6 @@ export async function createLitsxPipeline({ projectRoot, mode, config } = {}) {
558
591
  if (output.kind === "style" && output.document) manifest.documentStyles.push(record);
559
592
  }
560
593
  }
561
- rootOperations.push(operation);
562
594
  }
563
595
 
564
596
  for (const { output, integrationName } of pendingOutputs.values()) {
@@ -634,10 +666,22 @@ export async function createLitsxPipeline({ projectRoot, mode, config } = {}) {
634
666
  if (operation.stagingRoot) publishedRoots.set(operation.integrationName, operation.root);
635
667
  else publishedRoots.delete(operation.integrationName);
636
668
  }
637
- finalizedGeneration = generation;
669
+ finalizedGeneration = targetGeneration;
638
670
  return manifest;
671
+ };
672
+ const promise = publishGeneration();
673
+ finalizationFlight = { generation: targetGeneration, promise };
674
+ promise.then(
675
+ () => {
676
+ if (finalizationFlight?.promise === promise) finalizationFlight = null;
677
+ },
678
+ () => {
679
+ if (finalizationFlight?.promise === promise) finalizationFlight = null;
680
+ },
681
+ );
682
+ return promise;
639
683
  },
640
- async invalidate(changedPaths = null) {
684
+ invalidate(changedPaths = null) {
641
685
  assertActive();
642
686
  const paths = Array.isArray(changedPaths)
643
687
  ? changedPaths.map((entry) => canonicalDependency(resolvedProjectRoot, entry))
@@ -645,6 +689,26 @@ export async function createLitsxPipeline({ projectRoot, mode, config } = {}) {
645
689
  const affected = paths == null || paths.some((entry) => (
646
690
  dependencies.has(entry) || processedModules.has(entry)
647
691
  ));
692
+ const previousInvalidation = invalidationFlight?.promise ?? null;
693
+ const activeFinalization = finalizationFlight?.promise ?? null;
694
+ const promise = (async () => {
695
+ if (previousInvalidation) {
696
+ try {
697
+ await previousInvalidation;
698
+ } catch {
699
+ // A later invalidation is allowed to recover from an earlier failure.
700
+ }
701
+ }
702
+ if (activeFinalization) {
703
+ try {
704
+ await activeFinalization;
705
+ } catch {
706
+ // Failed finalization leaves the generation retryable before invalidation.
707
+ }
708
+ }
709
+ if (closing || disposed) {
710
+ throw new Error("This LitSX pipeline has already been disposed.");
711
+ }
648
712
  for (const { descriptor, instance } of instances) {
649
713
  if (typeof instance.invalidate !== "function") continue;
650
714
  try {
@@ -661,6 +725,17 @@ export async function createLitsxPipeline({ projectRoot, mode, config } = {}) {
661
725
  }
662
726
  }
663
727
  return affected;
728
+ })();
729
+ invalidationFlight = { promise };
730
+ promise.then(
731
+ () => {
732
+ if (invalidationFlight?.promise === promise) invalidationFlight = null;
733
+ },
734
+ () => {
735
+ if (invalidationFlight?.promise === promise) invalidationFlight = null;
736
+ },
737
+ );
738
+ return promise;
664
739
  },
665
740
  async forget(moduleId) {
666
741
  assertActive();
@@ -683,8 +758,19 @@ export async function createLitsxPipeline({ projectRoot, mode, config } = {}) {
683
758
  await removePath(virtualModule);
684
759
  }
685
760
  },
686
- async dispose() {
687
- if (disposed) return;
761
+ dispose() {
762
+ if (disposalPromise) return disposalPromise;
763
+ if (disposed) return Promise.resolve();
764
+ closing = true;
765
+ disposalPromise = (async () => {
766
+ for (const flight of [queuedFinalization, invalidationFlight, finalizationFlight]) {
767
+ if (!flight) continue;
768
+ try {
769
+ await flight.promise;
770
+ } catch {
771
+ // The original caller owns a lifecycle error; disposal still completes.
772
+ }
773
+ }
688
774
  disposed = true;
689
775
  let firstError = null;
690
776
  for (const { descriptor, instance } of [...instances].reverse()) {
@@ -696,6 +782,8 @@ export async function createLitsxPipeline({ projectRoot, mode, config } = {}) {
696
782
  }
697
783
  await removePath(virtualRoot);
698
784
  if (firstError) throw firstError;
785
+ })();
786
+ return disposalPromise;
699
787
  },
700
788
  };
701
789