Skip to content

Commit

Permalink
fix: escape css
Browse files Browse the repository at this point in the history
  • Loading branch information
JSerFeng committed Dec 13, 2024
1 parent bd04c60 commit f84b707
Show file tree
Hide file tree
Showing 15 changed files with 723 additions and 57 deletions.
37 changes: 25 additions & 12 deletions crates/rspack_plugin_css/src/parser_and_generator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,8 @@ impl ParserAndGenerator for CssParserAndGenerator {
css_module_lexer::Dependency::LocalClass { name, range, .. }
| css_module_lexer::Dependency::LocalId { name, range, .. } => {
let (_prefix, name) = name.split_at(1); // split '#' or '.'
let name = unescape(name);

let local_ident = LocalIdentOptions::new(
resource_data,
self
Expand All @@ -223,13 +225,13 @@ impl ParserAndGenerator for CssParserAndGenerator {
.expect("should have local_ident_name for module_type css/auto or css/module"),
compiler_options,
)
.get_local_ident(name);
.get_local_ident(&name);
let convention = self
.convention
.as_ref()
.expect("should have local_ident_name for module_type css/auto or css/module");
let exports = self.exports.get_or_insert_default();
let convention_names = export_locals_convention(name, convention);
let convention_names = export_locals_convention(&name, convention);
for name in convention_names.iter() {
update_css_exports(
exports,
Expand All @@ -249,6 +251,7 @@ impl ParserAndGenerator for CssParserAndGenerator {
)));
}
css_module_lexer::Dependency::LocalKeyframes { name, range, .. } => {
let name = unescape(name);
let local_ident = LocalIdentOptions::new(
resource_data,
self
Expand All @@ -257,13 +260,13 @@ impl ParserAndGenerator for CssParserAndGenerator {
.expect("should have local_ident_name for module_type css/auto or css/module"),
compiler_options,
)
.get_local_ident(name);
.get_local_ident(&name);
let exports = self.exports.get_or_insert_default();
let convention = self
.convention
.as_ref()
.expect("should have local_ident_name for module_type css/auto or css/module");
let convention_names = export_locals_convention(name, convention);
let convention_names = export_locals_convention(&name, convention);
for name in convention_names.iter() {
update_css_exports(
exports,
Expand All @@ -285,6 +288,7 @@ impl ParserAndGenerator for CssParserAndGenerator {
)));
}
css_module_lexer::Dependency::LocalKeyframesDecl { name, range, .. } => {
let name = unescape(name);
let local_ident = LocalIdentOptions::new(
resource_data,
self
Expand All @@ -293,13 +297,13 @@ impl ParserAndGenerator for CssParserAndGenerator {
.expect("should have local_ident_name for module_type css/auto or css/module"),
compiler_options,
)
.get_local_ident(name);
.get_local_ident(&name);
let exports = self.exports.get_or_insert_default();
let convention = self
.convention
.as_ref()
.expect("should have local_ident_name for module_type css/auto or css/module");
let convention_names = export_locals_convention(name, convention);
let convention_names = export_locals_convention(&name, convention);
for name in convention_names.iter() {
update_css_exports(
exports,
Expand All @@ -324,38 +328,47 @@ impl ParserAndGenerator for CssParserAndGenerator {
from,
range,
} => {
let local_classes = local_classes
.into_iter()
.map(|s| unescape(s).to_string())
.collect::<Vec<_>>();
let names = names
.into_iter()
.map(|s| unescape(s).to_string())
.collect::<Vec<_>>();

let mut dep_id = None;
if let Some(from) = from
&& from != "global"
{
let from = from.trim_matches(|c| c == '\'' || c == '"');
let dep = CssComposeDependency::new(
from.to_string(),
names.iter().map(|s| (*s).into()).collect(),
names.iter().map(|s| s.to_owned().into()).collect(),
DependencyRange::new(range.start, range.end),
);
dep_id = Some(*dep.id());
dependencies.push(Box::new(dep));
} else if from.is_none() {
dependencies.push(Box::new(CssSelfReferenceLocalIdentDependency::new(
names.iter().map(|s| (*s).into()).collect(),
names.iter().map(|s| s.to_string()).collect(),
vec![],
)));
}
let exports = self.exports.get_or_insert_default();
for name in names {
for &local_class in local_classes.iter() {
if let Some(existing) = exports.get(name)
for local_class in local_classes.iter() {
if let Some(existing) = exports.get(name.as_str())
&& from.is_none()
{
let existing = existing.clone();
exports
.get_mut(local_class)
.get_mut(local_class.as_str())
.expect("composes local class must already added to exports")
.extend(existing);
} else {
exports
.get_mut(local_class)
.get_mut(local_class.as_str())
.expect("composes local class must already added to exports")
.insert(CssExport {
ident: name.to_string(),
Expand Down
13 changes: 7 additions & 6 deletions crates/rspack_plugin_css/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ pub fn stringified_exports<'a>(
let content = elements
.iter()
.map(|CssExport { ident, from, id: _ }| match from {
None => json_stringify(&unescape(ident)),
None => json_stringify(&ident),
Some(from_name) => {
let from = module
.get_dependencies()
Expand Down Expand Up @@ -233,7 +233,7 @@ pub fn stringified_exports<'a>(
writeln!(
stringified_exports,
" {}: {},",
json_stringify(&unescape(key)),
json_stringify(&key),
content
)
.map_err(|e| error!(e.to_string()))?;
Expand Down Expand Up @@ -266,7 +266,7 @@ pub fn css_modules_exports_to_concatenate_module_string<'a>(
let content = elements
.iter()
.map(|CssExport { ident, from, id: _ }| match from {
None => json_stringify(&unescape(ident)),
None => json_stringify(&ident),
Some(from_name) => {
let from = module
.get_dependencies()
Expand Down Expand Up @@ -297,7 +297,7 @@ pub fn css_modules_exports_to_concatenate_module_string<'a>(
format!(
"{}({from})[{}]",
RuntimeGlobals::REQUIRE,
json_stringify(&unescape(ident))
json_stringify(&ident)
)
}
})
Expand All @@ -306,15 +306,15 @@ pub fn css_modules_exports_to_concatenate_module_string<'a>(
let mut identifier = to_identifier(key);
let mut i = 0;
while used_identifiers.contains(&identifier) {
identifier = Cow::Owned(format!("{key}{}", itoa!(i)));
identifier = Cow::Owned(format!("{identifier}{}", itoa!(i)));
i += 1;
}
// TODO: conditional support `const or var` after we finished runtimeTemplate utils
concate_source.add(RawStringSource::from(format!(
"var {identifier} = {content};\n"
)));
used_identifiers.insert(identifier.clone());
scope.register_export(unescape(key).as_ref().into(), identifier.into_owned());
scope.register_export(key.into(), identifier.into_owned());
}
Ok(())
}
Expand All @@ -330,6 +330,7 @@ static UNESCAPE: LazyLock<Regex> =

static DATA: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^(?i)data:").expect("Invalid RegExp"));

// `\/foo` in css should be treated as `foo` in js
pub fn unescape(s: &str) -> Cow<str> {
UNESCAPE.replace_all(s.as_ref(), |caps: &Captures| {
caps
Expand Down
66 changes: 33 additions & 33 deletions packages/rspack-test-tools/tests/__snapshots__/Config.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -54,46 +54,46 @@ Object {
exports[`config config/builtins/css-modules-local-ident-name-hash exported tests css modules localIdentName with hash 1`] = `
Object {
#: c36b985d54c6917,
##: ab513cc8abd7e7d,
#.#.#: b6aa9e623eb8be,
#fake-id: d65fd648c910d0f,
++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.: fe85c4cd33dc7b53,
#: ab5b3430521fed0b,
##: cb547534b9bb8e,
#.#.#: a4c570203a26b7a,
#fake-id: f7b6eb7b0e0c18,
++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.: c40d0c7abfea21c2,
-a-b-c-: b1d2002fed1364,
-a0-34a___f: bd6992764ef,
.: f8dc72200543c02,
123: fc54982b169de010,
1a2b3c: e0d288534c575c7c,
:): ce45aa5fbb24,
:\`(: c3a47328b233,
:hover: c9cb40d597d6145,
:hover:focus:active: adb078f8a010,
<><<<>><>: e57bed6057bf,
<p>: d4d795aa031981a3,
?: ca312bbe575926f7,
@: e73735f9a6fa1e2a,
B&W?: c28b11d8276955d9,
[attr=value]: aefb3cf784f22f09,
_: b892aeed406cef6,
.: aba9a87983bb4,
123: fc0eed74768cf0,
1a2b3c: cd4a1680594,
:): f173228435cfb0b1,
:\`(: c028ead51dd2190,
:hover: bcf55b240a0c8c3,
:hover:focus:active: a9b6b582b414966b,
<><<<>><>: c75f56ad7787952,
<p>: aeefcae4950d33f5,
?: e40713fc7c65c3f,
@: cd391b9e31254d,
B&W?: daaf197c50aa167,
[attr=value]: fa72982cc3b312,
_: bd050c4fe,
_test: f95d7802389a,
className: b88ec9c3ddaa088,
f!o!o: bc20be6f8aa6ba2b,
f'o'o: a1ad39e5bcb2a,
f*o*o: ee490440b4d27dd,
f+o+o: b2f2e8203ab92e0,
f/o/o: e8e6467eba546855,
f\\o\\o: ac4b388a402f1b,
foo.bar: bf09604a510669,
foo/bar: f06b4ed00041408a,
foo/bar/baz: b62901907350b529,
foo\\bar: cf1721b3e29,
foo\\bar\\baz: db46f3e1d8ecc82b,
f~o~o: e22953bae58b7,
m_x_@: eede355cc6b0366,
f!o!o: ae3ae8c7841a7dc,
f'o'o: df77f78fe66aae2,
f*o*o: a7fec00e3ce42886,
f+o+o: eb6cf0061b2d78e,
f/o/o: d0e703c722b26d0c,
f\\o\\o: a496c6306a14d13d,
foo.bar: cd2e4302e8b6edf3,
foo/bar: cd90db0f2125295,
foo/bar/baz: f253f688730bc76e,
foo\\bar: eb2a9acb231,
foo\\bar\\baz: b41adbd8ba363134,
f~o~o: b5f0b0aac1e4e,
m_x_@: deacd91fcb5633e3,
someId: f39bfe4a4606a57c,
subClass: dbb85e97d7af8a70,
test: db0a8ac9537cb87c,
{}: c849d7d50310c200,
{}: b674276dd02fd15,
©: fc4038317,
“‘’”: b1f8cf023766,
⌘⌥: cad802,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import * as styles from "./index.module.css";

it("should generate correct exports", () => {
const fs = __non_webpack_require__('fs')
const path = __non_webpack_require__('path')
expect(styles).toEqual(
nsObj({
a: '"aaa" 123',
b: "multiple lines bbb"
b: "multiple lines bbb",
'a/b': 'a/b-./'
})
);

const css = fs.readFileSync(path.resolve(__dirname, './bundle0.css')).toString()
const escape = css.replaceAll('\\', '')
expect(escape).toContain(styles['a/b'])
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,8 @@
comment2
comment3
*/ bbb/* comment4 */
}
}

.a\/b {
color: red
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,13 @@
module.exports = {
experiments: {
css: true
}
},
module: {
generator: {
'css/auto': {
exportsOnly: false,
localIdentName: '[local]-[path]'
}
}
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
findBundle() {
return ['bundle0.js']
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ it("should remove unused local idents", async () => {
const fs = __non_webpack_require__("fs");
const path = __non_webpack_require__("path");
expect(styles.a).toBe("./style.module-a");
expect(styles['local/used']).toBe("./style.module-local/used");

const css = await fs.promises.readFile(path.resolve(__dirname, "./bundle0.css"), "utf-8");
expect(css).not.toContain(".module-b")
if (!EXPORTS_ONLY) {
const css = await fs.promises.readFile(path.resolve(__dirname, "./bundle0.css"), "utf-8");
expect(css).not.toContain(".module-b")
expect(css).toContain("local\\/used")
expect(css).not.toContain("local\\/unused")
}
})
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const rspack = require("@rspack/core");

/** @type {import("@rspack/core").Configuration} */
module.exports = {
const common = {
target: 'web',
node: {
__dirname: false,
Expand All @@ -25,3 +25,37 @@ module.exports = {
css: true
}
};

module.exports = [
{
...common,
plugins: [
new rspack.DefinePlugin({
EXPORTS_ONLY: false
})
]
},
{
...common,
plugins: [
new rspack.DefinePlugin({
EXPORTS_ONLY: true
})
],
module: {
generator: {
"css/auto": {
localIdentName: "[path][name]-[local]",
exportsOnly: true,
}
}
},
optimization: {
minimize: true,
concatenateModules: true,
minimizer: [
new rspack.LightningCssMinimizerRspackPlugin(),
]
},
}
]
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,11 @@
.b {
color: blue;
}

.local\/used {
color: red;
}

.local\/unused {
color: blue
}
Loading

0 comments on commit f84b707

Please sign in to comment.