change: insert `<head>` metadata tags at the beginning of the head, not the end (#731)
This commit is contained in:
parent
7aa4d9e6db
commit
42360d109b
|
@ -20,7 +20,7 @@ use leptos::{
|
||||||
leptos_server::{server_fn_by_path, Payload},
|
leptos_server::{server_fn_by_path, Payload},
|
||||||
*,
|
*,
|
||||||
};
|
};
|
||||||
use leptos_integration_utils::{build_async_response, html_parts};
|
use leptos_integration_utils::{build_async_response, html_parts_separated};
|
||||||
use leptos_meta::*;
|
use leptos_meta::*;
|
||||||
use leptos_router::*;
|
use leptos_router::*;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
|
@ -728,7 +728,7 @@ async fn stream_app(
|
||||||
let (stream, runtime, scope) =
|
let (stream, runtime, scope) =
|
||||||
render_to_stream_with_prefix_undisposed_with_context(
|
render_to_stream_with_prefix_undisposed_with_context(
|
||||||
app,
|
app,
|
||||||
move |cx| generate_head_metadata(cx).into(),
|
move |cx| generate_head_metadata_separated(cx).1.into(),
|
||||||
additional_context,
|
additional_context,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -745,7 +745,7 @@ async fn stream_app_in_order(
|
||||||
leptos::ssr::render_to_stream_in_order_with_prefix_undisposed_with_context(
|
leptos::ssr::render_to_stream_in_order_with_prefix_undisposed_with_context(
|
||||||
app,
|
app,
|
||||||
move |cx| {
|
move |cx| {
|
||||||
generate_head_metadata(cx).into()
|
generate_head_metadata_separated(cx).1.into()
|
||||||
},
|
},
|
||||||
additional_context,
|
additional_context,
|
||||||
);
|
);
|
||||||
|
@ -762,7 +762,7 @@ async fn build_stream_response(
|
||||||
) -> HttpResponse {
|
) -> HttpResponse {
|
||||||
let cx = leptos::Scope { runtime, id: scope };
|
let cx = leptos::Scope { runtime, id: scope };
|
||||||
let (head, tail) =
|
let (head, tail) =
|
||||||
html_parts(options, use_context::<MetaContext>(cx).as_ref());
|
html_parts_separated(options, use_context::<MetaContext>(cx).as_ref());
|
||||||
|
|
||||||
let mut stream = Box::pin(
|
let mut stream = Box::pin(
|
||||||
futures::stream::once(async move { head.clone() })
|
futures::stream::once(async move { head.clone() })
|
||||||
|
|
|
@ -27,8 +27,8 @@ use leptos::{
|
||||||
ssr::*,
|
ssr::*,
|
||||||
*,
|
*,
|
||||||
};
|
};
|
||||||
use leptos_integration_utils::{build_async_response, html_parts};
|
use leptos_integration_utils::{build_async_response, html_parts_separated};
|
||||||
use leptos_meta::{generate_head_metadata, MetaContext};
|
use leptos_meta::{generate_head_metadata_separated, MetaContext};
|
||||||
use leptos_router::*;
|
use leptos_router::*;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use std::{io, pin::Pin, sync::Arc};
|
use std::{io, pin::Pin, sync::Arc};
|
||||||
|
@ -653,7 +653,7 @@ where
|
||||||
let (bundle, runtime, scope) =
|
let (bundle, runtime, scope) =
|
||||||
leptos::leptos_dom::ssr::render_to_stream_with_prefix_undisposed_with_context(
|
leptos::leptos_dom::ssr::render_to_stream_with_prefix_undisposed_with_context(
|
||||||
app,
|
app,
|
||||||
|cx| generate_head_metadata(cx).into(),
|
|cx| generate_head_metadata_separated(cx).1.into(),
|
||||||
add_context,
|
add_context,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -711,7 +711,7 @@ async fn forward_stream(
|
||||||
) {
|
) {
|
||||||
let cx = Scope { runtime, id: scope };
|
let cx = Scope { runtime, id: scope };
|
||||||
let (head, tail) =
|
let (head, tail) =
|
||||||
html_parts(options, use_context::<MetaContext>(cx).as_ref());
|
html_parts_separated(options, use_context::<MetaContext>(cx).as_ref());
|
||||||
|
|
||||||
_ = tx.send(head).await;
|
_ = tx.send(head).await;
|
||||||
let mut shell = Box::pin(bundle);
|
let mut shell = Box::pin(bundle);
|
||||||
|
@ -822,7 +822,7 @@ where
|
||||||
let (bundle, runtime, scope) =
|
let (bundle, runtime, scope) =
|
||||||
leptos::ssr::render_to_stream_in_order_with_prefix_undisposed_with_context(
|
leptos::ssr::render_to_stream_in_order_with_prefix_undisposed_with_context(
|
||||||
app,
|
app,
|
||||||
|cx| generate_head_metadata(cx).into(),
|
|cx| generate_head_metadata_separated(cx).1.into(),
|
||||||
add_context,
|
add_context,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -3,25 +3,10 @@ use leptos::{use_context, RuntimeId, ScopeId};
|
||||||
use leptos_config::LeptosOptions;
|
use leptos_config::LeptosOptions;
|
||||||
use leptos_meta::MetaContext;
|
use leptos_meta::MetaContext;
|
||||||
|
|
||||||
pub fn html_parts(
|
fn autoreload(options: &LeptosOptions) -> String {
|
||||||
options: &LeptosOptions,
|
|
||||||
meta: Option<&MetaContext>,
|
|
||||||
) -> (String, &'static str) {
|
|
||||||
let pkg_path = &options.site_pkg_dir;
|
|
||||||
let output_name = &options.output_name;
|
|
||||||
|
|
||||||
// Because wasm-pack adds _bg to the end of the WASM filename, and we want to maintain compatibility with it's default options
|
|
||||||
// we add _bg to the wasm files if cargo-leptos doesn't set the env var LEPTOS_OUTPUT_NAME
|
|
||||||
// Otherwise we need to add _bg because wasm_pack always does. This is not the same as options.output_name, which is set regardless
|
|
||||||
let mut wasm_output_name = output_name.clone();
|
|
||||||
if std::env::var("LEPTOS_OUTPUT_NAME").is_err() {
|
|
||||||
wasm_output_name.push_str("_bg");
|
|
||||||
}
|
|
||||||
|
|
||||||
let site_ip = &options.site_addr.ip().to_string();
|
let site_ip = &options.site_addr.ip().to_string();
|
||||||
let reload_port = options.reload_port;
|
let reload_port = options.reload_port;
|
||||||
|
match std::env::var("LEPTOS_WATCH").is_ok() {
|
||||||
let leptos_autoreload = match std::env::var("LEPTOS_WATCH").is_ok() {
|
|
||||||
true => format!(
|
true => format!(
|
||||||
r#"
|
r#"
|
||||||
<script crossorigin="">(function () {{
|
<script crossorigin="">(function () {{
|
||||||
|
@ -52,7 +37,25 @@ pub fn html_parts(
|
||||||
leptos_hot_reload::HOT_RELOAD_JS
|
leptos_hot_reload::HOT_RELOAD_JS
|
||||||
),
|
),
|
||||||
false => "".to_string(),
|
false => "".to_string(),
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn html_parts(
|
||||||
|
options: &LeptosOptions,
|
||||||
|
meta: Option<&MetaContext>,
|
||||||
|
) -> (String, &'static str) {
|
||||||
|
let pkg_path = &options.site_pkg_dir;
|
||||||
|
let output_name = &options.output_name;
|
||||||
|
|
||||||
|
// Because wasm-pack adds _bg to the end of the WASM filename, and we want to mantain compatibility with it's default options
|
||||||
|
// we add _bg to the wasm files if cargo-leptos doesn't set the env var LEPTOS_OUTPUT_NAME
|
||||||
|
// Otherwise we need to add _bg because wasm_pack always does. This is not the same as options.output_name, which is set regardless
|
||||||
|
let mut wasm_output_name = output_name.clone();
|
||||||
|
if std::env::var("LEPTOS_OUTPUT_NAME").is_err() {
|
||||||
|
wasm_output_name.push_str("_bg");
|
||||||
|
}
|
||||||
|
|
||||||
|
let leptos_autoreload = autoreload(options);
|
||||||
|
|
||||||
let html_metadata =
|
let html_metadata =
|
||||||
meta.and_then(|mc| mc.html.as_string()).unwrap_or_default();
|
meta.and_then(|mc| mc.html.as_string()).unwrap_or_default();
|
||||||
|
@ -72,6 +75,46 @@ pub fn html_parts(
|
||||||
(head, tail)
|
(head, tail)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn html_parts_separated(
|
||||||
|
options: &LeptosOptions,
|
||||||
|
meta: Option<&MetaContext>,
|
||||||
|
) -> (String, &'static str) {
|
||||||
|
let pkg_path = &options.site_pkg_dir;
|
||||||
|
let output_name = &options.output_name;
|
||||||
|
|
||||||
|
// Because wasm-pack adds _bg to the end of the WASM filename, and we want to mantain compatibility with it's default options
|
||||||
|
// we add _bg to the wasm files if cargo-leptos doesn't set the env var LEPTOS_OUTPUT_NAME
|
||||||
|
// Otherwise we need to add _bg because wasm_pack always does. This is not the same as options.output_name, which is set regardless
|
||||||
|
let mut wasm_output_name = output_name.clone();
|
||||||
|
if std::env::var("LEPTOS_OUTPUT_NAME").is_err() {
|
||||||
|
wasm_output_name.push_str("_bg");
|
||||||
|
}
|
||||||
|
|
||||||
|
let leptos_autoreload = autoreload(options);
|
||||||
|
|
||||||
|
let html_metadata =
|
||||||
|
meta.and_then(|mc| mc.html.as_string()).unwrap_or_default();
|
||||||
|
let head = meta
|
||||||
|
.as_ref()
|
||||||
|
.map(|meta| meta.dehydrate())
|
||||||
|
.unwrap_or_default();
|
||||||
|
let head = format!(
|
||||||
|
r#"<!DOCTYPE html>
|
||||||
|
<html{html_metadata}>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||||
|
{head}
|
||||||
|
<link rel="modulepreload" href="/{pkg_path}/{output_name}.js">
|
||||||
|
<link rel="preload" href="/{pkg_path}/{wasm_output_name}.wasm" as="fetch" type="application/wasm" crossorigin="">
|
||||||
|
<script type="module">import init, {{ hydrate }} from '/{pkg_path}/{output_name}.js'; init('/{pkg_path}/{wasm_output_name}.wasm').then(hydrate);</script>
|
||||||
|
{leptos_autoreload}
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
let tail = "</body></html>";
|
||||||
|
(head, tail)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn build_async_response(
|
pub async fn build_async_response(
|
||||||
stream: impl Stream<Item = String> + 'static,
|
stream: impl Stream<Item = String> + 'static,
|
||||||
options: &LeptosOptions,
|
options: &LeptosOptions,
|
||||||
|
@ -86,7 +129,7 @@ pub async fn build_async_response(
|
||||||
|
|
||||||
let cx = leptos::Scope { runtime, id: scope };
|
let cx = leptos::Scope { runtime, id: scope };
|
||||||
let (head, tail) =
|
let (head, tail) =
|
||||||
html_parts(options, use_context::<MetaContext>(cx).as_ref());
|
html_parts_separated(options, use_context::<MetaContext>(cx).as_ref());
|
||||||
|
|
||||||
// in async, we load the meta content *now*, after the suspenses have resolved
|
// in async, we load the meta content *now*, after the suspenses have resolved
|
||||||
let meta = use_context::<MetaContext>(cx);
|
let meta = use_context::<MetaContext>(cx);
|
||||||
|
|
|
@ -17,8 +17,8 @@ use leptos::{
|
||||||
ssr::*,
|
ssr::*,
|
||||||
*,
|
*,
|
||||||
};
|
};
|
||||||
use leptos_integration_utils::{build_async_response, html_parts};
|
use leptos_integration_utils::{build_async_response, html_parts_separated};
|
||||||
use leptos_meta::{generate_head_metadata, MetaContext};
|
use leptos_meta::{generate_head_metadata_separated, MetaContext};
|
||||||
use leptos_router::*;
|
use leptos_router::*;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use std::{pin::Pin, sync::Arc};
|
use std::{pin::Pin, sync::Arc};
|
||||||
|
@ -536,7 +536,7 @@ where
|
||||||
let (bundle, runtime, scope) =
|
let (bundle, runtime, scope) =
|
||||||
leptos::leptos_dom::ssr::render_to_stream_with_prefix_undisposed_with_context(
|
leptos::leptos_dom::ssr::render_to_stream_with_prefix_undisposed_with_context(
|
||||||
app,
|
app,
|
||||||
|cx| generate_head_metadata(cx).into(),
|
|cx| generate_head_metadata_separated(cx).1.into(),
|
||||||
add_context,
|
add_context,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -593,7 +593,7 @@ async fn forward_stream(
|
||||||
) {
|
) {
|
||||||
let cx = Scope { runtime, id: scope };
|
let cx = Scope { runtime, id: scope };
|
||||||
let (head, tail) =
|
let (head, tail) =
|
||||||
html_parts(options, use_context::<MetaContext>(cx).as_ref());
|
html_parts_separated(options, use_context::<MetaContext>(cx).as_ref());
|
||||||
|
|
||||||
_ = tx.send(head).await;
|
_ = tx.send(head).await;
|
||||||
let mut shell = Box::pin(bundle);
|
let mut shell = Box::pin(bundle);
|
||||||
|
@ -700,7 +700,7 @@ where
|
||||||
let (bundle, runtime, scope) =
|
let (bundle, runtime, scope) =
|
||||||
leptos::ssr::render_to_stream_in_order_with_prefix_undisposed_with_context(
|
leptos::ssr::render_to_stream_in_order_with_prefix_undisposed_with_context(
|
||||||
app,
|
app,
|
||||||
|cx| generate_head_metadata(cx).into(),
|
|cx| generate_head_metadata_separated(cx).1.into(),
|
||||||
add_context,
|
add_context,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -282,6 +282,15 @@ impl MetaContext {
|
||||||
/// server-side HTML rendering across crates.
|
/// server-side HTML rendering across crates.
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
pub fn generate_head_metadata(cx: Scope) -> String {
|
pub fn generate_head_metadata(cx: Scope) -> String {
|
||||||
|
let (head, body) = generate_head_metadata_separated(cx);
|
||||||
|
format!("{head}</head><{body}>")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extracts the metadata that should be inserted at the beginning of the `<head>` tag
|
||||||
|
/// and on the opening `<body>` tag. This is a helper function used in implementing
|
||||||
|
/// server-side HTML rendering across crates.
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
pub fn generate_head_metadata_separated(cx: Scope) -> (String, String) {
|
||||||
let meta = use_context::<MetaContext>(cx);
|
let meta = use_context::<MetaContext>(cx);
|
||||||
let head = meta
|
let head = meta
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -291,7 +300,7 @@ pub fn generate_head_metadata(cx: Scope) -> String {
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|meta| meta.body.as_string())
|
.and_then(|meta| meta.body.as_string())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
format!("{head}</head><body{body_meta}>")
|
(head, format!("<body{body_meta}>"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Describes a value that is either a static or a reactive string, i.e.,
|
/// Describes a value that is either a static or a reactive string, i.e.,
|
||||||
|
|
Loading…
Reference in New Issue