From 86191a2d740182ce09459e48d2a07940fac90bd7 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Wed, 30 Mar 2022 13:21:56 -0400 Subject: [PATCH 1/3] Fix css module hashing with implicit CSS grid line names --- src/bundler.rs | 4 +- src/css_modules.rs | 13 +++-- src/lib.rs | 142 ++++++++++++++++++++++++++++----------------- src/printer.rs | 8 +-- 4 files changed, 103 insertions(+), 64 deletions(-) diff --git a/src/bundler.rs b/src/bundler.rs index 02ef1cd1..efabbb1b 100644 --- a/src/bundler.rs +++ b/src/bundler.rs @@ -736,11 +736,11 @@ mod tests { "# }, "/a.css"); assert_eq!(res, indoc! { r#" - .a_6lixEq_1 { + .6lixEq_a { color: green; } - .a_6lixEq { + .6lixEq_a { color: red; } "#}); diff --git a/src/css_modules.rs b/src/css_modules.rs index 2ee0891a..3ea4bafc 100644 --- a/src/css_modules.rs +++ b/src/css_modules.rs @@ -49,10 +49,9 @@ pub(crate) struct CssModule<'a> { impl<'a> CssModule<'a> { pub fn add_local(&mut self, exported: &str, local: &str) { - let hash = &self.hash; self.exports.entry(exported.into()) .or_insert_with(|| CssModuleExport { - name: format!("{}_{}", local, hash), + name: get_hashed_name(self.hash, local), composes: vec![], is_referenced: false }); @@ -65,7 +64,7 @@ impl<'a> CssModule<'a> { } std::collections::hash_map::Entry::Vacant(entry) => { entry.insert(CssModuleExport { - name: format!("{}_{}", name, self.hash), + name: get_hashed_name(self.hash, name), composes: vec![], is_referenced: true }); @@ -80,7 +79,7 @@ impl<'a> CssModule<'a> { parcel_selectors::parser::Component::Class(ref id) => { for name in &composes.names { let reference = match &composes.from { - None => CssModuleReference::Local { name: format!("{}_{}", name.0, self.hash) }, + None => CssModuleReference::Local { name: get_hashed_name(self.hash, name.0.as_ref()) }, Some(ComposesFrom::Global) => CssModuleReference::Global { name: name.0.as_ref().into() }, Some(ComposesFrom::File(file)) => CssModuleReference::Dependency { name: name.0.to_string(), @@ -107,6 +106,12 @@ impl<'a> CssModule<'a> { } } +fn get_hashed_name(hash: &str, name: &str) -> String { + // Hash must come first so that CSS grid identifiers work. + // This is because grid lines may have an implicit -start or -end appended. + format!("{}_{}", hash, name) +} + pub(crate) fn hash(s: &str) -> String { let mut hasher = DefaultHasher::new(); s.hash(&mut hasher); diff --git a/src/lib.rs b/src/lib.rs index 4ac09abe..761e0570 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11466,15 +11466,15 @@ mod tests { to { opacity: 1 } } "#, indoc!{r#" - .foo_EgL3uq { + .EgL3uq_foo { color: red; } - #id_EgL3uq { - animation: test_EgL3uq 2s; + #EgL3uq_id { + animation: EgL3uq_test 2s; } - @keyframes test_EgL3uq { + @keyframes EgL3uq_test { from { color: red; } @@ -11484,12 +11484,12 @@ mod tests { } } - @counter-style circles_EgL3uq { + @counter-style EgL3uq_circles { symbols: Ⓐ Ⓑ Ⓒ; } ul { - list-style: circles_EgL3uq; + list-style: EgL3uq_circles; } ol { @@ -11500,7 +11500,7 @@ mod tests { list-style-type: disc; } - @keyframes fade_EgL3uq { + @keyframes EgL3uq_fade { from { opacity: 0; } @@ -11510,11 +11510,11 @@ mod tests { } } "#}, map! { - "foo" => "foo_EgL3uq", - "id" => "id_EgL3uq", - "test" => "test_EgL3uq" referenced: true, - "circles" => "circles_EgL3uq" referenced: true, - "fade" => "fade_EgL3uq" + "foo" => "EgL3uq_foo", + "id" => "EgL3uq_id", + "test" => "EgL3uq_test" referenced: true, + "circles" => "EgL3uq_circles" referenced: true, + "fade" => "EgL3uq_fade" }); #[cfg(feature = "grid")] @@ -11534,27 +11534,63 @@ mod tests { } "#, indoc!{r#" body { - grid: [header-top_EgL3uq] "a_EgL3uq a_EgL3uq a_EgL3uq" [header-bottom_EgL3uq] - [main-top_EgL3uq] "b_EgL3uq b_EgL3uq b_EgL3uq" 1fr [main-bottom_EgL3uq] + grid: [EgL3uq_header-top] "EgL3uq_a EgL3uq_a EgL3uq_a" [EgL3uq_header-bottom] + [EgL3uq_main-top] "EgL3uq_b EgL3uq_b EgL3uq_b" 1fr [EgL3uq_main-bottom] / auto 1fr auto; } header { - grid-area: a_EgL3uq; + grid-area: EgL3uq_a; } main { - grid-row: main-top_EgL3uq / main-bottom_EgL3uq; + grid-row: EgL3uq_main-top / EgL3uq_main-bottom; } "#}, map! { - "header-top" => "header-top_EgL3uq", - "header-bottom" => "header-bottom_EgL3uq", - "main-top" => "main-top_EgL3uq", - "main-bottom" => "main-bottom_EgL3uq", - "a" => "a_EgL3uq", - "b" => "b_EgL3uq" + "header-top" => "EgL3uq_header-top", + "header-bottom" => "EgL3uq_header-bottom", + "main-top" => "EgL3uq_main-top", + "main-bottom" => "EgL3uq_main-bottom", + "a" => "EgL3uq_a", + "b" => "EgL3uq_b" }); + #[cfg(feature = "grid")] + css_modules_test( + r#" + .grid { + grid-template-areas: "foo"; + } + + .foo { + grid-area: foo; + } + + .bar { + grid-column-start: foo-start; + } + "#, + indoc!{r#" + .EgL3uq_grid { + grid-template-areas: "EgL3uq_foo"; + } + + .EgL3uq_foo { + grid-area: EgL3uq_foo; + } + + .EgL3uq_bar { + grid-column-start: EgL3uq_foo-start; + } + "#}, + map! { + "foo" => "EgL3uq_foo", + "foo-start" => "EgL3uq_foo-start", + "grid" => "EgL3uq_grid", + "bar" => "EgL3uq_bar" + } + ); + css_modules_test(r#" test { transition-property: opacity; @@ -11582,15 +11618,15 @@ mod tests { color: red; } - .bar_EgL3uq { + .EgL3uq_bar { color: #ff0; } - .bar_EgL3uq .baz { + .EgL3uq_bar .baz { color: purple; } "#}, map! { - "bar" => "bar_EgL3uq" + "bar" => "EgL3uq_bar" }); @@ -11609,16 +11645,16 @@ mod tests { color: red; } "#, indoc!{r#" - .test_EgL3uq { + .EgL3uq_test { background: #fff; } - .foo_EgL3uq { + .EgL3uq_foo { color: red; } "#}, map! { - "test" => "test_EgL3uq" "foo_EgL3uq", - "foo" => "foo_EgL3uq" + "test" => "EgL3uq_test" "EgL3uq_foo", + "foo" => "EgL3uq_foo" }); css_modules_test(r#" @@ -11631,17 +11667,17 @@ mod tests { color: red; } "#, indoc!{r#" - .a_EgL3uq, .b_EgL3uq { + .EgL3uq_a, .EgL3uq_b { background: #fff; } - .foo_EgL3uq { + .EgL3uq_foo { color: red; } "#}, map! { - "a" => "a_EgL3uq" "foo_EgL3uq", - "b" => "b_EgL3uq" "foo_EgL3uq", - "foo" => "foo_EgL3uq" + "a" => "EgL3uq_a" "EgL3uq_foo", + "b" => "EgL3uq_b" "EgL3uq_foo", + "foo" => "EgL3uq_foo" }); css_modules_test(r#" @@ -11658,21 +11694,21 @@ mod tests { color: yellow; } "#, indoc!{r#" - .test_EgL3uq { + .EgL3uq_test { background: #fff; } - .foo_EgL3uq { + .EgL3uq_foo { color: red; } - .bar_EgL3uq { + .EgL3uq_bar { color: #ff0; } "#}, map! { - "test" => "test_EgL3uq" "foo_EgL3uq" "bar_EgL3uq", - "foo" => "foo_EgL3uq", - "bar" => "bar_EgL3uq" + "test" => "EgL3uq_test" "EgL3uq_foo" "EgL3uq_bar", + "foo" => "EgL3uq_foo", + "bar" => "EgL3uq_bar" }); css_modules_test(r#" @@ -11681,11 +11717,11 @@ mod tests { background: white; } "#, indoc!{r#" - .test_EgL3uq { + .EgL3uq_test { background: #fff; } "#}, map! { - "test" => "test_EgL3uq" "foo" global: true + "test" => "EgL3uq_test" "foo" global: true }); css_modules_test(r#" @@ -11694,11 +11730,11 @@ mod tests { background: white; } "#, indoc!{r#" - .test_EgL3uq { + .EgL3uq_test { background: #fff; } "#}, map! { - "test" => "test_EgL3uq" "foo" global: true "bar" global: true + "test" => "EgL3uq_test" "foo" global: true "bar" global: true }); css_modules_test(r#" @@ -11707,11 +11743,11 @@ mod tests { background: white; } "#, indoc!{r#" - .test_EgL3uq { + .EgL3uq_test { background: #fff; } "#}, map! { - "test" => "test_EgL3uq" "foo" from "foo.css" + "test" => "EgL3uq_test" "foo" from "foo.css" }); css_modules_test(r#" @@ -11720,11 +11756,11 @@ mod tests { background: white; } "#, indoc!{r#" - .test_EgL3uq { + .EgL3uq_test { background: #fff; } "#}, map! { - "test" => "test_EgL3uq" "foo" from "foo.css" "bar" from "foo.css" + "test" => "EgL3uq_test" "foo" from "foo.css" "bar" from "foo.css" }); css_modules_test(r#" @@ -11739,16 +11775,16 @@ mod tests { color: red; } "#, indoc!{r#" - .test_EgL3uq { + .EgL3uq_test { background: #fff; } - .foo_EgL3uq { + .EgL3uq_foo { color: red; } "#}, map! { - "test" => "test_EgL3uq" "foo_EgL3uq" "foo" from "foo.css" "bar" from "bar.css", - "foo" => "foo_EgL3uq" + "test" => "EgL3uq_test" "EgL3uq_foo" "foo" from "foo.css" "bar" from "bar.css", + "foo" => "EgL3uq_foo" }); } @@ -11801,7 +11837,7 @@ mod tests { "#; let expected = indoc! { r#" - .foo_EgL3uq.is-hovered_EgL3uq { + .EgL3uq_foo.EgL3uq_is-hovered { color: red; } "#}; diff --git a/src/printer.rs b/src/printer.rs index c2f439d0..978ee71e 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -151,7 +151,6 @@ impl<'a, W: std::fmt::Write + Sized> Printer<'a, W> { } pub fn write_ident(&mut self, ident: &str) -> Result<(), PrinterError> { - serialize_identifier(ident, self)?; let hash = if let Some(css_module) = &self.css_module { Some(css_module.hash) } else { @@ -159,13 +158,12 @@ impl<'a, W: std::fmt::Write + Sized> Printer<'a, W> { }; if let Some(hash) = hash { - self.write_char('_')?; self.write_str(hash)?; - if self.source_index > 0 { - self.write_str(&format!("_{}", self.source_index))?; - } + self.write_char('_')?; } + serialize_identifier(ident, self)?; + if let Some(css_module) = &mut self.css_module { css_module.add_local(&ident, &ident); } From a2f80ad1cc5f990900cbfeb8f35284f1a8439d6e Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Wed, 30 Mar 2022 13:43:34 -0400 Subject: [PATCH 2/3] Make sure hash is escaped --- src/printer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/printer.rs b/src/printer.rs index 978ee71e..b8b8b6b8 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -158,7 +158,7 @@ impl<'a, W: std::fmt::Write + Sized> Printer<'a, W> { }; if let Some(hash) = hash { - self.write_str(hash)?; + serialize_identifier(hash, self)?; self.write_char('_')?; } From 4cdc5b446a2192dd527408ee841151096d4c3d17 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Wed, 30 Mar 2022 14:13:16 -0400 Subject: [PATCH 3/3] Less weird escaping --- src/bundler.rs | 4 ++-- src/css_modules.rs | 7 ++++++- src/lib.rs | 12 ++++++------ 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/bundler.rs b/src/bundler.rs index efabbb1b..98bc785f 100644 --- a/src/bundler.rs +++ b/src/bundler.rs @@ -736,11 +736,11 @@ mod tests { "# }, "/a.css"); assert_eq!(res, indoc! { r#" - .6lixEq_a { + ._6lixEq_a { color: green; } - .6lixEq_a { + ._6lixEq_a { color: red; } "#}); diff --git a/src/css_modules.rs b/src/css_modules.rs index 3ea4bafc..e9b0d327 100644 --- a/src/css_modules.rs +++ b/src/css_modules.rs @@ -117,5 +117,10 @@ pub(crate) fn hash(s: &str) -> String { s.hash(&mut hasher); let hash = hasher.finish() as u32; - ENCODER.encode(&hash.to_le_bytes()) + let hash = ENCODER.encode(&hash.to_le_bytes()); + if matches!(hash.as_bytes()[0], b'0'..=b'9') { + format!("_{}", hash) + } else { + hash + } } diff --git a/src/lib.rs b/src/lib.rs index 761e0570..6f2de477 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13343,19 +13343,19 @@ mod tests { dep_test( ".foo { background: image-set('./img12x.png', './img21x.png' 2x)}", - ".foo{background:image-set(\"hXFI8W\",\"5TkpBa\" 2x)}", + ".foo{background:image-set(\"hXFI8W\",\"_5TkpBa\" 2x)}", vec![ ("./img12x.png", "hXFI8W"), - ("./img21x.png", "5TkpBa") + ("./img21x.png", "_5TkpBa") ] ); dep_test( ".foo { background: image-set(url(./img12x.png), url('./img21x.png') 2x)}", - ".foo{background:image-set(\"hXFI8W\",\"5TkpBa\" 2x)}", + ".foo{background:image-set(\"hXFI8W\",\"_5TkpBa\" 2x)}", vec![ ("./img12x.png", "hXFI8W"), - ("./img21x.png", "5TkpBa") + ("./img21x.png", "_5TkpBa") ] ); @@ -13377,9 +13377,9 @@ mod tests { dep_test( ".foo { --test: url(\"http://example.com/foo.png\") }", - ".foo{--test:url(\"3X1zSW\")}", + ".foo{--test:url(\"_3X1zSW\")}", vec![ - ("http://example.com/foo.png", "3X1zSW"), + ("http://example.com/foo.png", "_3X1zSW"), ] );