diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f90cba13..ab6ba8230 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -- Nothing yet! +### Fixed + +- Ensure content globs defined in `@config` files are relative to that file ([#14314](https://github.com/tailwindlabs/tailwindcss/pull/14314)) ## [4.0.0-alpha.21] - 2024-09-02 diff --git a/integrations/cli/index.test.ts b/integrations/cli/index.test.ts index d561ec673..50842aea4 100644 --- a/integrations/cli/index.test.ts +++ b/integrations/cli/index.test.ts @@ -52,15 +52,24 @@ describe.each([ addVariant('hocus', ['&:focus', '&:hover']) } `, + 'project-a/tailwind.config.js': js` + module.exports = { + content: ['../project-b/src/**/*.js'], + } + `, 'project-a/src/index.css': css` @import 'tailwindcss/utilities'; - @source '../../project-b/src/**/*.js'; + @config '../tailwind.config.js'; + @source '../../project-b/src/**/*.html'; @plugin '../plugin.js'; `, 'project-a/src/index.js': js` const className = "content-['project-a/src/index.js']" module.exports = { className } `, + 'project-b/src/index.html': html` +
+ `, 'project-b/src/index.js': js` const className = "content-['project-b/src/index.js']" module.exports = { className } @@ -74,6 +83,7 @@ describe.each([ await fs.expectFileToContain('project-a/dist/out.css', [ candidate`underline`, + candidate`flex`, candidate`content-['project-a/src/index.js']`, candidate`content-['project-b/src/index.js']`, candidate`inverted:flex`, @@ -111,15 +121,24 @@ describe.each([ addVariant('hocus', ['&:focus', '&:hover']) } `, + 'project-a/tailwind.config.js': js` + module.exports = { + content: ['../project-b/src/**/*.js'], + } + `, 'project-a/src/index.css': css` @import 'tailwindcss/utilities'; - @source '../../project-b/src/**/*.js'; + @config '../tailwind.config.js'; + @source '../../project-b/src/**/*.html'; @plugin '../plugin.js'; `, 'project-a/src/index.js': js` const className = "content-['project-a/src/index.js']" module.exports = { className } `, + 'project-b/src/index.html': html` +
+ `, 'project-b/src/index.js': js` const className = "content-['project-b/src/index.js']" module.exports = { className } @@ -133,6 +152,7 @@ describe.each([ await fs.expectFileToContain('project-a/dist/out.css', [ candidate`underline`, + candidate`flex`, candidate`content-['project-a/src/index.js']`, candidate`content-['project-b/src/index.js']`, candidate`inverted:flex`, diff --git a/integrations/postcss/index.test.ts b/integrations/postcss/index.test.ts index 61948ca74..d4e4f4d39 100644 --- a/integrations/postcss/index.test.ts +++ b/integrations/postcss/index.test.ts @@ -39,15 +39,24 @@ test( addVariant('hocus', ['&:focus', '&:hover']) } `, + 'project-a/tailwind.config.js': js` + module.exports = { + content: ['../project-b/src/**/*.js'], + } + `, 'project-a/src/index.css': css` @import 'tailwindcss/utilities'; - @source '../../project-b/src/**/*.js'; + @config '../tailwind.config.js'; + @source '../../project-b/src/**/*.html'; @plugin '../plugin.js'; `, 'project-a/src/index.js': js` const className = "content-['a/src/index.js']" module.exports = { className } `, + 'project-b/src/index.html': html` +
+ `, 'project-b/src/index.js': js` const className = "content-['b/src/index.js']" module.exports = { className } @@ -61,6 +70,7 @@ test( await fs.expectFileToContain('project-a/dist/out.css', [ candidate`underline`, + candidate`flex`, candidate`content-['a/src/index.js']`, candidate`content-['b/src/index.js']`, candidate`inverted:flex`, @@ -106,15 +116,24 @@ test( addVariant('hocus', ['&:focus', '&:hover']) } `, + 'project-a/tailwind.config.js': js` + module.exports = { + content: ['../project-b/src/**/*.js'], + } + `, 'project-a/src/index.css': css` @import 'tailwindcss/utilities'; - @source '../../project-b/src/**/*.js'; + @config '../tailwind.config.js'; + @source '../../project-b/src/**/*.html'; @plugin '../plugin.js'; `, 'project-a/src/index.js': js` const className = "content-['a/src/index.js']" module.exports = { className } `, + 'project-b/src/index.html': html` +
+ `, 'project-b/src/index.js': js` const className = "content-['b/src/index.js']" module.exports = { className } @@ -128,6 +147,7 @@ test( await fs.expectFileToContain('project-a/dist/out.css', [ candidate`underline`, + candidate`flex`, candidate`content-['a/src/index.js']`, candidate`content-['b/src/index.js']`, candidate`inverted:flex`, @@ -173,15 +193,24 @@ test( addVariant('hocus', ['&:focus', '&:hover']) } `, + 'project-a/tailwind.config.js': js` + module.exports = { + content: ['../project-b/src/**/*.js'], + } + `, 'project-a/src/index.css': css` @import 'tailwindcss/utilities'; - @source '../../project-b/src/**/*.js'; + @config '../tailwind.config.js'; + @source '../../project-b/src/**/*.html'; @plugin '../plugin.js'; `, 'project-a/src/index.js': js` const className = "content-['a/src/index.js']" module.exports = { className } `, + 'project-b/src/index.html': html` +
+ `, 'project-b/src/index.js': js` const className = "content-['b/src/index.js']" module.exports = { className } @@ -195,6 +224,7 @@ test( await fs.expectFileToContain('project-a/dist/out.css', [ candidate`underline`, + candidate`flex`, candidate`content-['a/src/index.js']`, candidate`content-['b/src/index.js']`, candidate`inverted:flex`, @@ -241,15 +271,24 @@ test( addVariant('hocus', ['&:focus', '&:hover']) } `, + 'project-a/tailwind.config.js': js` + module.exports = { + content: ['../project-b/src/**/*.js'], + } + `, 'project-a/src/index.css': css` @import 'tailwindcss/utilities'; - @source '../../project-b/src/**/*.js'; + @config '../tailwind.config.js'; + @source '../../project-b/src/**/*.html'; @plugin '../plugin.js'; `, 'project-a/src/index.js': js` const className = "content-['a/src/index.js']" module.exports = { className } `, + 'project-b/src/index.html': html` +
+ `, 'project-b/src/index.js': js` const className = "content-['b/src/index.js']" module.exports = { className } @@ -265,6 +304,7 @@ test( await fs.expectFileToContain('project-a/dist/out.css', [ candidate`underline`, + candidate`flex`, candidate`content-['a/src/index.js']`, candidate`content-['b/src/index.js']`, candidate`inverted:flex`, diff --git a/integrations/vite/index.test.ts b/integrations/vite/index.test.ts index c114828bc..05b2d1185 100644 --- a/integrations/vite/index.test.ts +++ b/integrations/vite/index.test.ts @@ -52,10 +52,19 @@ test(
Hello, world!
`, + 'project-a/tailwind.config.js': js` + export default { + content: ['../project-b/src/**/*.js'], + } + `, 'project-a/src/index.css': css` @import 'tailwindcss/theme' theme(reference); @import 'tailwindcss/utilities'; - @source '../../project-b/src/**/*.js'; + @config '../tailwind.config.js'; + @source '../../project-b/src/**/*.html'; + `, + 'project-b/src/index.html': html` +
`, 'project-b/src/index.js': js` const className = "content-['project-b/src/index.js']" @@ -72,6 +81,7 @@ test( await fs.expectFileToContain(filename, [ candidate`underline`, + candidate`flex`, candidate`m-2`, candidate`content-['project-b/src/index.js']`, ]) @@ -125,10 +135,19 @@ test(
Tailwind Labs
`, + 'project-a/tailwind.config.js': js` + export default { + content: ['../project-b/src/**/*.js'], + } + `, 'project-a/src/index.css': css` @import 'tailwindcss/theme' theme(reference); @import 'tailwindcss/utilities'; - @source '../../project-b/src/**/*.js'; + @config '../tailwind.config.js'; + @source '../../project-b/src/**/*.html'; + `, + 'project-b/src/index.html': html` +
`, 'project-b/src/index.js': js` const className = "content-['project-b/src/index.js']" @@ -147,6 +166,7 @@ test( await retryAssertion(async () => { let css = await fetchStyles(port, '/index.html') expect(css).toContain(candidate`underline`) + expect(css).toContain(candidate`flex`) expect(css).not.toContain(candidate`font-bold`) }) @@ -155,6 +175,7 @@ test( await retryAssertion(async () => { let css = await fetchStyles(port, '/about.html') expect(css).toContain(candidate`underline`) + expect(css).toContain(candidate`flex`) expect(css).toContain(candidate`font-bold`) }) diff --git a/packages/@tailwindcss-cli/src/commands/build/index.ts b/packages/@tailwindcss-cli/src/commands/build/index.ts index afa62ae04..823115555 100644 --- a/packages/@tailwindcss-cli/src/commands/build/index.ts +++ b/packages/@tailwindcss-cli/src/commands/build/index.ts @@ -143,8 +143,10 @@ export async function handle(args: Result>) { let compiler = await createCompiler(input) let scanner = new Scanner({ detectSources: { base }, - sources: compiler.globs.map((pattern) => ({ - base: inputBasePath, // Globs are relative to the input.css file + sources: compiler.globs.map(({ origin, pattern }) => ({ + // Ensure the glob is relative to the input CSS file or the config file + // where it is specified. + base: origin ? path.dirname(path.resolve(inputBasePath, origin)) : inputBasePath, pattern, })), }) @@ -212,8 +214,10 @@ export async function handle(args: Result>) { // Re-scan the directory to get the new `candidates` scanner = new Scanner({ detectSources: { base }, - sources: compiler.globs.map((pattern) => ({ - base: inputBasePath, // Globs are relative to the input.css file + sources: compiler.globs.map(({ origin, pattern }) => ({ + // Ensure the glob is relative to the input CSS file or the + // config file where it is specified. + base: origin ? path.dirname(path.resolve(inputBasePath, origin)) : inputBasePath, pattern, })), }) diff --git a/packages/@tailwindcss-postcss/src/index.ts b/packages/@tailwindcss-postcss/src/index.ts index 2660d3723..4f9eccbb5 100644 --- a/packages/@tailwindcss-postcss/src/index.ts +++ b/packages/@tailwindcss-postcss/src/index.ts @@ -141,8 +141,10 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin { // Look for candidates used to generate the CSS let scanner = new Scanner({ detectSources: { base }, - sources: context.compiler.globs.map((pattern) => ({ - base: inputBasePath, // Globs are relative to the input.css file + sources: context.compiler.globs.map(({ origin, pattern }) => ({ + // Ensure the glob is relative to the input CSS file or the config + // file where it is specified. + base: origin ? path.dirname(path.resolve(inputBasePath, origin)) : inputBasePath, pattern, })), }) diff --git a/packages/@tailwindcss-vite/src/index.ts b/packages/@tailwindcss-vite/src/index.ts index 51dff81c0..281c53aba 100644 --- a/packages/@tailwindcss-vite/src/index.ts +++ b/packages/@tailwindcss-vite/src/index.ts @@ -93,8 +93,10 @@ export default function tailwindcss(): Plugin[] { }) scanner = new Scanner({ - sources: globs.map((pattern) => ({ - base: inputBasePath, // Globs are relative to the input.css file + sources: globs.map(({ origin, pattern }) => ({ + // Ensure the glob is relative to the input CSS file or the config file + // where it is specified. + base: origin ? path.dirname(path.resolve(inputBasePath, origin)) : inputBasePath, pattern, })), }) diff --git a/packages/tailwindcss/src/compat/config.test.ts b/packages/tailwindcss/src/compat/config.test.ts index 911cadaa1..23d8aedd9 100644 --- a/packages/tailwindcss/src/compat/config.test.ts +++ b/packages/tailwindcss/src/compat/config.test.ts @@ -14,7 +14,7 @@ test('Config files can add content', async ({ expect }) => { loadConfig: async () => ({ content: ['./file.txt'] }), }) - expect(compiler.globs).toEqual(['./file.txt']) + expect(compiler.globs).toEqual([{ origin: './config.js', pattern: './file.txt' }]) }) test('Config files can change dark mode (media)', async ({ expect }) => { diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index a22097a60..9a52d5af4 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -1776,7 +1776,7 @@ describe('@source', () => { @source "./foo/bar/*.ts"; `) - expect(globs).toEqual(['./foo/bar/*.ts']) + expect(globs).toEqual([{ pattern: './foo/bar/*.ts' }]) }) test('emits multiple @source files', async () => { @@ -1785,7 +1785,7 @@ describe('@source', () => { @source "./php/secr3t/smarty.php"; `) - expect(globs).toEqual(['./foo/**/*.ts', './php/secr3t/smarty.php']) + expect(globs).toEqual([{ pattern: './foo/**/*.ts' }, { pattern: './php/secr3t/smarty.php' }]) }) }) diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index 2af735f0b..db93f39c6 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -54,7 +54,7 @@ async function parseCss( let customUtilities: ((designSystem: DesignSystem) => void)[] = [] let firstThemeRule: Rule | null = null let keyframesRules: Rule[] = [] - let globs: string[] = [] + let globs: { origin?: string; pattern: string }[] = [] walk(ast, (node, { parent, replaceWith }) => { if (node.kind !== 'rule') return @@ -178,7 +178,7 @@ async function parseCss( ) { throw new Error('`@source` paths must be quoted.') } - globs.push(path.slice(1, -1)) + globs.push({ pattern: path.slice(1, -1) }) replaceWith([]) return } @@ -398,7 +398,7 @@ async function parseCss( ) } - globs.push(file.pattern) + globs.push({ origin: file.base, pattern: file.pattern }) } return { @@ -413,7 +413,7 @@ export async function compile( css: string, opts: CompileOptions = {}, ): Promise<{ - globs: string[] + globs: { origin?: string; pattern: string }[] build(candidates: string[]): string }> { let { designSystem, ast, globs, pluginApi } = await parseCss(css, opts)