Añade estáticos y SCSS compilados para binarios

Módulo auxiliar para ejecutar durante la compilación de proyectos de
PageTop para incluir archivos estáticos o archivos SCSS compilados en
los binarios de los proyectos.
This commit is contained in:
Manuel Cillero 2025-07-10 22:51:47 +02:00
parent 880761fe9a
commit 432caf292f
4 changed files with 323 additions and 7 deletions

View file

@ -1,6 +1,6 @@
[package] [package]
name = "pagetop" name = "pagetop"
version = "0.0.6" version = "0.0.7"
edition = "2021" edition = "2021"
description = """\ description = """\
@ -20,7 +20,7 @@ colored = "3.0.0"
config = { version = "0.15.13", default-features = false, features = ["toml"] } config = { version = "0.15.13", default-features = false, features = ["toml"] }
figlet-rs = "0.1.5" figlet-rs = "0.1.5"
itoa = "1.0.15" itoa = "1.0.15"
serde.workspace = true paste = { package = "pastey", version = "0.1.0" }
substring = "1.4.5" substring = "1.4.5"
terminal_size = "0.4.2" terminal_size = "0.4.2"
@ -33,14 +33,18 @@ fluent-templates = "0.13.0"
unic-langid = { version = "0.9.6", features = ["macros"] } unic-langid = { version = "0.9.6", features = ["macros"] }
actix-web = "4.11.0" actix-web = "4.11.0"
static-files.workspace = true
pagetop-macros.workspace = true serde = { version = "1.0", features = ["derive"] }
pagetop-macros = { version = "0.0", path = "helpers/pagetop-macros" }
[workspace] [workspace]
resolver = "2" resolver = "2"
members = [ members = [
"helpers/pagetop-build",
"helpers/pagetop-macros", "helpers/pagetop-macros",
] ]
@ -51,7 +55,4 @@ license = "MIT OR Apache-2.0"
authors = ["Manuel Cillero <manuel@cillero.es>"] authors = ["Manuel Cillero <manuel@cillero.es>"]
[workspace.dependencies] [workspace.dependencies]
serde = { version = "1.0", features = ["derive"] } static-files = "0.2.5"
# Helpers
pagetop-macros = { version = "0.0", path = "helpers/pagetop-macros" }

View file

@ -0,0 +1,20 @@
[package]
name = "pagetop-build"
version = "0.0.1"
edition = "2021"
description = """\
Prepara un conjunto de archivos estáticos o archivos SCSS compilados para ser incluidos en el \
binario de un proyecto PageTop.\
"""
categories = ["development-tools::build-utils", "web-programming"]
keywords = ["pagetop", "build", "assets", "resources", "static"]
repository.workspace = true
homepage.workspace = true
license.workspace = true
authors.workspace = true
[dependencies]
grass = "0.13.4"
static-files.workspace = true

View file

@ -0,0 +1,36 @@
<div align="center">
<h1>PageTop Build</h1>
<p>Prepara un conjunto de archivos estáticos o archivos SCSS compilados para ser incluidos en el binario de un proyecto <strong>PageTop</strong>.</p>
[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-license)
</div>
## Sobre PageTop
[PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web
clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y
configurables, basadas en HTML, CSS y JavaScript.
# 🚧 Advertencia
`PageTop` es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su
ecosistema. Su API está sujeta a cambios frecuentes. No se recomienda su uso en producción, al menos
hasta que se libere la versión **1.0.0**.
# 📜 Licencia
El código está disponible bajo una doble licencia:
* **Licencia MIT**
([LICENSE-MIT](LICENSE-MIT) o también https://opensource.org/licenses/MIT)
* **Licencia Apache, Versión 2.0**
([LICENSE-APACHE](LICENSE-APACHE) o también https://www.apache.org/licenses/LICENSE-2.0)
Puedes elegir la licencia que prefieras. Este enfoque de doble licencia es el estándar de facto en
el ecosistema Rust.

View file

@ -0,0 +1,259 @@
//! <div align="center">
//!
//! <h1>PageTop Build</h1>
//!
//! <p>Prepara un conjunto de archivos estáticos o archivos SCSS compilados para ser incluidos en el binario de un proyecto <strong>PageTop</strong>.</p>
//!
//! [![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-license)
//!
//! </div>
//!
//! ## Sobre PageTop
//!
//! [PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la
//! web clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles
//! y configurables, basadas en HTML, CSS y JavaScript.
//!
//!
//! # ⚡️ Guía rápida
//!
//! Añadir en el archivo `Cargo.toml` del proyecto:
//!
//! ```toml
//! [build-dependencies]
//! pagetop-build = { ... }
//! ```
//!
//! Y crear un archivo `build.rs` a la altura de `Cargo.toml` para indicar cómo se van a incluir los
//! archivos estáticos o cómo se van a compilar los archivos SCSS para el proyecto. Casos de uso:
//!
//! ## Incluir archivos estáticos desde un directorio
//!
//! Hay que preparar una carpeta en el proyecto con todos los archivos que se quieren incluir, por
//! ejemplo `static`, y añadir el siguiente código en `build.rs` para crear el conjunto de recursos:
//!
//! ```rust,no_run
//! use pagetop_build::StaticFilesBundle;
//!
//! fn main() -> std::io::Result<()> {
//! StaticFilesBundle::from_dir("./static", None)
//! .with_name("guides")
//! .build()
//! }
//! ```
//!
//! Si es necesario, se puede añadir un filtro para seleccionar archivos específicos de la carpeta,
//! por ejemplo:
//!
//! ```rust,no_run
//! use pagetop_build::StaticFilesBundle;
//! use std::path::Path;
//!
//! fn main() -> std::io::Result<()> {
//! fn only_pdf_files(path: &Path) -> bool {
//! // Selecciona únicamente los archivos con extensión `.pdf`.
//! path.extension().map_or(false, |ext| ext == "pdf")
//! }
//!
//! StaticFilesBundle::from_dir("./static", Some(only_pdf_files))
//! .with_name("guides")
//! .build()
//! }
//! ```
//!
//! ## Compilar archivos SCSS a CSS
//!
//! Se puede compilar un archivo SCSS, que podría importar otros a su vez, para preparar un recurso
//! con el archivo CSS minificado obtenido. Por ejemplo:
//!
//! ```rust,no_run
//! use pagetop_build::StaticFilesBundle;
//!
//! fn main() -> std::io::Result<()> {
//! StaticFilesBundle::from_scss("./styles/main.scss", "styles.min.css")
//! .with_name("main_styles")
//! .build()
//! }
//! ```
//!
//! Este código compila el archivo `main.scss` de la carpeta `static` del proyecto, y prepara un
//! recurso llamado `main_styles` que contiene el archivo `styles.min.css` obtenido.
//!
//!
//! # 📦 Módulos generados
//!
//! Cada conjunto de recursos [`StaticFilesBundle`] genera un archivo en el directorio estándar
//! [OUT_DIR](https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts)
//! donde se incluyen los recursos necesarios para compilar el proyecto. Por ejemplo, para
//! `with_name("guides")` se crea un archivo llamado `guides.rs`.
//!
//! No hay ningún problema en generar más de un conjunto de recursos para cada proyecto.
//!
//! Normalmente no habrá que acceder a estos módulos; bastará con incluirlos en el proyecto con
//! [`include_files!`](https://docs.rs/pagetop/latest/pagetop/macro.include_files.html), y luego con
//! [`include_files_service!`](https://docs.rs/pagetop/latest/pagetop/macro.include_files_service.html)
//! configurar un servicio web para servir los recursos desde la ruta indicada:
//!
//! ```rust,ignore
//! use pagetop::prelude::*;
//!
//! include_files!(guides);
//!
//! pub struct MyExtension;
//!
//! impl ExtensionTrait for MyExtension {
//! // Servicio web que publica los recursos de `guides` en `/ruta/a/guides`.
//! fn configure_service(&self, scfg: &mut service::web::ServiceConfig) {
//! include_files_service!(scfg, guides => "/ruta/a/guides");
//! }
//! }
//! ```
//!
//! También se puede acceder al conjunto de recursos declarando un `HashMap` estático global:
//!
//! ```rust,ignore
//! include_files!(HM_GUIDES => guides);
//! ```
use grass::{from_path, Options, OutputStyle};
use static_files::{resource_dir, ResourceDir};
use std::fs::{create_dir_all, remove_dir_all, File};
use std::io::Write;
use std::path::Path;
/// Prepara un conjunto de recursos para ser incluidos en el binario del proyecto utilizando
/// [static_files](https://docs.rs/static-files/).
pub struct StaticFilesBundle {
resource_dir: ResourceDir,
}
impl StaticFilesBundle {
/// Prepara el conjunto de recursos con los archivos de un directorio. Opcionalmente se puede
/// aplicar un filtro para seleccionar un subconjunto de los archivos.
///
/// # Argumentos
///
/// * `dir` - Directorio que contiene los archivos.
/// * `filter` - Una función opcional para aceptar o no un archivo según su ruta.
///
/// # Ejemplo
///
/// ```rust,no_run
/// use pagetop_build::StaticFilesBundle;
/// use std::path::Path;
///
/// fn main() -> std::io::Result<()> {
/// fn only_images(path: &Path) -> bool {
/// matches!(
/// path.extension().and_then(|ext| ext.to_str()),
/// Some("jpg" | "png" | "gif")
/// )
/// }
///
/// StaticFilesBundle::from_dir("./static", Some(only_images))
/// .with_name("images")
/// .build()
/// }
/// ```
pub fn from_dir(dir: &'static str, filter: Option<fn(p: &Path) -> bool>) -> Self {
let mut resource_dir = resource_dir(dir);
// Aplica el filtro si está definido.
if let Some(f) = filter {
resource_dir.with_filter(f);
}
// Identifica el directorio temporal de recursos.
StaticFilesBundle { resource_dir }
}
/// Prepara un recurso CSS minimizado a partir de la compilación de un archivo SCSS (que puede a
/// su vez importar otros archivos SCSS).
///
/// # Argumentos
///
/// * `path` - Archivo SCSS a compilar.
/// * `target_name` - Nombre para el archivo CSS.
///
/// # Ejemplo
///
/// ```rust,no_run
/// use pagetop_build::StaticFilesBundle;
///
/// fn main() -> std::io::Result<()> {
/// StaticFilesBundle::from_scss("./bootstrap/scss/main.scss", "bootstrap.min.css")
/// .with_name("bootstrap_css")
/// .build()
/// }
/// ```
pub fn from_scss<P>(path: P, target_name: &str) -> Self
where
P: AsRef<Path>,
{
// Crea un directorio temporal para el archivo CSS.
let out_dir = std::env::var("OUT_DIR").unwrap();
let temp_dir = Path::new(&out_dir).join("from_scss_files");
// Limpia el directorio temporal de ejecuciones previas, si existe.
if temp_dir.exists() {
remove_dir_all(&temp_dir).unwrap_or_else(|e| {
panic!(
"Failed to clean temporary directory `{}`: {e}",
temp_dir.display()
);
});
}
create_dir_all(&temp_dir).unwrap_or_else(|e| {
panic!(
"Failed to create temporary directory `{}`: {e}",
temp_dir.display()
);
});
// Compila SCSS a CSS.
let css_content = from_path(
path.as_ref(),
&Options::default().style(OutputStyle::Compressed),
)
.unwrap_or_else(|e| {
panic!(
"Failed to compile SCSS file `{}`: {e}",
path.as_ref().display(),
)
});
// Guarda el archivo CSS compilado en el directorio temporal.
let css_path = temp_dir.join(target_name);
File::create(&css_path)
.expect(&format!(
"Failed to create CSS file `{}`",
css_path.display()
))
.write_all(css_content.as_bytes())
.expect(&format!(
"Failed to write CSS content to `{}`",
css_path.display()
));
// Identifica el directorio temporal de recursos.
StaticFilesBundle {
resource_dir: resource_dir(temp_dir.to_str().unwrap()),
}
}
/// Asigna un nombre al conjunto de recursos.
pub fn with_name(mut self, name: &'static str) -> Self {
let out_dir = std::env::var("OUT_DIR").unwrap();
let filename = Path::new(&out_dir).join(format!("{name}.rs"));
self.resource_dir.with_generated_filename(filename);
self.resource_dir.with_module_name(format!("bundle_{name}"));
self.resource_dir.with_generated_fn(name);
self
}
/// Contruye finalmente el conjunto de recursos para incluir en el binario de la aplicación.
pub fn build(self) -> std::io::Result<()> {
self.resource_dir.build()
}
}