Extend the spec with IO scanning functions (scan-io-refs, transitive-io-refs, compute-all-io-refs, component-pure?) that detect IO primitive references in component ASTs. Components are classified as pure (no IO deps, safe for client rendering) or IO-dependent (must expand server-side). The partial evaluator (_aser) now uses per-component IO metadata instead of the global _expand_components toggle: IO-dependent components expand server- side, pure components serialize for client. Layout slot context still expands all components for backwards compat. Spec: 5 new functions + 2 platform interface additions in deps.sx Host: io_refs field + is_pure property on Component, compute_all_io_refs() Bootstrap: both sx_ref.py and sx-ref.js updated with IO functions Bundle analyzer: shows pure/IO classification per page Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
83 lines
4.6 KiB
Plaintext
83 lines
4.6 KiB
Plaintext
;; Bundle analyzer — live demonstration of dependency analysis + IO detection.
|
|
;; Shows per-page component bundles vs total, visualizing payload savings.
|
|
;; Phase 2 adds IO classification: which components are pure vs IO-dependent.
|
|
;; @css bg-green-100 text-green-800 bg-violet-600 bg-stone-200 text-violet-600 text-stone-600 text-green-600 rounded-full h-2.5 grid-cols-3 bg-blue-100 text-blue-800 bg-amber-100 text-amber-800 grid-cols-4
|
|
|
|
(defcomp ~bundle-analyzer-content (&key pages total-components total-macros
|
|
pure-count io-count)
|
|
(~doc-page :title "Page Bundle Analyzer"
|
|
|
|
(p :class "text-stone-600 mb-6"
|
|
"Live analysis of component dependency graphs and IO classification across all pages. "
|
|
"Each bar shows how many of the "
|
|
(strong (str total-components))
|
|
" total components a page actually needs, computed by the "
|
|
(a :href "/specs/deps" :class "text-violet-700 underline" "deps.sx")
|
|
" transitive closure algorithm. "
|
|
"Phase 2 IO detection classifies each component as pure or IO-dependent.")
|
|
|
|
(div :class "mb-8 grid grid-cols-4 gap-4"
|
|
(~analyzer-stat :label "Total Components" :value (str total-components)
|
|
:cls "text-violet-600")
|
|
(~analyzer-stat :label "Total Macros" :value (str total-macros)
|
|
:cls "text-stone-600")
|
|
(~analyzer-stat :label "Pure Components" :value (str pure-count)
|
|
:cls "text-blue-600")
|
|
(~analyzer-stat :label "IO-Dependent" :value (str io-count)
|
|
:cls "text-amber-600"))
|
|
|
|
(~doc-section :title "Per-Page Bundles" :id "bundles"
|
|
(div :class "space-y-3"
|
|
(map (fn (page)
|
|
(~analyzer-row
|
|
:name (get page "name")
|
|
:path (get page "path")
|
|
:needed (get page "needed")
|
|
:direct (get page "direct")
|
|
:total total-components
|
|
:pct (get page "pct")
|
|
:savings (get page "savings")
|
|
:io-refs (get page "io-refs")
|
|
:pure-in-page (get page "pure-in-page")
|
|
:io-in-page (get page "io-in-page")))
|
|
pages)))
|
|
|
|
(~doc-section :title "How It Works" :id "how"
|
|
(ol :class "list-decimal pl-5 space-y-2 text-stone-700"
|
|
(li (strong "Scan: ") "Regex finds all " (code "(~name") " patterns in the page's content expression.")
|
|
(li (strong "Resolve: ") "Each referenced component's body AST is walked to find transitive " (code "~") " references.")
|
|
(li (strong "Closure: ") "The full set is the union of direct + transitive deps, following chains through the component graph.")
|
|
(li (strong "Bundle: ") "Only these component definitions are serialized into the page payload. Everything else is omitted.")
|
|
(li (strong "IO detect: ") "Each component body is scanned for references to IO primitives (frag, query, service, etc.). Components with zero transitive IO refs are pure — safe for client rendering."))
|
|
(p :class "mt-4 text-stone-600"
|
|
"The analysis handles circular references (via seen-set), "
|
|
"walks all branches of control flow (if/when/cond/case), "
|
|
"and includes macro definitions shared across components."))))
|
|
|
|
(defcomp ~analyzer-stat (&key label value cls)
|
|
(div :class "rounded-lg border border-stone-200 p-4 text-center"
|
|
(div :class (str "text-3xl font-bold " cls) value)
|
|
(div :class "text-sm text-stone-500 mt-1" label)))
|
|
|
|
(defcomp ~analyzer-row (&key name path needed direct total pct savings
|
|
io-refs pure-in-page io-in-page)
|
|
(div :class "rounded border border-stone-200 p-4"
|
|
(div :class "flex items-center justify-between mb-2"
|
|
(div
|
|
(span :class "font-mono font-semibold text-stone-800" name)
|
|
(span :class "text-stone-400 text-sm ml-2" path))
|
|
(div :class "flex items-center gap-2"
|
|
(span :class "inline-block px-1.5 py-0.5 rounded text-xs font-medium bg-blue-100 text-blue-800"
|
|
(str pure-in-page " pure"))
|
|
(span :class "inline-block px-1.5 py-0.5 rounded text-xs font-medium bg-amber-100 text-amber-800"
|
|
(str io-in-page " IO"))
|
|
(div :class "text-right"
|
|
(span :class "font-mono text-sm"
|
|
(span :class "text-violet-700 font-bold" (str needed))
|
|
(span :class "text-stone-400" (str " / " total)))
|
|
(span :class "ml-2 inline-block px-1.5 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800"
|
|
(str savings "% saved")))))
|
|
(div :class "w-full bg-stone-200 rounded-full h-2.5"
|
|
(div :class "bg-violet-600 h-2.5 rounded-full transition-all"
|
|
:style (str "width: " pct "%")))))
|