@vertz/ui-server 0.2.23 → 0.2.25

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.
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  captureDOMState,
4
4
  restoreDOMState
5
- } from "../shared/chunk-2qsqp9xj.js";
5
+ } from "../shared/chunk-eenfpa59.js";
6
6
  import"../shared/chunk-eb80r8e8.js";
7
7
  export {
8
8
  restoreDOMState,
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  captureDOMState,
4
4
  restoreDOMState
5
- } from "../shared/chunk-2qsqp9xj.js";
5
+ } from "../shared/chunk-eenfpa59.js";
6
6
  import"../shared/chunk-eb80r8e8.js";
7
7
 
8
8
  // src/bun-plugin/fast-refresh-runtime.ts
@@ -40,9 +40,10 @@ function __$refreshReg(moduleId, name, factory, hash) {
40
40
  return;
41
41
  existing.factory = factory;
42
42
  existing.hash = hash;
43
+ existing.dirty = true;
43
44
  dirtyModules.add(moduleId);
44
45
  } else {
45
- mod.set(name, { factory, instances: [], hash });
46
+ mod.set(name, { factory, instances: [], hash, dirty: false });
46
47
  }
47
48
  }
48
49
  function __$refreshTrack(moduleId, name, element, args, cleanups, contextScope, signals = []) {
@@ -68,6 +69,9 @@ function __$refreshPerform(moduleId) {
68
69
  return;
69
70
  performingRefresh = true;
70
71
  for (const [name, record] of mod) {
72
+ if (!record.dirty)
73
+ continue;
74
+ record.dirty = false;
71
75
  const { factory, instances } = record;
72
76
  const updatedInstances = [];
73
77
  for (const instance of instances) {
@@ -1,6 +1,6 @@
1
1
  type ErrorCategory = "build" | "resolve" | "runtime" | "ssr";
2
2
  import { CSSExtractionResult } from "@vertz/ui-compiler";
3
- type DebugCategory = "fields" | "manifest" | "plugin" | "ssr" | "watcher" | "ws";
3
+ type DebugCategory = "fields" | "manifest" | "plugin" | "prefetch" | "ssr" | "watcher" | "ws";
4
4
  interface DebugLogger {
5
5
  log(category: DebugCategory, message: string, data?: Record<string, unknown>): void;
6
6
  isEnabled(category: DebugCategory): boolean;
@@ -22,7 +22,7 @@ import {
22
22
  transformRouteSplitting
23
23
  } from "@vertz/ui-compiler";
24
24
  import MagicString3 from "magic-string";
25
- import { Project as Project2, ts as ts4 } from "ts-morph";
25
+ import { Project as Project2, ts as ts5 } from "ts-morph";
26
26
 
27
27
  // src/bun-plugin/context-stable-ids.ts
28
28
  import { ts } from "ts-morph";
@@ -72,19 +72,18 @@ function loadEntitySchema(schemaPath) {
72
72
  }
73
73
 
74
74
  // src/bun-plugin/fast-refresh-codegen.ts
75
- function generateRefreshPreamble(moduleId, contentHash) {
75
+ function generateRefreshPreamble(moduleId) {
76
76
  const escapedId = moduleId.replace(/['\\]/g, "\\$&");
77
- let code = `const __$fr = globalThis[Symbol.for('vertz:fast-refresh')];
78
- ` + `const { __$refreshReg, __$refreshTrack, __$refreshPerform, ` + `pushScope: __$pushScope, popScope: __$popScope, ` + `_tryOnCleanup: __$tryCleanup, runCleanups: __$runCleanups, ` + `getContextScope: __$getCtx, setContextScope: __$setCtx, ` + `startSignalCollection: __$startSigCol, stopSignalCollection: __$stopSigCol } = __$fr;
77
+ const noop = "() => {}";
78
+ const noopArr = "() => []";
79
+ const noopNull = "() => null";
80
+ const noopPassthrough = "(_m, _n, el) => el";
81
+ return `const __$fr = globalThis[Symbol.for('vertz:fast-refresh')] ?? {};
82
+ ` + `const { ` + `__$refreshReg = ${noop}, ` + `__$refreshTrack = ${noopPassthrough}, ` + `__$refreshPerform = ${noop}, ` + `pushScope: __$pushScope = ${noopArr}, ` + `popScope: __$popScope = ${noop}, ` + `_tryOnCleanup: __$tryCleanup = ${noop}, ` + `runCleanups: __$runCleanups = ${noop}, ` + `getContextScope: __$getCtx = ${noopNull}, ` + `setContextScope: __$setCtx = ${noopNull}, ` + `startSignalCollection: __$startSigCol = ${noop}, ` + `stopSignalCollection: __$stopSigCol = ${noopArr} } = __$fr;
79
83
  ` + `const __$moduleId = '${escapedId}';
80
84
  `;
81
- if (contentHash) {
82
- code += `const __$moduleHash = '${contentHash}';
83
- `;
84
- }
85
- return code;
86
85
  }
87
- function generateRefreshWrapper(componentName) {
86
+ function generateRefreshWrapper(componentName, componentHash) {
88
87
  return `
89
88
  const __$orig_${componentName} = ${componentName};
90
89
  ` + `${componentName} = function(...__$args) {
@@ -99,20 +98,22 @@ const __$orig_${componentName} = ${componentName};
99
98
  ` + ` }
100
99
  ` + ` return __$refreshTrack(__$moduleId, '${componentName}', __$ret, __$args, __$scope, __$ctx, __$sigs);
101
100
  ` + `};
102
- ` + `__$refreshReg(__$moduleId, '${componentName}', ${componentName}, __$moduleHash);
101
+ ` + `__$refreshReg(__$moduleId, '${componentName}', ${componentName}, '${componentHash}');
103
102
  `;
104
103
  }
105
104
  function generateRefreshPerform() {
106
105
  return `__$refreshPerform(__$moduleId);
107
106
  `;
108
107
  }
109
- function generateRefreshCode(moduleId, components, contentHash) {
108
+ function generateRefreshCode(moduleId, components, source) {
110
109
  if (components.length === 0)
111
110
  return null;
112
- const preamble = generateRefreshPreamble(moduleId, contentHash);
111
+ const preamble = generateRefreshPreamble(moduleId);
113
112
  let epilogue = "";
114
113
  for (const comp of components) {
115
- epilogue += generateRefreshWrapper(comp.name);
114
+ const body = source.slice(comp.bodyStart, comp.bodyEnd);
115
+ const hash = Bun.hash(body).toString(36);
116
+ epilogue += generateRefreshWrapper(comp.name, hash);
116
117
  }
117
118
  epilogue += generateRefreshPerform();
118
119
  return { preamble, epilogue };
@@ -267,6 +268,8 @@ function resolveCrossFileFields(filePath, propFlows, options) {
267
268
  if (childFields.hasOpaqueAccess) {
268
269
  hasOpaqueAccess = true;
269
270
  }
271
+ } else {
272
+ hasOpaqueAccess = true;
270
273
  }
271
274
  }
272
275
  return { fields, hasOpaqueAccess };
@@ -274,9 +277,33 @@ function resolveCrossFileFields(filePath, propFlows, options) {
274
277
 
275
278
  // src/bun-plugin/field-selection-manifest.ts
276
279
  import { analyzeComponentPropFields } from "@vertz/ui-compiler";
280
+ import { ts as ts2 } from "ts-morph";
281
+ var RE_EXPORT_PATTERN = /export\s+(?:\{|\*)\s*.*?\bfrom\b/;
282
+ function parseReExports(sourceText, filePath) {
283
+ if (!RE_EXPORT_PATTERN.test(sourceText))
284
+ return [];
285
+ const sourceFile = ts2.createSourceFile(filePath, sourceText, ts2.ScriptTarget.Latest, true);
286
+ const reExports = [];
287
+ for (const stmt of sourceFile.statements) {
288
+ if (!ts2.isExportDeclaration(stmt) || !stmt.moduleSpecifier)
289
+ continue;
290
+ const source = stmt.moduleSpecifier.getText(sourceFile).replace(/^['"]|['"]$/g, "");
291
+ if (!stmt.exportClause) {
292
+ reExports.push({ name: "*", originalName: "*", source });
293
+ } else if (ts2.isNamedExports(stmt.exportClause)) {
294
+ for (const el of stmt.exportClause.elements) {
295
+ const exportedName = el.name.getText(sourceFile);
296
+ const originalName = el.propertyName ? el.propertyName.getText(sourceFile) : exportedName;
297
+ reExports.push({ name: exportedName, originalName, source });
298
+ }
299
+ }
300
+ }
301
+ return reExports;
302
+ }
277
303
 
278
304
  class FieldSelectionManifest {
279
305
  fileComponents = new Map;
306
+ fileReExports = new Map;
280
307
  importResolver = () => {
281
308
  return;
282
309
  };
@@ -287,30 +314,68 @@ class FieldSelectionManifest {
287
314
  registerFile(filePath, sourceText) {
288
315
  const components = analyzeComponentPropFields(filePath, sourceText);
289
316
  this.fileComponents.set(filePath, components);
317
+ const reExports = parseReExports(sourceText, filePath);
318
+ this.fileReExports.set(filePath, reExports);
290
319
  this.resolvedCache.clear();
291
320
  }
292
321
  updateFile(filePath, sourceText) {
293
322
  const oldComponents = this.fileComponents.get(filePath);
294
323
  const newComponents = analyzeComponentPropFields(filePath, sourceText);
295
- const changed = !componentsEqual(oldComponents, newComponents);
296
- if (changed) {
324
+ const componentsChanged = !componentsEqual(oldComponents, newComponents);
325
+ if (componentsChanged) {
297
326
  this.fileComponents.set(filePath, newComponents);
327
+ }
328
+ const oldReExports = this.fileReExports.get(filePath);
329
+ const newReExports = parseReExports(sourceText, filePath);
330
+ const reExportsChanged = !reExportsEqual(oldReExports, newReExports);
331
+ if (reExportsChanged) {
332
+ this.fileReExports.set(filePath, newReExports);
333
+ }
334
+ const changed = componentsChanged || reExportsChanged;
335
+ if (changed) {
298
336
  this.resolvedCache.clear();
299
337
  }
300
338
  return { changed };
301
339
  }
302
340
  deleteFile(filePath) {
303
341
  this.fileComponents.delete(filePath);
342
+ this.fileReExports.delete(filePath);
304
343
  this.resolvedCache.clear();
305
344
  }
306
345
  getComponentPropFields(filePath, componentName, propName) {
307
346
  const components = this.fileComponents.get(filePath);
308
- if (!components)
347
+ if (components) {
348
+ const component = components.find((c) => c.componentName === componentName);
349
+ if (component)
350
+ return component.props[propName];
351
+ }
352
+ return this.followReExports(filePath, componentName, propName, new Set);
353
+ }
354
+ followReExports(filePath, componentName, propName, visited) {
355
+ if (visited.has(filePath))
309
356
  return;
310
- const component = components.find((c) => c.componentName === componentName);
311
- if (!component)
357
+ visited.add(filePath);
358
+ const reExports = this.fileReExports.get(filePath);
359
+ if (!reExports)
312
360
  return;
313
- return component.props[propName];
361
+ for (const re of reExports) {
362
+ if (re.name !== componentName && re.name !== "*")
363
+ continue;
364
+ const targetPath = this.importResolver(re.source, filePath);
365
+ if (!targetPath)
366
+ continue;
367
+ const targetName = re.name === "*" ? componentName : re.originalName;
368
+ const targetComponents = this.fileComponents.get(targetPath);
369
+ if (targetComponents) {
370
+ const component = targetComponents.find((c) => c.componentName === targetName);
371
+ if (component)
372
+ return component.props[propName];
373
+ }
374
+ const result = this.followReExports(targetPath, targetName, propName, visited);
375
+ if (result)
376
+ return result;
377
+ }
378
+ return;
314
379
  }
315
380
  getResolvedPropFields(filePath, componentName, propName) {
316
381
  const cacheKey = `${filePath}::${componentName}::${propName}`;
@@ -383,10 +448,37 @@ function componentsEqual(a, b) {
383
448
  if (aFieldsSorted[k] !== bFieldsSorted[k])
384
449
  return false;
385
450
  }
451
+ if (aProp.forwarded.length !== bProp.forwarded.length)
452
+ return false;
453
+ for (let f = 0;f < aProp.forwarded.length; f++) {
454
+ const af = aProp.forwarded[f];
455
+ const bf = bProp.forwarded[f];
456
+ if (af.componentName !== bf.componentName)
457
+ return false;
458
+ if (af.importSource !== bf.importSource)
459
+ return false;
460
+ if (af.propName !== bf.propName)
461
+ return false;
462
+ }
386
463
  }
387
464
  }
388
465
  return true;
389
466
  }
467
+ function reExportsEqual(a, b) {
468
+ if (!a)
469
+ return b.length === 0;
470
+ if (a.length !== b.length)
471
+ return false;
472
+ for (let i = 0;i < a.length; i++) {
473
+ if (a[i].name !== b[i].name)
474
+ return false;
475
+ if (a[i].originalName !== b[i].originalName)
476
+ return false;
477
+ if (a[i].source !== b[i].source)
478
+ return false;
479
+ }
480
+ return true;
481
+ }
390
482
 
391
483
  // src/bun-plugin/file-path-hash.ts
392
484
  function filePathHash(filePath) {
@@ -461,7 +553,7 @@ async function processImage(opts) {
461
553
 
462
554
  // src/bun-plugin/image-transform.ts
463
555
  import MagicString2 from "magic-string";
464
- import { Project, ts as ts2 } from "ts-morph";
556
+ import { Project, ts as ts3 } from "ts-morph";
465
557
  function escapeAttr(value) {
466
558
  return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
467
559
  }
@@ -535,13 +627,13 @@ function findImageImportName(source) {
535
627
  function findImageJsxElements(sourceFile, localName) {
536
628
  const results = [];
537
629
  function visit(node) {
538
- if (ts2.isJsxSelfClosingElement(node)) {
630
+ if (ts3.isJsxSelfClosingElement(node)) {
539
631
  const tagName = node.tagName.getText(sourceFile.compilerNode);
540
632
  if (tagName === localName) {
541
633
  results.push(node);
542
634
  }
543
635
  }
544
- ts2.forEachChild(node, visit);
636
+ ts3.forEachChild(node, visit);
545
637
  }
546
638
  visit(sourceFile.compilerNode);
547
639
  return results;
@@ -549,7 +641,7 @@ function findImageJsxElements(sourceFile, localName) {
549
641
  function extractStaticProps(element, sourceFile) {
550
642
  const attrs = element.attributes;
551
643
  for (const attr of attrs.properties) {
552
- if (ts2.isJsxSpreadAttribute(attr))
644
+ if (ts3.isJsxSpreadAttribute(attr))
553
645
  return null;
554
646
  }
555
647
  let src = null;
@@ -582,7 +674,7 @@ function extractStaticProps(element, sourceFile) {
582
674
  "fit"
583
675
  ]);
584
676
  for (const attr of attrs.properties) {
585
- if (!ts2.isJsxAttribute(attr))
677
+ if (!ts3.isJsxAttribute(attr))
586
678
  continue;
587
679
  const name = attr.name.getText(sourceFile.compilerNode);
588
680
  const value = attr.initializer;
@@ -686,15 +778,15 @@ function extractStaticProps(element, sourceFile) {
686
778
  function extractStaticString(value, _sourceFile) {
687
779
  if (!value)
688
780
  return null;
689
- if (ts2.isStringLiteral(value)) {
781
+ if (ts3.isStringLiteral(value)) {
690
782
  return value.text;
691
783
  }
692
- if (ts2.isJsxExpression(value) && value.expression) {
784
+ if (ts3.isJsxExpression(value) && value.expression) {
693
785
  const expr = value.expression;
694
- if (ts2.isStringLiteral(expr)) {
786
+ if (ts3.isStringLiteral(expr)) {
695
787
  return expr.text;
696
788
  }
697
- if (ts2.isNoSubstitutionTemplateLiteral(expr)) {
789
+ if (ts3.isNoSubstitutionTemplateLiteral(expr)) {
698
790
  return expr.text;
699
791
  }
700
792
  }
@@ -703,9 +795,9 @@ function extractStaticString(value, _sourceFile) {
703
795
  function extractStaticNumber(value, _sourceFile) {
704
796
  if (!value)
705
797
  return null;
706
- if (ts2.isJsxExpression(value) && value.expression) {
798
+ if (ts3.isJsxExpression(value) && value.expression) {
707
799
  const expr = value.expression;
708
- if (ts2.isNumericLiteral(expr)) {
800
+ if (ts3.isNumericLiteral(expr)) {
709
801
  return Number(expr.text);
710
802
  }
711
803
  }
@@ -714,18 +806,18 @@ function extractStaticNumber(value, _sourceFile) {
714
806
  function extractStaticBoolean(value, _sourceFile) {
715
807
  if (!value)
716
808
  return null;
717
- if (ts2.isJsxExpression(value) && value.expression) {
809
+ if (ts3.isJsxExpression(value) && value.expression) {
718
810
  const expr = value.expression;
719
- if (expr.kind === ts2.SyntaxKind.TrueKeyword)
811
+ if (expr.kind === ts3.SyntaxKind.TrueKeyword)
720
812
  return true;
721
- if (expr.kind === ts2.SyntaxKind.FalseKeyword)
813
+ if (expr.kind === ts3.SyntaxKind.FalseKeyword)
722
814
  return false;
723
815
  }
724
816
  return null;
725
817
  }
726
818
 
727
819
  // src/bun-plugin/island-id-inject.ts
728
- import { ts as ts3 } from "ts-morph";
820
+ import { ts as ts4 } from "ts-morph";
729
821
  function injectIslandIds(source, sourceFile, relFilePath) {
730
822
  const originalSource = source.original;
731
823
  if (!originalSource.includes("<Island") && !originalSource.includes("Island")) {
@@ -759,20 +851,20 @@ function findIslandImportName(source) {
759
851
  function findIslandJsxElements(sourceFile, localName) {
760
852
  const results = [];
761
853
  function visit(node) {
762
- if (ts3.isJsxSelfClosingElement(node)) {
854
+ if (ts4.isJsxSelfClosingElement(node)) {
763
855
  const tagName = node.tagName.getText(sourceFile.compilerNode);
764
856
  if (tagName === localName) {
765
857
  results.push(node);
766
858
  }
767
859
  }
768
- ts3.forEachChild(node, visit);
860
+ ts4.forEachChild(node, visit);
769
861
  }
770
862
  visit(sourceFile.compilerNode);
771
863
  return results;
772
864
  }
773
865
  function hasIdProp(element, sourceFile) {
774
866
  for (const attr of element.attributes.properties) {
775
- if (ts3.isJsxAttribute(attr)) {
867
+ if (ts4.isJsxAttribute(attr)) {
776
868
  const name = attr.name.getText(sourceFile.compilerNode);
777
869
  if (name === "id")
778
870
  return true;
@@ -782,7 +874,7 @@ function hasIdProp(element, sourceFile) {
782
874
  }
783
875
  function extractComponentName(element, sourceFile) {
784
876
  for (const attr of element.attributes.properties) {
785
- if (!ts3.isJsxAttribute(attr))
877
+ if (!ts4.isJsxAttribute(attr))
786
878
  continue;
787
879
  const name = attr.name.getText(sourceFile.compilerNode);
788
880
  if (name !== "component")
@@ -790,8 +882,8 @@ function extractComponentName(element, sourceFile) {
790
882
  const value = attr.initializer;
791
883
  if (!value)
792
884
  return null;
793
- if (ts3.isJsxExpression(value) && value.expression) {
794
- if (ts3.isIdentifier(value.expression)) {
885
+ if (ts4.isJsxExpression(value) && value.expression) {
886
+ if (ts4.isIdentifier(value.expression)) {
795
887
  return value.expression.text;
796
888
  }
797
889
  }
@@ -896,7 +988,7 @@ function createVertzBunPlugin(options) {
896
988
  fieldSelectionManifest.setImportResolver(fieldSelectionResolveImport);
897
989
  let fieldSelectionFileCount = 0;
898
990
  for (const [filePath] of manifests) {
899
- if (filePath.endsWith(".tsx")) {
991
+ if (filePath.endsWith(".tsx") || filePath.endsWith(".ts")) {
900
992
  try {
901
993
  const sourceText = readFileSync3(filePath, "utf-8");
902
994
  fieldSelectionManifest.registerFile(filePath, sourceText);
@@ -953,7 +1045,7 @@ function createVertzBunPlugin(options) {
953
1045
  const hydrationProject = new Project2({
954
1046
  useInMemoryFileSystem: true,
955
1047
  compilerOptions: {
956
- jsx: ts4.JsxEmit.Preserve,
1048
+ jsx: ts5.JsxEmit.Preserve,
957
1049
  strict: true
958
1050
  }
959
1051
  });
@@ -1066,8 +1158,7 @@ function createVertzBunPlugin(options) {
1066
1158
  let refreshEpilogue = "";
1067
1159
  if (fastRefresh) {
1068
1160
  const components = componentAnalyzer.analyze(hydrationSourceFile);
1069
- const contentHash = Bun.hash(source).toString(36);
1070
- const refreshCode = generateRefreshCode(args.path, components, contentHash);
1161
+ const refreshCode = generateRefreshCode(args.path, components, source);
1071
1162
  if (refreshCode) {
1072
1163
  refreshPreamble = refreshCode.preamble;
1073
1164
  refreshEpilogue = refreshCode.epilogue;
@@ -1094,7 +1185,7 @@ function createVertzBunPlugin(options) {
1094
1185
  }
1095
1186
  if (hmr) {
1096
1187
  contents += `
1097
- import.meta.hot.accept();
1188
+ if (import.meta.hot) import.meta.hot.accept();
1098
1189
  `;
1099
1190
  }
1100
1191
  contents += sourceMapComment;
@@ -1164,7 +1255,7 @@ import.meta.hot.accept();
1164
1255
  if (changed) {
1165
1256
  manifestsRecord = null;
1166
1257
  }
1167
- if (filePath.endsWith(".tsx")) {
1258
+ if (filePath.endsWith(".tsx") || filePath.endsWith(".ts")) {
1168
1259
  fieldSelectionManifest.updateFile(filePath, sourceText);
1169
1260
  }
1170
1261
  if (logger?.isEnabled("manifest")) {
@@ -13,6 +13,7 @@ interface VNode {
13
13
  * Base Node class for SSR — matches the browser's Node interface minimally
14
14
  */
15
15
  declare class SSRNode {
16
+ nodeType: number;
16
17
  childNodes: SSRNode[];
17
18
  parentNode: SSRNode | null;
18
19
  get firstChild(): SSRNode | null;
@@ -28,6 +29,7 @@ declare class SSRNode {
28
29
  * hydration cursor can claim during mount.
29
30
  */
30
31
  declare class SSRComment extends SSRNode {
32
+ nodeType: number;
31
33
  text: string;
32
34
  constructor(text: string);
33
35
  get data(): string;
@@ -37,6 +39,7 @@ declare class SSRComment extends SSRNode {
37
39
  * SSR text node
38
40
  */
39
41
  declare class SSRTextNode extends SSRNode {
42
+ nodeType: number;
40
43
  text: string;
41
44
  constructor(text: string);
42
45
  get data(): string;
@@ -46,6 +49,7 @@ declare class SSRTextNode extends SSRNode {
46
49
  * SSR document fragment
47
50
  */
48
51
  declare class SSRDocumentFragment extends SSRNode {
52
+ nodeType: number;
49
53
  children: (SSRElement | string)[];
50
54
  appendChild(child: SSRElement | SSRTextNode | SSRDocumentFragment): void;
51
55
  }
@@ -7,7 +7,7 @@ import {
7
7
  installDomShim,
8
8
  removeDomShim,
9
9
  toVNode
10
- } from "../shared/chunk-bm16zy8d.js";
10
+ } from "../shared/chunk-gcwqkynf.js";
11
11
  export {
12
12
  toVNode,
13
13
  removeDomShim,