Skip to content

Commit decbbd7

Browse files
authored
feat(plugin-css): should handle css layer supports media (#9221)
1 parent 2adb783 commit decbbd7

File tree

10 files changed

+252
-25
lines changed

10 files changed

+252
-25
lines changed

crates/rspack_plugin_css/src/dependency/import.rs

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::fmt::Display;
2+
13
use rspack_cacheable::{cacheable, cacheable_dyn};
24
use rspack_core::{
35
AsContextDependency, Compilation, Dependency, DependencyCategory, DependencyId, DependencyRange,
@@ -11,16 +13,47 @@ pub struct CssImportDependency {
1113
id: DependencyId,
1214
request: String,
1315
range: DependencyRange,
16+
media: Option<String>,
17+
supports: Option<String>,
18+
layer: Option<CssLayer>,
19+
}
20+
21+
#[cacheable]
22+
#[derive(Debug, Clone)]
23+
pub enum CssLayer {
24+
Anonymous,
25+
Named(String),
1426
}
1527

1628
impl CssImportDependency {
17-
pub fn new(request: String, range: DependencyRange) -> Self {
29+
pub fn new(
30+
request: String,
31+
range: DependencyRange,
32+
media: Option<String>,
33+
supports: Option<String>,
34+
layer: Option<CssLayer>,
35+
) -> Self {
1836
Self {
1937
id: DependencyId::new(),
2038
request,
2139
range,
40+
media,
41+
supports,
42+
layer,
2243
}
2344
}
45+
46+
pub fn media(&self) -> Option<&str> {
47+
self.media.as_deref()
48+
}
49+
50+
pub fn supports(&self) -> Option<&str> {
51+
self.supports.as_deref()
52+
}
53+
54+
pub fn layer(&self) -> Option<&CssLayer> {
55+
self.layer.as_ref()
56+
}
2457
}
2558

2659
#[cacheable_dyn]
@@ -61,6 +94,24 @@ impl ModuleDependency for CssImportDependency {
6194
}
6295
}
6396

97+
#[derive(Clone)]
98+
pub struct CssMedia(pub String);
99+
100+
#[derive(Clone)]
101+
pub struct CssSupports(pub String);
102+
103+
impl Display for CssMedia {
104+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105+
self.0.fmt(f)
106+
}
107+
}
108+
109+
impl Display for CssSupports {
110+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111+
self.0.fmt(f)
112+
}
113+
}
114+
64115
#[cacheable_dyn]
65116
impl DependencyTemplate for CssImportDependency {
66117
fn apply(

crates/rspack_plugin_css/src/parser_and_generator/mod.rs

Lines changed: 59 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ use rspack_core::{
1515
rspack_sources::{BoxSource, ConcatSource, RawStringSource, ReplaceSource, Source, SourceExt},
1616
BuildMetaDefaultObject, BuildMetaExportsType, ChunkGraph, Compilation, ConstDependency,
1717
CssExportsConvention, Dependency, DependencyId, DependencyRange, DependencyTemplate,
18-
GenerateContext, LocalIdentName, Module, ModuleDependency, ModuleGraph, ModuleIdentifier,
19-
ModuleType, NormalModule, ParseContext, ParseResult, ParserAndGenerator, RuntimeSpec, SourceType,
20-
TemplateContext, UsageState,
18+
DependencyType, GenerateContext, LocalIdentName, Module, ModuleDependency, ModuleGraph,
19+
ModuleIdentifier, ModuleType, NormalModule, ParseContext, ParseResult, ParserAndGenerator,
20+
RuntimeSpec, SourceType, TemplateContext, UsageState,
2121
};
2222
use rspack_core::{ModuleInitFragments, RuntimeGlobals};
2323
use rspack_error::{
@@ -26,14 +26,6 @@ use rspack_error::{
2626
use rspack_util::ext::DynHash;
2727
use rustc_hash::{FxHashMap, FxHashSet};
2828

29-
use crate::{
30-
dependency::CssSelfReferenceLocalIdentDependency,
31-
utils::{css_modules_exports_to_string, LocalIdentOptions},
32-
};
33-
use crate::{
34-
dependency::CssSelfReferenceLocalIdentReplacement,
35-
utils::{export_locals_convention, unescape},
36-
};
3729
use crate::{
3830
dependency::{
3931
CssComposeDependency, CssExportDependency, CssImportDependency, CssLocalIdentDependency,
@@ -44,6 +36,14 @@ use crate::{
4436
replace_module_request_prefix,
4537
},
4638
};
39+
use crate::{
40+
dependency::{CssLayer, CssSelfReferenceLocalIdentReplacement, CssSupports},
41+
utils::{export_locals_convention, unescape},
42+
};
43+
use crate::{
44+
dependency::{CssMedia, CssSelfReferenceLocalIdentDependency},
45+
utils::{css_modules_exports_to_string, LocalIdentOptions},
46+
};
4747

4848
static REGEX_IS_MODULES: LazyLock<Regex> =
4949
LazyLock::new(|| Regex::new(r"\.module(s)?\.[^.]+$").expect("Invalid regex"));
@@ -184,7 +184,13 @@ impl ParserAndGenerator for CssParserAndGenerator {
184184
dependencies.push(dep.clone());
185185
code_generation_dependencies.push(dep);
186186
}
187-
css_module_lexer::Dependency::Import { request, range, .. } => {
187+
css_module_lexer::Dependency::Import {
188+
request,
189+
range,
190+
media,
191+
supports,
192+
layer,
193+
} => {
188194
if request.is_empty() {
189195
presentational_dependencies.push(Box::new(ConstDependency::new(
190196
range.start,
@@ -204,6 +210,15 @@ impl ParserAndGenerator for CssParserAndGenerator {
204210
dependencies.push(Box::new(CssImportDependency::new(
205211
request.to_string(),
206212
DependencyRange::new(range.start, range.end),
213+
media.map(|s| s.to_string()),
214+
supports.map(|s| s.to_string()),
215+
layer.map(|s| {
216+
if s.is_empty() {
217+
CssLayer::Anonymous
218+
} else {
219+
CssLayer::Named(s.to_string())
220+
}
221+
}),
207222
)));
208223
}
209224
css_module_lexer::Dependency::Replace { content, range } => presentational_dependencies
@@ -476,17 +491,44 @@ impl ParserAndGenerator for CssParserAndGenerator {
476491
data: generate_context.data,
477492
};
478493

494+
let module_graph = compilation.get_module_graph();
479495
module.get_dependencies().iter().for_each(|id| {
480-
if let Some(dependency) = compilation
481-
.get_module_graph()
496+
let dep = module_graph
482497
.dependency_by_id(id)
483-
.expect("should have dependency")
484-
.as_dependency_template()
485-
{
498+
.expect("should have dependency");
499+
if let Some(dependency) = dep.as_dependency_template() {
486500
dependency.apply(&mut source, &mut context)
487501
}
488502
});
489503

504+
for conn in module_graph.get_incoming_connections(&module.identifier()) {
505+
let Some(dep) = module_graph.dependency_by_id(&conn.dependency_id) else {
506+
continue;
507+
};
508+
509+
if matches!(dep.dependency_type(), DependencyType::CssImport) {
510+
let Some(css_import_dep) = dep.downcast_ref::<CssImportDependency>() else {
511+
panic!(
512+
"dependency with type DependencyType::CssImport should only be CssImportDependency"
513+
);
514+
};
515+
516+
if let Some(media) = css_import_dep.media() {
517+
let media = CssMedia(media.to_string());
518+
context.data.insert(media);
519+
}
520+
521+
if let Some(supports) = css_import_dep.supports() {
522+
let supports = CssSupports(supports.to_string());
523+
context.data.insert(supports);
524+
}
525+
526+
if let Some(layer) = css_import_dep.layer() {
527+
context.data.insert(layer.clone());
528+
}
529+
}
530+
}
531+
490532
if let Some(dependencies) = module.get_presentational_dependencies() {
491533
dependencies
492534
.iter()

crates/rspack_plugin_css/src/plugin/impl_plugin_for_css_plugin.rs

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
#![allow(clippy::comparison_chain)]
22

3+
use std::borrow::Cow;
34
use std::hash::Hash;
45
use std::sync::Arc;
56

67
use async_trait::async_trait;
78
use rayon::prelude::*;
89
use rspack_collections::DatabaseItem;
9-
use rspack_core::rspack_sources::{BoxSource, CachedSource, ReplaceSource};
10+
use rspack_core::rspack_sources::{BoxSource, CachedSource, RawSource, ReplaceSource};
1011
use rspack_core::{
1112
get_css_chunk_filename_template,
1213
rspack_sources::{ConcatSource, RawStringSource, Source, SourceExt},
@@ -25,6 +26,7 @@ use rspack_hook::plugin_hook;
2526
use rspack_plugin_runtime::is_enabled_for_chunk;
2627
use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
2728

29+
use crate::dependency::{CssLayer, CssMedia, CssSupports};
2830
use crate::parser_and_generator::{CodeGenerationDataUnusedLocalIdent, CssParserAndGenerator};
2931
use crate::runtime::CssLoadingRuntimeModule;
3032
use crate::utils::AUTO_PUBLIC_PATH_PLACEHOLDER;
@@ -131,11 +133,13 @@ impl CssPlugin {
131133
.code_generation_results
132134
.get(module_id, Some(chunk.runtime()));
133135

134-
Ok(
135-
code_gen_result
136-
.get(&SourceType::Css)
137-
.map(|source| (CssModuleDebugInfo { module: *module }, source)),
138-
)
136+
Ok(code_gen_result.get(&SourceType::Css).map(|source| {
137+
(
138+
CssModuleDebugInfo { module: *module },
139+
&code_gen_result.data,
140+
source,
141+
)
142+
}))
139143
})
140144
.collect::<Result<Vec<_>>>()?;
141145

@@ -147,11 +151,42 @@ impl CssPlugin {
147151
.flatten()
148152
.fold(
149153
ConcatSource::default,
150-
|mut acc, (debug_info, cur_source)| {
154+
|mut acc, (debug_info, data, cur_source)| {
151155
let (start, end) = Self::render_module_debug_info(compilation, &debug_info);
152156
acc.add(start);
157+
158+
let mut num_close_bracket = 0;
159+
160+
// TODO: use PrefixSource to create indent
161+
if let Some(media) = data.get::<CssMedia>() {
162+
num_close_bracket += 1;
163+
acc.add(RawSource::from(format!("@media {}{{\n", media)));
164+
}
165+
166+
if let Some(supports) = data.get::<CssSupports>() {
167+
num_close_bracket += 1;
168+
acc.add(RawSource::from(format!("@supports ({}) {{\n", supports)));
169+
}
170+
171+
if let Some(layer) = data.get::<CssLayer>() {
172+
num_close_bracket += 1;
173+
acc.add(RawSource::from(format!(
174+
"@layer{} {{\n",
175+
if let CssLayer::Named(layer) = &layer {
176+
Cow::Owned(format!(" {}", layer))
177+
} else {
178+
Cow::Borrowed("")
179+
}
180+
)));
181+
}
182+
153183
acc.add(cur_source.clone());
184+
185+
for _ in 0..num_close_bracket {
186+
acc.add(RawStringSource::from_static("\n}"));
187+
}
154188
acc.add(RawStringSource::from_static("\n"));
189+
155190
acc.add(end);
156191
acc
157192
},

packages/rspack-test-tools/tests/__snapshots__/Config.test.js.snap

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,33 @@ exports[`config config/chunk-index/available-modules-order-index exported tests
220220
}
221221
`;
222222
223+
exports[`config config/css/at-import exported tests at-import-in-the-top 1`] = `
224+
@import url("https://fonts.googleapis.com/css2?family=Inter");
225+
@media (prefers-color-scheme: dark){
226+
227+
228+
.b {
229+
color: red;
230+
}
231+
232+
}
233+
@supports (display: grid) {
234+
@layer {
235+
.c {
236+
color: pink;
237+
}
238+
239+
}
240+
}
241+
242+
243+
244+
245+
.a {
246+
color: blue;
247+
}
248+
`;
249+
223250
exports[`config config/css/at-import-in-the-top exported tests at-import-in-the-top 1`] = `
224251
@import url("https://fonts.googleapis.com/css2?family=Roboto");
225252
@import url("https://fonts.googleapis.com/css2?family=Inter");

packages/rspack-test-tools/tests/__snapshots__/NewCodeSplitting-config.test.js.snap

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,33 @@ exports[`new-code-splitting config cases new-code-splitting config cases/chunk-i
220220
}
221221
`;
222222
223+
exports[`new-code-splitting config cases new-code-splitting config cases/css/at-import exported tests at-import-in-the-top 1`] = `
224+
@import url("https://fonts.googleapis.com/css2?family=Inter");
225+
@media (prefers-color-scheme: dark){
226+
227+
228+
.b {
229+
color: red;
230+
}
231+
232+
}
233+
@supports (display: grid) {
234+
@layer {
235+
.c {
236+
color: pink;
237+
}
238+
239+
}
240+
}
241+
242+
243+
244+
245+
.a {
246+
color: blue;
247+
}
248+
`;
249+
223250
exports[`new-code-splitting config cases new-code-splitting config cases/css/at-import-in-the-top exported tests at-import-in-the-top 1`] = `
224251
@import url("https://fonts.googleapis.com/css2?family=Roboto");
225252
@import url("https://fonts.googleapis.com/css2?family=Inter");
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
@import url("./b.css") (prefers-color-scheme: dark);
2+
3+
@import url("./c.css") layer supports(display: grid);
4+
5+
.a {
6+
color: blue;
7+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@import url("https://fonts.googleapis.com/css2?family=Inter");
2+
3+
.b {
4+
color: red;
5+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.c {
2+
color: pink;
3+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
require("./a.css");
2+
const fs = __non_webpack_require__("fs");
3+
const path = __non_webpack_require__("path");
4+
5+
it("at-import-in-the-top", async () => {
6+
const css = await fs.promises.readFile(
7+
path.resolve(__dirname, "bundle0.css"),
8+
"utf-8"
9+
);
10+
11+
expect(css).toMatchSnapshot();
12+
});

0 commit comments

Comments
 (0)