139 lines
5.1 KiB
Rust
Executable file
139 lines
5.1 KiB
Rust
Executable file
#!/usr/bin/env rust-script
|
|
//! This is a regular crate doc comment, but it also contains a partial
|
|
//! Cargo manifest. Note the use of a *fenced* code block, and the
|
|
//! `cargo` "language".
|
|
//!
|
|
//! ```cargo
|
|
//! [dependencies]
|
|
//! encre-css = "0.14"
|
|
//! walkdir = "2"
|
|
//! grass = { version = "0.13.4", features = ["macro", "nightly"] }
|
|
//! lightningcss = { version = "1.0.0-alpha.65", features = ["bundler", "browserslist"] }
|
|
//! anyhow = "1"
|
|
//! async-compression = { version = "0.4", features = ["tokio", "zstd"] }
|
|
//! tokio = { version = "1", features = [ "full" ] }
|
|
//! ```
|
|
use std::{collections::BTreeSet, fs, path::Path};
|
|
|
|
use async_compression::tokio::write::ZstdEncoder;
|
|
use encre_css::{Config, Scanner};
|
|
use lightningcss::{
|
|
bundler::{Bundler, FileProvider},
|
|
stylesheet::{ParserOptions, PrinterOptions},
|
|
targets::{Browsers, Targets},
|
|
};
|
|
use tokio::io::AsyncWriteExt as _; // for `write_all` and `shutdown`
|
|
use walkdir::WalkDir;
|
|
|
|
#[tokio::main]
|
|
async fn main() -> anyhow::Result<()> {
|
|
eprintln!("WEGL CSS 💅 © 2025==========================================");
|
|
eprintln!("look mum no js/npm");
|
|
eprintln!("Reading! Step 1/5 🚶 (Walking Dirs)");
|
|
// Walk through the directory structure
|
|
let contents = WalkDir::new("./")
|
|
.into_iter()
|
|
.filter_map(|e| e.ok())
|
|
.filter(|e| {
|
|
e.file_type().is_file()
|
|
&& e.path()
|
|
.extension()
|
|
.map_or(false, |ext| ["rs", "scss"].into_iter().any(|e| e == ext))
|
|
})
|
|
.map(|entry| {
|
|
let path = entry.path();
|
|
// eprintln!("Processing file: {}", path.display());
|
|
let content = fs::read_to_string(path).expect("Failed to read");
|
|
let bx = Box::new(content);
|
|
let static_ref: &'static str = Box::leak(bx);
|
|
static_ref
|
|
});
|
|
|
|
let config = {
|
|
let mut config = Config::default();
|
|
// FIXME TODO Add moar colors
|
|
|
|
config.scanner = Scanner::from_fn(|val| {
|
|
let mut is_arbitrary = false;
|
|
|
|
val.split(|ch| {
|
|
// Escape all characters in arbitrary values prefixed by a dash (used to avoid
|
|
// ignoring values in, for example, JS arrays, given that they are defined
|
|
// using square brackets)
|
|
match ch {
|
|
'[' => {
|
|
is_arbitrary = true;
|
|
false
|
|
}
|
|
']' => {
|
|
is_arbitrary = false;
|
|
false
|
|
}
|
|
_ => {
|
|
ch == ' '
|
|
|| (!is_arbitrary
|
|
&& ['.', '\'', '"', '`', '\n'].into_iter().any(|c| c == ch))
|
|
}
|
|
}
|
|
})
|
|
.collect::<BTreeSet<&str>>()
|
|
});
|
|
|
|
// config
|
|
// .theme
|
|
// .colors
|
|
// .add("blue-500", "oklch(.623 .214 259.815)");
|
|
// config.theme.colors.add("blue", "rgb(.1, .1, .9)");
|
|
config
|
|
};
|
|
|
|
eprintln!("Building Utilities Step 2/5 ⚙️ (EncrCSS)");
|
|
let generated = encre_css::generate(contents, &config);
|
|
tokio::fs::write("./public/css/encr.css", &generated).await?;
|
|
tokio::fs::write("./style/encr.css", &generated).await?;
|
|
|
|
eprintln!("Compiling SCSS Step 3/5 🏗 (Grass Compile)");
|
|
let compiled = grass::from_path("./style/main.scss", &grass::Options::default())?;
|
|
tokio::fs::write("./public/css/main.css", compiled.clone()).await?;
|
|
|
|
eprintln!("Bundling & Minifying Step 4/5 ⚡️ (Lightning CSS)");
|
|
let fs: FileProvider = FileProvider::new();
|
|
let fs: &'static _ = Box::leak(Box::new(fs)); // yolo it's a script, and we need a 'static ref
|
|
let mut bundler = Bundler::new(fs, None, ParserOptions::default());
|
|
let stylesheet = bundler.bundle(Path::new("./public/css/main.css"))?;
|
|
|
|
// Serialize it to a string.
|
|
let optimized_opts = PrinterOptions {
|
|
minify: true,
|
|
targets: Targets {
|
|
browsers: Browsers::from_browserslist([
|
|
"> 0.5%, last 3 versions, Firefox ESR, not dead"
|
|
])?,
|
|
..Default::default()
|
|
},
|
|
..Default::default()
|
|
};
|
|
let res = stylesheet.to_css(Default::default()).unwrap();
|
|
let opt_res = stylesheet.to_css(optimized_opts).unwrap();
|
|
|
|
// println!("{}", res.code);
|
|
tokio::fs::write("./public/main.css", res.code.clone()).await?;
|
|
tokio::fs::write("./public/main.min.css", opt_res.code).await?;
|
|
|
|
eprintln!("Compressing... Step 5/5 🐁️ (Zstd)");
|
|
async fn compress(in_data: &[u8]) -> anyhow::Result<Vec<u8>> {
|
|
let mut encoder = ZstdEncoder::new(Vec::new());
|
|
encoder.write_all(in_data).await?;
|
|
encoder.shutdown().await?;
|
|
Ok(encoder.into_inner())
|
|
}
|
|
|
|
// tokio::fs::write(
|
|
// "./public/main.min.css.zstd",
|
|
// compress(res.code.as_bytes()).await?,
|
|
// )
|
|
// .await?;
|
|
|
|
eprintln!("Done ! =====================================================");
|
|
Ok(())
|
|
}
|