build rework

This commit is contained in:
Robin Appelman 2023-04-09 16:32:30 +02:00
commit dc80d715a6
18 changed files with 1681 additions and 115 deletions

1410
build/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

10
build/Cargo.toml Normal file
View file

@ -0,0 +1,10 @@
[package]
name = "demostf-build"
version = "0.1.0"
edition = "2021"
[dependencies]
lightningcss = { version = "1.0.0-alpha.40", features = ["browserslist", "visitor"] }
base64 = "0.21.0"
urlencoding = "2.1.2"
const-fnv1a-hash = "1.1.0"

28
build/src/lib.rs Normal file
View file

@ -0,0 +1,28 @@
mod style;
use const_fnv1a_hash::fnv1a_hash_str_32;
pub use style::bundle_style;
#[macro_export]
macro_rules! save_asset {
($name:expr, $val:expr) => {
let val = $val;
let out_dir = std::env::var("OUT_DIR").unwrap();
std::fs::write(format!("{out_dir}/{}", $name), &val).expect("failed to write asset");
let hash = fnv1a_hash_str_32(&val);
std::fs::write(format!("{out_dir}/{}.hash", $name), format!("{:x}", hash))
.expect("failed to write asset hash");
};
}
pub fn hash(data: &str) -> String {
format!("{:x}", fnv1a_hash_str_32(data))
}
fn guess_mime(path: &str) -> (&'static str, bool) {
match path.split('.').last().unwrap() {
"svg" => ("image/svg+xml", false),
"png" => ("image/png", true),
ext => panic!("no mimetype known for {ext}"),
}
}

80
build/src/style.rs Normal file
View file

@ -0,0 +1,80 @@
use crate::guess_mime;
use base64::engine::general_purpose::STANDARD;
use base64::Engine;
use lightningcss::bundler::{Bundler, FileProvider};
use lightningcss::stylesheet::{MinifyOptions, ParserOptions, PrinterOptions};
use lightningcss::targets::Browsers;
use lightningcss::values::url::Url;
use lightningcss::visit_types;
use lightningcss::visitor::{Visit, VisitTypes, Visitor};
use std::convert::Infallible;
use std::fs::read;
use std::path::Path;
pub fn bundle_style(style: &str) -> String {
// todo build time?
let fs = FileProvider::new();
let mut bundler = Bundler::new(
&fs,
None,
ParserOptions {
nesting: true,
..ParserOptions::default()
},
);
let mut stylesheet = bundler
.bundle(Path::new(style))
.expect("failed to bundle css");
let browsers =
Browsers::from_browserslist(["last 2 versions"]).expect("failed to parse browserlist");
stylesheet
.minify(MinifyOptions {
targets: browsers.clone(),
..MinifyOptions::default()
})
.expect("failed to minify css");
#[cfg(debug_assertions)]
let minify = false;
#[cfg(not(debug_assertions))]
let minify = true;
stylesheet.visit(&mut InlineUrlVisitor).unwrap();
stylesheet
.to_css(PrinterOptions {
targets: browsers,
minify,
..PrinterOptions::default()
})
.expect("failed to output css")
.code
}
struct InlineUrlVisitor;
impl<'i> Visitor<'i> for InlineUrlVisitor {
type Error = Infallible;
const TYPES: VisitTypes = visit_types!(URLS);
fn visit_url(&mut self, url: &mut Url<'i>) -> Result<(), Self::Error> {
if let Some(path) = url.url.strip_prefix("inline://") {
let content = read(path).unwrap_or_else(|e| {
eprintln!("Failed to write inline file {path}: {e}");
panic!("Failed to inline");
});
let (mime, encode) = guess_mime(path);
if encode {
let encoded = STANDARD.encode(content);
url.url = format!("data:{mime};base64,{encoded}").into();
} else {
let content = String::from_utf8(content).expect("invalid utf8");
let encoded = urlencoding::encode(&content);
url.url = format!("data:{mime},{encoded}").into();
}
}
Ok(())
}
}