change: insert `<head>` metadata tags at the beginning of the head, not the end (#731)

This commit is contained in:
Greg Johnston 2023-03-31 14:51:27 -04:00 committed by GitHub
parent 7aa4d9e6db
commit 42360d109b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 86 additions and 34 deletions

View File

@ -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() })

View File

@ -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,
); );

View File

@ -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);

View File

@ -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,
); );

View File

@ -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.,