@jxsuite/runtime 1.2.0 → 1.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jxsuite/runtime",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "JSON-native reactive web component runtime",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -30,10 +30,10 @@
30
30
  "upgrade": "bunx npm-check-updates -u && bun install"
31
31
  },
32
32
  "dependencies": {
33
- "@jxsuite/schema": "^1.2.0",
34
- "@vue/reactivity": "^3.5.40"
33
+ "@jxsuite/schema": "^1.3.0",
34
+ "@vue/reactivity": "3.5.40"
35
35
  },
36
36
  "devDependencies": {
37
- "@happy-dom/global-registrator": "^20.10.6"
37
+ "@happy-dom/global-registrator": "^20.11.0"
38
38
  }
39
39
  }
package/src/runtime.ts CHANGED
@@ -26,6 +26,7 @@ import {
26
26
  import { evaluateExpression, isMutating } from "./expression.ts";
27
27
  import type { DynamicClass, JxEventHandler, JxPath, JxRenderOptions, JxScope } from "./types.ts";
28
28
  import {
29
+ bodyReturnsValue,
29
30
  hasStructuredBody,
30
31
  isExpressionDef,
31
32
  isFunctionDef,
@@ -35,6 +36,7 @@ import {
35
36
  isPrototypeDef,
36
37
  isRef as isRefValue,
37
38
  isServerFnDef,
39
+ isTemplateString,
38
40
  paramNames,
39
41
  } from "@jxsuite/schema/guards";
40
42
  import { runStatements } from "./statements.ts";
@@ -68,6 +70,29 @@ import type { Ref } from "@vue/reactivity";
68
70
  * @returns {Promise<JxScope>} Resolves with the live component scope (state reactive
69
71
  * proxy)
70
72
  */
73
+ /** The document-schema version this runtime understands (the `/schema/vN` in a hosted `$schema`). */
74
+ export const SUPPORTED_SCHEMA_VERSION = 1;
75
+ let _schemaVersionWarned = false;
76
+
77
+ /**
78
+ * Warn once when a document declares a hosted `$schema` whose version differs from the one this
79
+ * runtime supports. A relative or non-hosted `$schema` (local dev schema paths) is ignored. This is
80
+ * a diagnostic only — there is no document-format migration story yet (see spec §3.2 / §21.4).
81
+ */
82
+ function checkSchemaVersion($schema: unknown): void {
83
+ if (_schemaVersionWarned || typeof $schema !== "string") {
84
+ return;
85
+ }
86
+ const match = $schema.match(/\/schema\/v(\d+)\b/);
87
+ if (match && Number(match[1]) !== SUPPORTED_SCHEMA_VERSION) {
88
+ _schemaVersionWarned = true;
89
+ console.warn(
90
+ `Jx: document $schema is version v${match[1]} but this runtime supports ` +
91
+ `v${SUPPORTED_SCHEMA_VERSION}. Behavior may be undefined; there is no format migrator yet.`,
92
+ );
93
+ }
94
+ }
95
+
71
96
  export async function Jx(
72
97
  source: string | JxDocument,
73
98
  target: HTMLElement = document.body,
@@ -75,6 +100,7 @@ export async function Jx(
75
100
  ) {
76
101
  const base = typeof source === "string" ? new URL(source, location.href).href : location.href;
77
102
  const doc = await resolve(source);
103
+ checkSchemaVersion(doc.$schema);
78
104
 
79
105
  // Register custom elements declared in $elements (depth-first)
80
106
  if (doc.$elements) {
@@ -615,9 +641,9 @@ async function resolveFunction(def: JxFunctionDef, state: JxScope, key: string,
615
641
  let isComputed = false;
616
642
  if (!hasParams) {
617
643
  if (typeof def.body === "string") {
618
- isComputed = /\breturn\b/.test(def.body);
644
+ isComputed = bodyReturnsValue(def.body);
619
645
  } else if (fn) {
620
- isComputed = fn.length <= 1 && /\breturn\b/.test(fn.toString());
646
+ isComputed = fn.length <= 1 && bodyReturnsValue(fn.toString());
621
647
  }
622
648
  }
623
649
  if (isComputed) {
@@ -690,6 +716,9 @@ export const RESERVED_KEYS = new Set([
690
716
  * @param {JxRenderOptions} [options]
691
717
  * @returns {HTMLElement | Text}
692
718
  */
719
+ /** Refs already warned about, so the §13 diagnostic fires once per distinct target. */
720
+ const warnedRefChildren = new Set<string>();
721
+
693
722
  export function renderNode(
694
723
  def: JxElement | string | number | boolean,
695
724
  state: JxScope,
@@ -719,6 +748,22 @@ export function renderNode(
719
748
  }
720
749
  }
721
750
 
751
+ /*
752
+ * §13 diagnostic: a node-level external `$ref` (the withdrawn component-instance syntax) has no
753
+ * tagName and silently renders an empty <div>. Warn once per target instead of failing silently —
754
+ * the supported mechanism is $elements + a custom-element tag (see spec §13).
755
+ */
756
+ if ("$ref" in def && !def.tagName) {
757
+ const refTarget = String((def as { $ref?: unknown }).$ref ?? "");
758
+ if (!warnedRefChildren.has(refTarget)) {
759
+ warnedRefChildren.add(refTarget);
760
+ console.warn(
761
+ `Jx: a $ref child ("${refTarget}") is not a supported component instance; register the ` +
762
+ `component in $elements and use its custom-element tag instead (spec §13).`,
763
+ );
764
+ }
765
+ }
766
+
722
767
  // Custom element with $props: set JS properties on the element instance
723
768
  const tagName = def.tagName ?? "div";
724
769
  const isCustomEl = tagName.includes("-") && customElements.get(tagName);
@@ -780,10 +825,6 @@ export function renderNode(
780
825
  * @param {unknown} val
781
826
  * @returns {boolean}
782
827
  */
783
- function isTemplateString(val: unknown): val is string {
784
- return typeof val === "string" && val.includes("${");
785
- }
786
-
787
828
  // ─── Property / style / attribute application ─────────────────────────────────
788
829
 
789
830
  /**