Ensure content globs defined in @config files are relative to that file (#14314)
When you configure custom content globs inside an `@config` file, we want to tread these globs as being relative to that config file and not the CSS file that requires the content file. A config can be used by multiple CSS configs. --------- Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
This commit is contained in:
parent
dcfaaac8f6
commit
a1d56d8e24
10 changed files with 115 additions and 24 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
<div class="flex" />
|
||||
`,
|
||||
'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`
|
||||
<div class="flex" />
|
||||
`,
|
||||
'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`,
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
<div class="flex" />
|
||||
`,
|
||||
'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`
|
||||
<div class="flex" />
|
||||
`,
|
||||
'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`
|
||||
<div class="flex" />
|
||||
`,
|
||||
'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`
|
||||
<div class="flex" />
|
||||
`,
|
||||
'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`,
|
||||
|
|
|
|||
|
|
@ -52,10 +52,19 @@ test(
|
|||
<div class="underline m-2">Hello, world!</div>
|
||||
</body>
|
||||
`,
|
||||
'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`
|
||||
<div class="flex" />
|
||||
`,
|
||||
'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(
|
|||
<div class="font-bold ">Tailwind Labs</div>
|
||||
</body>
|
||||
`,
|
||||
'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`
|
||||
<div class="flex" />
|
||||
`,
|
||||
'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`)
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -143,8 +143,10 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
|
|||
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<ReturnType<typeof options>>) {
|
|||
// 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,
|
||||
})),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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 }) => {
|
||||
|
|
|
|||
|
|
@ -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' }])
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue