mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 06:44:17 +00:00
feat: optional branch-marking in HTML to support initial work on client-side islands routing
This commit is contained in:
parent
e3482b433b
commit
8635887ca7
48 changed files with 890 additions and 141 deletions
92
examples/islands_router/Cargo.toml
Normal file
92
examples/islands_router/Cargo.toml
Normal file
|
@ -0,0 +1,92 @@
|
|||
[package]
|
||||
name = "islands"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
console_error_panic_hook = "0.1"
|
||||
futures = "0.3"
|
||||
http = "1.0"
|
||||
leptos = { path = "../../leptos", features = [
|
||||
"tracing",
|
||||
"experimental-islands",
|
||||
] }
|
||||
leptos_router = { path = "../../router" }
|
||||
server_fn = { path = "../../server_fn", features = ["serde-lite"] }
|
||||
leptos_axum = { path = "../../integrations/axum", features = ["islands-router"], optional = true }
|
||||
log = "0.4"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
axum = { version = "0.7", optional = true }
|
||||
tower = { version = "0.4", optional = true }
|
||||
tower-http = { version = "0.5", features = ["fs"], optional = true }
|
||||
tokio = { version = "1", features = ["full"], optional = true }
|
||||
wasm-bindgen = "0.2"
|
||||
|
||||
[features]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
ssr = [
|
||||
"dep:axum",
|
||||
"dep:tower",
|
||||
"dep:tower-http",
|
||||
"dep:tokio",
|
||||
"leptos/ssr",
|
||||
"dep:leptos_axum",
|
||||
]
|
||||
|
||||
[profile.wasm-release]
|
||||
inherits = "release"
|
||||
opt-level = 'z'
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["axum", "tower", "tower-http", "tokio", "sqlx", "leptos_axum"]
|
||||
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
|
||||
|
||||
[package.metadata.leptos]
|
||||
# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name
|
||||
output-name = "islands"
|
||||
# The site root folder is where cargo-leptos generate all output. WARNING: all content of this folder will be erased on a rebuild. Use it in your server setup.
|
||||
site-root = "target/site"
|
||||
# The site-root relative folder where all compiled output (JS, WASM and CSS) is written
|
||||
# Defaults to pkg
|
||||
site-pkg-dir = "pkg"
|
||||
# [Optional] The source CSS file. If it ends with .sass or .scss then it will be compiled by dart-sass into CSS. The CSS is optimized by Lightning CSS before being written to <site-root>/<site-pkg>/app.css
|
||||
style-file = "./style.css"
|
||||
# [Optional] Files in the asset-dir will be copied to the site-root directory
|
||||
assets-dir = "public"
|
||||
# The IP and port (ex: 127.0.0.1:3000) where the server serves the content. Use it in your server setup.
|
||||
site-addr = "127.0.0.1:3000"
|
||||
# The port to use for automatic reload monitoring
|
||||
reload-port = 3001
|
||||
# The browserlist query used for optimizing the CSS.
|
||||
browserquery = "defaults"
|
||||
# Set by cargo-leptos watch when building with that tool. Controls whether autoreload JS will be included in the head
|
||||
watch = false
|
||||
# The environment Leptos will run in, usually either "DEV" or "PROD"
|
||||
env = "DEV"
|
||||
# The features to use when compiling the bin target
|
||||
#
|
||||
# Optional. Can be over-ridden with the command line parameter --bin-features
|
||||
bin-features = ["ssr"]
|
||||
|
||||
# If the --no-default-features flag should be used when compiling the bin target
|
||||
#
|
||||
# Optional. Defaults to false.
|
||||
bin-default-features = false
|
||||
|
||||
# The features to use when compiling the lib target
|
||||
#
|
||||
# Optional. Can be over-ridden with the command line parameter --lib-features
|
||||
lib-features = ["hydrate"]
|
||||
|
||||
# If the --no-default-features flag should be used when compiling the lib target
|
||||
#
|
||||
# Optional. Defaults to false.
|
||||
lib-default-features = false
|
||||
|
||||
lib-profile-release = "wasm-release"
|
21
examples/islands_router/LICENSE
Normal file
21
examples/islands_router/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 Greg Johnston
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
4
examples/islands_router/Makefile.toml
Normal file
4
examples/islands_router/Makefile.toml
Normal file
|
@ -0,0 +1,4 @@
|
|||
extend = [
|
||||
{ path = "../cargo-make/main.toml" },
|
||||
{ path = "../cargo-make/cargo-leptos.toml" },
|
||||
]
|
19
examples/islands_router/README.md
Normal file
19
examples/islands_router/README.md
Normal file
|
@ -0,0 +1,19 @@
|
|||
# Leptos Todo App Sqlite with Axum
|
||||
|
||||
This example creates a basic todo app with an Axum backend that uses Leptos' server functions to call sqlx from the client and seamlessly run it on the server.
|
||||
|
||||
## Getting Started
|
||||
|
||||
See the [Examples README](../README.md) for setup and run instructions.
|
||||
|
||||
## E2E Testing
|
||||
|
||||
See the [E2E README](./e2e/README.md) for more information about the testing strategy.
|
||||
|
||||
## Rendering
|
||||
|
||||
See the [SSR Notes](../SSR_NOTES.md) for more information about Server Side Rendering.
|
||||
|
||||
## Quick Start
|
||||
|
||||
Run `cargo leptos watch` to run this example.
|
BIN
examples/islands_router/public/favicon.ico
Normal file
BIN
examples/islands_router/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
135
examples/islands_router/public/routing.js
Normal file
135
examples/islands_router/public/routing.js
Normal file
|
@ -0,0 +1,135 @@
|
|||
window.addEventListener("click", async (ev) => {
|
||||
// confirm that this is an <a> that meets our requirements
|
||||
if (
|
||||
ev.defaultPrevented ||
|
||||
ev.button !== 0 ||
|
||||
ev.metaKey ||
|
||||
ev.altKey ||
|
||||
ev.ctrlKey ||
|
||||
ev.shiftKey
|
||||
)
|
||||
return;
|
||||
|
||||
/** @type HTMLAnchorElement | undefined;*/
|
||||
const a = ev
|
||||
.composedPath()
|
||||
.find(el => el instanceof Node && el.nodeName.toUpperCase() === "A");
|
||||
|
||||
if (!a) return;
|
||||
|
||||
const svg = a.namespaceURI === "http://www.w3.org/2000/svg";
|
||||
const href = svg ? a.href.baseVal : a.href;
|
||||
const target = svg ? a.target.baseVal : a.target;
|
||||
if (target || (!href && !a.hasAttribute("state"))) return;
|
||||
|
||||
const rel = (a.getAttribute("rel") || "").split(/\s+/);
|
||||
if (a.hasAttribute("download") || (rel && rel.includes("external"))) return;
|
||||
|
||||
const url = svg ? new URL(href, document.baseURI) : new URL(href);
|
||||
if (
|
||||
url.origin !== window.location.origin // ||
|
||||
// TODO base
|
||||
//(basePath && url.pathname && !url.pathname.toLowerCase().startsWith(basePath.toLowerCase()))
|
||||
)
|
||||
return;
|
||||
|
||||
ev.preventDefault();
|
||||
|
||||
// fetch the new page
|
||||
const resp = await fetch(url);
|
||||
const htmlString = await resp.text();
|
||||
|
||||
// Use DOMParser to parse the HTML string
|
||||
const parser = new DOMParser();
|
||||
// TODO parse from the request stream instead?
|
||||
const doc = parser.parseFromString(htmlString, 'text/html');
|
||||
|
||||
// The 'doc' variable now contains the parsed DOM
|
||||
const transition = document.startViewTransition(async () => {
|
||||
const oldDocWalker = document.createTreeWalker(document);
|
||||
const newDocWalker = doc.createTreeWalker(doc);
|
||||
let oldNode = oldDocWalker.currentNode;
|
||||
let newNode = newDocWalker.currentNode;
|
||||
while(oldDocWalker.nextNode() && newDocWalker.nextNode()) {
|
||||
oldNode = oldDocWalker.currentNode;
|
||||
newNode = newDocWalker.currentNode;
|
||||
// if the nodes are different, we need to replace the old with the new
|
||||
// because of the typed view tree, this should never actually happen
|
||||
if (oldNode.nodeType !== newNode.nodeType) {
|
||||
oldNode.replaceWith(newNode);
|
||||
}
|
||||
// if it's a text node, just update the text with the new text
|
||||
else if (oldNode.nodeType === Node.TEXT_NODE) {
|
||||
oldNode.textContent = newNode.textContent;
|
||||
}
|
||||
// if it's an element, replace if it's a different tag, or update attributes
|
||||
else if (oldNode.nodeType === Node.ELEMENT_NODE) {
|
||||
/** @type Element */
|
||||
const oldEl = oldNode;
|
||||
/** @type Element */
|
||||
const newEl = newNode;
|
||||
if (oldEl.tagName !== newEl.tagName) {
|
||||
oldEl.replaceWith(newEl);
|
||||
}
|
||||
else {
|
||||
for(const attr of newEl.attributes) {
|
||||
oldEl.setAttribute(attr.name, attr.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
// we use comment "branch marker" nodes to distinguish between different branches in the statically-typed view tree
|
||||
// if one of these marker is hit, then there are two options
|
||||
// 1) it's the same branch, and we just keep walking until the end
|
||||
// 2) it's a different branch, in which case the old can be replaced with the new wholesale
|
||||
else if (oldNode.nodeType === Node.COMMENT_NODE) {
|
||||
const oldText = oldNode.textContent;
|
||||
const newText = newNode.textContent;
|
||||
if(oldText.startsWith("bo") && newText !== oldText) {
|
||||
oldDocWalker.nextNode();
|
||||
newDocWalker.nextNode();
|
||||
const oldRange = new Range();
|
||||
const newRange = new Range();
|
||||
let oldBranches = 1;
|
||||
let newBranches = 1;
|
||||
while(oldBranches > 0 && newBranches > 0) {
|
||||
if(oldDocWalker.nextNode() && newDocWalker.nextNode()) {
|
||||
console.log(oldDocWalker.currentNode, newDocWalker.currentNode);
|
||||
if(oldDocWalker.currentNode.nodeType === Node.COMMENT_NODE) {
|
||||
if(oldDocWalker.currentNode.textContent.startsWith("bo")) {
|
||||
oldBranches += 1;
|
||||
} else if(oldDocWalker.currentNode.textContent.startsWith("bc")) {
|
||||
|
||||
oldBranches -= 1;
|
||||
}
|
||||
}
|
||||
if(newDocWalker.currentNode.nodeType === Node.COMMENT_NODE) {
|
||||
if(newDocWalker.currentNode.textContent.startsWith("bo")) {
|
||||
newBranches += 1;
|
||||
} else if(newDocWalker.currentNode.textContent.startsWith("bc")) {
|
||||
|
||||
newBranches -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
oldRange.setStartAfter(oldNode);
|
||||
oldRange.setEndBefore(oldDocWalker.currentNode);
|
||||
newRange.setStartAfter(newNode);
|
||||
newRange.setEndBefore(newDocWalker.currentNode);
|
||||
const newContents = newRange.extractContents();
|
||||
oldRange.deleteContents();
|
||||
oldRange.insertNode(newContents);
|
||||
oldNode.replaceWith(newNode);
|
||||
oldDocWalker.currentNode.replaceWith(newDocWalker.currentNode);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
} }
|
||||
}
|
||||
});
|
||||
await transition;
|
||||
window.history.pushState(undefined, null, url);
|
||||
});
|
||||
|
2
examples/islands_router/rust-toolchain.toml
Normal file
2
examples/islands_router/rust-toolchain.toml
Normal file
|
@ -0,0 +1,2 @@
|
|||
[toolchain]
|
||||
channel = "stable" # test change
|
59
examples/islands_router/src/app.rs
Normal file
59
examples/islands_router/src/app.rs
Normal file
|
@ -0,0 +1,59 @@
|
|||
use leptos::prelude::*;
|
||||
use leptos_router::{
|
||||
components::{FlatRoutes, Route, Router},
|
||||
StaticSegment,
|
||||
};
|
||||
|
||||
pub fn shell(options: LeptosOptions) -> impl IntoView {
|
||||
view! {
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<AutoReload options=options.clone()/>
|
||||
<HydrationScripts options=options islands=true/>
|
||||
<link rel="stylesheet" id="leptos" href="/pkg/islands.css"/>
|
||||
<link rel="shortcut icon" type="image/ico" href="/favicon.ico"/>
|
||||
</head>
|
||||
<body>
|
||||
<App/>
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn App() -> impl IntoView {
|
||||
view! {
|
||||
<script src="/routing.js"></script>
|
||||
<Router>
|
||||
<header>
|
||||
<h1>"My Application"</h1>
|
||||
</header>
|
||||
<nav>
|
||||
<a href="/">"Page A"</a>
|
||||
<a href="/b">"Page B"</a>
|
||||
</nav>
|
||||
<main>
|
||||
<p>
|
||||
<label>"Home Checkbox" <input type="checkbox"/></label>
|
||||
</p>
|
||||
<FlatRoutes fallback=|| "Not found.">
|
||||
<Route path=StaticSegment("") view=PageA/>
|
||||
<Route path=StaticSegment("b") view=PageB/>
|
||||
</FlatRoutes>
|
||||
</main>
|
||||
</Router>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn PageA() -> impl IntoView {
|
||||
view! { <label>"Page A" <input type="checkbox"/></label> }
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn PageB() -> impl IntoView {
|
||||
view! { <label>"Page B" <input type="checkbox"/></label> }
|
||||
}
|
8
examples/islands_router/src/lib.rs
Normal file
8
examples/islands_router/src/lib.rs
Normal file
|
@ -0,0 +1,8 @@
|
|||
pub mod app;
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
#[wasm_bindgen::prelude::wasm_bindgen]
|
||||
pub fn hydrate() {
|
||||
console_error_panic_hook::set_once();
|
||||
leptos::mount::hydrate_islands();
|
||||
}
|
30
examples/islands_router/src/main.rs
Normal file
30
examples/islands_router/src/main.rs
Normal file
|
@ -0,0 +1,30 @@
|
|||
use axum::Router;
|
||||
use islands::app::{shell, App};
|
||||
use leptos::prelude::*;
|
||||
use leptos_axum::{generate_route_list, LeptosRoutes};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Setting this to None means we'll be using cargo-leptos and its env vars
|
||||
let conf = get_configuration(None).unwrap();
|
||||
let leptos_options = conf.leptos_options;
|
||||
let addr = leptos_options.site_addr;
|
||||
let routes = generate_route_list(App);
|
||||
|
||||
// build our application with a route
|
||||
let app = Router::new()
|
||||
.leptos_routes(&leptos_options, routes, {
|
||||
let leptos_options = leptos_options.clone();
|
||||
move || shell(leptos_options.clone())
|
||||
})
|
||||
.fallback(leptos_axum::file_and_error_handler(shell))
|
||||
.with_state(leptos_options);
|
||||
|
||||
// run our app with hyper
|
||||
// `axum::Server` is a re-export of `hyper::Server`
|
||||
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
|
||||
println!("listening on http://{}", &addr);
|
||||
axum::serve(listener, app.into_make_service())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
3
examples/islands_router/style.css
Normal file
3
examples/islands_router/style.css
Normal file
|
@ -0,0 +1,3 @@
|
|||
.pending {
|
||||
color: purple;
|
||||
}
|
|
@ -28,3 +28,6 @@ send_wrapper = "0.6.0"
|
|||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["--generate-link-to-definition"]
|
||||
|
||||
[features]
|
||||
islands-router = []
|
||||
|
|
|
@ -37,6 +37,7 @@ tokio = { version = "1", features = ["net"] }
|
|||
[features]
|
||||
wasm = []
|
||||
default = ["tokio/fs", "tokio/sync", "tower-http/fs"]
|
||||
islands-router = []
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["--generate-link-to-definition"]
|
||||
|
|
|
@ -676,8 +676,12 @@ where
|
|||
_ = replace_blocks; // TODO
|
||||
handle_response(additional_context, app_fn, |app, chunks| {
|
||||
Box::pin(async move {
|
||||
Box::pin(app.to_html_stream_out_of_order().chain(chunks()))
|
||||
as PinnedStream<String>
|
||||
let app = if cfg!(feature = "islands-router") {
|
||||
app.to_html_stream_out_of_order_branching()
|
||||
} else {
|
||||
app.to_html_stream_out_of_order()
|
||||
};
|
||||
Box::pin(app.chain(chunks())) as PinnedStream<String>
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -723,9 +727,13 @@ where
|
|||
IV: IntoView + 'static,
|
||||
{
|
||||
handle_response(additional_context, app_fn, |app, chunks| {
|
||||
let app = if cfg!(feature = "islands-router") {
|
||||
app.to_html_stream_in_order_branching()
|
||||
} else {
|
||||
app.to_html_stream_in_order()
|
||||
};
|
||||
Box::pin(async move {
|
||||
Box::pin(app.to_html_stream_in_order().chain(chunks()))
|
||||
as PinnedStream<String>
|
||||
Box::pin(app.chain(chunks())) as PinnedStream<String>
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -922,7 +930,12 @@ where
|
|||
{
|
||||
handle_response(additional_context, app_fn, |app, chunks| {
|
||||
Box::pin(async move {
|
||||
let app = app.to_html_stream_in_order().collect::<String>().await;
|
||||
let app = if cfg!(feature = "islands-router") {
|
||||
app.to_html_stream_in_order_branching()
|
||||
} else {
|
||||
app.to_html_stream_in_order()
|
||||
};
|
||||
let app = app.collect::<String>().await;
|
||||
let chunks = chunks();
|
||||
Box::pin(once(async move { app }).chain(chunks))
|
||||
as PinnedStream<String>
|
||||
|
@ -971,7 +984,12 @@ where
|
|||
{
|
||||
handle_response(additional_context, app_fn, |app, chunks| {
|
||||
Box::pin(async move {
|
||||
let app = app.to_html_stream_in_order().collect::<String>().await;
|
||||
let app = if cfg!(feature = "islands-router") {
|
||||
app.to_html_stream_in_order_branching()
|
||||
} else {
|
||||
app.to_html_stream_in_order()
|
||||
};
|
||||
let app = app.collect::<String>().await;
|
||||
let chunks = chunks();
|
||||
Box::pin(once(async move { app }).chain(chunks))
|
||||
as PinnedStream<String>
|
||||
|
|
|
@ -274,20 +274,29 @@ where
|
|||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
// first, attempt to serialize the children to HTML, then check for errors
|
||||
let mut new_buf = String::with_capacity(Chil::MIN_LENGTH);
|
||||
let mut new_pos = *position;
|
||||
self.children
|
||||
.to_html_with_buf(&mut new_buf, &mut new_pos, escape);
|
||||
self.children.to_html_with_buf(
|
||||
&mut new_buf,
|
||||
&mut new_pos,
|
||||
escape,
|
||||
mark_branches,
|
||||
);
|
||||
|
||||
// any thrown errors would've been caught here
|
||||
if self.errors.with_untracked(|map| map.is_empty()) {
|
||||
buf.push_str(&new_buf);
|
||||
} else {
|
||||
// otherwise, serialize the fallback instead
|
||||
(self.fallback)(self.errors)
|
||||
.to_html_with_buf(buf, position, escape);
|
||||
(self.fallback)(self.errors).to_html_with_buf(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -296,6 +305,7 @@ where
|
|||
buf: &mut StreamBuilder,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -306,6 +316,7 @@ where
|
|||
&mut new_buf,
|
||||
&mut new_pos,
|
||||
escape,
|
||||
mark_branches,
|
||||
);
|
||||
|
||||
if let Some(sc) = Owner::current_shared_context() {
|
||||
|
@ -322,6 +333,7 @@ where
|
|||
&mut fallback,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
);
|
||||
buf.push_sync(&fallback);
|
||||
}
|
||||
|
|
|
@ -62,8 +62,10 @@ impl<T: IntoView> RenderHtml<Dom> for View<T> {
|
|||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
self.0.to_html_with_buf(buf, position, escape);
|
||||
self.0
|
||||
.to_html_with_buf(buf, position, escape, mark_branches);
|
||||
}
|
||||
|
||||
fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
|
||||
|
@ -71,11 +73,16 @@ impl<T: IntoView> RenderHtml<Dom> for View<T> {
|
|||
buf: &mut StreamBuilder,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
self.0
|
||||
.to_html_async_with_buf::<OUT_OF_ORDER>(buf, position, escape)
|
||||
self.0.to_html_async_with_buf::<OUT_OF_ORDER>(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
)
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
|
|
|
@ -192,8 +192,10 @@ where
|
|||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
self.fallback.to_html_with_buf(buf, position, escape);
|
||||
self.fallback
|
||||
.to_html_with_buf(buf, position, escape, mark_branches);
|
||||
}
|
||||
|
||||
fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
|
||||
|
@ -201,6 +203,7 @@ where
|
|||
buf: &mut StreamBuilder,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -292,13 +295,19 @@ where
|
|||
Some(Some(resolved)) => {
|
||||
Either::<Fal, _>::Right(resolved)
|
||||
.to_html_async_with_buf::<OUT_OF_ORDER>(
|
||||
buf, position, escape,
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
);
|
||||
}
|
||||
Some(None) => {
|
||||
Either::<_, Chil>::Left(self.fallback)
|
||||
.to_html_async_with_buf::<OUT_OF_ORDER>(
|
||||
buf, position, escape,
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
);
|
||||
}
|
||||
None => {
|
||||
|
@ -308,8 +317,12 @@ where
|
|||
// wrapped by suspense markers
|
||||
if OUT_OF_ORDER {
|
||||
let mut fallback_position = *position;
|
||||
buf.push_fallback(self.fallback, &mut fallback_position);
|
||||
buf.push_async_out_of_order(fut, position);
|
||||
buf.push_fallback(
|
||||
self.fallback,
|
||||
&mut fallback_position,
|
||||
mark_branches,
|
||||
);
|
||||
buf.push_async_out_of_order(fut, position, mark_branches);
|
||||
} else {
|
||||
buf.push_async({
|
||||
let mut position = *position;
|
||||
|
@ -323,6 +336,7 @@ where
|
|||
&mut builder,
|
||||
&mut position,
|
||||
escape,
|
||||
mark_branches,
|
||||
);
|
||||
builder.finish().take_chunks()
|
||||
}
|
||||
|
|
|
@ -266,9 +266,14 @@ mod view_implementations {
|
|||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
(move || Suspend::new(async move { self.await }))
|
||||
.to_html_with_buf(buf, position, escape);
|
||||
(move || Suspend::new(async move { self.await })).to_html_with_buf(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
);
|
||||
}
|
||||
|
||||
fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
|
||||
|
@ -276,11 +281,17 @@ mod view_implementations {
|
|||
buf: &mut StreamBuilder,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
(move || Suspend::new(async move { self.await }))
|
||||
.to_html_async_with_buf::<OUT_OF_ORDER>(buf, position, escape);
|
||||
.to_html_async_with_buf::<OUT_OF_ORDER>(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
);
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
|
|
|
@ -122,6 +122,7 @@ where
|
|||
_buf: &mut String,
|
||||
_position: &mut Position,
|
||||
_escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
if let Some(meta) = use_context::<ServerMetaContext>() {
|
||||
let mut buf = String::new();
|
||||
|
|
|
@ -122,6 +122,7 @@ where
|
|||
_buf: &mut String,
|
||||
_position: &mut Position,
|
||||
_escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
if let Some(meta) = use_context::<ServerMetaContext>() {
|
||||
let mut buf = String::new();
|
||||
|
|
|
@ -337,6 +337,7 @@ where
|
|||
&mut buf,
|
||||
&mut Position::NextChild,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
_ = cx.elements.send(buf); // fails only if the receiver is already dropped
|
||||
} else {
|
||||
|
@ -438,6 +439,7 @@ where
|
|||
_buf: &mut String,
|
||||
_position: &mut Position,
|
||||
_escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
// meta tags are rendered into the buffer stored into the context
|
||||
// the value has already been taken out, when we're on the server
|
||||
|
@ -547,6 +549,7 @@ impl RenderHtml<Dom> for MetaTagsView {
|
|||
buf: &mut String,
|
||||
_position: &mut Position,
|
||||
_escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
buf.push_str("<!--HEAD-->");
|
||||
}
|
||||
|
|
|
@ -250,6 +250,7 @@ impl RenderHtml<Dom> for TitleView {
|
|||
_buf: &mut String,
|
||||
_position: &mut Position,
|
||||
_escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
// meta tags are rendered into the buffer stored into the context
|
||||
// the value has already been taken out, when we're on the server
|
||||
|
|
|
@ -483,6 +483,7 @@ where
|
|||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
// if this is being run on the server for the first time, generating all possible routes
|
||||
if RouteList::is_generating() {
|
||||
|
@ -531,7 +532,7 @@ where
|
|||
RouteList::register(RouteList::from(routes));
|
||||
} else {
|
||||
let view = self.choose_ssr();
|
||||
view.to_html_with_buf(buf, position, escape);
|
||||
view.to_html_with_buf(buf, position, escape, mark_branches);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -540,11 +541,17 @@ where
|
|||
buf: &mut StreamBuilder,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
let view = self.choose_ssr();
|
||||
view.to_html_async_with_buf::<OUT_OF_ORDER>(buf, position, escape)
|
||||
view.to_html_async_with_buf::<OUT_OF_ORDER>(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
)
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
|
|
|
@ -88,9 +88,8 @@ impl<T: AsPath> PossibleRouteMatch for StaticSegment<T> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::AsPath;
|
||||
|
||||
use super::{PossibleRouteMatch, StaticSegment};
|
||||
use crate::AsPath;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Paths {
|
||||
|
|
|
@ -246,6 +246,7 @@ where
|
|||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
// if this is being run on the server for the first time, generating all possible routes
|
||||
if RouteList::is_generating() {
|
||||
|
@ -331,7 +332,7 @@ where
|
|||
})
|
||||
}
|
||||
};
|
||||
view.to_html_with_buf(buf, position, escape);
|
||||
view.to_html_with_buf(buf, position, escape, mark_branches);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -340,6 +341,7 @@ where
|
|||
buf: &mut StreamBuilder,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -381,7 +383,12 @@ where
|
|||
})
|
||||
}
|
||||
};
|
||||
view.to_html_async_with_buf::<OUT_OF_ORDER>(buf, position, escape);
|
||||
view.to_html_async_with_buf::<OUT_OF_ORDER>(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
);
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
|
|
|
@ -131,7 +131,7 @@ where
|
|||
self,
|
||||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
escape: bool, mark_branches: bool
|
||||
) {
|
||||
// if this is being run on the server for the first time, generating all possible routes
|
||||
if RouteList::is_generating() {
|
||||
|
@ -156,7 +156,7 @@ where
|
|||
self,
|
||||
buf: &mut StreamBuilder,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
escape: bool, mark_branches: bool
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -324,7 +324,7 @@ where
|
|||
self,
|
||||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
escape: bool, mark_branches: bool
|
||||
) {
|
||||
let MatchedRoute {
|
||||
search_params,
|
||||
|
@ -345,7 +345,7 @@ where
|
|||
self,
|
||||
buf: &mut StreamBuilder,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
escape: bool, mark_branches: bool
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
|
|
|
@ -205,7 +205,7 @@ where
|
|||
self
|
||||
}
|
||||
|
||||
fn to_html_with_buf(self, buf: &mut String, position: &mut Position, escape: bool) {
|
||||
fn to_html_with_buf(self, buf: &mut String, position: &mut Position, escape: bool, mark_branches: bool) {
|
||||
// if this is being run on the server for the first time, generating all possible routes
|
||||
if RouteList::is_generating() {
|
||||
// add routes
|
||||
|
@ -272,7 +272,7 @@ where
|
|||
|
||||
fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
|
||||
self,
|
||||
buf: &mut StreamBuilder, position: &mut Position, escape: bool) where
|
||||
buf: &mut StreamBuilder, position: &mut Position, escape: bool, mark_branches: bool) where
|
||||
Self: Sized,
|
||||
{
|
||||
let outer_owner =
|
||||
|
@ -701,14 +701,14 @@ where
|
|||
//(self.inner.read().or_poisoned().html_len)()
|
||||
}
|
||||
|
||||
fn to_html_with_buf(self, buf: &mut String, position: &mut Position, escape: bool) {
|
||||
fn to_html_with_buf(self, buf: &mut String, position: &mut Position, escape: bool, mark_branches: bool) {
|
||||
/*let view = self.inner.read().or_poisoned().view.take().unwrap();
|
||||
view.to_html_with_buf(buf, position);*/
|
||||
}
|
||||
|
||||
fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
|
||||
self,
|
||||
buf: &mut StreamBuilder, position: &mut Position, escape: bool) where
|
||||
buf: &mut StreamBuilder, position: &mut Position, escape: bool, mark_branches: bool) where
|
||||
Self: Sized,
|
||||
{
|
||||
/*let view = self
|
||||
|
@ -971,14 +971,14 @@ where
|
|||
self.view.html_len()
|
||||
}
|
||||
|
||||
fn to_html_with_buf(self, buf: &mut String, position: &mut Position, escape: bool) {
|
||||
fn to_html_with_buf(self, buf: &mut String, position: &mut Position, escape: bool, mark_branches: bool) {
|
||||
buf.reserve(self.html_len());
|
||||
self.view.to_html_with_buf(buf, position, escape);
|
||||
}
|
||||
|
||||
fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
|
||||
self,
|
||||
buf: &mut StreamBuilder, position: &mut Position, escape: bool) where
|
||||
buf: &mut StreamBuilder, position: &mut Position, escape: bool, mark_branches: bool) where
|
||||
Self: Sized,
|
||||
{
|
||||
buf.reserve(self.html_len());
|
||||
|
@ -1228,7 +1228,7 @@ where
|
|||
self
|
||||
}
|
||||
|
||||
fn to_html_with_buf(self, buf: &mut String, position: &mut Position, escape: bool) {
|
||||
fn to_html_with_buf(self, buf: &mut String, position: &mut Position, escape: bool, mark_branches: bool) {
|
||||
// if this is being run on the server for the first time, generating all possible routes
|
||||
if RouteList::is_generating() {
|
||||
// add routes
|
||||
|
@ -1315,7 +1315,7 @@ where
|
|||
|
||||
fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
|
||||
self,
|
||||
buf: &mut StreamBuilder, position: &mut Position, escape: bool) where
|
||||
buf: &mut StreamBuilder, position: &mut Position, escape: bool, mark_branches: bool) where
|
||||
Self: Sized,
|
||||
{
|
||||
let outer_owner =
|
||||
|
|
|
@ -36,7 +36,7 @@ macro_rules! attributes {
|
|||
attributes! {
|
||||
// HTML
|
||||
/// The `abbr` attribute specifies an abbreviated form of the element's content.
|
||||
abbr "abbr",
|
||||
abbr "abbr",
|
||||
/// The `accept-charset` attribute specifies the character encodings that are to be used for the form submission.
|
||||
accept_charset "accept-charset",
|
||||
/// The `accept` attribute specifies a list of types the server accepts, typically a file type.
|
||||
|
@ -582,7 +582,7 @@ attributes! {
|
|||
/// The `onwheel` attribute specifies the event handler for the wheel event.
|
||||
onwheel "onwheel",
|
||||
|
||||
// MathML attributes
|
||||
// MathML attributes
|
||||
/// The `accent` attribute specifies whether the element should be treated as an accent.
|
||||
accent "accent",
|
||||
/// The `accentunder` attribute specifies whether the element should be treated as an accent under the base element.
|
||||
|
|
|
@ -270,6 +270,7 @@ where
|
|||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
_escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
// opening tag
|
||||
buf.push('<');
|
||||
|
@ -289,6 +290,7 @@ where
|
|||
buf,
|
||||
position,
|
||||
E::ESCAPE_CHILDREN,
|
||||
mark_branches,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -305,6 +307,7 @@ where
|
|||
buffer: &mut StreamBuilder,
|
||||
position: &mut Position,
|
||||
_escape: bool,
|
||||
mark_branches: bool,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -328,6 +331,7 @@ where
|
|||
buffer,
|
||||
position,
|
||||
E::ESCAPE_CHILDREN,
|
||||
mark_branches,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -115,9 +115,11 @@ where
|
|||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
Self::open_tag(self.component, buf);
|
||||
self.view.to_html_with_buf(buf, position, escape);
|
||||
self.view
|
||||
.to_html_with_buf(buf, position, escape, mark_branches);
|
||||
Self::close_tag(buf);
|
||||
}
|
||||
|
||||
|
@ -126,6 +128,7 @@ where
|
|||
buf: &mut StreamBuilder,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -135,8 +138,12 @@ where
|
|||
buf.push_sync(&tag);
|
||||
|
||||
// streaming render for the view
|
||||
self.view
|
||||
.to_html_async_with_buf::<OUT_OF_ORDER>(buf, position, escape);
|
||||
self.view.to_html_async_with_buf::<OUT_OF_ORDER>(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
);
|
||||
|
||||
// and insert the closing tag synchronously
|
||||
tag.clear();
|
||||
|
@ -243,9 +250,11 @@ where
|
|||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
Self::open_tag(buf);
|
||||
self.view.to_html_with_buf(buf, position, escape);
|
||||
self.view
|
||||
.to_html_with_buf(buf, position, escape, mark_branches);
|
||||
Self::close_tag(buf);
|
||||
}
|
||||
|
||||
|
@ -254,6 +263,7 @@ where
|
|||
buf: &mut StreamBuilder,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -263,8 +273,12 @@ where
|
|||
buf.push_sync(&tag);
|
||||
|
||||
// streaming render for the view
|
||||
self.view
|
||||
.to_html_async_with_buf::<OUT_OF_ORDER>(buf, position, escape);
|
||||
self.view.to_html_async_with_buf::<OUT_OF_ORDER>(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
);
|
||||
|
||||
// and insert the closing tag synchronously
|
||||
tag.clear();
|
||||
|
|
|
@ -67,6 +67,7 @@ where
|
|||
buf: &mut String,
|
||||
_position: &mut Position,
|
||||
_escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
buf.push_str("<!DOCTYPE ");
|
||||
buf.push_str(self.value);
|
||||
|
|
|
@ -52,8 +52,15 @@ where
|
|||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
<&str as RenderHtml<R>>::to_html_with_buf(&self, buf, position, escape)
|
||||
<&str as RenderHtml<R>>::to_html_with_buf(
|
||||
&self,
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
)
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
|
|
|
@ -103,7 +103,7 @@ macro_rules! render_primitive {
|
|||
|
||||
const MIN_LENGTH: usize = 0;
|
||||
|
||||
fn to_html_with_buf(self, buf: &mut String, position: &mut Position, escape: bool) {
|
||||
fn to_html_with_buf(self, buf: &mut String, position: &mut Position, escape: bool, mark_branches: bool) {
|
||||
// add a comment node to separate from previous sibling, if any
|
||||
if matches!(position, Position::NextChildAfterText) {
|
||||
buf.push_str("<!>")
|
||||
|
@ -264,7 +264,7 @@ where
|
|||
|
||||
const MIN_LENGTH: usize = 0;
|
||||
|
||||
fn to_html_with_buf(self, buf: &mut String, position: &mut Position, escape: bool) {
|
||||
fn to_html_with_buf(self, buf: &mut String, position: &mut Position, escape: bool, mark_branches: bool) {
|
||||
<&str as RenderHtml<R>>::to_html_with_buf(&self, buf, position)
|
||||
}
|
||||
|
||||
|
|
|
@ -144,9 +144,10 @@ where
|
|||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
let value = self.invoke();
|
||||
value.to_html_with_buf(buf, position, escape)
|
||||
value.to_html_with_buf(buf, position, escape, mark_branches)
|
||||
}
|
||||
|
||||
fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
|
||||
|
@ -154,11 +155,17 @@ where
|
|||
buf: &mut StreamBuilder,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
let value = self.invoke();
|
||||
value.to_html_async_with_buf::<OUT_OF_ORDER>(buf, position, escape);
|
||||
value.to_html_async_with_buf::<OUT_OF_ORDER>(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
);
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
|
@ -568,9 +575,10 @@ mod stable {
|
|||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
let value = self.get();
|
||||
value.to_html_with_buf(buf, position, escape)
|
||||
value.to_html_with_buf(buf, position, escape, mark_branches)
|
||||
}
|
||||
|
||||
fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
|
||||
|
@ -578,12 +586,16 @@ mod stable {
|
|||
buf: &mut StreamBuilder,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
let value = self.get();
|
||||
value.to_html_async_with_buf::<OUT_OF_ORDER>(
|
||||
buf, position, escape,
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -730,9 +742,10 @@ mod stable {
|
|||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
let value = self.get();
|
||||
value.to_html_with_buf(buf, position, escape)
|
||||
value.to_html_with_buf(buf, position, escape, mark_branches)
|
||||
}
|
||||
|
||||
fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
|
||||
|
@ -740,12 +753,16 @@ mod stable {
|
|||
buf: &mut StreamBuilder,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
let value = self.get();
|
||||
value.to_html_async_with_buf::<OUT_OF_ORDER>(
|
||||
buf, position, escape,
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -123,9 +123,12 @@ where
|
|||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
self.owner
|
||||
.with(|| self.view.to_html_with_buf(buf, position, escape));
|
||||
self.owner.with(|| {
|
||||
self.view
|
||||
.to_html_with_buf(buf, position, escape, mark_branches)
|
||||
});
|
||||
}
|
||||
|
||||
fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
|
||||
|
@ -133,12 +136,17 @@ where
|
|||
buf: &mut StreamBuilder,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
self.owner.with(|| {
|
||||
self.view
|
||||
.to_html_async_with_buf::<OUT_OF_ORDER>(buf, position, escape)
|
||||
self.view.to_html_async_with_buf::<OUT_OF_ORDER>(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
)
|
||||
});
|
||||
|
||||
// if self.owner drops here, it can be disposed before the asynchronous rendering process
|
||||
|
|
|
@ -175,12 +175,13 @@ where
|
|||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
// TODO wrap this with a Suspense as needed
|
||||
// currently this is just used for Routes, which creates a Suspend but never actually needs
|
||||
// it (because we don't lazy-load routes on the server)
|
||||
if let Some(inner) = self.0.now_or_never() {
|
||||
inner.to_html_with_buf(buf, position, escape);
|
||||
inner.to_html_with_buf(buf, position, escape, mark_branches);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -189,13 +190,18 @@ where
|
|||
buf: &mut StreamBuilder,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
let mut fut = Box::pin(self.0);
|
||||
match fut.as_mut().now_or_never() {
|
||||
Some(inner) => inner
|
||||
.to_html_async_with_buf::<OUT_OF_ORDER>(buf, position, escape),
|
||||
Some(inner) => inner.to_html_async_with_buf::<OUT_OF_ORDER>(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
),
|
||||
None => {
|
||||
if use_context::<SuspenseContext>().is_none() {
|
||||
buf.next_id();
|
||||
|
@ -218,8 +224,13 @@ where
|
|||
buf.push_fallback::<(), Rndr>(
|
||||
(),
|
||||
&mut fallback_position,
|
||||
mark_branches,
|
||||
);
|
||||
buf.push_async_out_of_order(
|
||||
fut,
|
||||
position,
|
||||
mark_branches,
|
||||
);
|
||||
buf.push_async_out_of_order(fut, position);
|
||||
} else {
|
||||
buf.push_async({
|
||||
let mut position = *position;
|
||||
|
@ -230,6 +241,7 @@ where
|
|||
&mut builder,
|
||||
&mut position,
|
||||
escape,
|
||||
mark_branches,
|
||||
);
|
||||
builder.finish().take_chunks()
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ use std::{
|
|||
/// Manages streaming HTML rendering for the response to a single request.
|
||||
#[derive(Default)]
|
||||
pub struct StreamBuilder {
|
||||
sync_buf: String,
|
||||
pub(crate) sync_buf: String,
|
||||
pub(crate) chunks: VecDeque<StreamChunk>,
|
||||
pending: Option<ChunkFuture>,
|
||||
pending_ooo: VecDeque<PinnedFuture<OooChunk>>,
|
||||
|
@ -104,12 +104,18 @@ impl StreamBuilder {
|
|||
&mut self,
|
||||
fallback: View,
|
||||
position: &mut Position,
|
||||
mark_branches: bool,
|
||||
) where
|
||||
View: RenderHtml<Rndr>,
|
||||
Rndr: Renderer,
|
||||
{
|
||||
self.write_chunk_marker(true);
|
||||
fallback.to_html_with_buf(&mut self.sync_buf, position, true);
|
||||
fallback.to_html_with_buf(
|
||||
&mut self.sync_buf,
|
||||
position,
|
||||
true,
|
||||
mark_branches,
|
||||
);
|
||||
self.write_chunk_marker(false);
|
||||
*position = Position::NextChild;
|
||||
}
|
||||
|
@ -156,6 +162,7 @@ impl StreamBuilder {
|
|||
&mut self,
|
||||
view: impl Future<Output = Option<View>> + Send + 'static,
|
||||
position: &mut Position,
|
||||
mark_branches: bool,
|
||||
) where
|
||||
View: RenderHtml<Rndr>,
|
||||
Rndr: Renderer,
|
||||
|
@ -185,6 +192,7 @@ impl StreamBuilder {
|
|||
&mut subbuilder,
|
||||
&mut position,
|
||||
true,
|
||||
mark_branches,
|
||||
);
|
||||
}
|
||||
let chunks = subbuilder.finish().take_chunks();
|
||||
|
|
|
@ -38,12 +38,13 @@ where
|
|||
#[cfg(feature = "ssr")]
|
||||
html_len: usize,
|
||||
#[cfg(feature = "ssr")]
|
||||
to_html: fn(Box<dyn Any>, &mut String, &mut Position, bool),
|
||||
to_html: fn(Box<dyn Any>, &mut String, &mut Position, bool, bool),
|
||||
#[cfg(feature = "ssr")]
|
||||
to_html_async: fn(Box<dyn Any>, &mut StreamBuilder, &mut Position, bool),
|
||||
to_html_async:
|
||||
fn(Box<dyn Any>, &mut StreamBuilder, &mut Position, bool, bool),
|
||||
#[cfg(feature = "ssr")]
|
||||
to_html_async_ooo:
|
||||
fn(Box<dyn Any>, &mut StreamBuilder, &mut Position, bool),
|
||||
fn(Box<dyn Any>, &mut StreamBuilder, &mut Position, bool, bool),
|
||||
build: fn(Box<dyn Any>) -> AnyViewState<R>,
|
||||
rebuild: fn(TypeId, Box<dyn Any>, &mut AnyViewState<R>),
|
||||
#[cfg(feature = "ssr")]
|
||||
|
@ -175,32 +176,46 @@ where
|
|||
let to_html = |value: Box<dyn Any>,
|
||||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
escape: bool| {
|
||||
escape: bool,
|
||||
mark_branches: bool| {
|
||||
let value = value
|
||||
.downcast::<T>()
|
||||
.expect("AnyView::to_html could not be downcast");
|
||||
value.to_html_with_buf(buf, position, escape);
|
||||
value.to_html_with_buf(buf, position, escape, mark_branches);
|
||||
};
|
||||
#[cfg(feature = "ssr")]
|
||||
let to_html_async = |value: Box<dyn Any>,
|
||||
buf: &mut StreamBuilder,
|
||||
position: &mut Position,
|
||||
escape: bool| {
|
||||
escape: bool,
|
||||
mark_branches: bool| {
|
||||
let value = value
|
||||
.downcast::<T>()
|
||||
.expect("AnyView::to_html could not be downcast");
|
||||
value.to_html_async_with_buf::<false>(buf, position, escape);
|
||||
value.to_html_async_with_buf::<false>(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
);
|
||||
};
|
||||
#[cfg(feature = "ssr")]
|
||||
let to_html_async_ooo = |value: Box<dyn Any>,
|
||||
buf: &mut StreamBuilder,
|
||||
position: &mut Position,
|
||||
escape: bool| {
|
||||
let value = value
|
||||
.downcast::<T>()
|
||||
.expect("AnyView::to_html could not be downcast");
|
||||
value.to_html_async_with_buf::<true>(buf, position, escape);
|
||||
};
|
||||
let to_html_async_ooo =
|
||||
|value: Box<dyn Any>,
|
||||
buf: &mut StreamBuilder,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool| {
|
||||
let value = value
|
||||
.downcast::<T>()
|
||||
.expect("AnyView::to_html could not be downcast");
|
||||
value.to_html_async_with_buf::<true>(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
);
|
||||
};
|
||||
let build = |value: Box<dyn Any>| {
|
||||
let value = value
|
||||
.downcast::<T>()
|
||||
|
@ -347,9 +362,10 @@ where
|
|||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
#[cfg(feature = "ssr")]
|
||||
(self.to_html)(self.value, buf, position, escape);
|
||||
(self.to_html)(self.value, buf, position, escape, mark_branches);
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
{
|
||||
_ = buf;
|
||||
|
@ -367,14 +383,27 @@ where
|
|||
buf: &mut StreamBuilder,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
#[cfg(feature = "ssr")]
|
||||
if OUT_OF_ORDER {
|
||||
(self.to_html_async_ooo)(self.value, buf, position, escape);
|
||||
(self.to_html_async_ooo)(
|
||||
self.value,
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
);
|
||||
} else {
|
||||
(self.to_html_async)(self.value, buf, position, escape);
|
||||
(self.to_html_async)(
|
||||
self.value,
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
);
|
||||
}
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
{
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use super::{
|
||||
add_attr::AddAnyAttr, Mountable, Position, PositionState, Render,
|
||||
RenderHtml,
|
||||
add_attr::AddAnyAttr, MarkBranch, Mountable, Position, PositionState,
|
||||
Render, RenderHtml,
|
||||
};
|
||||
use crate::{
|
||||
html::attribute::Attribute, hydration::Cursor, renderer::Renderer,
|
||||
|
@ -155,11 +155,26 @@ where
|
|||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
match self {
|
||||
Either::Left(left) => left.to_html_with_buf(buf, position, escape),
|
||||
Either::Left(left) => {
|
||||
if mark_branches {
|
||||
buf.open_branch("0");
|
||||
}
|
||||
left.to_html_with_buf(buf, position, escape, mark_branches);
|
||||
if mark_branches {
|
||||
buf.close_branch("0");
|
||||
}
|
||||
}
|
||||
Either::Right(right) => {
|
||||
right.to_html_with_buf(buf, position, escape)
|
||||
if mark_branches {
|
||||
buf.open_branch("1");
|
||||
}
|
||||
right.to_html_with_buf(buf, position, escape, mark_branches);
|
||||
if mark_branches {
|
||||
buf.close_branch("1");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -169,14 +184,39 @@ where
|
|||
buf: &mut StreamBuilder,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
match self {
|
||||
Either::Left(left) => left
|
||||
.to_html_async_with_buf::<OUT_OF_ORDER>(buf, position, escape),
|
||||
Either::Right(right) => right
|
||||
.to_html_async_with_buf::<OUT_OF_ORDER>(buf, position, escape),
|
||||
Either::Left(left) => {
|
||||
if mark_branches {
|
||||
buf.open_branch("0");
|
||||
}
|
||||
left.to_html_async_with_buf::<OUT_OF_ORDER>(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
);
|
||||
if mark_branches {
|
||||
buf.close_branch("0");
|
||||
}
|
||||
}
|
||||
Either::Right(right) => {
|
||||
if mark_branches {
|
||||
buf.open_branch("1");
|
||||
}
|
||||
right.to_html_async_with_buf::<OUT_OF_ORDER>(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
);
|
||||
if mark_branches {
|
||||
buf.close_branch("1");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -317,6 +357,7 @@ where
|
|||
_buf: &mut String,
|
||||
_position: &mut Position,
|
||||
_escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
todo!()
|
||||
}
|
||||
|
@ -528,19 +569,35 @@ macro_rules! tuples {
|
|||
}
|
||||
}
|
||||
|
||||
fn to_html_with_buf(self, buf: &mut String, position: &mut Position, escape: bool) {
|
||||
fn to_html_with_buf(self, buf: &mut String, position: &mut Position, escape: bool, mark_branches: bool) {
|
||||
match self {
|
||||
$([<EitherOf $num>]::$ty(this) => this.to_html_with_buf(buf, position, escape),)*
|
||||
$([<EitherOf $num>]::$ty(this) => {
|
||||
if mark_branches {
|
||||
buf.open_branch(stringify!($ty));
|
||||
}
|
||||
this.to_html_with_buf(buf, position, escape, mark_branches);
|
||||
if mark_branches {
|
||||
buf.close_branch(stringify!($ty));
|
||||
}
|
||||
})*
|
||||
}
|
||||
}
|
||||
|
||||
fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
|
||||
self,
|
||||
buf: &mut StreamBuilder, position: &mut Position, escape: bool) where
|
||||
buf: &mut StreamBuilder, position: &mut Position, escape: bool, mark_branches: bool) where
|
||||
Self: Sized,
|
||||
{
|
||||
match self {
|
||||
$([<EitherOf $num>]::$ty(this) => this.to_html_async_with_buf::<OUT_OF_ORDER>(buf, position, escape),)*
|
||||
$([<EitherOf $num>]::$ty(this) => {
|
||||
if mark_branches {
|
||||
buf.open_branch(stringify!($ty));
|
||||
}
|
||||
this.to_html_async_with_buf::<OUT_OF_ORDER>(buf, position, escape, mark_branches);
|
||||
if mark_branches {
|
||||
buf.close_branch(stringify!($ty));
|
||||
}
|
||||
})*
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -157,9 +157,12 @@ where
|
|||
buf: &mut String,
|
||||
position: &mut super::Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
match self {
|
||||
Ok(inner) => inner.to_html_with_buf(buf, position, escape),
|
||||
Ok(inner) => {
|
||||
inner.to_html_with_buf(buf, position, escape, mark_branches)
|
||||
}
|
||||
Err(e) => {
|
||||
throw_error::throw(e);
|
||||
}
|
||||
|
@ -171,12 +174,17 @@ where
|
|||
buf: &mut StreamBuilder,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
match self {
|
||||
Ok(inner) => inner
|
||||
.to_html_async_with_buf::<OUT_OF_ORDER>(buf, position, escape),
|
||||
Ok(inner) => inner.to_html_async_with_buf::<OUT_OF_ORDER>(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
),
|
||||
Err(e) => {
|
||||
throw_error::throw(e);
|
||||
}
|
||||
|
|
|
@ -90,12 +90,13 @@ where
|
|||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
match self {
|
||||
Some(value) => Either::Left(value),
|
||||
None => Either::Right(()),
|
||||
}
|
||||
.to_html_with_buf(buf, position, escape)
|
||||
.to_html_with_buf(buf, position, escape, mark_branches)
|
||||
}
|
||||
|
||||
fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
|
||||
|
@ -103,6 +104,7 @@ where
|
|||
buf: &mut StreamBuilder,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -110,7 +112,12 @@ where
|
|||
Some(value) => Either::Left(value),
|
||||
None => Either::Right(()),
|
||||
}
|
||||
.to_html_async_with_buf::<OUT_OF_ORDER>(buf, position, escape)
|
||||
.to_html_async_with_buf::<OUT_OF_ORDER>(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
|
@ -282,13 +289,14 @@ where
|
|||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
let mut children = self.into_iter();
|
||||
if let Some(first) = children.next() {
|
||||
first.to_html_with_buf(buf, position, escape);
|
||||
first.to_html_with_buf(buf, position, escape, mark_branches);
|
||||
}
|
||||
for child in children {
|
||||
child.to_html_with_buf(buf, position, escape);
|
||||
child.to_html_with_buf(buf, position, escape, mark_branches);
|
||||
}
|
||||
buf.push_str("<!>");
|
||||
}
|
||||
|
@ -298,15 +306,26 @@ where
|
|||
buf: &mut StreamBuilder,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
let mut children = self.into_iter();
|
||||
if let Some(first) = children.next() {
|
||||
first.to_html_async_with_buf::<OUT_OF_ORDER>(buf, position, escape);
|
||||
first.to_html_async_with_buf::<OUT_OF_ORDER>(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
);
|
||||
}
|
||||
for child in children {
|
||||
child.to_html_async_with_buf::<OUT_OF_ORDER>(buf, position, escape);
|
||||
child.to_html_async_with_buf::<OUT_OF_ORDER>(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
);
|
||||
}
|
||||
buf.push_sync("<!>");
|
||||
}
|
||||
|
|
|
@ -221,10 +221,11 @@ where
|
|||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
for item in self.items.into_iter() {
|
||||
let item = (self.view_fn)(item);
|
||||
item.to_html_with_buf(buf, position, escape);
|
||||
item.to_html_with_buf(buf, position, escape, mark_branches);
|
||||
*position = Position::NextChild;
|
||||
}
|
||||
buf.push_str("<!>");
|
||||
|
@ -235,10 +236,16 @@ where
|
|||
buf: &mut StreamBuilder,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
for item in self.items.into_iter() {
|
||||
let item = (self.view_fn)(item);
|
||||
item.to_html_async_with_buf::<OUT_OF_ORDER>(buf, position, escape);
|
||||
item.to_html_async_with_buf::<OUT_OF_ORDER>(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
);
|
||||
*position = Position::NextChild;
|
||||
}
|
||||
buf.push_sync("<!>");
|
||||
|
|
|
@ -46,6 +46,40 @@ pub trait Render<R: Renderer>: Sized {
|
|||
fn rebuild(self, state: &mut Self::State);
|
||||
}
|
||||
|
||||
pub(crate) trait MarkBranch {
|
||||
fn open_branch(&mut self, branch_id: &str);
|
||||
|
||||
fn close_branch(&mut self, branch_id: &str);
|
||||
}
|
||||
|
||||
impl MarkBranch for String {
|
||||
fn open_branch(&mut self, branch_id: &str) {
|
||||
self.push_str("<!--bo-");
|
||||
self.push_str(branch_id);
|
||||
self.push_str("-->");
|
||||
}
|
||||
|
||||
fn close_branch(&mut self, branch_id: &str) {
|
||||
self.push_str("<!--bc-");
|
||||
self.push_str(branch_id);
|
||||
self.push_str("-->");
|
||||
}
|
||||
}
|
||||
|
||||
impl MarkBranch for StreamBuilder {
|
||||
fn open_branch(&mut self, branch_id: &str) {
|
||||
self.sync_buf.push_str("<!--bo-");
|
||||
self.sync_buf.push_str(branch_id);
|
||||
self.sync_buf.push_str("-->");
|
||||
}
|
||||
|
||||
fn close_branch(&mut self, branch_id: &str) {
|
||||
self.sync_buf.push_str("<!--bc-");
|
||||
self.sync_buf.push_str(branch_id);
|
||||
self.sync_buf.push_str("-->");
|
||||
}
|
||||
}
|
||||
|
||||
/// The `RenderHtml` trait allows rendering something to HTML, and transforming
|
||||
/// that HTML into an interactive interface.
|
||||
///
|
||||
|
@ -94,7 +128,19 @@ where
|
|||
Self: Sized,
|
||||
{
|
||||
let mut buf = String::with_capacity(self.html_len());
|
||||
self.to_html_with_buf(&mut buf, &mut Position::FirstChild, true);
|
||||
self.to_html_with_buf(&mut buf, &mut Position::FirstChild, true, false);
|
||||
buf
|
||||
}
|
||||
|
||||
/// Renders a view to HTML with branch markers. This can be used to support libraries that diff
|
||||
/// HTML pages against one another, by marking sections of the view that branch to different
|
||||
/// types with marker comments.
|
||||
fn to_html_branching(self) -> String
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let mut buf = String::with_capacity(self.html_len());
|
||||
self.to_html_with_buf(&mut buf, &mut Position::FirstChild, true, true);
|
||||
buf
|
||||
}
|
||||
|
||||
|
@ -108,6 +154,24 @@ where
|
|||
&mut builder,
|
||||
&mut Position::FirstChild,
|
||||
true,
|
||||
false,
|
||||
);
|
||||
builder.finish()
|
||||
}
|
||||
|
||||
/// Renders a view to an in-order stream of HTML with branch markers. This can be used to support libraries that diff
|
||||
/// HTML pages against one another, by marking sections of the view that branch to different
|
||||
/// types with marker comments.
|
||||
fn to_html_stream_in_order_branching(self) -> StreamBuilder
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let mut builder = StreamBuilder::with_capacity(self.html_len(), None);
|
||||
self.to_html_async_with_buf::<false>(
|
||||
&mut builder,
|
||||
&mut Position::FirstChild,
|
||||
true,
|
||||
true,
|
||||
);
|
||||
builder.finish()
|
||||
}
|
||||
|
@ -125,25 +189,29 @@ where
|
|||
&mut builder,
|
||||
&mut Position::FirstChild,
|
||||
true,
|
||||
false,
|
||||
);
|
||||
builder.finish()
|
||||
}
|
||||
|
||||
/// Renders a view to an out-of-order stream of HTML with branch markers. This can be used to support libraries that diff
|
||||
/// HTML pages against one another, by marking sections of the view that branch to different
|
||||
/// types with marker comments.
|
||||
|
||||
fn to_html_stream_out_of_order_branching(self) -> StreamBuilder
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let mut builder =
|
||||
StreamBuilder::with_capacity(self.html_len(), Some(vec![0]));
|
||||
|
||||
self.to_html_async_with_buf::<true>(
|
||||
&mut builder,
|
||||
&mut Position::FirstChild,
|
||||
true,
|
||||
true,
|
||||
);
|
||||
builder.finish()
|
||||
/*let mut b = builder.finish();
|
||||
let last = b.chunks.pop_back().unwrap();
|
||||
match &last {
|
||||
crate::ssr::StreamChunk::Sync(s) => {
|
||||
println!("actual = {}", s.len())
|
||||
}
|
||||
crate::ssr::StreamChunk::Async {
|
||||
chunks,
|
||||
should_block,
|
||||
} => todo!(),
|
||||
crate::ssr::StreamChunk::OutOfOrder {
|
||||
chunks,
|
||||
should_block,
|
||||
} => todo!(),
|
||||
}
|
||||
b.chunks.push_back(last);
|
||||
b*/
|
||||
}
|
||||
|
||||
/// Renders a view to HTML, writing it into the given buffer.
|
||||
|
@ -152,6 +220,7 @@ where
|
|||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
);
|
||||
|
||||
/// Renders a view into a buffer of (synchronous or asynchronous) HTML chunks.
|
||||
|
@ -160,10 +229,13 @@ where
|
|||
buf: &mut StreamBuilder,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
buf.with_buf(|buf| self.to_html_with_buf(buf, position, escape));
|
||||
buf.with_buf(|buf| {
|
||||
self.to_html_with_buf(buf, position, escape, mark_branches)
|
||||
});
|
||||
}
|
||||
|
||||
/// Makes a set of DOM nodes rendered from HTML interactive.
|
||||
|
|
|
@ -76,7 +76,7 @@ macro_rules! render_primitive {
|
|||
self
|
||||
}
|
||||
|
||||
fn to_html_with_buf(self, buf: &mut String, position: &mut Position, _escape: bool) {
|
||||
fn to_html_with_buf(self, buf: &mut String, position: &mut Position, _escape: bool, mark_branches: bool) {
|
||||
// add a comment node to separate from previous sibling, if any
|
||||
if matches!(position, Position::NextChildAfterText) {
|
||||
buf.push_str("<!>")
|
||||
|
|
|
@ -191,6 +191,7 @@ where
|
|||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
// add a comment node to separate from previous sibling, if any
|
||||
if matches!(position, Position::NextChildAfterText) {
|
||||
|
|
|
@ -59,6 +59,7 @@ where
|
|||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
// add a comment node to separate from previous sibling, if any
|
||||
if matches!(position, Position::NextChildAfterText) {
|
||||
|
@ -188,12 +189,14 @@ where
|
|||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
<&str as RenderHtml<R>>::to_html_with_buf(
|
||||
self.as_str(),
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -284,7 +287,7 @@ where
|
|||
self.len()
|
||||
}
|
||||
|
||||
fn to_html_with_buf(self, buf: &mut String, position: &mut Position, escape: bool) {
|
||||
fn to_html_with_buf(self, buf: &mut String, position: &mut Position, escape: bool, mark_branches: bool) {
|
||||
<&str as RenderHtml<R>>::to_html_with_buf(&self, buf, position)
|
||||
}
|
||||
|
||||
|
@ -380,8 +383,15 @@ where
|
|||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
<&str as RenderHtml<R>>::to_html_with_buf(&self, buf, position, escape)
|
||||
<&str as RenderHtml<R>>::to_html_with_buf(
|
||||
&self,
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
)
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
|
@ -476,8 +486,15 @@ where
|
|||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
<&str as RenderHtml<R>>::to_html_with_buf(&self, buf, position, escape)
|
||||
<&str as RenderHtml<R>>::to_html_with_buf(
|
||||
&self,
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
)
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
|
|
|
@ -91,8 +91,10 @@ where
|
|||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
self.view.to_html_with_buf(buf, position, escape)
|
||||
self.view
|
||||
.to_html_with_buf(buf, position, escape, mark_branches)
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
|
|
|
@ -35,6 +35,7 @@ where
|
|||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
_escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
buf.push_str("<!>");
|
||||
*position = Position::NextChild;
|
||||
|
@ -124,8 +125,10 @@ where
|
|||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
self.0.to_html_with_buf(buf, position, escape);
|
||||
self.0
|
||||
.to_html_with_buf(buf, position, escape, mark_branches);
|
||||
}
|
||||
|
||||
fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
|
||||
|
@ -133,11 +136,16 @@ where
|
|||
buf: &mut StreamBuilder,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
self.0
|
||||
.to_html_async_with_buf::<OUT_OF_ORDER>(buf, position, escape);
|
||||
self.0.to_html_async_with_buf::<OUT_OF_ORDER>(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
);
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
|
@ -238,22 +246,22 @@ macro_rules! impl_view_for_tuples {
|
|||
$($ty.html_len() +)* $first.html_len()
|
||||
}
|
||||
|
||||
fn to_html_with_buf(self, buf: &mut String, position: &mut Position, escape: bool) {
|
||||
fn to_html_with_buf(self, buf: &mut String, position: &mut Position, escape: bool, mark_branches: bool) {
|
||||
#[allow(non_snake_case)]
|
||||
let ($first, $($ty,)* ) = self;
|
||||
$first.to_html_with_buf(buf, position, escape);
|
||||
$($ty.to_html_with_buf(buf, position, escape));*
|
||||
$first.to_html_with_buf(buf, position, escape, mark_branches);
|
||||
$($ty.to_html_with_buf(buf, position, escape, mark_branches));*
|
||||
}
|
||||
|
||||
fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
|
||||
self,
|
||||
buf: &mut StreamBuilder, position: &mut Position, escape: bool) where
|
||||
buf: &mut StreamBuilder, position: &mut Position, escape: bool, mark_branches: bool) where
|
||||
Self: Sized,
|
||||
{
|
||||
#[allow(non_snake_case)]
|
||||
let ($first, $($ty,)* ) = self;
|
||||
$first.to_html_async_with_buf::<OUT_OF_ORDER>(buf, position, escape);
|
||||
$($ty.to_html_async_with_buf::<OUT_OF_ORDER>(buf, position, escape));*
|
||||
$first.to_html_async_with_buf::<OUT_OF_ORDER>(buf, position, escape, mark_branches);
|
||||
$($ty.to_html_async_with_buf::<OUT_OF_ORDER>(buf, position, escape, mark_branches));*
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(self, cursor: &Cursor<Rndr>, position: &PositionState) -> Self::State {
|
||||
|
|
Loading…
Reference in a new issue