made is_server and helpers const fn and added View::into_html_element helper

This commit is contained in:
Jose Quesada 2022-12-12 12:18:15 -06:00
parent ff21f38626
commit 9dbbb26100
5 changed files with 144 additions and 45 deletions

View file

@ -0,0 +1,2 @@
[build]
target = "wasm32-unknown-unknown"

View file

@ -23,7 +23,7 @@ pub fn location() -> web_sys::Location {
/// Current [`window.location.hash`](https://developer.mozilla.org/en-US/docs/Web/API/Window/location)
/// without the beginning #.
pub fn location_hash() -> Option<String> {
if is_server!() {
if is_server() {
None
} else {
location().hash().ok().map(|hash| hash.replace('#', ""))
@ -125,7 +125,7 @@ pub fn window_event_listener(
event_name: &str,
cb: impl Fn(web_sys::Event) + 'static,
) {
if !is_server!() {
if !is_server() {
let handler = Box::new(cb) as Box<dyn FnMut(web_sys::Event)>;
let cb = Closure::wrap(handler).into_js_value();

View file

@ -29,6 +29,13 @@ use std::{cell::LazyCell, ops::Deref};
#[cfg(all(target_arch = "wasm32", feature = "web"))]
use wasm_bindgen::JsCast;
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
const HTML_ELEMENT_DEREF_UNIMPLEMENTED_MSG: &str =
"`Deref<Target = web_sys::HtmlElement>` can only be used on web targets. \
This is for the same reason that normal `wasm_bindgen` methods can be used \
only in the browser. Please use `leptos::is_server()` or \
`leptos::is_browser()` to check where you're running.";
/// Trait alias for the trait bounts on [`IntoElement`].
#[cfg(all(target_arch = "wasm32", feature = "web"))]
pub trait IntoElementBounds:
@ -69,7 +76,8 @@ pub trait IntoElement: IntoElementBounds {
fn hydration_id(&self) -> usize;
}
/// Trait for converting [`web_sys::Element`] to [`HtmlElement`].
/// Trait for converting any type which impl [`AsRef<web_sys::Element>`]
/// to [`HtmlElement`].
pub trait ToHtmlElement {
/// Converts the type to [`HtmlElement`].
fn to_leptos_element(self, cx: Scope) -> HtmlElement<AnyElement>;
@ -104,16 +112,25 @@ where
/// Represents potentially any element.
#[derive(Clone, Debug)]
#[cfg_attr(all(target_arch = "wasm32", feature = "web"), derive(educe::Educe))]
#[cfg_attr(all(target_arch = "wasm32", feature = "web"), educe(Deref))]
pub struct AnyElement {
name: Cow<'static, str>,
pub(crate) name: Cow<'static, str>,
#[cfg(all(target_arch = "wasm32", feature = "web"))]
#[educe(Deref)]
element: web_sys::HtmlElement,
is_void: bool,
pub(crate) element: web_sys::HtmlElement,
pub(crate) is_void: bool,
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
id: usize,
pub(crate) id: usize,
}
impl Deref for AnyElement {
type Target = web_sys::HtmlElement;
fn deref(&self) -> &Self::Target {
#[cfg(all(target_arch = "wasm32", feature = "web"))]
return &self.element;
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
unimplemented!("{HTML_ELEMENT_DEREF_UNIMPLEMENTED_MSG}");
}
}
impl IntoElement for AnyElement {
@ -174,12 +191,9 @@ impl IntoElement for Custom {
cfg_if! {
if #[cfg(all(target_arch = "wasm32", feature = "web"))] {
/// Represents an HTML element.
#[derive(educe::Educe)]
#[educe(Debug, Deref)]
pub struct HtmlElement<El: IntoElement> {
cx: Scope,
#[educe(Deref)]
element: El,
pub(crate) cx: Scope,
pub(crate) element: El,
}
// Server needs to build a virtualized DOM tree
} else {
@ -189,7 +203,6 @@ cfg_if! {
pub struct HtmlElement<El: IntoElement> {
pub(crate) cx: Scope,
pub(crate) element: El,
pub(crate) id: OnceCell<Cow<'static, str>>,
#[educe(Debug(ignore))]
pub(crate) attrs: SmallVec<[(Cow<'static, str>, Cow<'static, str>); 4]>,
#[educe(Debug(ignore))]
@ -199,6 +212,18 @@ cfg_if! {
}
}
impl<El: IntoElement> std::ops::Deref for HtmlElement<El> {
type Target = web_sys::HtmlElement;
fn deref(&self) -> &Self::Target {
#[cfg(all(target_arch = "wasm32", feature = "web"))]
return &self.element;
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
unimplemented!("{HTML_ELEMENT_DEREF_UNIMPLEMENTED_MSG}");
}
}
impl<El: IntoElement> HtmlElement<El> {
fn new(cx: Scope, element: El) -> Self {
cfg_if! {
@ -210,7 +235,6 @@ impl<El: IntoElement> HtmlElement<El> {
} else {
Self {
cx,
id: Default::default(),
attrs: smallvec![],
children: smallvec![],
element,
@ -239,7 +263,6 @@ impl<El: IntoElement> HtmlElement<El> {
} else {
let Self {
cx,
id,
attrs,
children,
element,
@ -247,7 +270,6 @@ impl<El: IntoElement> HtmlElement<El> {
HtmlElement {
cx,
id,
attrs,
children,
element: AnyElement {
@ -608,12 +630,9 @@ macro_rules! generate_html_tags {
});
#[derive(Clone, Debug)]
#[cfg_attr(all(target_arch = "wasm32", feature = "web"), derive(educe::Educe))]
#[cfg_attr(all(target_arch = "wasm32", feature = "web"), educe(Deref))]
#[$meta]
pub struct [<$tag:camel $($trailing_)?>] {
#[cfg(all(target_arch = "wasm32", feature = "web"))]
#[educe(Deref)]
element: web_sys::HtmlElement,
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
id: usize,
@ -677,6 +696,18 @@ macro_rules! generate_html_tags {
}
}
impl std::ops::Deref for [<$tag:camel $($trailing_)?>] {
type Target = web_sys::HtmlElement;
fn deref(&self) -> &Self::Target {
#[cfg(all(target_arch = "wasm32", feature = "web"))]
return &self.element;
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
unimplemented!("{HTML_ELEMENT_DEREF_UNIMPLEMENTED_MSG}");
}
}
impl IntoElement for [<$tag:camel $($trailing_)?>] {
fn name(&self) -> Cow<'static, str> {
stringify!($tag).into()

View file

@ -174,6 +174,46 @@ cfg_if! {
}
}
impl Element {
/// Converts this leptos [`Element`] into [`HtmlElement<AnyElement`].
pub fn into_html_element(self, cx: Scope) -> HtmlElement<AnyElement> {
#[cfg(all(target_arch = "wasm32", feature = "web"))]
{
let Self { element, .. } = self;
let name = element.node_name().to_ascii_lowercase();
let element = AnyElement {
name: name.into(),
element,
is_void: false,
};
HtmlElement { cx, element }
}
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
{
let Self {
name,
is_void,
attrs,
children,
id,
} = self;
let element = AnyElement { name, is_void, id };
HtmlElement {
cx,
element,
attrs,
children: children.into_iter().collect(),
}
}
}
}
impl IntoView for Element {
#[cfg_attr(debug_assertions, instrument(level = "trace", name = "<Element />", skip_all, fields(tag = %self.name)))]
fn into_view(self, _: Scope) -> View {
@ -448,6 +488,19 @@ impl View {
_ => None,
}
}
/// Returns [`Ok(HtmlElement<AnyElement>)`] if this [`View`] is
/// of type [`Element`]. [`Err(View)`] otherwise.
pub fn into_html_element(
self,
cx: Scope,
) -> Result<HtmlElement<AnyElement>, Self> {
if let Self::Element(el) = self {
Ok(el.into_html_element(cx))
} else {
Err(self)
}
}
}
#[cfg_attr(debug_assertions, instrument)]
@ -567,7 +620,7 @@ pub fn document() -> web_sys::Document {
DOCUMENT.with(|document| document.clone())
}
/// Shorthand to test for whether an `ssr` feature is enabled.
/// Returns true if running on the server (SSR).
///
/// In the past, this was implemented by checking whether `not(target_arch = "wasm32")`.
/// Now that some cloud platforms are moving to run Wasm on the edge, we really can't
@ -576,31 +629,44 @@ pub fn document() -> web_sys::Document {
///
/// ```
/// # use leptos_dom::is_server;
/// let todos = if is_server!() {
/// let todos = if is_server() {
/// // if on the server, load from DB
/// } else {
/// // if on the browser, do something else
/// };
/// ```
#[macro_export]
macro_rules! is_server {
() => {
!cfg!(all(target_arch = "wasm32", feature = "web"))
};
pub const fn is_server() -> bool {
cfg!(all(target_arch = "wasm32", feature = "web"))
}
/// A shorthand macro to test whether this is a debug build.
/// Returns true if running on the browser (CSR).
///
/// ```
/// # use leptos_dom::is_browser;
/// let todos = if is_browser() {
/// // if on the browser, call `wasm_bindgen` methods
/// } else {
/// // if on the server, do something else
/// };
/// ```
pub const fn is_browser() -> bool {
!is_server()
}
/// Returns true if `debug_assertions` are enabled.
/// ```
/// # use leptos_dom::is_dev;
/// if is_dev!() {
/// // log something or whatever
/// }
/// ```
#[macro_export]
macro_rules! is_dev {
() => {
cfg!(debug_assertions)
};
pub const fn is_dev() -> bool {
cfg!(debug_assertions)
}
/// Returns true if `debug_assertions` are disabled.
pub const fn is_release() -> bool {
!is_dev()
}
macro_rules! impl_into_view_for_tuples {
@ -687,6 +753,12 @@ impl IntoView for String {
}
}
impl IntoView for &'static str {
fn into_view(self, _: Scope) -> View {
View::Text(Text::new(self.into()))
}
}
macro_rules! viewable_primitive {
($child_type:ty) => {
impl IntoView for $child_type {
@ -697,12 +769,6 @@ macro_rules! viewable_primitive {
};
}
impl IntoView for &'static str {
fn into_view(self, cx: Scope) -> View {
View::Text(Text::new(self.into()))
}
}
viewable_primitive!(&String);
viewable_primitive!(usize);
viewable_primitive!(u8);

View file

@ -44,7 +44,7 @@ macro_rules! debug_warn {
/// Log a string to the console (in the browser)
/// or via `println!()` (if not in the browser).
pub fn console_log(s: &str) {
if is_server!() {
if is_server() {
println!("{}", s);
} else {
web_sys::console::log_1(&JsValue::from_str(s));
@ -54,7 +54,7 @@ pub fn console_log(s: &str) {
/// Log a warning to the console (in the browser)
/// or via `println!()` (if not in the browser).
pub fn console_warn(s: &str) {
if is_server!() {
if is_server() {
eprintln!("{}", s);
} else {
web_sys::console::warn_1(&JsValue::from_str(s));
@ -64,7 +64,7 @@ pub fn console_warn(s: &str) {
/// Log an error to the console (in the browser)
/// or via `println!()` (if not in the browser).
pub fn console_error(s: &str) {
if is_server!() {
if is_server() {
eprintln!("{}", s);
} else {
web_sys::console::warn_1(&JsValue::from_str(s));
@ -76,7 +76,7 @@ pub fn console_error(s: &str) {
pub fn console_debug_warn(s: &str) {
cfg_if! {
if #[cfg(debug_assertions)] {
if is_server!() {
if is_server() {
eprintln!("{}", s);
} else {
web_sys::console::warn_1(&JsValue::from_str(s));