serialize.rs 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. use base64::Engine;
  2. use dioxus_lib::prelude::dioxus_core::DynamicNode;
  3. use dioxus_lib::prelude::{has_context, ErrorContext, ScopeId, SuspenseContext, VNode, VirtualDom};
  4. use super::SerializeContext;
  5. impl super::HTMLData {
  6. /// Walks through the suspense boundary in a depth first order and extracts the data from the context API.
  7. /// We use depth first order instead of relying on the order the hooks are called in because during suspense on the server, the order that futures are run in may be non deterministic.
  8. pub(crate) fn extract_from_suspense_boundary(vdom: &VirtualDom, scope: ScopeId) -> Self {
  9. let mut data = Self::default();
  10. data.serialize_errors(vdom, scope);
  11. data.take_from_scope(vdom, scope);
  12. data
  13. }
  14. /// Get the errors from the suspense boundary
  15. fn serialize_errors(&mut self, vdom: &VirtualDom, scope: ScopeId) {
  16. // If there is an error boundary on the suspense boundary, grab the error from the context API
  17. // and throw it on the client so that it bubbles up to the nearest error boundary
  18. let error = vdom.in_runtime(|| {
  19. scope
  20. .consume_context::<ErrorContext>()
  21. .and_then(|error_context| error_context.errors().first().cloned())
  22. });
  23. self.push(&error, std::panic::Location::caller());
  24. }
  25. fn take_from_scope(&mut self, vdom: &VirtualDom, scope: ScopeId) {
  26. vdom.in_runtime(|| {
  27. scope.in_runtime(|| {
  28. // Grab any serializable server context from this scope
  29. let context: Option<SerializeContext> = has_context();
  30. if let Some(context) = context {
  31. self.extend(&context.data.borrow());
  32. }
  33. });
  34. });
  35. // then continue to any children
  36. if let Some(scope) = vdom.get_scope(scope) {
  37. // If this is a suspense boundary, move into the children first (even if they are suspended) because that will be run first on the client
  38. if let Some(suspense_boundary) =
  39. SuspenseContext::downcast_suspense_boundary_from_scope(&vdom.runtime(), scope.id())
  40. {
  41. if let Some(node) = suspense_boundary.suspended_nodes() {
  42. self.take_from_vnode(vdom, &node);
  43. }
  44. }
  45. if let Some(node) = scope.try_root_node() {
  46. self.take_from_vnode(vdom, node);
  47. }
  48. }
  49. }
  50. fn take_from_vnode(&mut self, vdom: &VirtualDom, vnode: &VNode) {
  51. for (dynamic_node_index, dyn_node) in vnode.dynamic_nodes.iter().enumerate() {
  52. match dyn_node {
  53. DynamicNode::Component(comp) => {
  54. if let Some(scope) = comp.mounted_scope(dynamic_node_index, vnode, vdom) {
  55. self.take_from_scope(vdom, scope.id());
  56. }
  57. }
  58. DynamicNode::Fragment(nodes) => {
  59. for node in nodes {
  60. self.take_from_vnode(vdom, node);
  61. }
  62. }
  63. _ => {}
  64. }
  65. }
  66. }
  67. #[cfg(feature = "server")]
  68. /// Encode data as base64. This is intended to be used in the server to send data to the client.
  69. pub(crate) fn serialized(&self) -> SerializedHydrationData {
  70. let mut serialized = Vec::new();
  71. ciborium::into_writer(&self.data, &mut serialized).unwrap();
  72. let data = base64::engine::general_purpose::STANDARD.encode(serialized);
  73. let format_js_list_of_strings = |list: &[Option<String>]| {
  74. let body = list
  75. .iter()
  76. .map(|s| match s {
  77. Some(s) => format!(r#""{s}""#),
  78. None => r#""unknown""#.to_string(),
  79. })
  80. .collect::<Vec<_>>()
  81. .join(",");
  82. format!("[{}]", body)
  83. };
  84. SerializedHydrationData {
  85. data,
  86. #[cfg(debug_assertions)]
  87. debug_types: format_js_list_of_strings(&self.debug_types),
  88. #[cfg(debug_assertions)]
  89. debug_locations: format_js_list_of_strings(&self.debug_locations),
  90. }
  91. }
  92. }
  93. #[cfg(feature = "server")]
  94. /// Data that was serialized on the server for hydration on the client. This includes
  95. /// extra information about the types and sources of the serialized data in debug mode
  96. pub(crate) struct SerializedHydrationData {
  97. /// The base64 encoded serialized data
  98. pub data: String,
  99. /// A list of the types of each serialized data
  100. #[cfg(debug_assertions)]
  101. pub debug_types: String,
  102. /// A list of the locations of each serialized data
  103. #[cfg(debug_assertions)]
  104. pub debug_locations: String,
  105. }