fix(editor): 修复 SSR 直接访问时可运行代码块不挂载 CodeMirror
CodeRunner 原用 use_hook(|| format!("code-runner-{}", now_pseudo_unique()))
生成容器 id,后缀依赖 now_millis() 时间戳。Dioxus hydration 不传递
use_hook 状态——SSR 渲染与客户端 hydration 各执行一次 use_hook,时间戳
(以及顺带验证过的 ScopeId)两端不同,导致 CodeMirror create() 在 hydration
时用新 id 去 getElementById,找不到 SSR 渲染的容器(id 是旧值),返回 null
不挂载。路由跳转是纯客户端渲染,use_hook 只跑一次 id 一致,故正常。
改为由父组件传入确定性的 instance_id(PostContent 的片段索引 i,来自纯函数
split_content_fragments 对同一 content_html 的解析,SSR 与 hydration 一致),
container_id 直接派生自该 prop,不进 use_hook。admin 试运行页单实例固定 0。
Playwright(release 构建)验证:SSR 直接访问与路由跳转 .cm-editor 均为 1。
This commit is contained in:
parent
8f6c864f22
commit
b95777e58d
2
.gitignore
vendored
2
.gitignore
vendored
@ -32,3 +32,5 @@ profile.json.gz
|
|||||||
|
|
||||||
# FreeBSD cross-compile sysroot (extracted from base.txz, machine-local)
|
# FreeBSD cross-compile sysroot (extracted from base.txz, machine-local)
|
||||||
.freebsd-sysroot/
|
.freebsd-sysroot/
|
||||||
|
|
||||||
|
.zcode/
|
||||||
|
|||||||
@ -24,6 +24,11 @@ const MAX_POLLS: u32 = 240; // 500ms * 240 = 120s 上限
|
|||||||
/// - `source`:初始源码(首次挂载用于初始化编辑器;之后编辑器内容是唯一真源)。
|
/// - `source`:初始源码(首次挂载用于初始化编辑器;之后编辑器内容是唯一真源)。
|
||||||
/// - `language`:语言标识(python / node 等)。
|
/// - `language`:语言标识(python / node 等)。
|
||||||
/// - `overrides`:可选资源限制覆盖。
|
/// - `overrides`:可选资源限制覆盖。
|
||||||
|
/// - `instance_id`:实例在父级片段序列中的索引,用作 CodeMirror 容器 id 后缀。
|
||||||
|
/// 必须是 **SSR/hydration 确定性**的值(如父组件 `for (i, ..)` 的索引 `i`)——
|
||||||
|
/// Dioxus hydration 不传递 use_hook 状态,任何在 use_hook 内基于运行时状态
|
||||||
|
/// (时间戳 / 随机 / ScopeId)生成的 id,在 SSR 与 hydration 两端会不一致,
|
||||||
|
/// 导致 CodeMirror `create()` 在 hydration 时找不到 SSR 渲染的容器元素。
|
||||||
///
|
///
|
||||||
/// `mut` 信号仅在 WASM 的 spawn 闭包内被 `.set()`,server 构建会触发 unused_mut,
|
/// `mut` 信号仅在 WASM 的 spawn 闭包内被 `.set()`,server 构建会触发 unused_mut,
|
||||||
/// 故按项目惯例加 `cfg_attr` 放行(参见 AGENTS.md「mut bindings needed only on WASM」)。
|
/// 故按项目惯例加 `cfg_attr` 放行(参见 AGENTS.md「mut bindings needed only on WASM」)。
|
||||||
@ -33,6 +38,7 @@ pub fn CodeRunner(
|
|||||||
source: String,
|
source: String,
|
||||||
language: String,
|
language: String,
|
||||||
overrides: Option<ResourceLimits>,
|
overrides: Option<ResourceLimits>,
|
||||||
|
instance_id: usize,
|
||||||
) -> Element {
|
) -> Element {
|
||||||
let mut running = use_signal(|| false);
|
let mut running = use_signal(|| false);
|
||||||
let mut stage = use_signal(String::new);
|
let mut stage = use_signal(String::new);
|
||||||
@ -43,9 +49,10 @@ pub fn CodeRunner(
|
|||||||
// 编辑器内容的唯一真源;初始化为 prop 值。
|
// 编辑器内容的唯一真源;初始化为 prop 值。
|
||||||
let mut source_signal = use_signal(|| source.clone());
|
let mut source_signal = use_signal(|| source.clone());
|
||||||
|
|
||||||
// 为每个实例生成稳定的容器 id(CodeMirror 容器,由本组件在 WASM 端挂载)。
|
// CodeMirror 容器 id:直接由确定性 prop 派生(不进 use_hook)。
|
||||||
// use_hook 保证只算一次;后缀用时间戳+原子计数避免同页多实例 id 冲突。
|
// instance_id 由父组件从纯函数片段解析的索引传入,SSR 与 hydration 同一 content_html
|
||||||
let container_id = use_hook(|| format!("code-runner-{}", now_pseudo_unique()));
|
// → 同一片段序列 → 同一索引 → 同一 id,故 hydration 时 create() 能找到 SSR 渲染的容器。
|
||||||
|
let container_id = format!("code-runner-{instance_id}");
|
||||||
|
|
||||||
// —— CodeMirror 挂载(仅 WASM)——
|
// —— CodeMirror 挂载(仅 WASM)——
|
||||||
// 范式镜像 src/pages/admin/system.rs 的 SQL 控制台与 src/pages/admin/write.rs 的 Tiptap。
|
// 范式镜像 src/pages/admin/system.rs 的 SQL 控制台与 src/pages/admin/write.rs 的 Tiptap。
|
||||||
@ -282,12 +289,3 @@ fn status_label(status: &ExecStatus) -> String {
|
|||||||
ExecStatus::RateLimited => "请求过频".to_string(),
|
ExecStatus::RateLimited => "请求过频".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 生成一个伪唯一后缀(基于时间戳 + 计数器),用于容器 id。
|
|
||||||
/// 非安全用途,仅避免同页多实例 id 冲突。
|
|
||||||
fn now_pseudo_unique() -> String {
|
|
||||||
use std::sync::atomic::{AtomicU64, Ordering};
|
|
||||||
static COUNTER: AtomicU64 = AtomicU64::new(0);
|
|
||||||
let n = COUNTER.fetch_add(1, Ordering::Relaxed);
|
|
||||||
format!("{}-{}", crate::utils::time::now_millis(), n)
|
|
||||||
}
|
|
||||||
|
|||||||
@ -162,6 +162,10 @@ pub fn PostContent(content_html: String) -> Element {
|
|||||||
source: source.clone(),
|
source: source.clone(),
|
||||||
language: lang.clone(),
|
language: lang.clone(),
|
||||||
overrides: overrides.clone(),
|
overrides: overrides.clone(),
|
||||||
|
// i 是片段序列中的确定性索引(来自纯函数 split_content_fragments
|
||||||
|
// 对同一 content_html 的解析),SSR 与 hydration 一致,用作容器
|
||||||
|
// id 后缀保证 hydration 时 CodeMirror 能找到 SSR 渲染的容器。
|
||||||
|
instance_id: i,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -113,11 +113,12 @@ pub fn Runner() -> Element {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 运行器
|
// 运行器(admin 试运行页单实例、纯客户端渲染,instance_id 固定 0 即可)
|
||||||
CodeRunner {
|
CodeRunner {
|
||||||
source: source(),
|
source: source(),
|
||||||
language: lang(),
|
language: lang(),
|
||||||
overrides: overrides,
|
overrides: overrides,
|
||||||
|
instance_id: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user