pagetop/src/html/assets/javascript.rs

178 lines
6.4 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use crate::html::assets::AssetsTrait;
use crate::html::{html, Markup, Render};
use crate::{join, join_pair, AutoDefault, Weight};
// Define el origen del recurso JavaScript y cómo debe cargarse en el navegador.
//
// Los distintos modos de carga permiten optimizar el rendimiento y controlar el comportamiento del
// script.
//
// - [`From`] Carga el script de forma estándar con la etiqueta `<script src="...">`.
// - [`Defer`] Igual que [`From`], pero con el atributo `defer`.
// - [`Async`] Igual que [`From`], pero con el atributo `async`.
// - [`Inline`] Inserta el código directamente en la etiqueta `<script>`.
// - [`OnLoad`] Inserta el código JavaScript y lo ejecuta tras el evento `DOMContentLoaded`.
#[derive(AutoDefault)]
enum Source {
#[default]
From(String),
Defer(String),
Async(String),
Inline(String, String),
OnLoad(String, String),
}
/// Define un recurso **JavaScript** para incluir en un documento HTML.
///
/// Este tipo permite añadir *scripts* externos o embebidos con distintas estrategias de carga
/// (`defer`, `async`, *inline*, etc.) y [pesos](crate::Weight) para controlar el orden de inserción
/// en el documento.
///
/// > **Nota**
/// > Los archivos de los *scripts* deben estar disponibles en el servidor web de la aplicación.
/// > Pueden incluirse en el proyecto utilizando [`include_files!`](crate::include_files) y servirse
/// > con [`include_files_service!`](crate::include_files_service).
///
/// # Ejemplo
///
/// ```rust
/// use pagetop::prelude::*;
///
/// // Script externo con carga diferida, versión para control de caché y prioriza el renderizado.
/// let script = JavaScript::defer("/assets/js/app.js")
/// .with_version("1.2.3")
/// .with_weight(-10);
///
/// // Script embebido que se ejecuta tras la carga del documento.
/// let script = JavaScript::on_load("init_tooltips", r#"
/// const tooltips = document.querySelectorAll('[data-tooltip]');
/// for (const el of tooltips) {
/// el.addEventListener('mouseenter', showTooltip);
/// }
/// "#);
/// ```
#[rustfmt::skip]
#[derive(AutoDefault)]
pub struct JavaScript {
source : Source, // Fuente y modo de carga del script.
version: String, // Versión del recurso para la caché del navegador.
weight : Weight, // Peso que determina el orden.
}
impl JavaScript {
/// Crea un **script externo** que se carga y ejecuta de forma inmediata, en orden con el resto
/// del documento HTML.
///
/// Equivale a `<script src="...">`.
pub fn from(path: impl Into<String>) -> Self {
JavaScript {
source: Source::From(path.into()),
..Default::default()
}
}
/// Crea un **script externo** con el atributo `defer`, que se carga en segundo plano y se
/// ejecuta tras analizar completamente el documento HTML.
///
/// Equivale a `<script src="..." defer>`. Útil para mantener el orden de ejecución y evitar
/// bloquear el análisis del documento HTML.
pub fn defer(path: impl Into<String>) -> Self {
JavaScript {
source: Source::Defer(path.into()),
..Default::default()
}
}
/// Crea un **script externo** con el atributo `async`, que se carga y ejecuta de forma
/// asíncrona tan pronto como esté disponible.
///
/// Equivale a `<script src="..." async>`. La ejecución puede producirse fuera de orden respecto
/// a otros *scripts*.
pub fn asynchronous(path: impl Into<String>) -> Self {
JavaScript {
source: Source::Async(path.into()),
..Default::default()
}
}
/// Crea un **script embebido** directamente en el documento HTML.
///
/// Equivale a `<script>...</script>`. El parámetro `name` se usa como identificador interno del
/// *script*.
pub fn inline(name: impl Into<String>, script: impl Into<String>) -> Self {
JavaScript {
source: Source::Inline(name.into(), script.into()),
..Default::default()
}
}
/// Crea un **script embebido** que se ejecuta automáticamente al terminar de cargarse el
/// documento HTML.
///
/// El código se envuelve automáticamente en un `addEventListener('DOMContentLoaded', ...)`. El
/// parámetro `name` se usa como identificador interno del *script*.
pub fn on_load(name: impl Into<String>, script: impl Into<String>) -> Self {
JavaScript {
source: Source::OnLoad(name.into(), script.into()),
..Default::default()
}
}
// JavaScript BUILDER **************************************************************************
/// Asocia una versión al recurso (usada para control de la caché del navegador).
///
/// Si `version` está vacío, no se añade ningún parámetro a la URL.
pub fn with_version(mut self, version: impl Into<String>) -> Self {
self.version = version.into();
self
}
/// Modifica el peso del recurso.
///
/// Los recursos se renderizan de menor a mayor peso. Por defecto es `0`, que respeta el orden
/// de creación.
pub fn with_weight(mut self, value: Weight) -> Self {
self.weight = value;
self
}
}
impl AssetsTrait for JavaScript {
// Para *scripts* externos es la ruta; para *scripts* embebidos, un identificador.
fn name(&self) -> &str {
match &self.source {
Source::From(path) => path,
Source::Defer(path) => path,
Source::Async(path) => path,
Source::Inline(name, _) => name,
Source::OnLoad(name, _) => name,
}
}
fn weight(&self) -> Weight {
self.weight
}
}
impl Render for JavaScript {
fn render(&self) -> Markup {
match &self.source {
Source::From(path) => html! {
script src=(join_pair!(path, "?v=", self.version.as_str())) {};
},
Source::Defer(path) => html! {
script src=(join_pair!(path, "?v=", self.version.as_str())) defer {};
},
Source::Async(path) => html! {
script src=(join_pair!(path, "?v=", self.version.as_str())) async {};
},
Source::Inline(_, code) => html! {
script { (code) };
},
Source::OnLoad(_, code) => html! { (join!(
"document.addEventListener('DOMContentLoaded',function(){", code, "});"
)) },
}
}
}