mirror of
https://github.com/yewstack/yew.git
synced 2025-12-08 21:26:25 +00:00
Add the ability to use non-literal string as attribute names (#2593)
* Use AttrValue instead of &'static str in Attributes::IndexMap * avoid monomorphization to reduce binary size * fmt & `#[inline(always)]`s * more attempts to reduce the code size * make clippy happy
This commit is contained in:
parent
73771562c2
commit
e9b64e0a45
@ -7,6 +7,7 @@ use std::collections::HashMap;
|
|||||||
use std::iter;
|
use std::iter;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use web_sys::{Element, HtmlInputElement as InputElement, HtmlTextAreaElement as TextAreaElement};
|
use web_sys::{Element, HtmlInputElement as InputElement, HtmlTextAreaElement as TextAreaElement};
|
||||||
|
use yew::AttrValue;
|
||||||
|
|
||||||
impl<T: AccessValue> Apply for Value<T> {
|
impl<T: AccessValue> Apply for Value<T> {
|
||||||
type Element = T;
|
type Element = T;
|
||||||
@ -83,24 +84,20 @@ impl Apply for InputFields {
|
|||||||
|
|
||||||
impl Attributes {
|
impl Attributes {
|
||||||
#[cold]
|
#[cold]
|
||||||
fn apply_diff_index_maps<'a, A, B>(
|
fn apply_diff_index_maps(
|
||||||
el: &Element,
|
el: &Element,
|
||||||
// this makes it possible to diff `&'a IndexMap<_, A>` and `IndexMap<_, &'a A>`.
|
new: &IndexMap<AttrValue, AttrValue>,
|
||||||
mut new_iter: impl Iterator<Item = (&'static str, &'a str)>,
|
old: &IndexMap<AttrValue, AttrValue>,
|
||||||
new: &IndexMap<&'static str, A>,
|
) {
|
||||||
old: &IndexMap<&'static str, B>,
|
|
||||||
) where
|
|
||||||
A: AsRef<str>,
|
|
||||||
B: AsRef<str>,
|
|
||||||
{
|
|
||||||
let mut old_iter = old.iter();
|
let mut old_iter = old.iter();
|
||||||
|
let mut new_iter = new.iter();
|
||||||
loop {
|
loop {
|
||||||
match (new_iter.next(), old_iter.next()) {
|
match (new_iter.next(), old_iter.next()) {
|
||||||
(Some((new_key, new_value)), Some((old_key, old_value))) => {
|
(Some((new_key, new_value)), Some((old_key, old_value))) => {
|
||||||
if new_key != *old_key {
|
if new_key != old_key {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if new_value != old_value.as_ref() {
|
if new_value != old_value {
|
||||||
Self::set_attribute(el, new_key, new_value);
|
Self::set_attribute(el, new_key, new_value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,7 +106,7 @@ impl Attributes {
|
|||||||
for (key, value) in iter::once(attr).chain(new_iter) {
|
for (key, value) in iter::once(attr).chain(new_iter) {
|
||||||
match old.get(key) {
|
match old.get(key) {
|
||||||
Some(old_value) => {
|
Some(old_value) => {
|
||||||
if value != old_value.as_ref() {
|
if value != old_value {
|
||||||
Self::set_attribute(el, key, value);
|
Self::set_attribute(el, key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -123,6 +120,7 @@ impl Attributes {
|
|||||||
// removed attributes
|
// removed attributes
|
||||||
(None, Some(attr)) => {
|
(None, Some(attr)) => {
|
||||||
for (key, _) in iter::once(attr).chain(old_iter) {
|
for (key, _) in iter::once(attr).chain(old_iter) {
|
||||||
|
let key = key;
|
||||||
if !new.contains_key(key) {
|
if !new.contains_key(key) {
|
||||||
Self::remove_attribute(el, key);
|
Self::remove_attribute(el, key);
|
||||||
}
|
}
|
||||||
@ -138,7 +136,7 @@ impl Attributes {
|
|||||||
/// Works with any [Attributes] variants.
|
/// Works with any [Attributes] variants.
|
||||||
#[cold]
|
#[cold]
|
||||||
fn apply_diff_as_maps<'a>(el: &Element, new: &'a Self, old: &'a Self) {
|
fn apply_diff_as_maps<'a>(el: &Element, new: &'a Self, old: &'a Self) {
|
||||||
fn collect<'a>(src: &'a Attributes) -> HashMap<&'static str, &'a str> {
|
fn collect(src: &Attributes) -> HashMap<&str, &str> {
|
||||||
use Attributes::*;
|
use Attributes::*;
|
||||||
|
|
||||||
match src {
|
match src {
|
||||||
@ -148,7 +146,7 @@ impl Attributes {
|
|||||||
.zip(values.iter())
|
.zip(values.iter())
|
||||||
.filter_map(|(k, v)| v.as_ref().map(|v| (*k, v.as_ref())))
|
.filter_map(|(k, v)| v.as_ref().map(|v| (*k, v.as_ref())))
|
||||||
.collect(),
|
.collect(),
|
||||||
IndexMap(m) => m.iter().map(|(k, v)| (*k, v.as_ref())).collect(),
|
IndexMap(m) => m.iter().map(|(k, v)| (k.as_ref(), v.as_ref())).collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,8 +231,8 @@ impl Apply for Attributes {
|
|||||||
},
|
},
|
||||||
) if ptr_eq(new_k, old_k) => {
|
) if ptr_eq(new_k, old_k) => {
|
||||||
// Double zipping does not optimize well, so use asserts and unsafe instead
|
// Double zipping does not optimize well, so use asserts and unsafe instead
|
||||||
assert!(new_k.len() == new_v.len());
|
assert_eq!(new_k.len(), new_v.len());
|
||||||
assert!(new_k.len() == old_v.len());
|
assert_eq!(new_k.len(), old_v.len());
|
||||||
for i in 0..new_k.len() {
|
for i in 0..new_k.len() {
|
||||||
macro_rules! key {
|
macro_rules! key {
|
||||||
() => {
|
() => {
|
||||||
@ -263,8 +261,7 @@ impl Apply for Attributes {
|
|||||||
}
|
}
|
||||||
// For VTag's constructed outside the html! macro
|
// For VTag's constructed outside the html! macro
|
||||||
(Self::IndexMap(new), Self::IndexMap(ref old)) => {
|
(Self::IndexMap(new), Self::IndexMap(ref old)) => {
|
||||||
let new_iter = new.iter().map(|(k, v)| (*k, v.as_ref()));
|
Self::apply_diff_index_maps(el, &*new, &*old);
|
||||||
Self::apply_diff_index_maps(el, new_iter, new, old);
|
|
||||||
}
|
}
|
||||||
// Cold path. Happens only with conditional swapping and reordering of `VTag`s with the
|
// Cold path. Happens only with conditional swapping and reordering of `VTag`s with the
|
||||||
// same tag and no keys.
|
// same tag and no keys.
|
||||||
|
|||||||
@ -39,8 +39,9 @@ pub use self::vtag::VTag;
|
|||||||
pub use self::vtext::VText;
|
pub use self::vtext::VText;
|
||||||
|
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use std::borrow::Cow;
|
use std::borrow::{Borrow, Cow};
|
||||||
use std::fmt::Formatter;
|
use std::fmt::Formatter;
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::{fmt, hint::unreachable_unchecked};
|
use std::{fmt, hint::unreachable_unchecked};
|
||||||
@ -57,6 +58,7 @@ pub enum AttrValue {
|
|||||||
impl Deref for AttrValue {
|
impl Deref for AttrValue {
|
||||||
type Target = str;
|
type Target = str;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
match self {
|
match self {
|
||||||
AttrValue::Static(s) => *s,
|
AttrValue::Static(s) => *s,
|
||||||
@ -65,25 +67,36 @@ impl Deref for AttrValue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Hash for AttrValue {
|
||||||
|
#[inline(always)]
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
self.as_ref().hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<&'static str> for AttrValue {
|
impl From<&'static str> for AttrValue {
|
||||||
|
#[inline(always)]
|
||||||
fn from(s: &'static str) -> Self {
|
fn from(s: &'static str) -> Self {
|
||||||
AttrValue::Static(s)
|
AttrValue::Static(s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<String> for AttrValue {
|
impl From<String> for AttrValue {
|
||||||
|
#[inline(always)]
|
||||||
fn from(s: String) -> Self {
|
fn from(s: String) -> Self {
|
||||||
AttrValue::Rc(Rc::from(s))
|
AttrValue::Rc(Rc::from(s))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Rc<str>> for AttrValue {
|
impl From<Rc<str>> for AttrValue {
|
||||||
|
#[inline(always)]
|
||||||
fn from(s: Rc<str>) -> Self {
|
fn from(s: Rc<str>) -> Self {
|
||||||
AttrValue::Rc(s)
|
AttrValue::Rc(s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Cow<'static, str>> for AttrValue {
|
impl From<Cow<'static, str>> for AttrValue {
|
||||||
|
#[inline(always)]
|
||||||
fn from(s: Cow<'static, str>) -> Self {
|
fn from(s: Cow<'static, str>) -> Self {
|
||||||
match s {
|
match s {
|
||||||
Cow::Borrowed(s) => s.into(),
|
Cow::Borrowed(s) => s.into(),
|
||||||
@ -102,11 +115,18 @@ impl Clone for AttrValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<str> for AttrValue {
|
impl AsRef<str> for AttrValue {
|
||||||
|
#[inline(always)]
|
||||||
fn as_ref(&self) -> &str {
|
fn as_ref(&self) -> &str {
|
||||||
&*self
|
&*self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Borrow<str> for AttrValue {
|
||||||
|
fn borrow(&self) -> &str {
|
||||||
|
&*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl fmt::Display for AttrValue {
|
impl fmt::Display for AttrValue {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
@ -117,6 +137,7 @@ impl fmt::Display for AttrValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for AttrValue {
|
impl PartialEq for AttrValue {
|
||||||
|
#[inline(always)]
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.as_ref() == other.as_ref()
|
self.as_ref() == other.as_ref()
|
||||||
}
|
}
|
||||||
@ -292,7 +313,7 @@ pub enum Attributes {
|
|||||||
|
|
||||||
/// IndexMap is used to provide runtime attribute deduplication in cases where the html! macro
|
/// IndexMap is used to provide runtime attribute deduplication in cases where the html! macro
|
||||||
/// was not used to guarantee it.
|
/// was not used to guarantee it.
|
||||||
IndexMap(IndexMap<&'static str, AttrValue>),
|
IndexMap(IndexMap<AttrValue, AttrValue>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Attributes {
|
impl Attributes {
|
||||||
@ -303,7 +324,7 @@ impl Attributes {
|
|||||||
|
|
||||||
/// Return iterator over attribute key-value pairs.
|
/// Return iterator over attribute key-value pairs.
|
||||||
/// This function is suboptimal and does not inline well. Avoid on hot paths.
|
/// This function is suboptimal and does not inline well. Avoid on hot paths.
|
||||||
pub fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = (&'static str, &'a str)> + 'a> {
|
pub fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = (&'a str, &'a str)> + 'a> {
|
||||||
match self {
|
match self {
|
||||||
Self::Static(arr) => Box::new(arr.iter().map(|kv| (kv[0], kv[1] as &'a str))),
|
Self::Static(arr) => Box::new(arr.iter().map(|kv| (kv[0], kv[1] as &'a str))),
|
||||||
Self::Dynamic { keys, values } => Box::new(
|
Self::Dynamic { keys, values } => Box::new(
|
||||||
@ -311,13 +332,13 @@ impl Attributes {
|
|||||||
.zip(values.iter())
|
.zip(values.iter())
|
||||||
.filter_map(|(k, v)| v.as_ref().map(|v| (*k, v.as_ref()))),
|
.filter_map(|(k, v)| v.as_ref().map(|v| (*k, v.as_ref()))),
|
||||||
),
|
),
|
||||||
Self::IndexMap(m) => Box::new(m.iter().map(|(k, v)| (*k, v.as_ref()))),
|
Self::IndexMap(m) => Box::new(m.iter().map(|(k, v)| (k.as_ref(), v.as_ref()))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a mutable reference to the underlying `IndexMap`.
|
/// Get a mutable reference to the underlying `IndexMap`.
|
||||||
/// If the attributes are stored in the `Vec` variant, it will be converted.
|
/// If the attributes are stored in the `Vec` variant, it will be converted.
|
||||||
pub fn get_mut_index_map(&mut self) -> &mut IndexMap<&'static str, AttrValue> {
|
pub fn get_mut_index_map(&mut self) -> &mut IndexMap<AttrValue, AttrValue> {
|
||||||
macro_rules! unpack {
|
macro_rules! unpack {
|
||||||
() => {
|
() => {
|
||||||
match self {
|
match self {
|
||||||
@ -331,7 +352,7 @@ impl Attributes {
|
|||||||
match self {
|
match self {
|
||||||
Self::IndexMap(m) => m,
|
Self::IndexMap(m) => m,
|
||||||
Self::Static(arr) => {
|
Self::Static(arr) => {
|
||||||
*self = Self::IndexMap(arr.iter().map(|kv| (kv[0], kv[1].into())).collect());
|
*self = Self::IndexMap(arr.iter().map(|kv| (kv[0].into(), kv[1].into())).collect());
|
||||||
unpack!()
|
unpack!()
|
||||||
}
|
}
|
||||||
Self::Dynamic { keys, values } => {
|
Self::Dynamic { keys, values } => {
|
||||||
@ -339,7 +360,7 @@ impl Attributes {
|
|||||||
std::mem::take(values)
|
std::mem::take(values)
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.zip(keys.iter())
|
.zip(keys.iter())
|
||||||
.filter_map(|(v, k)| v.take().map(|v| (*k, v)))
|
.filter_map(|(v, k)| v.take().map(|v| (AttrValue::from(*k), v)))
|
||||||
.collect(),
|
.collect(),
|
||||||
);
|
);
|
||||||
unpack!()
|
unpack!()
|
||||||
@ -348,8 +369,18 @@ impl Attributes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<IndexMap<AttrValue, AttrValue>> for Attributes {
|
||||||
|
fn from(v: IndexMap<AttrValue, AttrValue>) -> Self {
|
||||||
|
Self::IndexMap(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<IndexMap<&'static str, AttrValue>> for Attributes {
|
impl From<IndexMap<&'static str, AttrValue>> for Attributes {
|
||||||
fn from(v: IndexMap<&'static str, AttrValue>) -> Self {
|
fn from(v: IndexMap<&'static str, AttrValue>) -> Self {
|
||||||
|
let v = v
|
||||||
|
.into_iter()
|
||||||
|
.map(|(k, v)| (AttrValue::Static(k), v))
|
||||||
|
.collect();
|
||||||
Self::IndexMap(v)
|
Self::IndexMap(v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -351,7 +351,7 @@ impl VTag {
|
|||||||
pub fn add_attribute(&mut self, key: &'static str, value: impl Into<AttrValue>) {
|
pub fn add_attribute(&mut self, key: &'static str, value: impl Into<AttrValue>) {
|
||||||
self.attributes
|
self.attributes
|
||||||
.get_mut_index_map()
|
.get_mut_index_map()
|
||||||
.insert(key, value.into());
|
.insert(AttrValue::Static(key), value.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets attributes to a virtual node.
|
/// Sets attributes to a virtual node.
|
||||||
@ -366,7 +366,7 @@ impl VTag {
|
|||||||
pub fn __macro_push_attr(&mut self, key: &'static str, value: impl IntoPropValue<AttrValue>) {
|
pub fn __macro_push_attr(&mut self, key: &'static str, value: impl IntoPropValue<AttrValue>) {
|
||||||
self.attributes
|
self.attributes
|
||||||
.get_mut_index_map()
|
.get_mut_index_map()
|
||||||
.insert(key, value.into_prop_value());
|
.insert(AttrValue::from(key), value.into_prop_value());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add event listener on the [VTag]'s [Element](web_sys::Element).
|
/// Add event listener on the [VTag]'s [Element](web_sys::Element).
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user