sabry/sabry_intrnl/src/compiler/mod.rs

139 lines
4.3 KiB
Rust

use crate::config::SabryConfig;
/// Convenience wrapper on Grass and Lightningcss
pub struct CompilerAdapter {
#[allow(dead_code)] // without 'compile' feature, still want to set this
config: SabryConfig,
}
impl CompilerAdapter {
pub fn new(config: SabryConfig) -> Self {
Self { config }
}
/// Compile given SASS/SCSS into CSS
/// with respect to self.config
#[cfg(feature = "compile")]
pub fn compile_module(
&self,
syntax: crate::syntax::ostrta::OneSyntaxToRuleThemAll,
code: &str,
) -> Result<String, SabryCompilerError> {
let options = grass::Options::from(&self.config).input_syntax(syntax.into());
let css = self.lightningcss(&grass::from_string(code, &options)?)?;
Ok(css)
}
/// Perform lightningcss transformations on given css
/// with respect to self.config.css.minify on minification
#[cfg(feature = "compile")]
pub fn lightningcss(&self, css: &str) -> Result<String, SabryCompilerError> {
use lightningcss::{printer::PrinterOptions, stylesheet::MinifyOptions, targets::Targets};
let mut lightsheet = match lightningcss::stylesheet::StyleSheet::parse(
css,
lightningcss::stylesheet::ParserOptions::default(),
) {
Ok(s) => s,
Err(e) => {
return Err(SabryCompilerError::LightParse {
kind: e.kind.to_string(),
loc: e.loc,
})
}
};
let targets = Targets {
browsers: Some(self.config.lightningcss.targets.clone().into()),
..Default::default()
};
let printer_options = PrinterOptions {
minify: self.config.css.minify,
targets,
..Default::default()
};
let minify_options = MinifyOptions {
targets,
..Default::default()
};
if self.config.css.minify {
lightsheet.minify(minify_options)?
}
let css = match lightsheet.to_css(printer_options) {
Ok(c) => {
drop(lightsheet);
c.code
}
Err(e) => return Err(SabryCompilerError::LightPrint(e)),
};
Ok(css)
}
}
#[cfg(feature = "compile")]
impl From<&SabryConfig> for grass::Options<'_> {
fn from(config: &SabryConfig) -> Self {
let mut options = grass::Options::default();
options = options.style(if config.css.minify {
grass::OutputStyle::Compressed
} else {
grass::OutputStyle::Expanded
});
options = options.load_path(&config.sass.intermediate_dir);
options
}
}
#[cfg(feature = "compile")]
#[derive(thiserror::Error)]
pub enum SabryCompilerError {
#[error("Could not compile into CSS with grass")]
GrassCompile(#[from] Box<grass::Error>),
#[error("Could not print compiled CSS with lightningcss")]
LightPrint(#[from] lightningcss::error::Error<lightningcss::error::PrinterErrorKind>),
#[error("Could not minify CSS with lightningcss")]
LightMinify(#[from] lightningcss::error::Error<lightningcss::error::MinifyErrorKind>),
#[error("Could not parse CSS with lightningcss")]
LightParse {
kind: String,
loc: Option<lightningcss::error::ErrorLocation>,
},
}
#[cfg(feature = "compile")]
impl std::fmt::Debug for SabryCompilerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let explain = match self {
Self::LightPrint(e) => format!(
"{} at {:?}",
e.kind,
e.loc
.clone()
.map(|l| format!("file {}, line {}, col {}", l.filename, l.line, l.column))
),
Self::GrassCompile(err) => format!("{err}"),
Self::LightMinify(e) => format!(
"{} at {:?}",
e.kind,
e.loc
.clone()
.map(|l| format!("file {}, line {}, col {}", l.filename, l.line, l.column))
),
Self::LightParse { kind, loc } => format!(
"{kind} at {:?}",
loc.clone()
.map(|l| format!("file {}, line {}, col {}", l.filename, l.line, l.column))
),
};
write!(f, "{explain}")
}
}