@vertz/ui-server 0.2.31 → 0.2.33

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/dist/index.js CHANGED
@@ -1,13 +1,15 @@
1
1
  import {
2
- createSSRHandler
3
- } from "./shared/chunk-wb5fv233.js";
2
+ createSSRHandler,
3
+ loadAotManifest
4
+ } from "./shared/chunk-2kx402c1.js";
4
5
  import {
5
6
  createNodeHandler
6
- } from "./shared/chunk-es0406qq.js";
7
+ } from "./shared/chunk-xdb8qn68.js";
7
8
  import {
8
9
  collectStreamChunks,
9
10
  compileThemeCached,
10
11
  createAccessSetScript,
12
+ createHoles,
11
13
  createRequestContext,
12
14
  createSSRDataChunk,
13
15
  createSessionScript,
@@ -19,6 +21,7 @@ import {
19
21
  evaluateAccessRule,
20
22
  getAccessSetForSSR,
21
23
  getStreamingRuntimeScript,
24
+ isAotDebugEnabled,
22
25
  matchUrlToPatterns,
23
26
  reconstructDescriptors,
24
27
  renderToStream,
@@ -26,32 +29,35 @@ import {
26
29
  safeSerialize,
27
30
  serializeToHtml,
28
31
  ssrDiscoverQueries,
32
+ ssrRenderAot,
29
33
  ssrRenderSinglePass,
30
34
  ssrRenderToString,
31
35
  streamToString,
32
36
  toPrefetchSession
33
- } from "./shared/chunk-34fexgex.js";
37
+ } from "./shared/chunk-hx7drzm3.js";
34
38
  import {
35
39
  clearGlobalSSRTimeout,
36
40
  createSSRAdapter,
37
41
  getGlobalSSRTimeout,
38
42
  getSSRQueries,
39
43
  getSSRUrl,
40
- installDomShim,
41
44
  isInSSR,
42
45
  rawHtml,
43
46
  registerSSRQuery,
44
47
  setGlobalSSRTimeout,
45
- ssrStorage,
46
- toVNode
48
+ ssrStorage
47
49
  } from "./shared/chunk-ybftdw1r.js";
48
50
 
51
+ // src/index.ts
52
+ import { extractRoutes } from "@vertz/ui-compiler";
53
+
49
54
  // src/aot-manifest-build.ts
50
55
  import { readdirSync, readFileSync } from "node:fs";
51
- import { join } from "node:path";
56
+ import { basename, join } from "node:path";
52
57
  import { compileForSSRAot } from "@vertz/ui-compiler";
53
58
  function generateAotBuildManifest(srcDir) {
54
59
  const components = {};
60
+ const compiledFiles = {};
55
61
  const classificationLog = [];
56
62
  const tsxFiles = collectTsxFiles(srcDir);
57
63
  for (const filePath of tsxFiles) {
@@ -61,7 +67,14 @@ function generateAotBuildManifest(srcDir) {
61
67
  for (const comp of result.components) {
62
68
  components[comp.name] = {
63
69
  tier: comp.tier,
64
- holes: comp.holes
70
+ holes: comp.holes,
71
+ queryKeys: comp.queryKeys
72
+ };
73
+ }
74
+ if (result.components.length > 0) {
75
+ compiledFiles[filePath] = {
76
+ code: result.code,
77
+ components: result.components
65
78
  };
66
79
  }
67
80
  } catch (e) {
@@ -88,7 +101,61 @@ function generateAotBuildManifest(srcDir) {
88
101
  const pct = Math.round(aotCount / total * 100);
89
102
  classificationLog.push(`Coverage: ${aotCount}/${total} components (${pct}%)`);
90
103
  }
91
- return { components, classificationLog };
104
+ return { components, compiledFiles, classificationLog };
105
+ }
106
+ function buildAotRouteMap(components, routes) {
107
+ const routeMap = {};
108
+ for (const route of routes) {
109
+ const comp = components[route.componentName];
110
+ if (!comp || comp.tier === "runtime-fallback")
111
+ continue;
112
+ routeMap[route.pattern] = {
113
+ renderFn: `__ssr_${route.componentName}`,
114
+ holes: comp.holes,
115
+ queryKeys: comp.queryKeys
116
+ };
117
+ }
118
+ return routeMap;
119
+ }
120
+ function generateAotBarrel(compiledFiles, routeMap) {
121
+ const neededFns = new Set;
122
+ for (const entry of Object.values(routeMap)) {
123
+ neededFns.add(entry.renderFn);
124
+ }
125
+ const fnToFile = new Map;
126
+ for (const [filePath, compiled] of Object.entries(compiledFiles)) {
127
+ for (const comp of compiled.components) {
128
+ const fnName = `__ssr_${comp.name}`;
129
+ if (neededFns.has(fnName)) {
130
+ fnToFile.set(fnName, filePath);
131
+ }
132
+ }
133
+ }
134
+ const fileToFns = new Map;
135
+ for (const [fnName, filePath] of fnToFile) {
136
+ const existing = fileToFns.get(filePath) ?? [];
137
+ existing.push(fnName);
138
+ fileToFns.set(filePath, existing);
139
+ }
140
+ const lines = [];
141
+ const files = {};
142
+ let fileIndex = 0;
143
+ for (const [filePath, fns] of fileToFns) {
144
+ const moduleName = basename(filePath, ".tsx").replace(/[^a-zA-Z0-9_-]/g, "_");
145
+ const tempFileName = `__aot_${fileIndex}_${moduleName}`;
146
+ const moduleRef = `./${tempFileName}`;
147
+ lines.push(`export { ${fns.sort().join(", ")} } from '${moduleRef}';`);
148
+ const compiled = compiledFiles[filePath];
149
+ if (compiled) {
150
+ files[`${tempFileName}.tsx`] = compiled.code;
151
+ }
152
+ fileIndex++;
153
+ }
154
+ return {
155
+ barrelSource: lines.join(`
156
+ `),
157
+ files
158
+ };
92
159
  }
93
160
  function collectTsxFiles(dir) {
94
161
  const files = [];
@@ -670,147 +737,6 @@ function createAotManifestManager(options) {
670
737
  }
671
738
  };
672
739
  }
673
- // src/ssr-aot-pipeline.ts
674
- function createHoles(holeNames, module, url, queryCache, ssrAuth) {
675
- if (holeNames.length === 0)
676
- return {};
677
- const holes = {};
678
- for (const name of holeNames) {
679
- holes[name] = () => {
680
- const holeCtx = createRequestContext(url);
681
- for (const [key, data] of queryCache) {
682
- holeCtx.queryCache.set(key, data);
683
- }
684
- if (ssrAuth) {
685
- holeCtx.ssrAuth = ssrAuth;
686
- }
687
- holeCtx.resolvedComponents = new Map;
688
- return ssrStorage.run(holeCtx, () => {
689
- ensureDomShim();
690
- const factory = resolveHoleComponent(module, name);
691
- if (!factory) {
692
- return `<!-- AOT hole: ${name} not found -->`;
693
- }
694
- const node = factory();
695
- const vnode = toVNode(node);
696
- return serializeToHtml(vnode);
697
- });
698
- };
699
- }
700
- return holes;
701
- }
702
- function resolveHoleComponent(module, name) {
703
- const moduleRecord = module;
704
- const exported = moduleRecord[name];
705
- if (typeof exported === "function") {
706
- return exported;
707
- }
708
- return;
709
- }
710
- async function ssrRenderAot(module, url, options) {
711
- const { aotManifest, manifest } = options;
712
- const ssrTimeout = options.ssrTimeout ?? 300;
713
- const normalizedUrl = url.endsWith("/index.html") ? url.slice(0, -"/index.html".length) || "/" : url;
714
- const fallbackOptions = {
715
- ssrTimeout,
716
- fallbackMetrics: options.fallbackMetrics,
717
- ssrAuth: options.ssrAuth,
718
- manifest,
719
- prefetchSession: options.prefetchSession
720
- };
721
- const aotPatterns = Object.keys(aotManifest.routes);
722
- const matches = matchUrlToPatterns(normalizedUrl, aotPatterns);
723
- if (matches.length === 0) {
724
- return ssrRenderSinglePass(module, normalizedUrl, fallbackOptions);
725
- }
726
- const match = matches[matches.length - 1];
727
- if (!match) {
728
- return ssrRenderSinglePass(module, normalizedUrl, fallbackOptions);
729
- }
730
- const aotEntry = aotManifest.routes[match.pattern];
731
- if (!aotEntry) {
732
- return ssrRenderSinglePass(module, normalizedUrl, fallbackOptions);
733
- }
734
- const queryCache = new Map;
735
- try {
736
- setGlobalSSRTimeout(ssrTimeout);
737
- const holes = createHoles(aotEntry.holes, module, normalizedUrl, queryCache, options.ssrAuth);
738
- const ctx = {
739
- holes,
740
- getData: (key) => queryCache.get(key),
741
- session: options.prefetchSession,
742
- params: match.params
743
- };
744
- const data = {};
745
- for (const [key, value] of queryCache) {
746
- data[key] = value;
747
- }
748
- const html = aotEntry.render(data, ctx);
749
- if (options.diagnostics && isAotDebugEnabled()) {
750
- try {
751
- const domResult = await ssrRenderSinglePass(module, normalizedUrl, fallbackOptions);
752
- if (domResult.html !== html) {
753
- options.diagnostics.recordDivergence(match.pattern, html, domResult.html);
754
- }
755
- } catch {}
756
- }
757
- const css = collectCSSFromModule(module, options.fallbackMetrics);
758
- const ssrData = [];
759
- for (const [key, data2] of queryCache) {
760
- ssrData.push({ key, data: data2 });
761
- }
762
- return {
763
- html,
764
- css: css.cssString,
765
- ssrData,
766
- headTags: css.preloadTags
767
- };
768
- } finally {
769
- clearGlobalSSRTimeout();
770
- }
771
- }
772
- function isAotDebugEnabled() {
773
- const env = process.env.VERTZ_DEBUG;
774
- if (!env)
775
- return false;
776
- return env === "1" || env.split(",").includes("aot");
777
- }
778
- var domShimInstalled = false;
779
- function ensureDomShim() {
780
- if (domShimInstalled && typeof document !== "undefined")
781
- return;
782
- domShimInstalled = true;
783
- installDomShim();
784
- }
785
- function collectCSSFromModule(module, fallbackMetrics) {
786
- let themeCss = "";
787
- let preloadTags = "";
788
- if (module.theme) {
789
- try {
790
- const compiled = compileThemeCached(module.theme, fallbackMetrics);
791
- themeCss = compiled.css;
792
- preloadTags = compiled.preloadTags;
793
- } catch (e) {
794
- console.error("[vertz] Failed to compile theme export. Ensure your theme is created with defineTheme().", e);
795
- }
796
- }
797
- const alreadyIncluded = new Set;
798
- if (themeCss)
799
- alreadyIncluded.add(themeCss);
800
- if (module.styles) {
801
- for (const s of module.styles)
802
- alreadyIncluded.add(s);
803
- }
804
- const componentCss = module.getInjectedCSS ? module.getInjectedCSS().filter((s) => !alreadyIncluded.has(s)) : [];
805
- const themeTag = themeCss ? `<style data-vertz-css>${themeCss}</style>` : "";
806
- const globalTag = module.styles && module.styles.length > 0 ? `<style data-vertz-css>${module.styles.join(`
807
- `)}</style>` : "";
808
- const componentTag = componentCss.length > 0 ? `<style data-vertz-css>${componentCss.join(`
809
- `)}</style>` : "";
810
- const cssString = [themeTag, globalTag, componentTag].filter(Boolean).join(`
811
- `);
812
- return { cssString, preloadTags };
813
- }
814
740
  // src/ssr-aot-runtime.ts
815
741
  var UNITLESS_PROPERTIES = new Set([
816
742
  "animationIterationCount",
@@ -1094,6 +1020,7 @@ export {
1094
1020
  reconstructDescriptors,
1095
1021
  rawHtml,
1096
1022
  matchUrlToPatterns,
1023
+ loadAotManifest,
1097
1024
  isInSSR,
1098
1025
  isAotDebugEnabled,
1099
1026
  inlineCriticalCss,
@@ -1104,6 +1031,8 @@ export {
1104
1031
  getAccessSetForSSR,
1105
1032
  generateSSRHtml,
1106
1033
  generateAotBuildManifest,
1034
+ generateAotBarrel,
1035
+ extractRoutes,
1107
1036
  extractFontMetrics,
1108
1037
  evaluateAccessRule,
1109
1038
  encodeChunk,
@@ -1121,6 +1050,7 @@ export {
1121
1050
  createAccessSetScript,
1122
1051
  collectStreamChunks,
1123
1052
  clearGlobalSSRTimeout,
1053
+ buildAotRouteMap,
1124
1054
  __ssr_style_object,
1125
1055
  __ssr_spread,
1126
1056
  __esc_attr,
@@ -1,26 +1,5 @@
1
1
  import { IncomingMessage, ServerResponse } from "node:http";
2
- import { FontFallbackMetrics as FontFallbackMetrics3 } from "@vertz/ui";
3
- import { CompiledRoute, Theme } from "@vertz/ui";
4
- interface SSRModule {
5
- default?: () => unknown;
6
- App?: () => unknown;
7
- theme?: Theme;
8
- /** Global CSS strings to include in every SSR response (e.g. resets, body styles). */
9
- styles?: string[];
10
- /**
11
- * Return all CSS tracked by the bundled @vertz/ui instance.
12
- * The Vite SSR build inlines @vertz/ui into the server bundle, creating
13
- * a separate module instance from @vertz/ui-server's dependency. Without
14
- * this, component CSS from module-level css() calls is invisible to the
15
- * SSR renderer. Export `getInjectedCSS` from @vertz/ui in the app entry.
16
- */
17
- getInjectedCSS?: () => string[];
18
- /** Compiled routes exported from the app for build-time SSG with generateParams. */
19
- routes?: CompiledRoute[];
20
- /** Code-generated API client for manifest-driven zero-discovery prefetching. */
21
- api?: Record<string, Record<string, (...args: unknown[]) => unknown>>;
22
- }
23
- import { ExtractedQuery } from "@vertz/ui-compiler";
2
+ import { FontFallbackMetrics as FontFallbackMetrics4 } from "@vertz/ui";
24
3
  /**
25
4
  * SSR prefetch access rule evaluator.
26
5
  *
@@ -60,6 +39,39 @@ type SerializedAccessRule = {
60
39
  } | {
61
40
  type: "deny";
62
41
  };
42
+ /**
43
+ * Minimal session shape needed for prefetch access evaluation.
44
+ * Extracted from the JWT at SSR request time.
45
+ */
46
+ type PrefetchSession = {
47
+ status: "authenticated";
48
+ roles?: string[];
49
+ entitlements?: Record<string, boolean>;
50
+ tenantId?: string;
51
+ } | {
52
+ status: "unauthenticated";
53
+ };
54
+ import { CompiledRoute, Theme } from "@vertz/ui";
55
+ interface SSRModule {
56
+ default?: () => unknown;
57
+ App?: () => unknown;
58
+ theme?: Theme;
59
+ /** Global CSS strings to include in every SSR response (e.g. resets, body styles). */
60
+ styles?: string[];
61
+ /**
62
+ * Return all CSS tracked by the bundled @vertz/ui instance.
63
+ * The Vite SSR build inlines @vertz/ui into the server bundle, creating
64
+ * a separate module instance from @vertz/ui-server's dependency. Without
65
+ * this, component CSS from module-level css() calls is invisible to the
66
+ * SSR renderer. Export `getInjectedCSS` from @vertz/ui in the app entry.
67
+ */
68
+ getInjectedCSS?: () => string[];
69
+ /** Compiled routes exported from the app for build-time SSG with generateParams. */
70
+ routes?: CompiledRoute[];
71
+ /** Code-generated API client for manifest-driven zero-discovery prefetching. */
72
+ api?: Record<string, Record<string, (...args: unknown[]) => unknown>>;
73
+ }
74
+ import { ExtractedQuery } from "@vertz/ui-compiler";
63
75
  /** Serialized entity access rules from the prefetch manifest. */
64
76
  type EntityAccessMap = Record<string, Partial<Record<string, SerializedAccessRule>>>;
65
77
  interface SSRPrefetchManifest {
@@ -72,6 +84,37 @@ interface SSRPrefetchManifest {
72
84
  queries: ExtractedQuery[];
73
85
  }>;
74
86
  }
87
+ /** Context passed to AOT render functions for accessing data and runtime holes. */
88
+ interface SSRAotContext {
89
+ /** Pre-generated closures for runtime-rendered components. */
90
+ holes: Record<string, () => string>;
91
+ /** Access query data by cache key. */
92
+ getData(key: string): unknown;
93
+ /** Auth session for conditional rendering. */
94
+ session: PrefetchSession | undefined;
95
+ /** Route params for the current request. */
96
+ params: Record<string, string>;
97
+ }
98
+ /** An AOT render function: takes props/data and context, returns HTML string. */
99
+ type AotRenderFn = (data: Record<string, unknown>, ctx: SSRAotContext) => string;
100
+ /** Per-route AOT entry in the manifest. */
101
+ interface AotRouteEntry {
102
+ /** The pre-compiled render function. */
103
+ render: AotRenderFn;
104
+ /** Component names that need runtime fallback (holes). */
105
+ holes: string[];
106
+ /** Query cache keys this route reads via ctx.getData(). */
107
+ queryKeys?: string[];
108
+ }
109
+ /**
110
+ * AOT manifest — maps route patterns to pre-compiled render functions.
111
+ *
112
+ * Generated at build time by the AOT compiler pipeline.
113
+ */
114
+ interface AotManifest {
115
+ /** Route pattern → AOT entry. */
116
+ routes: Record<string, AotRouteEntry>;
117
+ }
75
118
  import { AccessSet } from "@vertz/ui/auth";
76
119
  interface SessionData {
77
120
  user: {
@@ -125,7 +168,7 @@ interface SSRHandlerOptions {
125
168
  */
126
169
  nonce?: string;
127
170
  /** Pre-computed font fallback metrics (computed at server startup). */
128
- fallbackMetrics?: Record<string, FontFallbackMetrics3>;
171
+ fallbackMetrics?: Record<string, FontFallbackMetrics4>;
129
172
  /** Paths to inject as `<link rel="modulepreload">` in `<head>`. */
130
173
  modulepreload?: string[];
131
174
  /**
@@ -164,6 +207,16 @@ interface SSRHandlerOptions {
164
207
  * which always use buffered rendering.
165
208
  */
166
209
  progressiveHTML?: boolean;
210
+ /**
211
+ * AOT manifest with pre-compiled render functions.
212
+ *
213
+ * When provided, routes matching AOT entries are rendered via string-builder
214
+ * functions (no DOM shim), bypassing the reactive runtime for 4-6x speedup.
215
+ * Routes not in the manifest fall back to `ssrRenderSinglePass()`.
216
+ *
217
+ * Load via `loadAotManifest(serverDir)` at startup.
218
+ */
219
+ aotManifest?: AotManifest;
167
220
  }
168
221
  type NodeHandlerOptions = SSRHandlerOptions;
169
222
  declare function createNodeHandler(options: NodeHandlerOptions): (req: IncomingMessage, res: ServerResponse) => void;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createNodeHandler
3
- } from "./shared/chunk-es0406qq.js";
4
- import"./shared/chunk-34fexgex.js";
3
+ } from "./shared/chunk-xdb8qn68.js";
4
+ import"./shared/chunk-hx7drzm3.js";
5
5
  import"./shared/chunk-ybftdw1r.js";
6
6
  export {
7
7
  createNodeHandler
@@ -6,10 +6,54 @@ import {
6
6
  resolveRouteModulepreload,
7
7
  resolveSession,
8
8
  safeSerialize,
9
+ ssrRenderAot,
9
10
  ssrRenderProgressive,
10
11
  ssrRenderSinglePass,
11
- ssrStreamNavQueries
12
- } from "./chunk-34fexgex.js";
12
+ ssrStreamNavQueries,
13
+ toPrefetchSession
14
+ } from "./chunk-hx7drzm3.js";
15
+
16
+ // src/aot-manifest-loader.ts
17
+ import { existsSync, readFileSync } from "node:fs";
18
+ import { join } from "node:path";
19
+ async function loadAotManifest(serverDir) {
20
+ const manifestPath = join(serverDir, "aot-manifest.json");
21
+ const routesModulePath = join(serverDir, "aot-routes.js");
22
+ if (!existsSync(manifestPath) || !existsSync(routesModulePath)) {
23
+ return null;
24
+ }
25
+ let manifestJson;
26
+ try {
27
+ const raw = readFileSync(manifestPath, "utf-8");
28
+ manifestJson = JSON.parse(raw);
29
+ } catch {
30
+ return null;
31
+ }
32
+ if (!manifestJson.routes || Object.keys(manifestJson.routes).length === 0) {
33
+ return null;
34
+ }
35
+ let routesModule;
36
+ try {
37
+ routesModule = await import(routesModulePath);
38
+ } catch {
39
+ return null;
40
+ }
41
+ const routes = {};
42
+ for (const [pattern, entry] of Object.entries(manifestJson.routes)) {
43
+ const renderFn = routesModule[entry.renderFn];
44
+ if (typeof renderFn !== "function")
45
+ continue;
46
+ routes[pattern] = {
47
+ render: renderFn,
48
+ holes: entry.holes,
49
+ queryKeys: entry.queryKeys
50
+ };
51
+ }
52
+ if (Object.keys(routes).length === 0) {
53
+ return null;
54
+ }
55
+ return { routes };
56
+ }
13
57
 
14
58
  // src/ssr-progressive-response.ts
15
59
  function buildProgressiveResponse(options) {
@@ -63,7 +107,8 @@ function createSSRHandler(options) {
63
107
  cacheControl,
64
108
  sessionResolver,
65
109
  manifest,
66
- progressiveHTML
110
+ progressiveHTML,
111
+ aotManifest
67
112
  } = options;
68
113
  const { template, linkHeader, modulepreloadTags, splitResult } = precomputeHandlerState(options);
69
114
  return async (request) => {
@@ -83,7 +128,7 @@ function createSSRHandler(options) {
83
128
  if (useProgressive) {
84
129
  return handleProgressiveHTMLRequest(module, splitResult, pathname + url.search, ssrTimeout, nonce, fallbackMetrics, linkHeader, modulepreloadTags, routeChunkManifest, cacheControl, sessionScript, ssrAuth, manifest);
85
130
  }
86
- return handleHTMLRequest(module, template, pathname + url.search, ssrTimeout, nonce, fallbackMetrics, linkHeader, modulepreloadTags, routeChunkManifest, cacheControl, sessionScript, ssrAuth, manifest);
131
+ return handleHTMLRequest(module, template, pathname + url.search, ssrTimeout, nonce, fallbackMetrics, linkHeader, modulepreloadTags, routeChunkManifest, cacheControl, sessionScript, ssrAuth, manifest, aotManifest);
87
132
  };
88
133
  }
89
134
  async function handleNavRequest(module, url, ssrTimeout) {
@@ -172,13 +217,22 @@ async function handleProgressiveHTMLRequest(module, split, url, ssrTimeout, nonc
172
217
  });
173
218
  }
174
219
  }
175
- async function handleHTMLRequest(module, template, url, ssrTimeout, nonce, fallbackMetrics, linkHeader, staticModulepreloadTags, routeChunkManifest, cacheControl, sessionScript, ssrAuth, manifest) {
220
+ async function handleHTMLRequest(module, template, url, ssrTimeout, nonce, fallbackMetrics, linkHeader, staticModulepreloadTags, routeChunkManifest, cacheControl, sessionScript, ssrAuth, manifest, aotManifest) {
176
221
  try {
177
- const result = await ssrRenderSinglePass(module, url, {
222
+ const prefetchSession = ssrAuth ? toPrefetchSession(ssrAuth) : undefined;
223
+ const result = aotManifest ? await ssrRenderAot(module, url, {
224
+ aotManifest,
225
+ manifest,
178
226
  ssrTimeout,
179
227
  fallbackMetrics,
180
228
  ssrAuth,
181
- manifest
229
+ prefetchSession
230
+ }) : await ssrRenderSinglePass(module, url, {
231
+ ssrTimeout,
232
+ fallbackMetrics,
233
+ ssrAuth,
234
+ manifest,
235
+ prefetchSession
182
236
  });
183
237
  if (result.redirect) {
184
238
  return new Response(null, {
@@ -213,4 +267,4 @@ async function handleHTMLRequest(module, template, url, ssrTimeout, nonce, fallb
213
267
  }
214
268
  }
215
269
 
216
- export { createSSRHandler };
270
+ export { loadAotManifest, createSSRHandler };