Skip to content

Commit

Permalink
Add projectRoot option and use relative paths for CSS module hashes
Browse files Browse the repository at this point in the history
Fixes #355
  • Loading branch information
devongovett committed Dec 9, 2022
1 parent 6a7d19e commit 33febb4
Show file tree
Hide file tree
Showing 10 changed files with 170 additions and 18 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ parcel_sourcemap = { version = "2.1.1", features = ["json"] }
data-encoding = "2.3.2"
lazy_static = "1.4.0"
const-str = "0.3.1"
pathdiff = "0.2.1"
# CLI deps
clap = { version = "3.0.6", features = ["derive"], optional = true }
pathdiff = { version = "0.2.1", optional = true }
browserslist-rs = { version = "0.7.0", optional = true }
rayon = "1.5.1"
dashmap = "5.0.0"
Expand All @@ -59,7 +59,7 @@ serde_json = "1"
[features]
default = ["grid"]
browserslist = ["browserslist-rs"]
cli = ["clap", "serde_json", "pathdiff", "browserslist", "jemallocator"]
cli = ["clap", "serde_json", "browserslist", "jemallocator"]
grid = []
serde = ["smallvec/serde", "cssparser/serde"]

Expand Down
8 changes: 7 additions & 1 deletion c/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ pub struct ToCssOptions {
source_map: bool,
input_source_map: *const c_char,
input_source_map_len: usize,
project_root: *const c_char,
targets: Targets,
analyze_dependencies: bool,
pseudo_classes: PseudoClasses,
Expand Down Expand Up @@ -284,7 +285,7 @@ pub extern "C" fn lightningcss_stylesheet_parse(
error_recovery: options.error_recovery,
source_index: 0,
warnings: Some(warnings.clone()),
at_rule_parser: None
at_rule_parser: None,
};

let stylesheet = unwrap!(StyleSheet::parse(code, opts), error, std::ptr::null_mut());
Expand Down Expand Up @@ -324,6 +325,11 @@ pub extern "C" fn lightningcss_stylesheet_to_css(

let opts = PrinterOptions {
minify: options.minify,
project_root: if options.project_root.is_null() {
None
} else {
Some(unsafe { std::str::from_utf8_unchecked(CStr::from_ptr(options.project_root).to_bytes()) })
},
source_map: source_map.as_mut(),
targets: if options.targets != Targets::default() {
Some(options.targets.into())
Expand Down
5 changes: 5 additions & 0 deletions node/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ export interface TransformOptions {
sourceMap?: boolean,
/** An input source map to extend. */
inputSourceMap?: string,
/**
* An optional project root path, used as the source root in the output source map.
* Also used to generate relative paths for sources used in CSS module hashes.
*/
projectRoot?: string,
/** The browser targets for the generated code. */
targets?: Targets,
/** Whether to enable various draft syntax. */
Expand Down
11 changes: 9 additions & 2 deletions node/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,7 @@ fn init(mut exports: JsObject) -> napi::Result<()> {
#[serde(rename_all = "camelCase")]
struct Config {
pub filename: Option<String>,
pub project_root: Option<String>,
#[serde(with = "serde_bytes")]
pub code: Vec<u8>,
pub targets: Option<Browsers>,
Expand Down Expand Up @@ -432,6 +433,7 @@ struct CssModulesConfig {
#[serde(rename_all = "camelCase")]
struct BundleConfig {
pub filename: String,
pub project_root: Option<String>,
pub targets: Option<Browsers>,
pub minify: Option<bool>,
pub source_map: Option<bool>,
Expand Down Expand Up @@ -479,8 +481,9 @@ fn compile<'i>(code: &'i str, config: &Config) -> Result<TransformResult<'i>, Co
let warnings = Some(Arc::new(RwLock::new(Vec::new())));

let filename = config.filename.clone().unwrap_or_default();
let project_root = config.project_root.as_ref().map(|p| p.as_ref());
let mut source_map = if config.source_map.unwrap_or_default() {
let mut sm = SourceMap::new("/");
let mut sm = SourceMap::new(project_root.unwrap_or("/"));
sm.add_source(&filename);
sm.set_source_content(0, code)?;
Some(sm)
Expand Down Expand Up @@ -528,6 +531,7 @@ fn compile<'i>(code: &'i str, config: &Config) -> Result<TransformResult<'i>, Co
stylesheet.to_css(PrinterOptions {
minify: config.minify.unwrap_or_default(),
source_map: source_map.as_mut(),
project_root,
targets: config.targets,
analyze_dependencies: if let Some(d) = &config.analyze_dependencies {
match d {
Expand Down Expand Up @@ -578,8 +582,9 @@ fn compile_bundle<'i, P: SourceProvider>(
fs: &'i P,
config: &BundleConfig,
) -> Result<TransformResult<'i>, CompileError<'i, P::Error>> {
let project_root = config.project_root.as_ref().map(|p| p.as_ref());
let mut source_map = if config.source_map.unwrap_or_default() {
Some(SourceMap::new("/"))
Some(SourceMap::new(project_root.unwrap_or("/")))
} else {
None
};
Expand Down Expand Up @@ -624,6 +629,7 @@ fn compile_bundle<'i, P: SourceProvider>(
stylesheet.to_css(PrinterOptions {
minify: config.minify.unwrap_or_default(),
source_map: source_map.as_mut(),
project_root,
targets: config.targets,
analyze_dependencies: if let Some(d) = &config.analyze_dependencies {
match d {
Expand Down Expand Up @@ -729,6 +735,7 @@ fn compile_attr<'i>(
attr.to_css(PrinterOptions {
minify: config.minify,
source_map: None,
project_root: None,
targets: config.targets,
analyze_dependencies: if config.analyze_dependencies {
Some(DependencyOptions::default())
Expand Down
82 changes: 77 additions & 5 deletions src/bundler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ use crate::{
layer::{LayerBlockRule, LayerName},
Location,
},
values::ident::DashedIdentReference, traits::ToCss,
traits::ToCss,
values::ident::DashedIdentReference,
};
use crate::{
error::{Error, ParserError},
Expand Down Expand Up @@ -189,7 +190,7 @@ impl<'i, T: std::error::Error> BundleErrorKind<'i, T> {

impl<'a, 'o, 's, P: SourceProvider, T: AtRuleParser<'a> + Clone + Sync + Send> Bundler<'a, 'o, 's, P, T>
where
T::AtRule: Sync + Send + ToCss
T::AtRule: Sync + Send + ToCss,
{
/// Creates a new Bundler using the given source provider.
/// If a source map is given, the content of each source file included in the bundle will
Expand Down Expand Up @@ -547,7 +548,11 @@ where
fn order(&mut self) {
process(self.stylesheets.get_mut().unwrap(), 0, &mut HashSet::new());

fn process<'i, T: AtRuleParser<'i>>(stylesheets: &mut Vec<BundleStyleSheet<'i, '_, T>>, source_index: u32, visited: &mut HashSet<u32>) {
fn process<'i, T: AtRuleParser<'i>>(
stylesheets: &mut Vec<BundleStyleSheet<'i, '_, T>>,
source_index: u32,
visited: &mut HashSet<u32>,
) {
if visited.contains(&source_index) {
return;
}
Expand Down Expand Up @@ -722,6 +727,7 @@ mod tests {
use indoc::indoc;
use std::collections::HashMap;

#[derive(Clone)]
struct TestProvider {
map: HashMap<PathBuf, String>,
}
Expand Down Expand Up @@ -789,7 +795,11 @@ mod tests {
stylesheet.to_css(PrinterOptions::default()).unwrap().code
}

fn bundle_css_module<P: SourceProvider>(fs: P, entry: &str) -> (String, CssModuleExports) {
fn bundle_css_module<P: SourceProvider>(
fs: P,
entry: &str,
project_root: Option<&str>,
) -> (String, CssModuleExports) {
let mut bundler = Bundler::new(
&fs,
None,
Expand All @@ -803,7 +813,12 @@ mod tests {
);
let mut stylesheet = bundler.bundle(Path::new(entry)).unwrap();
stylesheet.minify(MinifyOptions::default()).unwrap();
let res = stylesheet.to_css(PrinterOptions::default()).unwrap();
let res = stylesheet
.to_css(PrinterOptions {
project_root,
..PrinterOptions::default()
})
.unwrap();
(res.code, res.exports.unwrap())
}

Expand Down Expand Up @@ -1688,6 +1703,7 @@ mod tests {
},
},
"/a.css",
None,
);
assert_eq!(
code,
Expand Down Expand Up @@ -1722,6 +1738,7 @@ mod tests {
},
},
"/a.css",
None,
);
assert_eq!(
code,
Expand Down Expand Up @@ -1763,6 +1780,7 @@ mod tests {
},
},
"/a.css",
None,
);
assert_eq!(
code,
Expand Down Expand Up @@ -1802,6 +1820,7 @@ mod tests {
},
},
"/a.css",
None,
);
assert_eq!(
code,
Expand All @@ -1824,6 +1843,59 @@ mod tests {
"a" => "_6lixEq_a"
}
);

// Hashes are stable between project roots.
let expected = indoc! { r#"
.dyGcAa_b {
background: #ff0;
}
.CK9avG_a {
background: #fff;
}
"#};

let (code, _) = bundle_css_module(
TestProvider {
map: fs! {
"/foo/bar/a.css": r#"
@import "b.css";
.a {
background: white;
}
"#,
"/foo/bar/b.css": r#"
.b {
background: yellow;
}
"#
},
},
"/foo/bar/a.css",
Some("/foo/bar"),
);
assert_eq!(code, expected);

let (code, _) = bundle_css_module(
TestProvider {
map: fs! {
"/x/y/z/a.css": r#"
@import "b.css";
.a {
background: white;
}
"#,
"/x/y/z/b.css": r#"
.b {
background: yellow;
}
"#
},
},
"/x/y/z/a.css",
Some("/x/y/z"),
);
assert_eq!(code, expected);
}

#[test]
Expand Down
28 changes: 23 additions & 5 deletions src/css_modules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ use crate::properties::css_modules::{Composes, Specifier};
use crate::selector::SelectorList;
use data_encoding::{Encoding, Specification};
use lazy_static::lazy_static;
use pathdiff::diff_paths;
use serde::Serialize;
use smallvec::{smallvec, SmallVec};
use std::borrow::Cow;
use std::collections::hash_map::DefaultHasher;
use std::collections::HashMap;
use std::fmt::Write;
Expand Down Expand Up @@ -224,16 +226,32 @@ impl<'a, 'b, 'c> CssModule<'a, 'b, 'c> {
pub fn new(
config: &'a Config<'b>,
sources: &'c Vec<String>,
project_root: Option<&'c str>,
references: &'a mut HashMap<String, CssModuleReference>,
) -> Self {
let project_root = project_root.map(|p| Path::new(p));
let sources: Vec<&Path> = sources.iter().map(|filename| Path::new(filename)).collect();
let hashes = sources
.iter()
.map(|path| {
// Make paths relative to project root so hashes are stable.
let source = match project_root {
Some(project_root) if path.is_absolute() => {
diff_paths(path, project_root).map_or(Cow::Borrowed(*path), Cow::Owned)
}
_ => Cow::Borrowed(*path),
};
hash(
&source.to_string_lossy(),
matches!(config.pattern.segments[0], Segment::Hash),
)
})
.collect();
Self {
config,
sources: sources.iter().map(|filename| Path::new(filename)).collect(),
hashes: sources
.iter()
.map(|source| hash(&source, matches!(config.pattern.segments[0], Segment::Hash)))
.collect(),
exports_by_source_index: sources.iter().map(|_| HashMap::new()).collect(),
sources,
hashes,
references,
}
}
Expand Down
39 changes: 39 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20224,6 +20224,45 @@ mod tests {
..Default::default()
},
);

// Stable hashes between project roots.
fn test_project_root(project_root: &str, filename: &str, hash: &str) {
let stylesheet = StyleSheet::parse(
r#"
.foo {
background: red;
}
"#,
ParserOptions {
filename: filename.into(),
css_modules: Some(Default::default()),
..ParserOptions::default()
},
)
.unwrap();
let res = stylesheet
.to_css(PrinterOptions {
project_root: Some(project_root),
..PrinterOptions::default()
})
.unwrap();
assert_eq!(
res.code,
format!(
indoc! {r#"
.{}_foo {{
background: red;
}}
"#},
hash
)
);
}

test_project_root("/foo/bar", "/foo/bar/test.css", "EgL3uq");
test_project_root("/foo", "/foo/test.css", "EgL3uq");
test_project_root("/foo/bar", "/foo/bar/baz/test.css", "xLEkNW");
test_project_root("/foo", "/foo/baz/test.css", "xLEkNW");
}

#[test]
Expand Down

0 comments on commit 33febb4

Please sign in to comment.