Skip to content

Commit 08972f2

Browse files
Scan Next.js dynamic route segments with manual @source rules (#16457)
Part of #16287 ## Test plan Added unit and integration tests --------- Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
1 parent f014108 commit 08972f2

File tree

7 files changed

+133
-11
lines changed

7 files changed

+133
-11
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2121
- Ensure drop shadow utilities don't inherit unexpectedly ([#16471](https://github.com/tailwindlabs/tailwindcss/pull/16471))
2222
- Export backwards compatible config and plugin types from `tailwindcss/plugin` ([#16505](https://github.com/tailwindlabs/tailwindcss/pull/16505))
2323
- Ensure JavaScript plugins that emit nested rules referencing to the utility name work as expected ([#16539](https://github.com/tailwindlabs/tailwindcss/pull/16539))
24+
- Ensure that Next.js splat routes are automatically scanned for classes ([#16457](https://github.com/tailwindlabs/tailwindcss/pull/16457))
2425
- Pin exact versions of `tailwindcss` and `@tailwindcss/*` ([#16623](https://github.com/tailwindlabs/tailwindcss/pull/16623))
2526
- Upgrade: Report errors when updating dependencies ([#16504](https://github.com/tailwindlabs/tailwindcss/pull/16504))
2627
- Upgrade: Ensure a `darkMode` JS config setting with block syntax converts to use `@slot` ([#16507](https://github.com/tailwindlabs/tailwindcss/pull/16507))

Cargo.lock

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

crates/oxide/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ walkdir = "2.5.0"
1616
ignore = "0.4.23"
1717
dunce = "1.0.5"
1818
bexpand = "1.2.0"
19-
glob-match = "0.2.1"
19+
fast-glob = "0.4.3"
2020

2121
[dev-dependencies]
2222
tempfile = "3.13.0"

crates/oxide/src/glob.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
use fast_glob::glob_match;
12
use fxhash::{FxHashMap, FxHashSet};
2-
use glob_match::glob_match;
33
use std::path::{Path, PathBuf};
44
use tracing::event;
55

@@ -173,7 +173,7 @@ pub fn path_matches_globs(path: &Path, globs: &[GlobEntry]) -> bool {
173173

174174
globs
175175
.iter()
176-
.any(|g| glob_match(&format!("{}/{}", g.base, g.pattern), &path))
176+
.any(|g| glob_match(&format!("{}/{}", g.base, g.pattern), path.as_bytes()))
177177
}
178178

179179
#[cfg(test)]

crates/oxide/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ use crate::scanner::allowed_paths::resolve_paths;
44
use crate::scanner::detect_sources::DetectSources;
55
use bexpand::Expression;
66
use bstr::ByteSlice;
7+
use fast_glob::glob_match;
78
use fxhash::{FxHashMap, FxHashSet};
89
use glob::optimize_patterns;
9-
use glob_match::glob_match;
1010
use paths::Path;
1111
use rayon::prelude::*;
1212
use scanner::allowed_paths::read_dir;

crates/oxide/tests/scanner.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,32 @@ mod scanner {
415415
assert_eq!(candidates, vec!["content-['foo.styl']"]);
416416
}
417417

418+
#[test]
419+
fn it_should_scan_next_dynamic_folders() {
420+
let candidates = scan_with_globs(
421+
&[
422+
// We know that `.styl` extensions are ignored, so they are not covered by auto content
423+
// detection.
424+
("app/[slug]/page.styl", "content-['[slug]']"),
425+
("app/[...slug]/page.styl", "content-['[...slug]']"),
426+
("app/[[...slug]]/page.styl", "content-['[[...slug]]']"),
427+
("app/(theme)/page.styl", "content-['(theme)']"),
428+
],
429+
vec!["./**/*.{styl}"],
430+
)
431+
.1;
432+
433+
assert_eq!(
434+
candidates,
435+
vec![
436+
"content-['(theme)']",
437+
"content-['[...slug]']",
438+
"content-['[[...slug]]']",
439+
"content-['[slug]']",
440+
],
441+
);
442+
}
443+
418444
#[test]
419445
fn it_should_scan_absolute_paths() {
420446
// Create a temporary working directory

integrations/postcss/next.test.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,3 +162,89 @@ describe.each(['turbo', 'webpack'])('%s', (bundler) => {
162162
},
163163
)
164164
})
165+
166+
test(
167+
'should scan dynamic route segments',
168+
{
169+
fs: {
170+
'package.json': json`
171+
{
172+
"dependencies": {
173+
"react": "^18",
174+
"react-dom": "^18",
175+
"next": "^14"
176+
},
177+
"devDependencies": {
178+
"@tailwindcss/postcss": "workspace:^",
179+
"tailwindcss": "workspace:^"
180+
}
181+
}
182+
`,
183+
'postcss.config.mjs': js`
184+
/** @type {import('postcss-load-config').Config} */
185+
const config = {
186+
plugins: {
187+
'@tailwindcss/postcss': {},
188+
},
189+
}
190+
191+
export default config
192+
`,
193+
'next.config.mjs': js`
194+
/** @type {import('next').NextConfig} */
195+
const nextConfig = {}
196+
197+
export default nextConfig
198+
`,
199+
'app/a/[slug]/page.js': js`
200+
export default function Page() {
201+
return <h1 className="content-['[slug]']">Hello, Next.js!</h1>
202+
}
203+
`,
204+
'app/b/[...slug]/page.js': js`
205+
export default function Page() {
206+
return <h1 className="content-['[...slug]']">Hello, Next.js!</h1>
207+
}
208+
`,
209+
'app/c/[[...slug]]/page.js': js`
210+
export default function Page() {
211+
return <h1 className="content-['[[...slug]]']">Hello, Next.js!</h1>
212+
}
213+
`,
214+
'app/d/(theme)/page.js': js`
215+
export default function Page() {
216+
return <h1 className="content-['(theme)']">Hello, Next.js!</h1>
217+
}
218+
`,
219+
'app/layout.js': js`
220+
import './globals.css'
221+
222+
export default function RootLayout({ children }) {
223+
return (
224+
<html>
225+
<body>{children}</body>
226+
</html>
227+
)
228+
}
229+
`,
230+
'app/globals.css': css`
231+
@import 'tailwindcss/utilities' source(none);
232+
@source './**/*.{js,ts,jsx,tsx,mdx}';
233+
`,
234+
},
235+
},
236+
async ({ fs, exec, expect }) => {
237+
await exec('pnpm next build')
238+
239+
let files = await fs.glob('.next/static/css/**/*.css')
240+
expect(files).toHaveLength(1)
241+
let [filename] = files[0]
242+
243+
await fs.expectFileToContain(filename, [
244+
candidate`content-['[slug]']`,
245+
candidate`content-['[...slug]']`,
246+
candidate`content-['[[...slug]]']`,
247+
candidate`content-['(theme)']`,
248+
])
249+
},
250+
)

0 commit comments

Comments
 (0)