@streetui/compiler 1.6.0 → 1.7.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/dist/diagnostics.cjs
CHANGED
|
@@ -55,7 +55,8 @@ function analyzeGraph(graph) {
|
|
|
55
55
|
const hasEvents = node.events.length > 0;
|
|
56
56
|
const isList = node.type === "reactive-list";
|
|
57
57
|
const isConditional = node.type === "conditional";
|
|
58
|
-
const
|
|
58
|
+
const isPortal = node.type === "portal";
|
|
59
|
+
const isStatic = node.stateRefs.length === 0 && !hasEvents && !isList && !isConditional && !isPortal;
|
|
59
60
|
const isStaticSubtree = isStatic && allChildrenStatic;
|
|
60
61
|
nodes.set(node.id, {
|
|
61
62
|
isStatic,
|
package/dist/diagnostics.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/diagnostics.ts","../src/analysis/analyze.ts","../src/analysis/inspect.ts"],"sourcesContent":["/**\n * `@streetui/compiler/diagnostics` — opt-in, build/dev-time compiler diagnostics.\n *\n * The static-graph analysis (`analyzeGraph`) and the human/machine-readable\n * compiler-inspection report (`inspectCompilation` / `formatInspection`) live on\n * this dedicated subpath — deliberately NOT re-exported from the compiler's main\n * barrel — so they never enter the runtime bundle (`streetui`'s `dist/index.js`).\n * They are reached only via the diagnostic subpath (`streetui/testing`), keeping\n * the shipped runtime lean (spec §6/§7/§14).\n */\nexport * from './analysis/analyze.js';\nexport * from './analysis/inspect.js';\n","/**\n * Static graph analysis (v1.2, spec §3/§12/§14).\n *\n * A single post-order walk over the Semantic Application Graph that classifies\n * every node as static (no bound signals, no events, not a reactive\n * region) or dynamic, and rolls that up into whole-static-subtree flags. This\n * is COMPILE-TIME metadata only — it introduces no virtual DOM, no second tree\n * and no runtime reactive system. It is consumed by:\n * - the hydration path, to skip per-node attribute/text re-verification on\n * provably-static subtrees (§12), and\n * - the diagnostic compiler-inspection report (§14).\n *\n * The renderer's initial-mount fast path does not need this map: it already\n * skips reactive wiring per node via cheap `events.length`/`stateRefs.length`\n * guards. Keeping the analysis out of the mount hot path avoids adding lookup\n * cost (and keeps the shipped runtime lean).\n */\n\nimport type { NodeId } from '@streetui/core';\nimport type { ApplicationGraph, GraphNode } from '@streetui/graph';\n\n/** Prop keys whose bound signal drives text content rather than an attribute. */\nconst TEXT_PROP_KEYS: ReadonlySet<string> = new Set(['text', 'label', 'value']);\n\nexport interface NodeAnalysis {\n /** No bound signals, no events, and not a reactive-list/conditional region. */\n readonly isStatic: boolean;\n /** This node is static AND every descendant is a static subtree. */\n readonly isStaticSubtree: boolean;\n /** A signal is bound to this node's text/label/value content. */\n readonly hasDynamicText: boolean;\n /** A signal is bound to a non-content prop (a reactive attribute). */\n readonly hasDynamicAttr: boolean;\n /** This node wires one or more DOM event handlers. */\n readonly hasEvents: boolean;\n /** This node is a keyed reactive list. */\n readonly isList: boolean;\n /** This node is a conditional (0..1 branch) region. */\n readonly isConditional: boolean;\n}\n\nexport interface GraphAnalysisSummary {\n readonly totalNodes: number;\n readonly staticNodes: number;\n readonly staticSubtrees: number;\n readonly dynamicTextNodes: number;\n readonly dynamicAttrNodes: number;\n readonly eventNodes: number;\n readonly lists: number;\n readonly conditionals: number;\n}\n\nexport interface GraphAnalysis {\n readonly nodes: ReadonlyMap<NodeId, NodeAnalysis>;\n readonly summary: GraphAnalysisSummary;\n}\n\n/**\n * Analyze a fully-built graph. O(n) single post-order pass; allocates one small\n * record per node. Safe to skip entirely when neither hydration nor diagnostics\n * need it.\n */\nexport function analyzeGraph(graph: ApplicationGraph): GraphAnalysis {\n const nodes = new Map<NodeId, NodeAnalysis>();\n const summary = {\n totalNodes: 0, staticNodes: 0, staticSubtrees: 0, dynamicTextNodes: 0,\n dynamicAttrNodes: 0, eventNodes: 0, lists: 0, conditionals: 0,\n };\n\n const visit = (node: GraphNode): boolean => {\n // Post-order: children first so subtree rollup is exact.\n let allChildrenStatic = true;\n for (const child of node.children) {\n const childSubtreeStatic = visit(child);\n if (!childSubtreeStatic) allChildrenStatic = false;\n }\n\n let hasDynamicText = false;\n let hasDynamicAttr = false;\n for (const ref of node.stateRefs) {\n if (TEXT_PROP_KEYS.has(ref.propKey)) hasDynamicText = true;\n else hasDynamicAttr = true;\n }\n const hasEvents = node.events.length > 0;\n const isList = node.type === 'reactive-list';\n const isConditional = node.type === 'conditional';\n const isStatic =\n node.stateRefs.length === 0 && !hasEvents && !isList && !isConditional;\n const isStaticSubtree = isStatic && allChildrenStatic;\n\n nodes.set(node.id, {\n isStatic, isStaticSubtree, hasDynamicText, hasDynamicAttr,\n hasEvents, isList, isConditional,\n });\n\n summary.totalNodes += 1;\n if (isStatic) summary.staticNodes += 1;\n if (isStaticSubtree) summary.staticSubtrees += 1;\n if (hasDynamicText) summary.dynamicTextNodes += 1;\n if (hasDynamicAttr) summary.dynamicAttrNodes += 1;\n if (hasEvents) summary.eventNodes += 1;\n if (isList) summary.lists += 1;\n if (isConditional) summary.conditionals += 1;\n\n return isStaticSubtree;\n };\n\n visit(graph.root);\n return { nodes, summary };\n}\n","/**\n * Diagnostic compiler-inspection mode (v1.2, spec §14).\n *\n * Produces a machine-readable (and optionally text-formatted) description of\n * what the compiler understands about an application: which nodes are static\n * vs dynamic, which carry dynamic text or attributes, which wire events, and\n * which are conditional regions or keyed lists — plus the hydration metadata\n * derived from that classification.\n *\n * This is a DIAGNOSTIC tool. It is not part of the runtime, is not consulted\n * during mount, and is tree-shakeable out of any app that never calls it.\n */\n\nimport type { ApplicationGraph, GraphNode } from '@streetui/graph';\nimport { analyzeGraph, type GraphAnalysisSummary } from './analyze.js';\n\nexport interface InspectedCompilationNode {\n readonly id: string;\n readonly type: string;\n readonly depth: number;\n readonly classification: 'static' | 'static-subtree-root' | 'dynamic';\n readonly dynamicText: boolean;\n readonly dynamicAttrs: boolean;\n readonly events: readonly string[];\n readonly boundProps: readonly string[];\n readonly isList: boolean;\n readonly isConditional: boolean;\n /** Hydration hint: how the hydration path should treat this node. */\n readonly hydration: 'adopt-static' | 'verify-dynamic';\n}\n\nexport interface CompilerInspection {\n readonly name: string;\n readonly version: string;\n readonly summary: GraphAnalysisSummary & {\n /** Fraction of nodes provably static (0..1). */\n readonly staticRatio: number;\n };\n readonly nodes: readonly InspectedCompilationNode[];\n}\n\n/**\n * Inspect a compiled/built graph and return a structured diagnostic report.\n * Purely observational — never mutates the graph.\n */\nexport function inspectCompilation(graph: ApplicationGraph): CompilerInspection {\n const analysis = analyzeGraph(graph);\n const nodes: InspectedCompilationNode[] = [];\n\n const walk = (node: GraphNode, depth: number): void => {\n const a = analysis.nodes.get(node.id);\n if (a !== undefined) {\n const classification: InspectedCompilationNode['classification'] = a.isStaticSubtree\n ? 'static-subtree-root'\n : a.isStatic\n ? 'static'\n : 'dynamic';\n nodes.push({\n id: node.id,\n type: node.type,\n depth,\n classification,\n dynamicText: a.hasDynamicText,\n dynamicAttrs: a.hasDynamicAttr,\n events: node.events.map((e) => e.type),\n boundProps: node.stateRefs.map((r) => r.propKey),\n isList: a.isList,\n isConditional: a.isConditional,\n hydration: a.isStaticSubtree ? 'adopt-static' : 'verify-dynamic',\n });\n }\n for (const child of node.children) walk(child, depth + 1);\n };\n walk(graph.root, 0);\n\n const staticRatio =\n analysis.summary.totalNodes === 0\n ? 0\n : analysis.summary.staticNodes / analysis.summary.totalNodes;\n\n return {\n name: graph.name,\n version: graph.version,\n summary: { ...analysis.summary, staticRatio: +staticRatio.toFixed(4) },\n nodes,\n };\n}\n\n/** Render an inspection as a compact human-readable text report. */\nexport function formatInspection(inspection: CompilerInspection): string {\n const s = inspection.summary;\n const lines: string[] = [];\n lines.push(`StreetUI compiler inspection — ${inspection.name} v${inspection.version}`);\n lines.push(\n ` nodes=${s.totalNodes} static=${s.staticNodes} ` +\n `staticSubtrees=${s.staticSubtrees} dynamicText=${s.dynamicTextNodes} ` +\n `dynamicAttrs=${s.dynamicAttrNodes} events=${s.eventNodes} ` +\n `lists=${s.lists} conditionals=${s.conditionals} ` +\n `staticRatio=${(s.staticRatio * 100).toFixed(1)}%`,\n );\n for (const n of inspection.nodes) {\n const flags: string[] = [];\n if (n.dynamicText) flags.push('text');\n if (n.dynamicAttrs) flags.push('attr:' + n.boundProps.join(','));\n if (n.events.length > 0) flags.push('on:' + n.events.join(','));\n if (n.isList) flags.push('list');\n if (n.isConditional) flags.push('cond');\n lines.push(\n ` ${' '.repeat(n.depth)}${n.type}#${n.id} [${n.classification}]` +\n (flags.length > 0 ? ` {${flags.join(' ')}}` : ''),\n );\n }\n return lines.join('\\n');\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/diagnostics.ts","../src/analysis/analyze.ts","../src/analysis/inspect.ts"],"sourcesContent":["/**\n * `@streetui/compiler/diagnostics` — opt-in, build/dev-time compiler diagnostics.\n *\n * The static-graph analysis (`analyzeGraph`) and the human/machine-readable\n * compiler-inspection report (`inspectCompilation` / `formatInspection`) live on\n * this dedicated subpath — deliberately NOT re-exported from the compiler's main\n * barrel — so they never enter the runtime bundle (`streetui`'s `dist/index.js`).\n * They are reached only via the diagnostic subpath (`streetui/testing`), keeping\n * the shipped runtime lean (spec §6/§7/§14).\n */\nexport * from './analysis/analyze.js';\nexport * from './analysis/inspect.js';\n","/**\n * Static graph analysis (v1.2, spec §3/§12/§14).\n *\n * A single post-order walk over the Semantic Application Graph that classifies\n * every node as static (no bound signals, no events, not a reactive\n * region) or dynamic, and rolls that up into whole-static-subtree flags. This\n * is COMPILE-TIME metadata only — it introduces no virtual DOM, no second tree\n * and no runtime reactive system. It is consumed by:\n * - the hydration path, to skip per-node attribute/text re-verification on\n * provably-static subtrees (§12), and\n * - the diagnostic compiler-inspection report (§14).\n *\n * The renderer's initial-mount fast path does not need this map: it already\n * skips reactive wiring per node via cheap `events.length`/`stateRefs.length`\n * guards. Keeping the analysis out of the mount hot path avoids adding lookup\n * cost (and keeps the shipped runtime lean).\n */\n\nimport type { NodeId } from '@streetui/core';\nimport type { ApplicationGraph, GraphNode } from '@streetui/graph';\n\n/** Prop keys whose bound signal drives text content rather than an attribute. */\nconst TEXT_PROP_KEYS: ReadonlySet<string> = new Set(['text', 'label', 'value']);\n\nexport interface NodeAnalysis {\n /** No bound signals, no events, and not a reactive-list/conditional region. */\n readonly isStatic: boolean;\n /** This node is static AND every descendant is a static subtree. */\n readonly isStaticSubtree: boolean;\n /** A signal is bound to this node's text/label/value content. */\n readonly hasDynamicText: boolean;\n /** A signal is bound to a non-content prop (a reactive attribute). */\n readonly hasDynamicAttr: boolean;\n /** This node wires one or more DOM event handlers. */\n readonly hasEvents: boolean;\n /** This node is a keyed reactive list. */\n readonly isList: boolean;\n /** This node is a conditional (0..1 branch) region. */\n readonly isConditional: boolean;\n}\n\nexport interface GraphAnalysisSummary {\n readonly totalNodes: number;\n readonly staticNodes: number;\n readonly staticSubtrees: number;\n readonly dynamicTextNodes: number;\n readonly dynamicAttrNodes: number;\n readonly eventNodes: number;\n readonly lists: number;\n readonly conditionals: number;\n}\n\nexport interface GraphAnalysis {\n readonly nodes: ReadonlyMap<NodeId, NodeAnalysis>;\n readonly summary: GraphAnalysisSummary;\n}\n\n/**\n * Analyze a fully-built graph. O(n) single post-order pass; allocates one small\n * record per node. Safe to skip entirely when neither hydration nor diagnostics\n * need it.\n */\nexport function analyzeGraph(graph: ApplicationGraph): GraphAnalysis {\n const nodes = new Map<NodeId, NodeAnalysis>();\n const summary = {\n totalNodes: 0, staticNodes: 0, staticSubtrees: 0, dynamicTextNodes: 0,\n dynamicAttrNodes: 0, eventNodes: 0, lists: 0, conditionals: 0,\n };\n\n const visit = (node: GraphNode): boolean => {\n // Post-order: children first so subtree rollup is exact.\n let allChildrenStatic = true;\n for (const child of node.children) {\n const childSubtreeStatic = visit(child);\n if (!childSubtreeStatic) allChildrenStatic = false;\n }\n\n let hasDynamicText = false;\n let hasDynamicAttr = false;\n for (const ref of node.stateRefs) {\n if (TEXT_PROP_KEYS.has(ref.propKey)) hasDynamicText = true;\n else hasDynamicAttr = true;\n }\n const hasEvents = node.events.length > 0;\n const isList = node.type === 'reactive-list';\n const isConditional = node.type === 'conditional';\n // A portal relocates its children to a different DOM location (document.body)\n // at mount time, so its subtree is never a contiguous inline static blob.\n // Treating it as dynamic stops the static-subtree rollup at the portal, so\n // both SSR and hydration always take the real portal mount/hydrate branch\n // (which does the relocation) rather than emitting/adopting a raw HTML blob.\n const isPortal = node.type === 'portal';\n const isStatic =\n node.stateRefs.length === 0 && !hasEvents && !isList && !isConditional && !isPortal;\n const isStaticSubtree = isStatic && allChildrenStatic;\n\n nodes.set(node.id, {\n isStatic, isStaticSubtree, hasDynamicText, hasDynamicAttr,\n hasEvents, isList, isConditional,\n });\n\n summary.totalNodes += 1;\n if (isStatic) summary.staticNodes += 1;\n if (isStaticSubtree) summary.staticSubtrees += 1;\n if (hasDynamicText) summary.dynamicTextNodes += 1;\n if (hasDynamicAttr) summary.dynamicAttrNodes += 1;\n if (hasEvents) summary.eventNodes += 1;\n if (isList) summary.lists += 1;\n if (isConditional) summary.conditionals += 1;\n\n return isStaticSubtree;\n };\n\n visit(graph.root);\n return { nodes, summary };\n}\n","/**\n * Diagnostic compiler-inspection mode (v1.2, spec §14).\n *\n * Produces a machine-readable (and optionally text-formatted) description of\n * what the compiler understands about an application: which nodes are static\n * vs dynamic, which carry dynamic text or attributes, which wire events, and\n * which are conditional regions or keyed lists — plus the hydration metadata\n * derived from that classification.\n *\n * This is a DIAGNOSTIC tool. It is not part of the runtime, is not consulted\n * during mount, and is tree-shakeable out of any app that never calls it.\n */\n\nimport type { ApplicationGraph, GraphNode } from '@streetui/graph';\nimport { analyzeGraph, type GraphAnalysisSummary } from './analyze.js';\n\nexport interface InspectedCompilationNode {\n readonly id: string;\n readonly type: string;\n readonly depth: number;\n readonly classification: 'static' | 'static-subtree-root' | 'dynamic';\n readonly dynamicText: boolean;\n readonly dynamicAttrs: boolean;\n readonly events: readonly string[];\n readonly boundProps: readonly string[];\n readonly isList: boolean;\n readonly isConditional: boolean;\n /** Hydration hint: how the hydration path should treat this node. */\n readonly hydration: 'adopt-static' | 'verify-dynamic';\n}\n\nexport interface CompilerInspection {\n readonly name: string;\n readonly version: string;\n readonly summary: GraphAnalysisSummary & {\n /** Fraction of nodes provably static (0..1). */\n readonly staticRatio: number;\n };\n readonly nodes: readonly InspectedCompilationNode[];\n}\n\n/**\n * Inspect a compiled/built graph and return a structured diagnostic report.\n * Purely observational — never mutates the graph.\n */\nexport function inspectCompilation(graph: ApplicationGraph): CompilerInspection {\n const analysis = analyzeGraph(graph);\n const nodes: InspectedCompilationNode[] = [];\n\n const walk = (node: GraphNode, depth: number): void => {\n const a = analysis.nodes.get(node.id);\n if (a !== undefined) {\n const classification: InspectedCompilationNode['classification'] = a.isStaticSubtree\n ? 'static-subtree-root'\n : a.isStatic\n ? 'static'\n : 'dynamic';\n nodes.push({\n id: node.id,\n type: node.type,\n depth,\n classification,\n dynamicText: a.hasDynamicText,\n dynamicAttrs: a.hasDynamicAttr,\n events: node.events.map((e) => e.type),\n boundProps: node.stateRefs.map((r) => r.propKey),\n isList: a.isList,\n isConditional: a.isConditional,\n hydration: a.isStaticSubtree ? 'adopt-static' : 'verify-dynamic',\n });\n }\n for (const child of node.children) walk(child, depth + 1);\n };\n walk(graph.root, 0);\n\n const staticRatio =\n analysis.summary.totalNodes === 0\n ? 0\n : analysis.summary.staticNodes / analysis.summary.totalNodes;\n\n return {\n name: graph.name,\n version: graph.version,\n summary: { ...analysis.summary, staticRatio: +staticRatio.toFixed(4) },\n nodes,\n };\n}\n\n/** Render an inspection as a compact human-readable text report. */\nexport function formatInspection(inspection: CompilerInspection): string {\n const s = inspection.summary;\n const lines: string[] = [];\n lines.push(`StreetUI compiler inspection — ${inspection.name} v${inspection.version}`);\n lines.push(\n ` nodes=${s.totalNodes} static=${s.staticNodes} ` +\n `staticSubtrees=${s.staticSubtrees} dynamicText=${s.dynamicTextNodes} ` +\n `dynamicAttrs=${s.dynamicAttrNodes} events=${s.eventNodes} ` +\n `lists=${s.lists} conditionals=${s.conditionals} ` +\n `staticRatio=${(s.staticRatio * 100).toFixed(1)}%`,\n );\n for (const n of inspection.nodes) {\n const flags: string[] = [];\n if (n.dynamicText) flags.push('text');\n if (n.dynamicAttrs) flags.push('attr:' + n.boundProps.join(','));\n if (n.events.length > 0) flags.push('on:' + n.events.join(','));\n if (n.isList) flags.push('list');\n if (n.isConditional) flags.push('cond');\n lines.push(\n ` ${' '.repeat(n.depth)}${n.type}#${n.id} [${n.classification}]` +\n (flags.length > 0 ? ` {${flags.join(' ')}}` : ''),\n );\n }\n return lines.join('\\n');\n}\n"],"mappings":"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","names":[]}
|
package/dist/diagnostics.js
CHANGED
|
@@ -27,7 +27,8 @@ function analyzeGraph(graph) {
|
|
|
27
27
|
const hasEvents = node.events.length > 0;
|
|
28
28
|
const isList = node.type === "reactive-list";
|
|
29
29
|
const isConditional = node.type === "conditional";
|
|
30
|
-
const
|
|
30
|
+
const isPortal = node.type === "portal";
|
|
31
|
+
const isStatic = node.stateRefs.length === 0 && !hasEvents && !isList && !isConditional && !isPortal;
|
|
31
32
|
const isStaticSubtree = isStatic && allChildrenStatic;
|
|
32
33
|
nodes.set(node.id, {
|
|
33
34
|
isStatic,
|
package/dist/diagnostics.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/analysis/analyze.ts","../src/analysis/inspect.ts"],"sourcesContent":["/**\n * Static graph analysis (v1.2, spec §3/§12/§14).\n *\n * A single post-order walk over the Semantic Application Graph that classifies\n * every node as static (no bound signals, no events, not a reactive\n * region) or dynamic, and rolls that up into whole-static-subtree flags. This\n * is COMPILE-TIME metadata only — it introduces no virtual DOM, no second tree\n * and no runtime reactive system. It is consumed by:\n * - the hydration path, to skip per-node attribute/text re-verification on\n * provably-static subtrees (§12), and\n * - the diagnostic compiler-inspection report (§14).\n *\n * The renderer's initial-mount fast path does not need this map: it already\n * skips reactive wiring per node via cheap `events.length`/`stateRefs.length`\n * guards. Keeping the analysis out of the mount hot path avoids adding lookup\n * cost (and keeps the shipped runtime lean).\n */\n\nimport type { NodeId } from '@streetui/core';\nimport type { ApplicationGraph, GraphNode } from '@streetui/graph';\n\n/** Prop keys whose bound signal drives text content rather than an attribute. */\nconst TEXT_PROP_KEYS: ReadonlySet<string> = new Set(['text', 'label', 'value']);\n\nexport interface NodeAnalysis {\n /** No bound signals, no events, and not a reactive-list/conditional region. */\n readonly isStatic: boolean;\n /** This node is static AND every descendant is a static subtree. */\n readonly isStaticSubtree: boolean;\n /** A signal is bound to this node's text/label/value content. */\n readonly hasDynamicText: boolean;\n /** A signal is bound to a non-content prop (a reactive attribute). */\n readonly hasDynamicAttr: boolean;\n /** This node wires one or more DOM event handlers. */\n readonly hasEvents: boolean;\n /** This node is a keyed reactive list. */\n readonly isList: boolean;\n /** This node is a conditional (0..1 branch) region. */\n readonly isConditional: boolean;\n}\n\nexport interface GraphAnalysisSummary {\n readonly totalNodes: number;\n readonly staticNodes: number;\n readonly staticSubtrees: number;\n readonly dynamicTextNodes: number;\n readonly dynamicAttrNodes: number;\n readonly eventNodes: number;\n readonly lists: number;\n readonly conditionals: number;\n}\n\nexport interface GraphAnalysis {\n readonly nodes: ReadonlyMap<NodeId, NodeAnalysis>;\n readonly summary: GraphAnalysisSummary;\n}\n\n/**\n * Analyze a fully-built graph. O(n) single post-order pass; allocates one small\n * record per node. Safe to skip entirely when neither hydration nor diagnostics\n * need it.\n */\nexport function analyzeGraph(graph: ApplicationGraph): GraphAnalysis {\n const nodes = new Map<NodeId, NodeAnalysis>();\n const summary = {\n totalNodes: 0, staticNodes: 0, staticSubtrees: 0, dynamicTextNodes: 0,\n dynamicAttrNodes: 0, eventNodes: 0, lists: 0, conditionals: 0,\n };\n\n const visit = (node: GraphNode): boolean => {\n // Post-order: children first so subtree rollup is exact.\n let allChildrenStatic = true;\n for (const child of node.children) {\n const childSubtreeStatic = visit(child);\n if (!childSubtreeStatic) allChildrenStatic = false;\n }\n\n let hasDynamicText = false;\n let hasDynamicAttr = false;\n for (const ref of node.stateRefs) {\n if (TEXT_PROP_KEYS.has(ref.propKey)) hasDynamicText = true;\n else hasDynamicAttr = true;\n }\n const hasEvents = node.events.length > 0;\n const isList = node.type === 'reactive-list';\n const isConditional = node.type === 'conditional';\n const isStatic =\n node.stateRefs.length === 0 && !hasEvents && !isList && !isConditional;\n const isStaticSubtree = isStatic && allChildrenStatic;\n\n nodes.set(node.id, {\n isStatic, isStaticSubtree, hasDynamicText, hasDynamicAttr,\n hasEvents, isList, isConditional,\n });\n\n summary.totalNodes += 1;\n if (isStatic) summary.staticNodes += 1;\n if (isStaticSubtree) summary.staticSubtrees += 1;\n if (hasDynamicText) summary.dynamicTextNodes += 1;\n if (hasDynamicAttr) summary.dynamicAttrNodes += 1;\n if (hasEvents) summary.eventNodes += 1;\n if (isList) summary.lists += 1;\n if (isConditional) summary.conditionals += 1;\n\n return isStaticSubtree;\n };\n\n visit(graph.root);\n return { nodes, summary };\n}\n","/**\n * Diagnostic compiler-inspection mode (v1.2, spec §14).\n *\n * Produces a machine-readable (and optionally text-formatted) description of\n * what the compiler understands about an application: which nodes are static\n * vs dynamic, which carry dynamic text or attributes, which wire events, and\n * which are conditional regions or keyed lists — plus the hydration metadata\n * derived from that classification.\n *\n * This is a DIAGNOSTIC tool. It is not part of the runtime, is not consulted\n * during mount, and is tree-shakeable out of any app that never calls it.\n */\n\nimport type { ApplicationGraph, GraphNode } from '@streetui/graph';\nimport { analyzeGraph, type GraphAnalysisSummary } from './analyze.js';\n\nexport interface InspectedCompilationNode {\n readonly id: string;\n readonly type: string;\n readonly depth: number;\n readonly classification: 'static' | 'static-subtree-root' | 'dynamic';\n readonly dynamicText: boolean;\n readonly dynamicAttrs: boolean;\n readonly events: readonly string[];\n readonly boundProps: readonly string[];\n readonly isList: boolean;\n readonly isConditional: boolean;\n /** Hydration hint: how the hydration path should treat this node. */\n readonly hydration: 'adopt-static' | 'verify-dynamic';\n}\n\nexport interface CompilerInspection {\n readonly name: string;\n readonly version: string;\n readonly summary: GraphAnalysisSummary & {\n /** Fraction of nodes provably static (0..1). */\n readonly staticRatio: number;\n };\n readonly nodes: readonly InspectedCompilationNode[];\n}\n\n/**\n * Inspect a compiled/built graph and return a structured diagnostic report.\n * Purely observational — never mutates the graph.\n */\nexport function inspectCompilation(graph: ApplicationGraph): CompilerInspection {\n const analysis = analyzeGraph(graph);\n const nodes: InspectedCompilationNode[] = [];\n\n const walk = (node: GraphNode, depth: number): void => {\n const a = analysis.nodes.get(node.id);\n if (a !== undefined) {\n const classification: InspectedCompilationNode['classification'] = a.isStaticSubtree\n ? 'static-subtree-root'\n : a.isStatic\n ? 'static'\n : 'dynamic';\n nodes.push({\n id: node.id,\n type: node.type,\n depth,\n classification,\n dynamicText: a.hasDynamicText,\n dynamicAttrs: a.hasDynamicAttr,\n events: node.events.map((e) => e.type),\n boundProps: node.stateRefs.map((r) => r.propKey),\n isList: a.isList,\n isConditional: a.isConditional,\n hydration: a.isStaticSubtree ? 'adopt-static' : 'verify-dynamic',\n });\n }\n for (const child of node.children) walk(child, depth + 1);\n };\n walk(graph.root, 0);\n\n const staticRatio =\n analysis.summary.totalNodes === 0\n ? 0\n : analysis.summary.staticNodes / analysis.summary.totalNodes;\n\n return {\n name: graph.name,\n version: graph.version,\n summary: { ...analysis.summary, staticRatio: +staticRatio.toFixed(4) },\n nodes,\n };\n}\n\n/** Render an inspection as a compact human-readable text report. */\nexport function formatInspection(inspection: CompilerInspection): string {\n const s = inspection.summary;\n const lines: string[] = [];\n lines.push(`StreetUI compiler inspection — ${inspection.name} v${inspection.version}`);\n lines.push(\n ` nodes=${s.totalNodes} static=${s.staticNodes} ` +\n `staticSubtrees=${s.staticSubtrees} dynamicText=${s.dynamicTextNodes} ` +\n `dynamicAttrs=${s.dynamicAttrNodes} events=${s.eventNodes} ` +\n `lists=${s.lists} conditionals=${s.conditionals} ` +\n `staticRatio=${(s.staticRatio * 100).toFixed(1)}%`,\n );\n for (const n of inspection.nodes) {\n const flags: string[] = [];\n if (n.dynamicText) flags.push('text');\n if (n.dynamicAttrs) flags.push('attr:' + n.boundProps.join(','));\n if (n.events.length > 0) flags.push('on:' + n.events.join(','));\n if (n.isList) flags.push('list');\n if (n.isConditional) flags.push('cond');\n lines.push(\n ` ${' '.repeat(n.depth)}${n.type}#${n.id} [${n.classification}]` +\n (flags.length > 0 ? ` {${flags.join(' ')}}` : ''),\n );\n }\n return lines.join('\\n');\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/analysis/analyze.ts","../src/analysis/inspect.ts"],"sourcesContent":["/**\n * Static graph analysis (v1.2, spec §3/§12/§14).\n *\n * A single post-order walk over the Semantic Application Graph that classifies\n * every node as static (no bound signals, no events, not a reactive\n * region) or dynamic, and rolls that up into whole-static-subtree flags. This\n * is COMPILE-TIME metadata only — it introduces no virtual DOM, no second tree\n * and no runtime reactive system. It is consumed by:\n * - the hydration path, to skip per-node attribute/text re-verification on\n * provably-static subtrees (§12), and\n * - the diagnostic compiler-inspection report (§14).\n *\n * The renderer's initial-mount fast path does not need this map: it already\n * skips reactive wiring per node via cheap `events.length`/`stateRefs.length`\n * guards. Keeping the analysis out of the mount hot path avoids adding lookup\n * cost (and keeps the shipped runtime lean).\n */\n\nimport type { NodeId } from '@streetui/core';\nimport type { ApplicationGraph, GraphNode } from '@streetui/graph';\n\n/** Prop keys whose bound signal drives text content rather than an attribute. */\nconst TEXT_PROP_KEYS: ReadonlySet<string> = new Set(['text', 'label', 'value']);\n\nexport interface NodeAnalysis {\n /** No bound signals, no events, and not a reactive-list/conditional region. */\n readonly isStatic: boolean;\n /** This node is static AND every descendant is a static subtree. */\n readonly isStaticSubtree: boolean;\n /** A signal is bound to this node's text/label/value content. */\n readonly hasDynamicText: boolean;\n /** A signal is bound to a non-content prop (a reactive attribute). */\n readonly hasDynamicAttr: boolean;\n /** This node wires one or more DOM event handlers. */\n readonly hasEvents: boolean;\n /** This node is a keyed reactive list. */\n readonly isList: boolean;\n /** This node is a conditional (0..1 branch) region. */\n readonly isConditional: boolean;\n}\n\nexport interface GraphAnalysisSummary {\n readonly totalNodes: number;\n readonly staticNodes: number;\n readonly staticSubtrees: number;\n readonly dynamicTextNodes: number;\n readonly dynamicAttrNodes: number;\n readonly eventNodes: number;\n readonly lists: number;\n readonly conditionals: number;\n}\n\nexport interface GraphAnalysis {\n readonly nodes: ReadonlyMap<NodeId, NodeAnalysis>;\n readonly summary: GraphAnalysisSummary;\n}\n\n/**\n * Analyze a fully-built graph. O(n) single post-order pass; allocates one small\n * record per node. Safe to skip entirely when neither hydration nor diagnostics\n * need it.\n */\nexport function analyzeGraph(graph: ApplicationGraph): GraphAnalysis {\n const nodes = new Map<NodeId, NodeAnalysis>();\n const summary = {\n totalNodes: 0, staticNodes: 0, staticSubtrees: 0, dynamicTextNodes: 0,\n dynamicAttrNodes: 0, eventNodes: 0, lists: 0, conditionals: 0,\n };\n\n const visit = (node: GraphNode): boolean => {\n // Post-order: children first so subtree rollup is exact.\n let allChildrenStatic = true;\n for (const child of node.children) {\n const childSubtreeStatic = visit(child);\n if (!childSubtreeStatic) allChildrenStatic = false;\n }\n\n let hasDynamicText = false;\n let hasDynamicAttr = false;\n for (const ref of node.stateRefs) {\n if (TEXT_PROP_KEYS.has(ref.propKey)) hasDynamicText = true;\n else hasDynamicAttr = true;\n }\n const hasEvents = node.events.length > 0;\n const isList = node.type === 'reactive-list';\n const isConditional = node.type === 'conditional';\n // A portal relocates its children to a different DOM location (document.body)\n // at mount time, so its subtree is never a contiguous inline static blob.\n // Treating it as dynamic stops the static-subtree rollup at the portal, so\n // both SSR and hydration always take the real portal mount/hydrate branch\n // (which does the relocation) rather than emitting/adopting a raw HTML blob.\n const isPortal = node.type === 'portal';\n const isStatic =\n node.stateRefs.length === 0 && !hasEvents && !isList && !isConditional && !isPortal;\n const isStaticSubtree = isStatic && allChildrenStatic;\n\n nodes.set(node.id, {\n isStatic, isStaticSubtree, hasDynamicText, hasDynamicAttr,\n hasEvents, isList, isConditional,\n });\n\n summary.totalNodes += 1;\n if (isStatic) summary.staticNodes += 1;\n if (isStaticSubtree) summary.staticSubtrees += 1;\n if (hasDynamicText) summary.dynamicTextNodes += 1;\n if (hasDynamicAttr) summary.dynamicAttrNodes += 1;\n if (hasEvents) summary.eventNodes += 1;\n if (isList) summary.lists += 1;\n if (isConditional) summary.conditionals += 1;\n\n return isStaticSubtree;\n };\n\n visit(graph.root);\n return { nodes, summary };\n}\n","/**\n * Diagnostic compiler-inspection mode (v1.2, spec §14).\n *\n * Produces a machine-readable (and optionally text-formatted) description of\n * what the compiler understands about an application: which nodes are static\n * vs dynamic, which carry dynamic text or attributes, which wire events, and\n * which are conditional regions or keyed lists — plus the hydration metadata\n * derived from that classification.\n *\n * This is a DIAGNOSTIC tool. It is not part of the runtime, is not consulted\n * during mount, and is tree-shakeable out of any app that never calls it.\n */\n\nimport type { ApplicationGraph, GraphNode } from '@streetui/graph';\nimport { analyzeGraph, type GraphAnalysisSummary } from './analyze.js';\n\nexport interface InspectedCompilationNode {\n readonly id: string;\n readonly type: string;\n readonly depth: number;\n readonly classification: 'static' | 'static-subtree-root' | 'dynamic';\n readonly dynamicText: boolean;\n readonly dynamicAttrs: boolean;\n readonly events: readonly string[];\n readonly boundProps: readonly string[];\n readonly isList: boolean;\n readonly isConditional: boolean;\n /** Hydration hint: how the hydration path should treat this node. */\n readonly hydration: 'adopt-static' | 'verify-dynamic';\n}\n\nexport interface CompilerInspection {\n readonly name: string;\n readonly version: string;\n readonly summary: GraphAnalysisSummary & {\n /** Fraction of nodes provably static (0..1). */\n readonly staticRatio: number;\n };\n readonly nodes: readonly InspectedCompilationNode[];\n}\n\n/**\n * Inspect a compiled/built graph and return a structured diagnostic report.\n * Purely observational — never mutates the graph.\n */\nexport function inspectCompilation(graph: ApplicationGraph): CompilerInspection {\n const analysis = analyzeGraph(graph);\n const nodes: InspectedCompilationNode[] = [];\n\n const walk = (node: GraphNode, depth: number): void => {\n const a = analysis.nodes.get(node.id);\n if (a !== undefined) {\n const classification: InspectedCompilationNode['classification'] = a.isStaticSubtree\n ? 'static-subtree-root'\n : a.isStatic\n ? 'static'\n : 'dynamic';\n nodes.push({\n id: node.id,\n type: node.type,\n depth,\n classification,\n dynamicText: a.hasDynamicText,\n dynamicAttrs: a.hasDynamicAttr,\n events: node.events.map((e) => e.type),\n boundProps: node.stateRefs.map((r) => r.propKey),\n isList: a.isList,\n isConditional: a.isConditional,\n hydration: a.isStaticSubtree ? 'adopt-static' : 'verify-dynamic',\n });\n }\n for (const child of node.children) walk(child, depth + 1);\n };\n walk(graph.root, 0);\n\n const staticRatio =\n analysis.summary.totalNodes === 0\n ? 0\n : analysis.summary.staticNodes / analysis.summary.totalNodes;\n\n return {\n name: graph.name,\n version: graph.version,\n summary: { ...analysis.summary, staticRatio: +staticRatio.toFixed(4) },\n nodes,\n };\n}\n\n/** Render an inspection as a compact human-readable text report. */\nexport function formatInspection(inspection: CompilerInspection): string {\n const s = inspection.summary;\n const lines: string[] = [];\n lines.push(`StreetUI compiler inspection — ${inspection.name} v${inspection.version}`);\n lines.push(\n ` nodes=${s.totalNodes} static=${s.staticNodes} ` +\n `staticSubtrees=${s.staticSubtrees} dynamicText=${s.dynamicTextNodes} ` +\n `dynamicAttrs=${s.dynamicAttrNodes} events=${s.eventNodes} ` +\n `lists=${s.lists} conditionals=${s.conditionals} ` +\n `staticRatio=${(s.staticRatio * 100).toFixed(1)}%`,\n );\n for (const n of inspection.nodes) {\n const flags: string[] = [];\n if (n.dynamicText) flags.push('text');\n if (n.dynamicAttrs) flags.push('attr:' + n.boundProps.join(','));\n if (n.events.length > 0) flags.push('on:' + n.events.join(','));\n if (n.isList) flags.push('list');\n if (n.isConditional) flags.push('cond');\n lines.push(\n ` ${' '.repeat(n.depth)}${n.type}#${n.id} [${n.classification}]` +\n (flags.length > 0 ? ` {${flags.join(' ')}}` : ''),\n );\n }\n return lines.join('\\n');\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@streetui/compiler",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "StreetUI compiler — DSL → validation → Semantic Application Graph",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -35,10 +35,10 @@
|
|
|
35
35
|
"clean": "rm -rf dist"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@streetui/core": "1.
|
|
39
|
-
"@streetui/graph": "1.
|
|
40
|
-
"@streetui/dsl": "1.
|
|
41
|
-
"@streetui/state": "1.
|
|
38
|
+
"@streetui/core": "1.7.0",
|
|
39
|
+
"@streetui/graph": "1.7.0",
|
|
40
|
+
"@streetui/dsl": "1.7.0",
|
|
41
|
+
"@streetui/state": "1.7.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"typescript": "*",
|