Skip to content

Commit a2fa9d7

Browse files
authored
Merge 4c9cfa7 into fe3ff76
2 parents fe3ff76 + 4c9cfa7 commit a2fa9d7

10 files changed

Lines changed: 152 additions & 25 deletions

crates/next-dev-tests/tests/integration/next/font-google/basic/pages/index.js

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ function runTests() {
5555
async function getRuleMatchingClassName(className) {
5656
const selector = `.${CSS.escape(className)}`;
5757

58-
let matchingRule;
5958
for (const stylesheet of document.querySelectorAll("link[rel=stylesheet]")) {
6059
if (stylesheet.sheet == null) {
6160
// Wait for the stylesheet to load completely if it hasn't already
@@ -64,13 +63,30 @@ async function getRuleMatchingClassName(className) {
6463
});
6564
}
6665

67-
for (const rule of stylesheet.sheet.cssRules) {
68-
if (rule.selectorText === selector) {
69-
matchingRule = rule;
70-
break;
66+
const sheet = stylesheet.sheet;
67+
68+
const res = getRuleMatchingClassNameRec(selector, sheet.cssRules);
69+
if (res != null) {
70+
return res;
71+
}
72+
}
73+
74+
return null;
75+
}
76+
77+
function getRuleMatchingClassNameRec(selector, rules) {
78+
for (const rule of rules) {
79+
if (rule instanceof CSSStyleRule && rule.selectorText === selector) {
80+
return rule;
81+
}
82+
83+
if (rule instanceof CSSLayerBlockRule) {
84+
const res = getRuleMatchingClassNameRec(selector, rule.cssRules);
85+
if (res != null) {
86+
return res;
7187
}
7288
}
7389
}
7490

75-
return matchingRule;
91+
return null;
7692
}

crates/turbopack-css/src/chunk/mod.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ pub(crate) mod optimize;
22
pub mod source_map;
33
pub(crate) mod writer;
44

5-
use std::io::Write;
6-
75
use anyhow::{anyhow, Result};
86
use indexmap::IndexSet;
97
use turbo_tasks::{primitives::StringVc, TryJoinIterExt, ValueToString, ValueToStringVc};
@@ -16,6 +14,7 @@ use turbopack_core::{
1614
optimize::{ChunkOptimizerVc, OptimizableChunk, OptimizableChunkVc},
1715
Chunk, ChunkContentResult, ChunkGroupReferenceVc, ChunkGroupVc, ChunkItem, ChunkItemVc,
1816
ChunkReferenceVc, ChunkVc, ChunkableAssetVc, ChunkingContextVc, FromChunkableAsset,
17+
ModuleId, ModuleIdVc,
1918
},
2019
code_builder::{CodeBuilder, CodeVc},
2120
reference::{AssetReferenceVc, AssetReferencesVc},
@@ -113,6 +112,8 @@ impl CssChunkContentVc {
113112

114113
#[turbo_tasks::function]
115114
async fn code(self) -> Result<CodeVc> {
115+
use std::io::Write;
116+
116117
let this = self.await?;
117118
let chunk_name = this.chunk_path.to_string();
118119

@@ -345,6 +346,23 @@ impl CssChunkContextVc {
345346
pub fn of(context: ChunkingContextVc) -> CssChunkContextVc {
346347
CssChunkContext { context }.cell()
347348
}
349+
350+
#[turbo_tasks::function]
351+
pub async fn chunk_item_id(self, chunk_item: CssChunkItemVc) -> Result<ModuleIdVc> {
352+
use std::fmt::Write;
353+
354+
let layer = &*self.await?.context.layer().await?;
355+
let mut s = chunk_item.to_string().await?.clone_value();
356+
if !layer.is_empty() {
357+
if s.ends_with(')') {
358+
s.pop();
359+
write!(s, ", {layer})")?;
360+
} else {
361+
write!(s, " ({layer})")?;
362+
}
363+
}
364+
Ok(ModuleId::String(s).cell())
365+
}
348366
}
349367

350368
#[turbo_tasks::value_trait]
@@ -373,6 +391,9 @@ pub struct CssChunkItemContent {
373391
pub trait CssChunkItem: ChunkItem + ValueToString {
374392
fn content(&self) -> CssChunkItemContentVc;
375393
fn chunking_context(&self) -> ChunkingContextVc;
394+
fn id(&self) -> ModuleIdVc {
395+
CssChunkContextVc::of(self.chunking_context()).chunk_item_id(*self)
396+
}
376397
}
377398

378399
#[async_trait::async_trait]

crates/turbopack-css/src/chunk/writer.rs

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::{collections::VecDeque, io::Write};
22

33
use anyhow::Result;
44
use turbo_tasks::{primitives::StringVc, ValueToString};
5-
use turbopack_core::code_builder::CodeBuilder;
5+
use turbopack_core::{chunk::ModuleId, code_builder::CodeBuilder};
66

77
use super::{CssChunkItemVc, CssImport};
88

@@ -39,14 +39,24 @@ pub async fn expand_imports(
3939
external_imports.push(url_vc);
4040
}
4141
None => {
42-
let id = &*chunk_item.to_string().await?;
43-
writeln!(code, "/* {} */", id)?;
42+
let id = module_id_to_css_ident(&*chunk_item.id().await?);
43+
44+
// CSS chunk items can be duplicated across chunks. This can cause precedence
45+
// issues (WEB-456). We use CSS layers to make sure that the first occurrence of
46+
// a CSS chunk item determines its precedence.
47+
// TODO(alexkirsz) This currently breaks users using @layer. We can fix that by
48+
// moving our @layer into the user layer.
49+
writeln!(code, "@layer {id} {{")?;
4450

4551
let content = chunk_item.content().await?;
4652
code.push_source(
4753
&content.inner_code,
4854
content.source_map.map(|sm| sm.as_generate_source_map()),
4955
);
56+
57+
// Closing @layer.
58+
writeln!(code, "\n}}")?;
59+
5060
writeln!(code, "\n{}", close)?;
5161

5262
stack.pop();
@@ -56,3 +66,67 @@ pub async fn expand_imports(
5666

5767
Ok(external_imports)
5868
}
69+
70+
fn module_id_to_css_ident(id: &ModuleId) -> String {
71+
match id {
72+
ModuleId::Number(n) => format!("n{}", n),
73+
ModuleId::String(s) => format!("s{}", escape_css_ident(s)),
74+
}
75+
}
76+
77+
/// Escapes a string to be a valid CSS identifier, according to the rules
78+
/// defined in https://developer.mozilla.org/en-US/docs/Web/CSS/ident
79+
fn escape_css_ident(s: &str) -> String {
80+
let mut escaped = String::new();
81+
82+
let mut starts_as_a_number = true;
83+
for char in s.chars() {
84+
if starts_as_a_number {
85+
if char.is_ascii_digit() {
86+
escaped.push('_');
87+
starts_as_a_number = false;
88+
} else if char != '-' {
89+
starts_as_a_number = false;
90+
}
91+
}
92+
93+
if char.is_ascii_alphanumeric() || char == '-' || char == '_' {
94+
escaped.push(char);
95+
} else {
96+
escaped.push('\\');
97+
escaped.push(char);
98+
}
99+
}
100+
101+
escaped
102+
}
103+
104+
#[cfg(test)]
105+
mod tests {
106+
//! These cases are taken from https://developer.mozilla.org/en-US/docs/Web/CSS/ident#examples.
107+
108+
use super::*;
109+
110+
#[test]
111+
fn test_escape_css_ident_noop() {
112+
assert_eq!(escape_css_ident("nono79"), "nono79");
113+
assert_eq!(escape_css_ident("ground-level"), "ground-level");
114+
assert_eq!(escape_css_ident("-test"), "-test");
115+
assert_eq!(escape_css_ident("--toto"), "--toto");
116+
assert_eq!(escape_css_ident("_internal"), "_internal");
117+
// TODO(alexkirsz) Support unicode characters?
118+
// assert_eq!(escape_css_ident("\\22 toto"), "\\22 toto");
119+
// TODO(alexkirsz) This CSS identifier is already valid, but we escape
120+
// it anyway.
121+
assert_eq!(escape_css_ident("bili\\.bob"), "bili\\\\\\.bob");
122+
}
123+
124+
#[test]
125+
fn test_escape_css_ident() {
126+
assert_eq!(escape_css_ident("34rem"), "_34rem");
127+
assert_eq!(escape_css_ident("-12rad"), "-_12rad");
128+
assert_eq!(escape_css_ident("bili.bob"), "bili\\.bob");
129+
assert_eq!(escape_css_ident("'bilibob'"), "\\'bilibob\\'");
130+
assert_eq!(escape_css_ident("\"bilibob\""), "\\\"bilibob\\\"");
131+
}
132+
}

crates/turbopack-tests/tests/snapshot/css/absolute-uri-import/output/crates_turbopack-tests_tests_snapshot_css_absolute-uri-import_input_index.css

Lines changed: 9 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/turbopack-tests/tests/snapshot/css/absolute-uri-import/output/crates_turbopack-tests_tests_snapshot_css_absolute-uri-import_input_index.css.map

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/turbopack-tests/tests/snapshot/css/css/output/8697f_foo_style.css

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/turbopack-tests/tests/snapshot/css/css/output/8697f_foo_style.module.css

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/turbopack-tests/tests/snapshot/css/css/output/crates_turbopack-tests_tests_snapshot_css_css_input_style.css

Lines changed: 6 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/turbopack-tests/tests/snapshot/css/css/output/crates_turbopack-tests_tests_snapshot_css_css_input_style.css.map

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/turbopack-tests/tests/snapshot/css/css/output/crates_turbopack-tests_tests_snapshot_css_css_input_style.module.css

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)