From a785c93b54c770b65364a8c781761dd97ac684d5 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Wed, 29 Mar 2023 15:32:37 +0200 Subject: [PATCH 1/8] Try resolving `config.default` before `config` to ensure the config file is resolved correctly (#10898) * try to use `config.default` before using `config` * update changelog * add quick `SHOW_OUTPUT` toggle for integration tests Setting this to `true` shows the output of the executed commands. * add integration tests for `tailwind.config.ts` and `tailwind.config.js` with ESM syntax --- CHANGELOG.md | 4 +- integrations/execute.js | 8 ++ integrations/parcel/tests/integration.test.js | 133 +++++++++++++++++- integrations/rollup/tests/integration.test.js | 116 ++++++++++++++- .../tailwindcss-cli/tests/integration.test.js | 116 ++++++++++++++- integrations/vite/tests/integration.test.js | 112 ++++++++++++++- .../webpack-4/tests/integration.test.js | 117 ++++++++++++++- .../webpack-5/tests/integration.test.js | 117 ++++++++++++++- src/lib/load-config.ts | 14 +- 9 files changed, 719 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 879ed210eea2..270fc089d6e8 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 + +- Try resolving `config.default` before `config` to ensure the config file is resolved correctly ([#10898](https://github.com/tailwindlabs/tailwindcss/pull/10898)) ## [3.3.0] - 2023-03-27 diff --git a/integrations/execute.js b/integrations/execute.js index f5ab2131804a..6c5f2036327d 100644 --- a/integrations/execute.js +++ b/integrations/execute.js @@ -3,6 +3,8 @@ let path = require('path') let { spawn } = require('child_process') let resolveToolRoot = require('./resolve-tool-root') +let SHOW_OUTPUT = false + let runningProcessess = [] afterEach(() => { @@ -92,6 +94,9 @@ module.exports = function $(command, options = {}) { let combined = '' child.stdout.on('data', (data) => { + if (SHOW_OUTPUT) { + console.log(data.toString()) + } stdoutMessages.push(data.toString()) notifyNextStdoutActor() stdout += data @@ -99,6 +104,9 @@ module.exports = function $(command, options = {}) { }) child.stderr.on('data', (data) => { + if (SHOW_OUTPUT) { + console.error(data.toString()) + } stderrMessages.push(data.toString()) notifyNextStderrActor() stderr += data diff --git a/integrations/parcel/tests/integration.test.js b/integrations/parcel/tests/integration.test.js index 0fc539f37eec..55e7f83a38b0 100644 --- a/integrations/parcel/tests/integration.test.js +++ b/integrations/parcel/tests/integration.test.js @@ -3,11 +3,12 @@ let { css, html, javascript } = require('../../syntax') let { env } = require('../../../lib/lib/sharedState') let { - readOutputFile, appendToInputFile, - writeInputFile, - waitForOutputFileCreation, + readOutputFile, + removeFile, waitForOutputFileChange, + waitForOutputFileCreation, + writeInputFile, } = require('../../io')({ output: 'dist', input: 'src' }) describe('static build', () => { @@ -32,6 +33,132 @@ describe('static build', () => { ` ) }) + + it('can use a tailwind.config.js configuration file with ESM syntax', async () => { + await removeFile('tailwind.config.js') + await writeInputFile( + 'index.html', + html` + +
+ ` + ) + await writeInputFile( + 'index.css', + css` + @tailwind base; + @tailwind components; + @tailwind utilities; + ` + ) + await writeInputFile( + '../tailwind.config.js', + javascript` + export default { + content: ['./src/index.html'], + theme: { + extend: { + colors: { + primary: 'black', + }, + }, + }, + corePlugins: { + preflight: false, + }, + } + ` + ) + + await $('parcel build ./src/index.html --no-cache', { + env: { NODE_ENV: 'production' }, + }) + + if (!env.OXIDE) { + expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss( + css` + .bg-primary { + --tw-bg-opacity: 1; + background-color: rgb(0 0 0 / var(--tw-bg-opacity)); + } + ` + ) + } + + if (env.OXIDE) { + expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss( + css` + .bg-primary { + background-color: black; + } + ` + ) + } + }) + + it('can use a tailwind.config.ts configuration file', async () => { + await removeFile('tailwind.config.js') + await writeInputFile( + 'index.html', + html` + +
+ ` + ) + await writeInputFile( + 'index.css', + css` + @tailwind base; + @tailwind components; + @tailwind utilities; + ` + ) + await writeInputFile( + '../tailwind.config.ts', + javascript` + import type { Config } from 'tailwindcss' + + export default { + content: ['./src/index.html'], + theme: { + extend: { + colors: { + primary: 'black', + }, + }, + }, + corePlugins: { + preflight: false, + }, + } satisfies Config + ` + ) + + await $('parcel build ./src/index.html --no-cache', { + env: { NODE_ENV: 'production' }, + }) + + if (!env.OXIDE) { + expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss( + css` + .bg-primary { + --tw-bg-opacity: 1; + background-color: rgb(0 0 0 / var(--tw-bg-opacity)); + } + ` + ) + } + + if (env.OXIDE) { + expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss( + css` + .bg-primary { + background-color: black; + } + ` + ) + } + }) }) describe('watcher', () => { diff --git a/integrations/rollup/tests/integration.test.js b/integrations/rollup/tests/integration.test.js index 1daea888a875..dbbfcc8d11f1 100644 --- a/integrations/rollup/tests/integration.test.js +++ b/integrations/rollup/tests/integration.test.js @@ -2,7 +2,7 @@ let $ = require('../../execute') let { css, html, javascript } = require('../../syntax') let { env } = require('../../../lib/lib/sharedState') -let { readOutputFile, appendToInputFile, writeInputFile } = require('../../io')({ +let { readOutputFile, appendToInputFile, writeInputFile, removeFile } = require('../../io')({ output: 'dist', input: 'src', }) @@ -27,6 +27,120 @@ describe('static build', () => { ` ) }) + + it('can use a tailwind.config.js configuration file with ESM syntax', async () => { + await removeFile('tailwind.config.js') + await writeInputFile('index.html', html`
`) + await writeInputFile( + 'index.css', + css` + @tailwind base; + @tailwind components; + @tailwind utilities; + ` + ) + await writeInputFile( + '../tailwind.config.js', + javascript` + export default { + content: ['./src/index.html'], + theme: { + extend: { + colors: { + primary: 'black', + }, + }, + }, + corePlugins: { + preflight: false, + }, + } + ` + ) + + await $('rollup -c', { + env: { NODE_ENV: 'production' }, + }) + + if (!env.OXIDE) { + expect(await readOutputFile('index.css')).toIncludeCss( + css` + .bg-primary { + --tw-bg-opacity: 1; + background-color: rgb(0 0 0 / var(--tw-bg-opacity)); + } + ` + ) + } + + if (env.OXIDE) { + expect(await readOutputFile('index.css')).toIncludeCss( + css` + .bg-primary { + background-color: black; + } + ` + ) + } + }) + + it('can use a tailwind.config.ts configuration file', async () => { + await removeFile('tailwind.config.js') + await writeInputFile('index.html', html`
`) + await writeInputFile( + 'index.css', + css` + @tailwind base; + @tailwind components; + @tailwind utilities; + ` + ) + await writeInputFile( + '../tailwind.config.ts', + javascript` + import type { Config } from 'tailwindcss' + + export default { + content: ['./src/index.html'], + theme: { + extend: { + colors: { + primary: 'black', + }, + }, + }, + corePlugins: { + preflight: false, + }, + } satisfies Config + ` + ) + + await $('rollup -c', { + env: { NODE_ENV: 'production' }, + }) + + if (!env.OXIDE) { + expect(await readOutputFile('index.css')).toIncludeCss( + css` + .bg-primary { + --tw-bg-opacity: 1; + background-color: rgb(0 0 0 / var(--tw-bg-opacity)); + } + ` + ) + } + + if (env.OXIDE) { + expect(await readOutputFile('index.css')).toIncludeCss( + css` + .bg-primary { + background-color: black; + } + ` + ) + } + }) }) describe('watcher', () => { diff --git a/integrations/tailwindcss-cli/tests/integration.test.js b/integrations/tailwindcss-cli/tests/integration.test.js index ff8db20b8a2d..6efa9fdf5e44 100644 --- a/integrations/tailwindcss-cli/tests/integration.test.js +++ b/integrations/tailwindcss-cli/tests/integration.test.js @@ -3,7 +3,7 @@ let $ = require('../../execute') let { css, html, javascript } = require('../../syntax') let { env } = require('../../../lib/lib/sharedState') -let { readOutputFile, appendToInputFile, writeInputFile } = require('../../io')({ +let { readOutputFile, appendToInputFile, writeInputFile, removeFile } = require('../../io')({ output: 'dist', input: 'src', }) @@ -111,6 +111,120 @@ describe('static build', () => { } }) + it('can use a tailwind.config.js configuration file with ESM syntax', async () => { + await removeFile('tailwind.config.js') + await writeInputFile('index.html', html`
`) + await writeInputFile( + 'index.css', + css` + @tailwind base; + @tailwind components; + @tailwind utilities; + ` + ) + await writeInputFile( + '../tailwind.config.js', + javascript` + export default { + content: ['./src/index.html'], + theme: { + extend: { + colors: { + primary: 'black', + }, + }, + }, + corePlugins: { + preflight: false, + }, + } + ` + ) + + await $('node ../../lib/cli.js -i ./src/index.css -o ./dist/main.css', { + env: { NODE_ENV: 'production' }, + }) + + if (!env.OXIDE) { + expect(await readOutputFile('main.css')).toIncludeCss( + css` + .bg-primary { + --tw-bg-opacity: 1; + background-color: rgb(0 0 0 / var(--tw-bg-opacity)); + } + ` + ) + } + + if (env.OXIDE) { + expect(await readOutputFile('main.css')).toIncludeCss( + css` + .bg-primary { + background-color: black; + } + ` + ) + } + }) + + it('can use a tailwind.config.ts configuration file', async () => { + await removeFile('tailwind.config.js') + await writeInputFile('index.html', html`
`) + await writeInputFile( + 'index.css', + css` + @tailwind base; + @tailwind components; + @tailwind utilities; + ` + ) + await writeInputFile( + '../tailwind.config.ts', + javascript` + import type { Config } from 'tailwindcss' + + export default { + content: ['./src/index.html'], + theme: { + extend: { + colors: { + primary: 'black', + }, + }, + }, + corePlugins: { + preflight: false, + }, + } satisfies Config + ` + ) + + await $('node ../../lib/cli.js -i ./src/index.css -o ./dist/main.css', { + env: { NODE_ENV: 'production' }, + }) + + if (!env.OXIDE) { + expect(await readOutputFile('main.css')).toIncludeCss( + css` + .bg-primary { + --tw-bg-opacity: 1; + background-color: rgb(0 0 0 / var(--tw-bg-opacity)); + } + ` + ) + } + + if (env.OXIDE) { + expect(await readOutputFile('main.css')).toIncludeCss( + css` + .bg-primary { + background-color: black; + } + ` + ) + } + }) + it('can read from a config file from an @config directive', async () => { await writeInputFile('index.html', html`
`) await writeInputFile( diff --git a/integrations/vite/tests/integration.test.js b/integrations/vite/tests/integration.test.js index 1e5cd2d3345e..a07a2a9a6efc 100644 --- a/integrations/vite/tests/integration.test.js +++ b/integrations/vite/tests/integration.test.js @@ -4,7 +4,7 @@ let $ = require('../../execute') let { css, html, javascript } = require('../../syntax') let { env } = require('../../../lib/lib/sharedState') -let { readOutputFile, appendToInputFile, writeInputFile } = require('../../io')({ +let { readOutputFile, appendToInputFile, writeInputFile, removeFile } = require('../../io')({ output: 'dist', input: '.', }) @@ -42,6 +42,116 @@ describe('static build', () => { ` ) }) + + it('can use a tailwind.config.js configuration file with ESM syntax', async () => { + await writeInputFile( + 'index.html', + html` + +
+ ` + ) + await removeFile('tailwind.config.js') + await writeInputFile( + 'tailwind.config.js', + javascript` + export default { + content: ['index.html'], + theme: { + extend: { + colors: { + primary: 'black', + }, + }, + }, + corePlugins: { + preflight: false, + }, + } + ` + ) + + await $('vite build', { + env: { NODE_ENV: 'production', NO_COLOR: '1' }, + }) + + if (!env.OXIDE) { + expect(await readOutputFile(/index.\w+\.css$/)).toIncludeCss( + css` + .bg-primary { + --tw-bg-opacity: 1; + background-color: rgb(0 0 0 / var(--tw-bg-opacity)); + } + ` + ) + } + + if (env.OXIDE) { + expect(await readOutputFile(/index.\w+\.css$/)).toIncludeCss( + css` + .bg-primary { + background-color: black; + } + ` + ) + } + }) + + it('can use a tailwind.config.ts configuration file', async () => { + await writeInputFile( + 'index.html', + html` + +
+ ` + ) + await removeFile('tailwind.config.js') + await writeInputFile( + 'tailwind.config.ts', + javascript` + import type { Config } from 'tailwindcss' + + export default { + content: ['index.html'], + theme: { + extend: { + colors: { + primary: 'black', + }, + }, + }, + corePlugins: { + preflight: false, + }, + } satisfies Config + ` + ) + + await $('vite build', { + env: { NODE_ENV: 'production', NO_COLOR: '1' }, + }) + + if (!env.OXIDE) { + expect(await readOutputFile(/index.\w+\.css$/)).toIncludeCss( + css` + .bg-primary { + --tw-bg-opacity: 1; + background-color: rgb(0 0 0 / var(--tw-bg-opacity)); + } + ` + ) + } + + if (env.OXIDE) { + expect(await readOutputFile(/index.\w+\.css$/)).toIncludeCss( + css` + .bg-primary { + background-color: black; + } + ` + ) + } + }) }) describe('watcher', () => { diff --git a/integrations/webpack-4/tests/integration.test.js b/integrations/webpack-4/tests/integration.test.js index 6bbf3f879fcd..a666721bbdc1 100644 --- a/integrations/webpack-4/tests/integration.test.js +++ b/integrations/webpack-4/tests/integration.test.js @@ -3,11 +3,12 @@ let { css, html, javascript } = require('../../syntax') let { env } = require('../../../lib/lib/sharedState') let { - readOutputFile, appendToInputFile, - writeInputFile, - waitForOutputFileCreation, + readOutputFile, + removeFile, waitForOutputFileChange, + waitForOutputFileCreation, + writeInputFile, } = require('../../io')({ output: 'dist', input: 'src' }) describe('static build', () => { @@ -24,6 +25,116 @@ describe('static build', () => { ` ) }) + + it('can use a tailwind.config.js configuration file with ESM syntax', async () => { + await removeFile('tailwind.config.js') + await writeInputFile('index.html', html`
`) + await writeInputFile( + 'index.css', + css` + @tailwind base; + @tailwind components; + @tailwind utilities; + ` + ) + await writeInputFile( + '../tailwind.config.js', + javascript` + export default { + content: ['./src/index.html'], + theme: { + extend: { + colors: { + primary: 'black', + }, + }, + }, + corePlugins: { + preflight: false, + }, + } + ` + ) + + await $('webpack --mode=production') + + if (!env.OXIDE) { + expect(await readOutputFile('main.css')).toIncludeCss( + css` + .bg-primary { + --tw-bg-opacity: 1; + background-color: rgb(0 0 0 / var(--tw-bg-opacity)); + } + ` + ) + } + + if (env.OXIDE) { + expect(await readOutputFile('main.css')).toIncludeCss( + css` + .bg-primary { + background-color: black; + } + ` + ) + } + }) + + it('can use a tailwind.config.ts configuration file', async () => { + await removeFile('tailwind.config.js') + await writeInputFile('index.html', html`
`) + await writeInputFile( + 'index.css', + css` + @tailwind base; + @tailwind components; + @tailwind utilities; + ` + ) + await writeInputFile( + '../tailwind.config.ts', + javascript` + import type { Config } from 'tailwindcss' + + export default { + content: ['./src/index.html'], + theme: { + extend: { + colors: { + primary: 'black', + }, + }, + }, + corePlugins: { + preflight: false, + }, + } satisfies Config + ` + ) + + await $('webpack --mode=production') + + if (!env.OXIDE) { + expect(await readOutputFile('main.css')).toIncludeCss( + css` + .bg-primary { + --tw-bg-opacity: 1; + background-color: rgb(0 0 0 / var(--tw-bg-opacity)); + } + ` + ) + } + + if (env.OXIDE) { + expect(await readOutputFile('main.css')).toIncludeCss( + css` + .bg-primary { + background-color: black; + } + ` + ) + } + }) }) describe('watcher', () => { diff --git a/integrations/webpack-5/tests/integration.test.js b/integrations/webpack-5/tests/integration.test.js index fde0d60c5364..ae493fbdbb66 100644 --- a/integrations/webpack-5/tests/integration.test.js +++ b/integrations/webpack-5/tests/integration.test.js @@ -3,11 +3,12 @@ let { css, html, javascript } = require('../../syntax') let { env } = require('../../../lib/lib/sharedState') let { - readOutputFile, appendToInputFile, - writeInputFile, - waitForOutputFileCreation, + readOutputFile, + removeFile, waitForOutputFileChange, + waitForOutputFileCreation, + writeInputFile, } = require('../../io')({ output: 'dist', input: 'src' }) describe('static build', () => { @@ -24,6 +25,116 @@ describe('static build', () => { ` ) }) + + it('can use a tailwind.config.js configuration file with ESM syntax', async () => { + await removeFile('tailwind.config.js') + await writeInputFile('index.html', html`
`) + await writeInputFile( + 'index.css', + css` + @tailwind base; + @tailwind components; + @tailwind utilities; + ` + ) + await writeInputFile( + '../tailwind.config.js', + javascript` + export default { + content: ['./src/index.html'], + theme: { + extend: { + colors: { + primary: 'black', + }, + }, + }, + corePlugins: { + preflight: false, + }, + } + ` + ) + + await $('webpack --mode=production') + + if (!env.OXIDE) { + expect(await readOutputFile('main.css')).toIncludeCss( + css` + .bg-primary { + --tw-bg-opacity: 1; + background-color: rgb(0 0 0 / var(--tw-bg-opacity)); + } + ` + ) + } + + if (env.OXIDE) { + expect(await readOutputFile('main.css')).toIncludeCss( + css` + .bg-primary { + background-color: black; + } + ` + ) + } + }) + + it('can use a tailwind.config.ts configuration file', async () => { + await removeFile('tailwind.config.js') + await writeInputFile('index.html', html`
`) + await writeInputFile( + 'index.css', + css` + @tailwind base; + @tailwind components; + @tailwind utilities; + ` + ) + await writeInputFile( + '../tailwind.config.ts', + javascript` + import type { Config } from 'tailwindcss' + + export default { + content: ['./src/index.html'], + theme: { + extend: { + colors: { + primary: 'black', + }, + }, + }, + corePlugins: { + preflight: false, + }, + } satisfies Config + ` + ) + + await $('webpack --mode=production') + + if (!env.OXIDE) { + expect(await readOutputFile('main.css')).toIncludeCss( + css` + .bg-primary { + --tw-bg-opacity: 1; + background-color: rgb(0 0 0 / var(--tw-bg-opacity)); + } + ` + ) + } + + if (env.OXIDE) { + expect(await readOutputFile('main.css')).toIncludeCss( + css` + .bg-primary { + background-color: black; + } + ` + ) + } + }) }) describe('watcher', () => { diff --git a/src/lib/load-config.ts b/src/lib/load-config.ts index 89ce3a368d0b..645e8e1d55c4 100644 --- a/src/lib/load-config.ts +++ b/src/lib/load-config.ts @@ -19,9 +19,13 @@ function lazyJiti() { } export function loadConfig(path: string): Config { - try { - return path ? require(path) : {} - } catch { - return lazyJiti()(path) - } + let config = (function () { + try { + return path ? require(path) : {} + } catch { + return lazyJiti()(path) + } + })() + + return config.default ?? config } From 0ecc4642fc80f574f2dc7f0fe89884e392dc56c4 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Wed, 29 Mar 2023 15:37:26 -0400 Subject: [PATCH 2/8] Pull pseudo elements outside of `:is` and `:has` when using `@apply` (#10903) * Pull pseudo elements outside of `:is` and `:has` when using `@apply` * Update changelog * Refactor * Update important selector handling for :is and :has * fixup * fixup * trigger CI --------- Co-authored-by: Robin Malfait --- CHANGELOG.md | 1 + src/lib/expandApplyAtRules.js | 12 +++++ src/util/applyImportantSelector.js | 34 ++++++++++----- src/util/formatVariantSelector.js | 46 +++++++++++++++----- tests/apply.test.js | 70 ++++++++++++++++++++++++++++++ tests/important-selector.test.js | 7 +++ 6 files changed, 149 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 270fc089d6e8..1fc7165535cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Try resolving `config.default` before `config` to ensure the config file is resolved correctly ([#10898](https://github.com/tailwindlabs/tailwindcss/pull/10898)) +- Pull pseudo elements outside of `:is` and `:has` when using `@apply` ([#10903](https://github.com/tailwindlabs/tailwindcss/pull/10903)) ## [3.3.0] - 2023-03-27 diff --git a/src/lib/expandApplyAtRules.js b/src/lib/expandApplyAtRules.js index cdcd0b5688c9..c6d53d80e29f 100644 --- a/src/lib/expandApplyAtRules.js +++ b/src/lib/expandApplyAtRules.js @@ -4,6 +4,7 @@ import parser from 'postcss-selector-parser' import { resolveMatches } from './generateRules' import escapeClassName from '../util/escapeClassName' import { applyImportantSelector } from '../util/applyImportantSelector' +import { collectPseudoElements, sortSelector } from '../util/formatVariantSelector.js' /** @typedef {Map} ApplyCache */ @@ -562,6 +563,17 @@ function processApply(root, context, localCache) { rule.walkDecls((d) => { d.important = meta.important || important }) + + // Move pseudo elements to the end of the selector (if necessary) + let selector = parser().astSync(rule.selector) + selector.each((sel) => { + let [pseudoElements] = collectPseudoElements(sel) + if (pseudoElements.length > 0) { + sel.nodes.push(...pseudoElements.sort(sortSelector)) + } + }) + + rule.selector = selector.toString() }) } diff --git a/src/util/applyImportantSelector.js b/src/util/applyImportantSelector.js index 69de63325c79..dbaf136cce20 100644 --- a/src/util/applyImportantSelector.js +++ b/src/util/applyImportantSelector.js @@ -1,19 +1,31 @@ import { splitAtTopLevelOnly } from './splitAtTopLevelOnly' +import parser from 'postcss-selector-parser' +import { collectPseudoElements, sortSelector } from './formatVariantSelector.js' export function applyImportantSelector(selector, important) { - let matches = /^(.*?)(:before|:after|::[\w-]+)(\)*)$/g.exec(selector) - if (!matches) return `${important} ${wrapWithIs(selector)}` + let sel = parser().astSync(selector) - let [, before, pseudo, brackets] = matches - return `${important} ${wrapWithIs(before + brackets)}${pseudo}` -} + sel.each((sel) => { + // Wrap with :is if it's not already wrapped + let isWrapped = + sel.nodes[0].type === 'pseudo' && + sel.nodes[0].value === ':is' && + sel.nodes.every((node) => node.type !== 'combinator') -function wrapWithIs(selector) { - let parts = splitAtTopLevelOnly(selector, ' ') + if (!isWrapped) { + sel.nodes = [ + parser.pseudo({ + value: ':is', + nodes: [sel.clone()], + }), + ] + } - if (parts.length === 1 && parts[0].startsWith(':is(') && parts[0].endsWith(')')) { - return selector - } + let [pseudoElements] = collectPseudoElements(sel) + if (pseudoElements.length > 0) { + sel.nodes.push(...pseudoElements.sort(sortSelector)) + } + }) - return `:is(${selector})` + return `${important} ${sel.toString()}` } diff --git a/src/util/formatVariantSelector.js b/src/util/formatVariantSelector.js index da5e2c78dfbc..8a1a3f50c1f8 100644 --- a/src/util/formatVariantSelector.js +++ b/src/util/formatVariantSelector.js @@ -246,9 +246,9 @@ export function finalizeSelector(current, formats, { context, candidate, base }) // Move pseudo elements to the end of the selector (if necessary) selector.each((sel) => { - let pseudoElements = collectPseudoElements(sel) + let [pseudoElements] = collectPseudoElements(sel) if (pseudoElements.length > 0) { - sel.nodes.push(pseudoElements.sort(sortSelector)) + sel.nodes.push(...pseudoElements.sort(sortSelector)) } }) @@ -351,23 +351,45 @@ let pseudoElementExceptions = [ * `::before:hover` doesn't work, which means that we can make it work for you by flipping the order. * * @param {Selector} selector + * @param {boolean} force **/ -function collectPseudoElements(selector) { +export function collectPseudoElements(selector, force = false) { /** @type {Node[]} */ let nodes = [] + let seenPseudoElement = null - for (let node of selector.nodes) { - if (isPseudoElement(node)) { + for (let node of [...selector.nodes]) { + if (isPseudoElement(node, force)) { nodes.push(node) selector.removeChild(node) + seenPseudoElement = node.value + } else if (seenPseudoElement !== null) { + if (pseudoElementExceptions.includes(seenPseudoElement) && isPseudoClass(node, force)) { + nodes.push(node) + selector.removeChild(node) + } else { + seenPseudoElement = null + } } if (node?.nodes) { - nodes.push(...collectPseudoElements(node)) + let hasPseudoElementRestrictions = + node.type === 'pseudo' && (node.value === ':is' || node.value === ':has') + + let [collected, seenPseudoElementInSelector] = collectPseudoElements( + node, + force || hasPseudoElementRestrictions + ) + + if (seenPseudoElementInSelector) { + seenPseudoElement = seenPseudoElementInSelector + } + + nodes.push(...collected) } } - return nodes + return [nodes, seenPseudoElement] } // This will make sure to move pseudo's to the correct spot (the end for @@ -380,7 +402,7 @@ function collectPseudoElements(selector) { // // `::before:hover` doesn't work, which means that we can make it work // for you by flipping the order. -function sortSelector(a, z) { +export function sortSelector(a, z) { // Both nodes are non-pseudo's so we can safely ignore them and keep // them in the same order. if (a.type !== 'pseudo' && z.type !== 'pseudo') { @@ -404,9 +426,13 @@ function sortSelector(a, z) { return isPseudoElement(a) - isPseudoElement(z) } -function isPseudoElement(node) { +function isPseudoElement(node, force = false) { if (node.type !== 'pseudo') return false - if (pseudoElementExceptions.includes(node.value)) return false + if (pseudoElementExceptions.includes(node.value) && !force) return false return node.value.startsWith('::') || pseudoElementsBC.includes(node.value) } + +function isPseudoClass(node, force) { + return node.type === 'pseudo' && !isPseudoElement(node, force) +} diff --git a/tests/apply.test.js b/tests/apply.test.js index f7fab7d70938..0ab9ec17567f 100644 --- a/tests/apply.test.js +++ b/tests/apply.test.js @@ -2357,4 +2357,74 @@ crosscheck(({ stable, oxide }) => { `) }) }) + + it('pseudo elements inside apply are moved outside of :is() or :has()', () => { + let config = { + darkMode: 'class', + content: [ + { + raw: html`
`, + }, + ], + } + + let input = css` + .foo::before { + @apply dark:bg-black/100; + } + + .bar::before { + @apply rtl:dark:bg-black/100; + } + + .baz::before { + @apply rtl:dark:hover:bg-black/100; + } + + .qux::file-selector-button { + @apply rtl:dark:hover:bg-black/100; + } + + .steve::before { + @apply rtl:hover:dark:bg-black/100; + } + + .bob::file-selector-button { + @apply rtl:hover:dark:bg-black/100; + } + + .foo::before { + @apply [:has([dir="rtl"]_&)]:hover:bg-black/100; + } + + .bar::file-selector-button { + @apply [:has([dir="rtl"]_&)]:hover:bg-black/100; + } + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + :is(.dark .foo)::before, + :is([dir='rtl'] :is(.dark .bar))::before, + :is([dir='rtl'] :is(.dark .baz:hover))::before { + background-color: #000; + } + :is([dir='rtl'] :is(.dark .qux))::file-selector-button:hover { + background-color: #000; + } + :is([dir='rtl'] :is(.dark .steve):hover):before { + background-color: #000; + } + :is([dir='rtl'] :is(.dark .bob))::file-selector-button:hover { + background-color: #000; + } + :has([dir='rtl'] .foo:hover):before { + background-color: #000; + } + :has([dir='rtl'] .bar)::file-selector-button:hover { + background-color: #000; + } + `) + }) + }) }) diff --git a/tests/important-selector.test.js b/tests/important-selector.test.js index 5c6515e43086..22edafb94dd1 100644 --- a/tests/important-selector.test.js +++ b/tests/important-selector.test.js @@ -21,6 +21,7 @@ crosscheck(({ stable, oxide }) => {
+
`, }, ], @@ -155,6 +156,12 @@ crosscheck(({ stable, oxide }) => { text-align: right; } } + #app + :is( + [dir='rtl'] :is(.dark .hover\:\[\&\:\:file-selector-button\]\:rtl\:dark\:bg-black\/100) + )::file-selector-button:hover { + background-color: #000; + } `) }) }) From 60c06dc293acd3d2d91fd5105b7bd5550684225a Mon Sep 17 00:00:00 2001 From: Joey Jan Date: Wed, 29 Mar 2023 16:54:57 -0400 Subject: [PATCH 3/8] Update the types for the `safelist` config (#10901) * Revert prepare of v3.3 * Revert "Revert prepare of v3.3" This reverts commit 14d5a0a7c6d457a9b73fa1a9aca6ed41c46b27ad. * update SafelistConfig type I think this type was meant to be like this? * format types * update changelog --------- Co-authored-by: Robin Malfait --- CHANGELOG.md | 1 + types/config.d.ts | 7 +------ 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fc7165535cc..e73dc309a188 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Try resolving `config.default` before `config` to ensure the config file is resolved correctly ([#10898](https://github.com/tailwindlabs/tailwindcss/pull/10898)) - Pull pseudo elements outside of `:is` and `:has` when using `@apply` ([#10903](https://github.com/tailwindlabs/tailwindcss/pull/10903)) +- Update the types for the `safelist` config ([#10901](https://github.com/tailwindlabs/tailwindcss/pull/10901)) ## [3.3.0] - 2023-03-27 diff --git a/types/config.d.ts b/types/config.d.ts index 81e3c342db16..bb40c8b71a10 100644 --- a/types/config.d.ts +++ b/types/config.d.ts @@ -46,12 +46,7 @@ type PrefixConfig = string type SeparatorConfig = string // Safelist related config -type SafelistConfig = - | string[] - | { - pattern: RegExp - variants?: string[] - }[] +type SafelistConfig = (string | { pattern: RegExp; variants?: string[] })[] // Blocklist related config type BlocklistConfig = string[] From 3193dae61d201b6a80bf93e72652c5b59d529588 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Wed, 29 Mar 2023 23:03:37 +0200 Subject: [PATCH 4/8] ensure workflows run for the 3.3 branch --- .github/workflows/ci-stable.yml | 2 +- .github/workflows/ci.yml | 4 ++-- .github/workflows/integration-tests-oxide.yml | 4 ++-- .github/workflows/integration-tests-stable.yml | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci-stable.yml b/.github/workflows/ci-stable.yml index e5807f50672c..60d6abb9a0c8 100644 --- a/.github/workflows/ci-stable.yml +++ b/.github/workflows/ci-stable.yml @@ -7,7 +7,7 @@ on: push: branches: [master] pull_request: - branches: [master] + branches: [master, 3.3] permissions: contents: read diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fc17ba99dd0e..29b96700c8e8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: CI — Oxide on: push: - branches: [master] + branches: [master, 3.3] pull_request: - branches: [master] + branches: [master, 3.3] permissions: contents: read diff --git a/.github/workflows/integration-tests-oxide.yml b/.github/workflows/integration-tests-oxide.yml index 27cd8f562417..b9b3e513d6f2 100644 --- a/.github/workflows/integration-tests-oxide.yml +++ b/.github/workflows/integration-tests-oxide.yml @@ -2,9 +2,9 @@ name: Integration Tests — Oxide on: push: - branches: [master] + branches: [master, 3.3] pull_request: - branches: [master] + branches: [master, 3.3] permissions: contents: read diff --git a/.github/workflows/integration-tests-stable.yml b/.github/workflows/integration-tests-stable.yml index 547d20f1c283..86329d4044bd 100644 --- a/.github/workflows/integration-tests-stable.yml +++ b/.github/workflows/integration-tests-stable.yml @@ -2,9 +2,9 @@ name: Integration Tests — Stable on: push: - branches: [master] + branches: [master, 3.3] pull_request: - branches: [master] + branches: [master, 3.3] permissions: contents: read From 447384bcbe658d2cf72949c04d4a5d7954181e90 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Wed, 29 Mar 2023 23:05:19 +0200 Subject: [PATCH 5/8] cleanup unused import --- src/util/applyImportantSelector.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/util/applyImportantSelector.js b/src/util/applyImportantSelector.js index dbaf136cce20..d85efcad2fd0 100644 --- a/src/util/applyImportantSelector.js +++ b/src/util/applyImportantSelector.js @@ -1,4 +1,3 @@ -import { splitAtTopLevelOnly } from './splitAtTopLevelOnly' import parser from 'postcss-selector-parser' import { collectPseudoElements, sortSelector } from './formatVariantSelector.js' From 9cd0301b0af16fb40acbe71443ab4d4f20346167 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 30 Mar 2023 18:42:38 +0200 Subject: [PATCH 6/8] Drop `@tailwindcss/line-clamp` warning (#10915) * drop `@tailwindcss/line-clamp` check This won't work in places where `require` calls are hoisted so that they become static imports. This means that in some projects this `require` call was breaking the full application even though it was intentionally put in a try/catch block... * update changelog --- CHANGELOG.md | 1 + src/util/normalizeConfig.js | 17 ----------------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e73dc309a188..1b40827804e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Try resolving `config.default` before `config` to ensure the config file is resolved correctly ([#10898](https://github.com/tailwindlabs/tailwindcss/pull/10898)) - Pull pseudo elements outside of `:is` and `:has` when using `@apply` ([#10903](https://github.com/tailwindlabs/tailwindcss/pull/10903)) - Update the types for the `safelist` config ([#10901](https://github.com/tailwindlabs/tailwindcss/pull/10901)) +- Drop `@tailwindcss/line-clamp` warning ([#10915](https://github.com/tailwindlabs/tailwindcss/pull/10915)) ## [3.3.0] - 2023-03-27 diff --git a/src/util/normalizeConfig.js b/src/util/normalizeConfig.js index 1c8c93db1c5d..7e1a592decda 100644 --- a/src/util/normalizeConfig.js +++ b/src/util/normalizeConfig.js @@ -297,22 +297,5 @@ export function normalizeConfig(config) { } } - // Warn if the line-clamp plugin is installed - if (config.plugins.length > 0) { - let plugin - try { - plugin = require('@tailwindcss/line-clamp') - } catch {} - - if (plugin && config.plugins.includes(plugin)) { - log.warn('line-clamp-in-core', [ - 'As of Tailwind CSS v3.3, the `@tailwindcss/line-clamp` plugin is now included by default.', - 'Remove it from the `plugins` array in your configuration to eliminate this warning.', - ]) - - config.plugins = config.plugins.filter((p) => p !== plugin) - } - } - return config } From 474178055eb67336d7d4a158a394b048faa3cf10 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Thu, 30 Mar 2023 15:06:17 -0400 Subject: [PATCH 7/8] Fix `@tailwindcss/line-clamp` warning (#10919) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * WIP * Move warning to validateConfig This only happens in setupTrackingContext outside of resolveConfig * Use original dynamic require approach in `validateConfig` The important thing is that this happens in Node-land only. It is outside of `resolveConfig` which is public and importable into user projects. That is the scenario that breaks because of static import hoisting. * Don’t reference process when it might be undefined The `resolveConfig` dep path is public which should not reference process. However, we have some behavior that changes based on env vars so we need to conditionalize it instead. * Update changelog * Formatting * More formatting * Update changelog --------- Co-authored-by: Robin Malfait Co-authored-by: Jonathan Reinink --- CHANGELOG.md | 3 ++- src/lib/sharedState.js | 21 +++++++++++++++------ src/util/validateConfig.js | 13 +++++++++++++ 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b40827804e3..50b2fc77d717 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Try resolving `config.default` before `config` to ensure the config file is resolved correctly ([#10898](https://github.com/tailwindlabs/tailwindcss/pull/10898)) - Pull pseudo elements outside of `:is` and `:has` when using `@apply` ([#10903](https://github.com/tailwindlabs/tailwindcss/pull/10903)) - Update the types for the `safelist` config ([#10901](https://github.com/tailwindlabs/tailwindcss/pull/10901)) -- Drop `@tailwindcss/line-clamp` warning ([#10915](https://github.com/tailwindlabs/tailwindcss/pull/10915)) +- Fix `@tailwindcss/line-clamp` warning ([#10915](https://github.com/tailwindlabs/tailwindcss/pull/10915), [#10919](https://github.com/tailwindlabs/tailwindcss/pull/10919)) +- Fix `process` is not defined error ([#10919](https://github.com/tailwindlabs/tailwindcss/pull/10919)) ## [3.3.0] - 2023-03-27 diff --git a/src/lib/sharedState.js b/src/lib/sharedState.js index 736a5d8bcc64..1a4a8d5e5b17 100644 --- a/src/lib/sharedState.js +++ b/src/lib/sharedState.js @@ -1,12 +1,21 @@ import pkg from '../../package.json' let OXIDE_DEFAULT_ENABLED = pkg.tailwindcss.engine === 'oxide' -export const env = { - NODE_ENV: process.env.NODE_ENV, - DEBUG: resolveDebug(process.env.DEBUG), - ENGINE: pkg.tailwindcss.engine, - OXIDE: resolveBoolean(process.env.OXIDE, OXIDE_DEFAULT_ENABLED), -} +export const env = + typeof process !== 'undefined' + ? { + NODE_ENV: process.env.NODE_ENV, + DEBUG: resolveDebug(process.env.DEBUG), + ENGINE: pkg.tailwindcss.engine, + OXIDE: resolveBoolean(process.env.OXIDE, OXIDE_DEFAULT_ENABLED), + } + : { + NODE_ENV: 'production', + DEBUG: false, + ENGINE: pkg.tailwindcss.engine, + OXIDE: OXIDE_DEFAULT_ENABLED, + } + export const contextMap = new Map() export const configContextMap = new Map() export const contextSourcesMap = new Map() diff --git a/src/util/validateConfig.js b/src/util/validateConfig.js index 23b4627fbb54..8c22e445035b 100644 --- a/src/util/validateConfig.js +++ b/src/util/validateConfig.js @@ -9,5 +9,18 @@ export function validateConfig(config) { ]) } + // Warn if the line-clamp plugin is installed + try { + let plugin = require('@tailwindcss/line-clamp') + if (config.plugins.includes(plugin)) { + log.warn('line-clamp-in-core', [ + 'As of Tailwind CSS v3.3, the `@tailwindcss/line-clamp` plugin is now included by default.', + 'Remove it from the `plugins` array in your configuration to eliminate this warning.', + ]) + + config.plugins = config.plugins.filter((p) => p !== plugin) + } + } catch {} + return config } From 1e55b798d74baa5a50921668c4e3231175c74cd7 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 30 Mar 2023 18:43:25 +0200 Subject: [PATCH 8/8] 3.3.1 --- CHANGELOG.md | 7 ++++++- package-lock.json | 4 ++-- package-lock.stable.json | 4 ++-- package.json | 2 +- package.stable.json | 2 +- 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50b2fc77d717..b654143f46d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Nothing yet! + +## [3.3.1] - 2023-03-30 + ### Fixed - Try resolving `config.default` before `config` to ensure the config file is resolved correctly ([#10898](https://github.com/tailwindlabs/tailwindcss/pull/10898)) @@ -2217,7 +2221,8 @@ No release notes - Everything! -[unreleased]: https://github.com/tailwindlabs/tailwindcss/compare/v3.3.0...HEAD +[unreleased]: https://github.com/tailwindlabs/tailwindcss/compare/v3.3.1...HEAD +[3.3.1]: https://github.com/tailwindlabs/tailwindcss/compare/v3.3.0...v3.3.1 [3.3.0]: https://github.com/tailwindlabs/tailwindcss/compare/v3.2.7...v3.3.0 [3.2.7]: https://github.com/tailwindlabs/tailwindcss/compare/v3.2.6...v3.2.7 [3.2.6]: https://github.com/tailwindlabs/tailwindcss/compare/v3.2.5...v3.2.6 diff --git a/package-lock.json b/package-lock.json index ed1615e6b1a1..10608712006b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "tailwindcss", - "version": "3.3.0", + "version": "3.3.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "tailwindcss", - "version": "3.3.0", + "version": "3.3.1", "license": "MIT", "workspaces": [ "integrations/*", diff --git a/package-lock.stable.json b/package-lock.stable.json index 1c419c1855ac..4fda6babf23b 100644 --- a/package-lock.stable.json +++ b/package-lock.stable.json @@ -1,12 +1,12 @@ { "name": "tailwindcss", - "version": "3.3.0", + "version": "3.3.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "tailwindcss", - "version": "3.3.0", + "version": "3.3.1", "license": "MIT", "dependencies": { "arg": "^5.0.2", diff --git a/package.json b/package.json index e2093e183061..6a240fd5c697 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tailwindcss", - "version": "3.3.0", + "version": "3.3.1", "description": "A utility-first CSS framework for rapidly building custom user interfaces.", "license": "MIT", "main": "lib/index.js", diff --git a/package.stable.json b/package.stable.json index 26cca2eea1f4..f758ded10e78 100644 --- a/package.stable.json +++ b/package.stable.json @@ -1,6 +1,6 @@ { "name": "tailwindcss", - "version": "3.3.0", + "version": "3.3.1", "description": "A utility-first CSS framework for rapidly building custom user interfaces.", "license": "MIT", "main": "lib/index.js",