Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add @content support to @tailwindcss/postcss #14080

Merged
164 changes: 164 additions & 0 deletions integrations/postcss/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import path from 'node:path'
import { candidate, css, html, js, json, test, yaml } from '../utils'

test(
'production build',
{
fs: {
'package.json': json`{}`,
'pnpm-workspace.yaml': yaml`
#
packages:
- project-a
`,
'project-a/package.json': json`
{
"dependencies": {
"postcss": "^8",
"postcss-cli": "^10",
"tailwindcss": "workspace:^",
"@tailwindcss/postcss": "workspace:^"
}
}
`,
'project-a/postcss.config.js': js`
module.exports = {
plugins: {
'@tailwindcss/postcss': {},
},
}
`,
'project-a/index.html': html`
<div
class="underline 2xl:font-bold hocus:underline inverted:flex"
></div>
`,
'project-a/plugin.js': js`
module.exports = function ({ addVariant }) {
addVariant('inverted', '@media (inverted-colors: inverted)')
addVariant('hocus', ['&:focus', '&:hover'])
}
`,
'project-a/src/index.css': css`
@import 'tailwindcss/utilities';
@content '../../project-b/src/**/*.js';
@plugin '../plugin.js';
`,
'project-a/src/index.js': js`
const className = "content-['a/src/index.js']"
module.exports = { className }
`,
'project-b/src/index.js': js`
const className = "content-['b/src/index.js']"
module.exports = { className }
`,
},
},
async ({ root, fs, exec }) => {
await exec('pnpm postcss src/index.css --output dist/out.css', {
cwd: path.join(root, 'project-a'),
})

await fs.expectFileToContain('project-a/dist/out.css', [
candidate`underline`,
candidate`content-['a/src/index.js']`,
candidate`content-['b/src/index.js']`,
candidate`inverted:flex`,
candidate`hocus:underline`,
])
},
)

test(
'watch mode',
{
fs: {
'package.json': json`{}`,
'pnpm-workspace.yaml': yaml`
#
packages:
- project-a
`,
'project-a/package.json': json`
{
"dependencies": {
"postcss": "^8",
"postcss-cli": "^10",
"tailwindcss": "workspace:^",
"@tailwindcss/postcss": "workspace:^"
}
}
`,
'project-a/postcss.config.js': js`
module.exports = {
plugins: {
'@tailwindcss/postcss': {},
},
}
`,
'project-a/index.html': html`
<div
class="underline 2xl:font-bold hocus:underline inverted:flex"
></div>
`,
'project-a/plugin.js': js`
module.exports = function ({ addVariant }) {
addVariant('inverted', '@media (inverted-colors: inverted)')
addVariant('hocus', ['&:focus', '&:hover'])
}
`,
'project-a/src/index.css': css`
@import 'tailwindcss/utilities';
@content '../../project-b/src/**/*.js';
@plugin '../plugin.js';
`,
'project-a/src/index.js': js`
const className = "content-['a/src/index.js']"
module.exports = { className }
`,
'project-b/src/index.js': js`
const className = "content-['b/src/index.js']"
module.exports = { className }
`,
},
},
async ({ root, fs, spawn }) => {
let process = await spawn(
'pnpm postcss src/index.css --output dist/out.css --watch --verbose',
{ cwd: path.join(root, 'project-a') },
)
await process.onStderr((message) => message.includes('Waiting for file changes...'))

await fs.expectFileToContain('project-a/dist/out.css', [
candidate`underline`,
candidate`content-['a/src/index.js']`,
candidate`content-['b/src/index.js']`,
candidate`inverted:flex`,
candidate`hocus:underline`,
])

await fs.write(
'project-a/src/index.js',
js`
const className = "[.changed_&]:content-['project-a/src/index.js']"
module.exports = { className }
`,
)

await fs.expectFileToContain('project-a/dist/out.css', [
candidate`[.changed_&]:content-['project-a/src/index.js']`,
])

await fs.write(
'project-b/src/index.js',
js`
const className = "[.changed_&]:content-['project-b/src/index.js']"
module.exports = { className }
`,
)

await fs.expectFileToContain('project-a/dist/out.css', [
candidate`[.changed_&]:content-['project-b/src/index.js']`,
])
},
)
10 changes: 9 additions & 1 deletion integrations/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,18 @@ export function test(
}

let disposables: (() => Promise<void>)[] = []

async function dispose() {
await Promise.all(disposables.map((dispose) => dispose()))
await fs.rm(root, { recursive: true, maxRetries: 3, force: true })
try {
await fs.rm(root, { recursive: true, maxRetries: 5, force: true })
} catch (err) {
if (!process.env.CI) {
throw err
}
}
}

options.onTestFinished(dispose)

let context = {
Expand Down
2 changes: 1 addition & 1 deletion packages/@tailwindcss-postcss/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const INPUT_CSS_PATH = `${__dirname}/fixtures/example-project/input.css`
const css = String.raw

beforeEach(async () => {
const { clearCache } = await import('@tailwindcss/oxide')
let { clearCache } = await import('@tailwindcss/oxide')
clearCache()
})

Expand Down
47 changes: 30 additions & 17 deletions packages/@tailwindcss-postcss/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
let cache = new DefaultMap(() => {
return {
mtimes: new Map<string, number>(),
build: null as null | ReturnType<typeof compile>['build'],
compiler: null as null | ReturnType<typeof compile>,
css: '',
optimizedCss: '',
}
Expand Down Expand Up @@ -76,6 +76,23 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
OnceExit(root, { result }) {
let inputFile = result.opts.from ?? ''
let context = cache.get(inputFile)
let inputBasePath = path.dirname(path.resolve(inputFile))

function createCompiler() {
return compile(root.toString(), {
loadPlugin: (pluginPath) => {
if (pluginPath[0] === '.') {
return require(path.resolve(inputBasePath, pluginPath))
}

return require(pluginPath)
},
})
}

// Setup the compiler if it doesn't exist yet. This way we can
// guarantee a `compile()` function is available.
context.compiler ??= createCompiler()

let rebuildStrategy: 'full' | 'incremental' = 'incremental'

Expand Down Expand Up @@ -109,10 +126,16 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
let css = ''

// Look for candidates used to generate the CSS
let { candidates, files, globs } = scanDir({ base })
let scanDirResult = scanDir({
base, // Root directory, mainly used for auto content detection
contentPaths: context.compiler.globs.map((glob) => ({
base: inputBasePath, // Globs are relative to the input.css file
glob,
})),
})

// Add all found files as direct dependencies
for (let file of files) {
for (let file of scanDirResult.files) {
result.messages.push({
type: 'dependency',
plugin: '@tailwindcss/postcss',
Expand All @@ -124,7 +147,7 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
// Register dependencies so changes in `base` cause a rebuild while
// giving tools like Vite or Parcel a glob that can be used to limit
// the files that cause a rebuild to only those that match it.
for (let { base, glob } of globs) {
for (let { base, glob } of scanDirResult.globs) {
result.messages.push({
type: 'dir-dependency',
plugin: '@tailwindcss/postcss',
Expand All @@ -135,20 +158,10 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
}

if (rebuildStrategy === 'full') {
let basePath = path.dirname(path.resolve(inputFile))
let { build } = compile(root.toString(), {
loadPlugin: (pluginPath) => {
if (pluginPath[0] === '.') {
return require(path.resolve(basePath, pluginPath))
}

return require(pluginPath)
},
})
context.build = build
css = build(hasTailwind ? candidates : [])
context.compiler = createCompiler()
css = context.compiler.build(hasTailwind ? scanDirResult.candidates : [])
} else if (rebuildStrategy === 'incremental') {
css = context.build!(candidates)
css = context.compiler.build!(scanDirResult.candidates)
}

// Replace CSS
Expand Down
Loading