Skip to main content

topcoat_runtime/
shard_scope.rs

1use topcoat_core::context::Cx;
2use topcoat_view::{NodeViewParts, PartsWriter, ViewHandle, identity::Identity};
3
4use crate::{Js, ShardId};
5
6pub struct ShardScope {
7    /// The identity of the shard invocation, which names the scope in its
8    /// markers and which the browser sends back with a re-render request so
9    /// the shard body derives the same identities as the inline render.
10    identity: Identity,
11    shard_id: ShardId,
12    exprs: Vec<Js>,
13    placeholder: ViewHandle,
14}
15
16impl ShardScope {
17    #[inline]
18    #[must_use]
19    pub fn new(
20        identity: Identity,
21        shard_id: ShardId,
22        exprs: Vec<Js>,
23        placeholder: ViewHandle,
24    ) -> Self {
25        Self {
26            identity,
27            shard_id,
28            exprs,
29            placeholder,
30        }
31    }
32}
33
34impl NodeViewParts for ShardScope {
35    fn into_view_parts(self, cx: &Cx, parts: &mut PartsWriter<'_>) {
36        let shard_id = self.shard_id.as_str();
37        let identity = serde_json::to_string(&self.identity.to_string()).unwrap();
38
39        // <!-- ::topcoat::shard::start("<shard id>", "<identity>", ["<js>", ...]) -->
40        //
41        // The browser runtime derives the shard's route from its id. The
42        // identity is stable across renders, so a re-render of the enclosing
43        // content produces the same markers and the browser can keep them.
44        // Each parameter's JavaScript source is wrapped in a quoted string.
45        // The source parts are sealed with the comment context, so any `"`
46        // inside the source renders as `&quot;` and the quotes stay
47        // unambiguous delimiters on the client.
48        parts.push_comment(|comment| {
49            comment
50                .push_promoted_str_unescaped(&"::topcoat::shard::start(")
51                .push_string_unescaped(serde_json::to_string(shard_id).unwrap())
52                .push_promoted_str_unescaped(&", ")
53                .push_string_unescaped(identity.clone())
54                .push_promoted_str_unescaped(&", [");
55            let last = self.exprs.len().saturating_sub(1);
56            for (index, expr) in self.exprs.iter().enumerate() {
57                comment.push_promoted_str_unescaped(&"\"");
58                expr.write(comment);
59                comment.push_promoted_str_unescaped(&"\"");
60                if index != last {
61                    comment.push_promoted_str_unescaped(&", ");
62                }
63            }
64            comment.push_promoted_str_unescaped(&"])");
65        });
66        self.placeholder.into_view_parts(cx, parts);
67        parts.push_comment(|comment| {
68            comment
69                .push_promoted_str_unescaped(&"::topcoat::shard::end(")
70                .push_string_unescaped(identity)
71                .push_promoted_str_unescaped(&")");
72        });
73    }
74}