Introducción
Si quieres aprender a crear soluciones web que rescaten la esencia de los orígenes usando HTML, CSS y JavaScript para crear páginas web ofrecidas desde un servidor, pero con la potencia de un lenguaje de programación rápido y seguro como Rust, entonces... ¡has llegado a buen puerto!
¿Qué es PageTop?
PageTop es un marco de desarrollo web que proporciona herramientas y patrones de diseño predefinidos para el desarrollo de soluciones web seguras, modulares y personalizables con Renderizado desde el Servidor (SSR).
PageTop está desarrollado en el lenguaje de programación Rust y se apoya sobre los hombros de auténticos gigantes, usando algunas de las librerías (crates) más estables y reconocidas del ecosistema Rust como:
- Actix Web para la gestión de los servicios y del servidor web.
- Tracing para el sistema de diagnóstico y mensajes de registro estructurados.
- Fluent templates que incorpora Fluent para internacionalizar los proyectos.
- SeaORM que usa SQLx para modelar el acceso a bases de datos.
- Además de integrar versiones ad hoc de config-rs y Maud en su código.
- Y otras que puedes consultar en el archivo
Cargo.toml
de PageTop.
La API de PageTop permite adaptar y extender sus funcionalidades a los diferentes escenarios de una solución web usando Acciones, Componentes, Paquetes y Temas:
- Las Acciones funcionan como un mecanismo para personalizar el comportamiento interno de PageTop interceptando su flujo de ejecución.
- Los Componentes permiten encapsular HTML, CSS y JavaScript en unidades funcionales, configurables y bien definidas.
- Los Paquetes amplían o personalizan funcionalidades interactuando con las APIs de PageTop o las APIs de paquetes de terceros.
- Y los Temas son paquetes que van a permitir a los desarrolladores cambiar la apariencia de páginas y componentes sin afectar su funcionalidad.
SSR
El Renderizado desde el Servidor (SSR) es una técnica de desarrollo web en la que el contenido HTML se genera en el servidor antes de enviarlo al navegador del usuario, donde CSS y JavaScript añaden la interactividad necesaria. PageTop encapsula todos estos elementos en componentes unitarios que pueden mantenerse de forma independiente y ser extendidos o modificados por otras librerías.
Esto contrasta con la Renderización desde el Cliente (CSR), donde es el navegador el que genera el contenido HTML tras recibir el código WebAssembly o JavaScript necesario desde el servidor.
PageTop usa SSR como una solución robusta para la creación de soluciones web complejas. Pero también presenta desafíos, como ciclos de desarrollo más lentos por la necesidad de recompilar cada cambio en el código Rust. No obstante, ofrece excelentes tiempos de carga iniciales, mejora en el SEO, y unifica el desarrollo en cliente y servidor bajo un mismo lenguaje.
Contribuciones
PageTop empezó como un proyecto personal para aprender a programar con Rust. Es libre y de código abierto, para siempre. Y puedes contribuir aumentando su versatilidad, documentando, traduciendo o corrigiendo errores. Pero también puedes crear tus propios paquetes o temas que otros desarrolladores podrán utilizar en sus proyectos.
Advertencia
PageTop está aún en las primeras etapas de desarrollo. Faltan características importantes y otras no funcionan como deberían. Y la documentación es escasa. Sólo se liberan versiones de desarrollo con cambios importantes en la API que desaconseja su uso en producción. Úsalo si estás interesado en conocerlo o quieres contribuir.
Si necesitas un entorno fullstack estable y robusto para tu próximo proyecto, puedes mirar Perseus basado en la excelente librería Sycamore, también te entusiasmará Rocket, sin descartar MoonZoon o Percy. Y puedes crear tu propio framework combinando soluciones como Yew, Leptos o Dioxus con el servidor Axum y el ORM Diesel para construir increíbles aplicaciones SSR.
Si aún sigues por aquí, ¡ha llegado el momento de empezar a aprender algo de PageTop!
La guía de Inicio Rápido te enseñará a probar los ejemplos y ejecutar Drust. También te ayudará con la configuración de tu entorno de desarrollo y te orientará con los próximos pasos a seguir.
Comenzando
Esta sección te ayudará a conocer PageTop de la manera más rápida posible. Te enseñará a preparar un entorno de desarrollo apropiado para crear una aplicación web sencilla usando PageTop.
Inicio rápido
Si quieres entrar de lleno en el código de PageTop y ya cuentas con un entorno de Rust operativo puedes seguir leyendo este apartado de "inicio rápido".
En otro caso puedes pasar a la siguiente página para preparar un entorno de Rust desde cero y empezar a programar tu primera aplicación web con PageTop.
Empieza con los ejemplos
-
Clona el repositorio de PageTop:
git clone https://github.com/manuelcillero/pagetop
-
Cambia a la carpeta recién creada "pagetop":
cd pagetop
-
Asegurate de que trabajas con la última versión de PageTop (ya que por defecto se descarga la rama principal de git):
git checkout latest
-
Prueba los ejemplos de la carpeta de ejemplos:
cargo run --example hello-world
Prueba Drust
Drust es un Sistema de Gestión de Contenidos (CMS) desarrollado con PageTop y modestamente inspirado en Drupal. Permitirá crear blogs personales, foros, portales web o aplicaciones más sofisticadas, y se fundamenta en estos principios clave:
- Simplicidad: Intuitivo para principiantes y versátil para usuarios avanzados.
- Seguridad: Diseñado para proteger contra las vulnerabilidades web más comunes.
- Eficiencia: Capaz de soportar la demanda que se espera de un sitio web fluido y ágil.
- Funcionalidad: Facilitando la creación de sitios web dinámicos sin necesidad de programar.
- Modularidad: Integrando los paquetes y temas de PageTop, y admitiendo paquetes externos para extender sus capacidades.
Drust requiere una base de datos para funcionar. Usa el script tools/drust-create-db.sh
para crearla. Desde la carpeta "pagetop":
cd tools
./drust-create-db.sh
Cambia a la carpeta "drust":
cd ..
cd drust
Verifica que los datos de conexión a la base de datos de Drust están correctamente asignados en el archivo de configuración config/local.default.toml
.
Y ejecuta la aplicación:
cargo run
Configuración
PageTop está escrito en Rust. Antes de empezar a crear tu aplicación web, es importante dedicar un tiempo a preparar tu entorno de desarrollo con Rust.
Instalación de Rust
PageTop depende en gran medida de las mejoras que se aplican en el lenguaje y el compilador Rust. Procura tener instalada "la última versión estable" para admitir la Versión Mínima de Rust Soportada (MSRV) por PageTop.
Puedes instalar Rust siguiendo la Guía de Inicio Rápido de Rust.
Una vez completada la instalación, tendrás disponibles en tu sistema el compilador rustc
y el comando cargo
para la construcción y gestión de paquetes (crates) de Rust.
Recursos para aprender Rust
El objetivo de esta guía es aprender a programar con PageTop rápidamente, por lo que no te va a servir como material de aprendizaje de Rust. Si deseas saber más sobre el lenguaje de programación Rust, consulta los siguientes recursos:
- El Libro de Rust: el mejor lugar para aprender Rust desde cero.
- Rust con Ejemplos: aprende Rust programando ejemplos de todo tipo.
- Rustlings: una serie de ejercicios divertidos e interactivos para conocer Rust.
Editor de código / IDE
Puedes usar tu editor de código preferido, pero se recomienda uno que permita instalar la extensión de rust-analyzer. Aunque aún está en desarrollo, proporciona autocompletado y una inteligencia de código avanzada. Visual Studio Code tiene una extensión de rust-analyzer oficialmente soportada.
Tu primer proyecto PageTop
¡Ha llegado el momento de programar con PageTop! Para empezar, PageTop es simplemente una dependencia más en tu proyecto. Puedes añadirlo a un proyecto existente o crear uno nuevo. Para ser completos, asumiremos que estás empezando desde cero.
Crea un nuevo proyecto de ejecutable Rust
Primero, navega a una carpeta donde quieras crear tu nuevo proyecto. Luego, ejecuta el siguiente comando para crear una nueva carpeta que contenga nuestro proyecto de ejecutable Rust:
cargo new my_pagetop_app
cd my_pagetop_app
Ahora ejecuta cargo run
para compilar y ejecutar tu proyecto. Deberías ver el texto "Hello, world!" en tu terminal. Abre la carpeta "my_pagetop_app" en tu editor de código preferido y tómate un tiempo para revisar los archivos.
main.rs
es el punto de entrada de tu programa:
fn main() { println!("Hello, world!"); }
Cargo.toml
es tu "archivo de proyecto". Contiene metadatos sobre tu proyecto, como su nombre, dependencias y configuración para compilarlo.
[package]
name = "my_pagetop_app"
version = "0.1.0"
edition = "2021"
[dependencies]
Añade PageTop como dependencia
PageTop está disponible como biblioteca en crates.io, el repositorio oficial de paquetes (crates) Rust.
La forma más fácil de incorporarlo en tu proyecto es usar cargo add
:
cargo add pagetop
O puedes añadirlo manualmente en el archivo Cargo.toml
del proyecto escribiendo:
[package]
name = "my_pagetop_app"
version = "0.1.0"
edition = "2021" # debe ser 2021, o necesitarás configurar "resolver=2"
[dependencies]
pagetop = "0.0.52" # verifica que esta es la última versión
Asegúrate de que añades la úiltima versión disponible de PageTop:
Construye PageTop
Ahora ejecuta cargo run
nuevamente. Las dependencias de PageTop deberían comenzar a compilarse. Tomará algo de tiempo ya que es la primera compilación de tu proyecto con PageTop. Esto sólo ocurrirá la primera vez. ¡Cada compilación después de esta será más rápida!
Ahora que tenemos nuestro proyecto PageTop preparado, ¡estamos listos para programar nuestra primera aplicación PageTop!
Aplicaciones
Los programas de PageTop se denominan Aplicaciones. La aplicación PageTop más simple luce así:
use pagetop::prelude::*; #[pagetop::main] async fn main() -> std::io::Result<()> { Application::new().run()?.await }
La línea use pagetop::prelude::*;
sirve para importar la API esencial de PageTop. Por brevedad, esta guía podría omitirla en ejemplos posteriores.
Ahora sólo tienes que copiar el código anterior en tu archivo main.rs
y desde la carpeta del proyecto ejecutar:
cargo run
Si todo ha ido bien, después de compilar el código se ejecutará la aplicación. El terminal quedará en espera mostrando el nombre de la aplicación y lema predefinidos.
Ahora abre un navegador en el mismo equipo y escribe http://localhost:8088
en la barra de direcciones. Y ya está, ¡la página de presentación de PageTop te dará la bienvenida!
Sin embargo, aún no hemos indicado a nuestra aplicación qué hacer. Si quieres saber más sobre el funcionamiento interno de las aplicaciones, continúa leyendo. De lo contrario, puedes ir a la siguiente página para aprender cómo añadir lógica a nuestra aplicación.
¿Qué hace una aplicación?
Como hemos visto, primero debemos instanciar la Aplicación. Podemos hacerlo con dos métodos, new()
, que hemos usado en el ejemplo anterior, o prepare()
, que veremos en la siguiente página. Ambos se encargan de iniciar los diferentes subsistemas de PageTop por este orden:
-
Inicializa la traza de mensajes de registro y eventos.
-
Valida el identificador global de idioma.
-
Conecta con la base de datos.
-
Registra los paquetes de la aplicación según sus dependencias internas.
-
Registra las acciones de los paquetes.
-
Inicializa los paquetes.
-
Ejecuta las actualizaciones pendientes de la base de datos.
Pero no ejecuta la aplicación. Para eso se usa el método run()
, que arranca el servidor web para empezar a responder las peticiones desde cualquier navegador.
Hablaremos más de todos estos subsistemas en próximas páginas. Mientras tanto, ¡vamos a añadir algo de lógica a nuestra aplicación creando un paquete con un nuevo servicio web!
Paquetes
Una de las características más poderosas de PageTop es su extensibilidad mediante el uso de Paquetes. Los paquetes amplían o personalizan funcionalidades de PageTop o de otros paquetes.
Un paquete es una estructura unitaria (unit struct) que implementa el trait PackageTrait
. Los métodos de PackageTrait
tiene un funcionamiento predefinido que se puede personalizar.
Los paquetes tienen acceso a puntos en tu aplicación donde PageTop permite que el código de terceros haga ciertas cosas.
¡Hola mundo!
Para añadir lógica a nuestra aplicación puedes crear un paquete en tu archivo main.rs
sustituyendo el código de ejemplo por este nuevo código:
use pagetop::prelude::*; struct HelloWorld; impl PackageTrait for HelloWorld { fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { scfg.route("/", service::web::get().to(hello_world)); } } async fn hello_world(request: HttpRequest) -> ResultPage<Markup, ErrorPage> { Page::new(request) .with_component(Html::with(html! { h1 { "Hello World!" } })) .render() } #[pagetop::main] async fn main() -> std::io::Result<()> { Application::prepare(&HelloWorld).run()?.await }
La función main()
instancia la aplicación usando el método prepare()
con una referencia (PackageRef
) al paquete HelloWorld
. Así se indica a PageTop que debe incluirlo en su registro interno de paquetes.
HelloWorld
configura un servicio en la ruta raíz ("/") que se implementa en hello_world()
. Esta función devuelve una página web con un componente que renderiza directamente código HTML para mostrar un título con el texto Hello World!.
Ahora si en el navegador volvemos a cargar la dirección http://localhost:8088
veremos el saludo esperado.
Librerías
Los paquetes en PageTop son crates de biblioteca, usualmente publicados en crates.io, que puedes usar como dependencias en tu aplicación.
Seguridad
Los paquetes ajenos a PageTop contienen código desarrollado por terceros y, dado que pueden hacer básicamente lo que quieran, pueden representar un serio riesgo para la seguridad de tu sistema. Por ejemplo, un paquete podría indicar que está analizando la entrada del usuario y realmente está descargando ransomware en tu computadora.
Cualquier sospecha sobre paquetes malintencionados debe ser reportado confidencialmente al administrador de PageTop para ser analizado por la comunidad.
Cómo crear un paquete desde cero
Este tutorial describe cómo se ha creado el módulo pagetop-jquery
para incluir la librería jQuery en las páginas web generadas por otros módulos o aplicaciones desarrolladas con PageTop.
Primeros pasos
Para este tutorial se suponen conocimientos de programación con Rust, del gestor de paquetes cargo
, así como de la API de PageTop. Los ejemplos están pensados para entornos Linux pero no deberían ser muy diferentes en otros sistemas operativos.
Crear el proyecto
La forma más sencilla de empezar es rompiendo el hielo con el gestor de paquetes cargo
siguiendo los mismos pasos que en cualquier otro proyecto Rust:
cargo new --lib pagetop-jquery
Accede al proyecto recién creado y añade la dependiencia a PageTop:
cd pagetop-jquery
cargo add pagetop
Soporte multilingüe
La API de localización de PageTop proporciona soporte multilingüe a los módulos. Primero crea la estructura de carpetas para los archivos de los textos en inglés y español (únicos idiomas soportados actualmente):
mkdir src/locale
mkdir src/locale/en-US
mkdir src/locale/es-ES
Crea el archivo src\locale\en-US\module.flt
con las siguientes asignaciones para identificar y describir el módulo:
package_name = jQuery support
package_description = Integrate the jQuery library into web pages generated by other modules.
Y su archivo equivalente src\locale\es-ES\module.flt
para las mismas asignaciones en español:
package_name = Soporte a jQuery
package_description = Incorpora la librería jQuery en páginas web generadas por otros módulos.
Estas primeras asignaciones suelen ser habituales en todos los módulos.
Iniciar el módulo
Para desarrollar un módulo PageTop hay que implementar los métodos necesarios del trait ModuleTrait
sobre una estructura vacía que se puede inicializar en el archivo src/lib.rs
:
#![allow(unused)] fn main() { use pagetop::prelude::*; static_locales!(LOCALES_JQUERY); #[derive(AssignHandle)] pub struct JQuery; impl ModuleTrait for JQuery { fn name(&self) -> L10n { L10n::t("package_name", &LOCALES_JQUERY) } fn description(&self) -> L10n { L10n::t("package_description", &LOCALES_JQUERY) } } }
La función handle()
es la única que obligatoriamente debe implementarse porque permite asignar al módulo un identificador único creado previamente con la macro create_handle!()
.
El soporte multilingüe se incorpora con la macro static_locales!()
asignando un identificador a la ruta de los archivos de localización (que puede omitirse si la ruta es src/locale
).
Las funciones name()
y description()
son opcionales, aunque se recomienda su implementación para identificar y describir adecuadamente el módulo. Hacen uso del componente L10n
y de los archivos de localización creados en el apartado anterior para devolver los textos en el idioma del contexto.
Archivos estáticos
Seguimos en el directorio del proyecto, al mismo nivel de src
. Es buena práctica crear una carpeta de nombre static
para los archivos estáticos. En ella descargaremos los archivos jquery.min.js
y jquery.min.map
de la librería jQuery:
mkdir static
Crear el archivo build.rs
Para que estos archivos estáticos formen parte del binario de la aplicación hay que añadir dos nuevas dependencias al proyecto:
cargo add static-files
cargo add pagetop-build --build
Y crear un archivo build.rs
con el siguiente código para incorporar el directorio ./static
en los recursos de compilación del proyecto:
use pagetop_build::StaticFilesBundle; fn main() -> std::io::Result<()> { StaticFilesBundle::from_dir("./static") .with_name("jquery") .build() }
En este momento el proyecto tiene la siguiente estructura de directorios y archivos:
pagetop-jquery/
├── src/
│ ├── locale/
│ │ ├── en-ES/
│ │ │ └── module.flt
│ │ └── es-ES/
│ │ └── module.flt
│ └── lib.rs
├── static/
│ ├── jquery.min.js
│ └── jquery.min.map
└── Cargo.toml
Declarar los archivos en el módulo
En src/lib.rs
incorpora los recursos estáticos con static_files!()
usando como identificador el nombre proporcionado al bundle de archivos en build.rs
con .with_name("jquery")
, pero ahora sin las comillas dobles:
#![allow(unused)] fn main() { static_files!(jquery); }
Y en la implementación de JQuery
añade la función configure_service()
para configurar el servicio web que responderá a las peticiones cuando el path comience por /jquery/*
usando la macro configure_service_for_static_files!()
:
#![allow(unused)] fn main() { impl ModuleTrait for JQuery { ... fn configure_service(&self, cfg: &mut service::web::ServiceConfig) { configure_service_for_static_files!(cfg, jquery => "/jquery"); } ... } }
De esta forma, a la petición /jquery/jquery.min.js
el servidor web responderá devolviendo el archivo estático ./static/jquery.min.js
.
La API del módulo
Este módulo proporciona funciones públicas para añadir o quitar del contexto los recursos de jQuery:
Notas
- Puede que el código del módulo no sea el mismo que aquí se reproduce. El sentido de este tutorial es proporcionar una explicación sencilla de los principales elementos de un módulo.