Respo 的开发版本以 deps.cirru 固定 Calcit 与 js-ffi 依赖,通过普通 :modules 和 :require 调用 js-ffi.browser/document-available?。该实现由 js-ffi 模块携带单表达式 JS 文件,Respo 不引用文件路径,也不安装片段专用 npm 包。运行 caps --strict --ci、yarn install --immutable 和 yarn test-dom-host,可验证干净安装、生成模块中的内嵌实现及有无 document 时的行为。文件修改后需显式重新构建。
资源 ID helper 已省去可由编译器证明的重复 schema,普通 .add 和既有测试保持不变;见推断验收记录。
升级时同时固定 deps.cirru :calcit-version 和 package.json 的 @calcit/procs 为同一精确版本,并执行 yarn install --immutable。只更新 CLI 而沿用旧 JS runtime,可能在运行时缺失新的 trait 实现;编译成功不能代替 DOM/SSR 测试。核心 API 迁移先用当前 CLI 的 --check-only 诊断和 analyze weak-types 只读视图定位,再根据真实类型契约和调用点,通过 query、tree、edit 按需改写。不要长期依赖旧版本的固定迁移 preset;开放输入和 macro 能力仍需审阅,并运行原有测试。
使用者影响:本分支计划作为 Respo 0.17.0 发布,配对 Calcit CLI / @calcit/procs 0.31.0-alpha.1,保留 JS-FFI 0.2.3。这是破坏性改动:d! 只接受一个参数,类型是当前 entry 用 :dispatch-op 类型槽绑定的应用 Op;wrap-dispatch 已删除,旧的 d! cursor state、d! :tag payload 写法改为 d! $ :: :states cursor state、d! $ :: :tag payload。render!、render-with!、realize-ssr! 直接接收应用的 dispatch!,demo 不再需要运行时解码。迁移步骤见类型槽指南。升级应用时需一起选择请求同一 Respo / JS-FFI 版本的 Router、UI 等正式模块;不能只改根依赖并放宽 Caps。旧正式 tag 保持原依赖,不改写历史版本。候选需通过原门禁并正式发版后才可消费。
当前源码逐步使用已有 .assoc / .dissoc 方法,显式 slice 终点传 Option :some,省略终点仍表示到末尾。SSR 内容查询直接匹配 get-in 的 Result :ok / :err,保留缺失属性、混合键及内容优先级。兼容 helper append-dynamic! 保留名字,但以同一个泛型关联 Ref<List<T>> 与写入值;初始化空 Ref 时声明实际元素类型,不把可变引用扩宽为任意元素类型。
js-ffi 使用正式 Git tag,不使用提交 hash。Respo 0.16.115 增加 respo.ffi.browser/query-selector,返回可直接交给 Respo renderer 的 Option<respo.dom/DomElement>;应用无需自行强转跨库 DOM 类型。通过普通 Calcit 引用复用 JS FFI,不需要片段专用 npm 包。版本以 deps.cirru :version 为准,旧 Snapshot 的镜像字段不作为发布依据。此前 alpha 工具链的历史验收见发布依赖验收,不代表当前组合的完整验收。
演示页前端构建使用 https://cos-sh.tiye.me/Respo/respo.calcit/ 作为资源 base。仅 main push 在测试、构建通过后上传 dist/,使用 cos-upload-action@v1.2.0 的 public-base-url 内置逐文件校验,不维护额外验证脚本。PR 只构建,不读取部署 secrets。原 rsync 页面路径 /web-assets/repo/${github.repository} 保持不变;生产运行串行且上传前检查 main SHA,跳过已过期提交,这并非原子发布。
当前组合保留原 definition :tests 及其断言,并增加缺失值、状态分支、名义类型和属性 key 合同回归;native 和生成 JS 均不排除测试。框架检查继续覆盖 23 个命名空间的 340 个定义。测试中的空列表 Ref 显式声明元素类型,避免初始化为 List<Never> 后再写入其他值;dispatch 测试使用 entry 绑定的 Op 类型,yarn test-typed-dispatch 覆盖正例与反例。SSR、DOM patch/lifecycle、正反类型检查、Markdown 示例和生产构建继续保留。EDN 解析所得 Task 名称与字段布局不是名义类型证明:恢复时复用受检 decoder 校验字段,恢复当前 Task 身份;已验证的当前 Task 保留对象身份。yarn test-persisted-tasks 验证生成 JS 的序列化往返和错误字段拒绝;真实浏览器继续验证新增、编辑、切换状态及刷新后,同一份任务的 ID、内容和完成状态恢复且控制台无错误。with-attrs 接收已序列化的 SVG 扩展属性;DOM anchor 末尾移动使用 null,保留宿主边界的运行时验证。
缺失值查询使用公共 get、first、last、struct-definition 和 enum-definition 的 Option 合同,不依赖底层原语曾经返回 nil 的行为。已证明非空的列表或已存在的内部索引用 .unwrap;可能缺失的属性与类型查询使用 match。宿主 ref、旧 states 和开放事件数据仍在既有边界保留 nil,并由原来的校验、默认值或错误分支处理;这不扩大参数类型,也不把 nil payload 与缺失混为一谈。源码使用 canonical core 名称。已弃用的 quality 计数基线、check-quality 命令及对应 CI 步骤已移除;类型正确性由严格检查、definition 测试和 DOM/SSR 等实际语义验收保证,不再以 nil/Dynamic 数量判断回退。新的 Calcit 版本仍须与同版本 @calcit/procs 配对验收后才升级 pin。
SSR 的属性 Map 保留自身 key 类型:内容读取通过现有路径 API 查询 :value、:innerHTML、:inner-text,不会把额外的 String 或 Number 键转换成 Tag。属性序列化仍沿用原有排序、事件属性过滤和转义规则;nil、false、0 与空字符串的内容优先级不变。非 Map 输入在读取内容前明确报错。演示程序的 microtask callback 显式返回 Unit,忽略宿主 timer handle,但不会吞掉调度错误。
Memo 帧生命周期、依赖记录、DOM 缓存失效和 demo 派发的 Unit 合同显式返回 &unit。reset! / swap! 本身返回写入值;! 表示有副作用,不代表返回 Unit。附带测试覆盖帧状态和空缓存的两条路径,原有 JS memo 与 DOM patch 测试继续检查缓存保留、失效顺序和宿主效果,同时断言这些接口的 Unit 返回。
早期候选工具链的七项失败分为五项回调边界和两项列表构造,恢复过程见回调边界历史记录。这是历史验证,不代表当前组合;当前套件保留原有列表构造的 nil 过滤与替换/移除用例,当前均已通过。
完整库的 Node target 和应用级 type-slot 贯通仍需分别验收;默认入口、框架定义检查和 demo 正例不能证明所有应用的 typed dispatch 已完成。
内部事件刷新沿用 DomPatch 的渲染坐标合同 List<Dynamic>:路径会组合组件名 Tag 与子节点 key(例如 String、Number),不会把它们统一转换成字符串;实际 DOM 子节点坐标仍为 List<Number>。这条内部路径不改变应用 StateKey 泛型或状态树结构,开放 key 边界的进一步收窄仍需另行验收。
yarn check-framework-types 使用当前固定工具链检查默认入口的动态方法,并逐一
检查 23 个非空框架命名空间中的全部定义;CI 执行相同命令,避免遗漏 demo 没有
调用的 helper。空的 respo.schema.listener 不计作覆盖;检查不执行 JavaScript
宿主,也不能替代 DOM host 和真实下游回归。事件 target 的现行合同见
DOM events。
yarn test-nominal-accessors 检查 Component、Effect、Element 和 RespoListener
访问器和转换 helper 的名义类型参数、37 个错误或未验证输入的编译期拒绝,以及原有列表、事件表、回调
和 tree payload 的身份。开放输入应先在调用方完成分类或受检转换。
component-tree 保留 Option<Struct> 兼容返回值;该检查不证明 effect 参数、
Listener 回调或整个 strict workflow 已全部封闭。
Inspired by React and Reagent. Previously Respo/respo.cljs.
- Version: 见
deps.cirru的:version - Init Function:
respo.main/main! - Reload Function:
respo.main/reload! - Core Namespaces: 33 namespaces providing virtual DOM, rendering, components, and utilities
- Testing: language built-in definition tests, plus a small JS-target smoke suite
In deps.cirru and run caps (replace the version with the compatible published release):
{}
:dependencies $ {}
|Respo/respo.calcit |0.16.113DOM syntax
; ns app.demo $ :require
respo.core :refer $ div
let
comp-demo $ fn (dispatch!)
respo.core/div
{}
:class-name "|demo-container"
:style $ {} (:color :red)
:on-click $ fn (event dispatch!)
dispatch! $ :: :clicked
respo.core/div $ {}
create-element 创建的 :svg 会为自身及其子节点使用 SVG 命名空间;进入 :foreignObject 后,子节点恢复 HTML 命名空间。SVG 的普通属性在首次渲染及后续补丁中都以 DOM attribute 写入、更新和移除,strokeWidth 等常见驼峰名称会转为 stroke-width。事件、data-* 和样式仍沿用 Respo 的现有处理路径。
共同属性继续传给 create-element,不把 SVG Map 强转为闭合的 DomProps。with-attrs 接收 Element 和 Map<Tag, String>,将已序列化的扩展属性合并到 Element;同名属性覆盖,其他属性、子节点、事件、ref 和样式不变。数字须显式转成字符串;它不是事件或样式 props 入口。
respo.core/with-attrs
respo.core/create-element :rect $ {} $ :class-name |shape
{} (:fill |red)
:strokeWidth $ to-string 2
本仓库的 yarn test-dom-host 覆盖 SVG 初次创建及属性增删改;消费者可用 Cross Stitch 页面验证图案显示及点击后的增量更新。
More examples adapted from calcit.cirru:
; ns app.demo $ :require
respo.core :refer $ defcomp a <>
let
comp-link $ fn (href text)
respo.core/a
{} $ :href href
respo.core/<> text
; ns app.demo $ :require
respo.core :refer $ list-> div
let
comp-list $ fn ()
respo.core/list->
{}
[] $ [] :a $ respo.core/div ({})
Text Node:
; ns app.demo $ :require
respo.core :refer $ <>
let
comp-text $ fn (content)
respo.core/<> content
; with styles
respo.core/<> "|demo" $ {}
:color :red
:font-size 14
Component definition:
; ns app.demo $ :require
respo.core :refer $ div <>
let
comp-container $ fn (content)
respo.core/div
{}
:class-name |demo-container
:style $ {} (:color :red)
respo.core/<> content
App initialization:
; ns app.demo $ :require
respo.core :refer $ render-with!
; initialize store and update store
let
*store $ ref $ {} (:point 0) (:states {})
updater $ fn (store op)
hint-fn $ {}
:args $ [] (:: 'Map 'Tag 'Dynamic) 'Dynamic
:return $ :: 'Map 'Tag 'Dynamic
match op
(:TODO a b) store
_ store
dispatch! $ fn (op)
reset! *store $ updater @*store op
mount-point nil
comp-container $ fn (state) state
dispatch! $ :: :TODO 1 2
; 在受管理的 memo 帧内构建组件树,再渲染到 DOM
defn render-app! ()
respo.core/render-with! mount-point
fn () $ comp-container @*store
, dispatch!
与 demo 一样,将同一微任务前的 store 更新合并为一次渲染:
let
schedule! $ respo.core/make-render-scheduler
fn () $ render-app!
%:: Option :none
add-watch! *store :changes $ fn (_current _previous) (schedule!)
render! 和 render-with! 仍同步执行。如果 dispatch 后需要立即读取更新后的 DOM,
让 watch 直接调用 render-app!。使用调度器的 DOM 测试应等待微任务或清空注入的队列。
详见调度时机与测试示例及
热更新时替换 watch。
在 render-with! 构建的组件树中,为带业务 key 的列表组件启用缓存:
; ns app.demo $ :require
respo.core :refer $ list-> memo-comp-by >>
list->
{} (:class-name |task-list)
-> tasks .to-list .reverse $ map
fn (task)
let
task-id $ :id task
[] task-id $ memo-comp-by task-id comp-task (>> states task-id) task
memo-comp-by 按组件函数、key 和完整参数列表匹配缓存。每次 render-with! 调用都会记录
活跃的 key,并清除已从最新组件树中消失的条目。外层 memo 命中时,会保留其嵌套依赖的缓存。
在受管理的帧之外调用时,直接计算结果,不读取或增加缓存;传入 nil key 也会绕过缓存。
Respo 内部管理这些条目,应用无需再依赖 memof 缓存组件。
如果组件树构建抛出异常,当前帧会被丢弃,保留上次成功提交的缓存,并重新抛出原错误。
配置、生命周期和迁移方式见列表渲染指南。
Reset virtual DOM caching during hot code swapping, and rerender:
; ns app.demo $ :require
respo.core :refer $ clear-cache!
let
*store $ ref $ {} (:point 0)
render-app! $ fn () &unit
add-watch! *store :changes $ fn (_previous _next)
render-app!
remove-watch *store :changes
add-watch! *store :changes $ fn (_previous _next)
render-app!
respo.core/clear-cache!
render-app!
Adding effects to component:
; ns app.demo $ :require
respo.core :refer $ div
let
effect-a $ fn (text)
fn (action parent-element at-place?)
println action
; action could be :mount :update :amount
when (&= :mount action) nil
comp-a $ fn (text)
[]
effect-a text
respo.core/div ({})
Define a hooks plugin based on Calcit Record, better use a pure function:
; ns app.demo $ :require
respo.core :refer $ div <>
let
plugin-x $ fn (states options)
%::
%{} :PluginX
:render $ fn (self) (nth self 1)
:show $ fn (self d! text) nil
, :plugin-name
respo.core/div ({}) (respo.core/<> "|Demo")
MIT
This index helps LLM tools automatically fetch and reference documentation using relative paths and the calcit CLI.
- Beginner Guide - Introduction to Respo concepts and component definition
- Respo-Agent Guide - 🤖 Detailed guide for LLM agents developing Respo apps (debugging, patterns, CLI tools)
- API Documentation Overview - Quick reference for all APIs
| Topic | Path | Overview |
|---|---|---|
| Why Respo | docs/guide/why-respo.md | Motivation and design philosophy |
| Virtual DOM | docs/guide/virtual-dom.md | Understanding virtual DOM concepts |
| Base Components | docs/guide/base-components.md | Core component patterns |
| DOM Elements | docs/guide/dom-elements.md | HTML element creation and usage |
| Component States | docs/guide/component-states.md | Managing component state |
| DOM Properties | docs/guide/dom-properties.md | DOM property binding |
| DOM Events | docs/guide/dom-events.md | Event handling in Respo |
| Typed Dispatch | docs/guide/type-slots.md | Entry-level Op binding and checks |
| Styles | docs/guide/styles.md | CSS and styling approach |
| Render Lists | docs/guide/render-list.md | Efficient list rendering |
| Renderer Upgrades | docs/guide/upgrade.md | Internal patch protocol changes |
| Common Primitives | docs/guide/common-primitives.md | Conditional UI, lifecycle, resources, errors, and batching |
| Hot Swapping | docs/guide/hot-swapping.md | Hot code reloading setup |
| Server Rendering | docs/guide/server-rendering.md | SSR capabilities |
| Pros and Cons | docs/guide/pros-and-cons.md | Framework comparison |
Core API descriptions are now stored in source doc strings inside calcit.cirru.
Use docs/api.md for the overview, or inspect a definition directly with Calcit CLI:
calcit query def respo.core/defcomp
calcit query def respo.core/render!
calcit query def respo.render.html/make-string| API | Namespace | Purpose |
|---|---|---|
defcomp |
respo.core/defcomp |
Define components with macro |
defeffect |
respo.core/defeffect |
Define lifecycle effects |
dispatch-fn |
respo.core/dispatch-fn |
Inline callback with a typed d! |
div |
respo.core/div |
Create div elements |
create-element |
respo.core/create-element |
Dynamically create elements |
render! |
respo.core/render! |
Sync virtual DOM to real DOM |
render-with! |
respo.core/render-with! |
Render with managed memo frame |
memo-comp-by |
respo.core/memo-comp-by |
Memoize keyed components |
memo-value-by |
respo.core/memo-value-by |
Memoize immutable derived data |
show |
respo.core/show |
Render a child conditionally |
for-keyed |
respo.core/for-keyed |
Build ordered keyed child pairs |
effect-on-mount |
respo.core/effect-on-mount |
Run a mount-only lifecycle callback |
effect-on-update |
respo.core/effect-on-update |
Run when immutable dependencies change |
effect-on-unmount |
respo.core/effect-on-unmount |
Run an unmount-only lifecycle callback |
effect-watch |
respo.core/effect-watch |
Dependency-aware lifecycle effect |
error-boundary |
respo.core/error-boundary |
Render a synchronous fallback |
make-render-scheduler |
respo.core/make-render-scheduler |
Coalesce render requests |
resource-reducer |
respo.resource/resource-reducer |
Reduce immutable async state |
resource-idle |
respo.resource/resource-idle |
Create initial resource state |
load-resource! |
respo.resource/load-resource! |
Emit async resource actions |
<> |
respo.core/<> |
Create text nodes |
comp-space |
respo.comp.space/comp-space |
Spacing component |
comp-inspect |
respo.comp.inspect/comp-inspect |
Inspection/debugging component |
clear-cache! |
respo.core/clear-cache! |
Clear memoization cache |
patch-instance! |
respo.controller.client/patch-instance! |
Patch DOM instances |
activate-instance! |
respo.controller.client/activate-instance! |
Activate DOM instances |
>> |
respo.core/>> |
Create state cursors |
purify-element |
respo.util.format/purify-element |
Clean element markup |
mute-element |
respo.util.format/mute-element |
Silence element output |
make-string |
respo.render.html/make-string |
Serialize to string |
find-element-diffs |
respo.render.diff/find-element-diffs |
Find DOM differences |
apply-dom-changes |
respo.render.patch/apply-dom-changes |
Apply DOM patches |
realize-ssr! |
respo.core/realize-ssr! |
Server-side rendering |
list-> |
respo.core/list-> |
Create list containers |
Legacy page names such as make-html and render-app were removed during the migration to source doc strings.
Agent-oriented CLI workflows (query/check-md automation) are maintained in Agents.md.