diff --git a/integrations/postcss/index.test.ts b/integrations/postcss/index.test.ts
index a811cf637661..dd07fd90ebbd 100644
--- a/integrations/postcss/index.test.ts
+++ b/integrations/postcss/index.test.ts
@@ -1,5 +1,5 @@
import path from 'node:path'
-import { candidate, css, html, js, json, retryAssertion, test, ts, yaml } from '../utils'
+import { candidate, css, html, js, json, test, ts, yaml } from '../utils'
test(
'production build (string)',
@@ -662,23 +662,56 @@ test(
`,
'src/index.css': css` @import './tailwind.css'; `,
'src/tailwind.css': css`
- @reference 'tailwindcss/does-not-exist';
+ @reference 'tailwindcss/theme';
@import 'tailwindcss/utilities';
`,
},
},
async ({ fs, expect, spawn }) => {
+ // 1. Start the watcher
+ //
+ // It must have valid CSS for the initial build
let process = await spawn('pnpm postcss src/index.css --output dist/out.css --watch --verbose')
+ await process.onStderr((message) => message.includes('Waiting for file changes...'))
+
+ expect(await fs.dumpFiles('dist/*.css')).toMatchInlineSnapshot(`
+ "
+ --- dist/out.css ---
+ .underline {
+ text-decoration-line: underline;
+ }
+ "
+ `)
+
+ // 2. Cause an error
+ await fs.write(
+ 'src/tailwind.css',
+ css`
+ @reference 'tailwindcss/does-not-exist';
+ @import 'tailwindcss/utilities';
+ `,
+ )
+
+ // 2.5 Write to a content file
+ await fs.write('src/index.html', html`
+
+ `)
+
await process.onStderr((message) =>
message.includes('does-not-exist is not exported from package'),
)
- await retryAssertion(async () => expect(await fs.read('dist/out.css')).toEqual(''))
-
- await process.onStderr((message) => message.includes('Waiting for file changes...'))
+ expect(await fs.dumpFiles('dist/*.css')).toMatchInlineSnapshot(`
+ "
+ --- dist/out.css ---
+ .underline {
+ text-decoration-line: underline;
+ }
+ "
+ `)
- // Fix the CSS file
+ // 3. Fix the CSS file
await fs.write(
'src/tailwind.css',
css`
@@ -686,11 +719,15 @@ test(
@import 'tailwindcss/utilities';
`,
)
- await process.onStderr((message) => message.includes('Finished'))
+
+ await process.onStderr((message) => message.includes('Waiting for file changes...'))
expect(await fs.dumpFiles('dist/*.css')).toMatchInlineSnapshot(`
"
--- dist/out.css ---
+ .flex {
+ display: flex;
+ }
.underline {
text-decoration-line: underline;
}
@@ -705,11 +742,22 @@ test(
@import 'tailwindcss/utilities';
`,
)
+
await process.onStderr((message) =>
message.includes('does-not-exist is not exported from package'),
)
- await retryAssertion(async () => expect(await fs.read('dist/out.css')).toEqual(''))
+ expect(await fs.dumpFiles('dist/*.css')).toMatchInlineSnapshot(`
+ "
+ --- dist/out.css ---
+ .flex {
+ display: flex;
+ }
+ .underline {
+ text-decoration-line: underline;
+ }
+ "
+ `)
},
)
diff --git a/packages/@tailwindcss-postcss/src/ast.test.ts b/packages/@tailwindcss-postcss/src/ast.test.ts
index dd7095428e1c..cb0161bcea1b 100644
--- a/packages/@tailwindcss-postcss/src/ast.test.ts
+++ b/packages/@tailwindcss-postcss/src/ast.test.ts
@@ -84,7 +84,7 @@ it('should convert a Tailwind CSS AST into a PostCSS AST', () => {
`
let ast = parse(input)
- let transformedAst = cssAstToPostCssAst(ast)
+ let transformedAst = cssAstToPostCssAst(ast, undefined, postcss)
expect(transformedAst.toString()).toMatchInlineSnapshot(`
"@charset "UTF-8";
diff --git a/packages/@tailwindcss-postcss/src/ast.ts b/packages/@tailwindcss-postcss/src/ast.ts
index 1912fc84b789..2ec8b88b6caf 100644
--- a/packages/@tailwindcss-postcss/src/ast.ts
+++ b/packages/@tailwindcss-postcss/src/ast.ts
@@ -1,10 +1,4 @@
-import postcss, {
- Input,
- type ChildNode as PostCssChildNode,
- type Container as PostCssContainerNode,
- type Root as PostCssRoot,
- type Source as PostcssSource,
-} from 'postcss'
+import type * as postcss from 'postcss'
import { atRule, comment, decl, rule, type AstNode } from '../../tailwindcss/src/ast'
import { createLineTable, type LineTable } from '../../tailwindcss/src/source-maps/line-table'
import type { Source, SourceLocation } from '../../tailwindcss/src/source-maps/source'
@@ -12,9 +6,13 @@ import { DefaultMap } from '../../tailwindcss/src/utils/default-map'
const EXCLAMATION_MARK = 0x21
-export function cssAstToPostCssAst(ast: AstNode[], source: PostcssSource | undefined): PostCssRoot {
- let inputMap = new DefaultMap((src) => {
- return new Input(src.code, {
+export function cssAstToPostCssAst(
+ ast: AstNode[],
+ source: postcss.Source | undefined,
+ postcss: postcss.Postcss,
+): postcss.Root {
+ let inputMap = new DefaultMap((src) => {
+ return new postcss.Input(src.code, {
map: source?.input.map,
from: src.file ?? undefined,
})
@@ -25,7 +23,7 @@ export function cssAstToPostCssAst(ast: AstNode[], source: PostcssSource | undef
let root = postcss.root()
root.source = source
- function toSource(loc: SourceLocation | undefined): PostcssSource | undefined {
+ function toSource(loc: SourceLocation | undefined): postcss.Source | undefined {
// Use the fallback if this node has no location info in the AST
if (!loc) return
if (!loc[0]) return
@@ -49,7 +47,7 @@ export function cssAstToPostCssAst(ast: AstNode[], source: PostcssSource | undef
}
}
- function updateSource(astNode: PostCssChildNode, loc: SourceLocation | undefined) {
+ function updateSource(astNode: postcss.ChildNode, loc: SourceLocation | undefined) {
let source = toSource(loc)
// The `source` property on PostCSS nodes must be defined if present because
@@ -63,7 +61,7 @@ export function cssAstToPostCssAst(ast: AstNode[], source: PostcssSource | undef
}
}
- function transform(node: AstNode, parent: PostCssContainerNode) {
+ function transform(node: AstNode, parent: postcss.Container) {
// Declaration
if (node.kind === 'declaration') {
let astNode = postcss.decl({
@@ -125,13 +123,13 @@ export function cssAstToPostCssAst(ast: AstNode[], source: PostcssSource | undef
return root
}
-export function postCssAstToCssAst(root: PostCssRoot): AstNode[] {
- let inputMap = new DefaultMap((input) => ({
+export function postCssAstToCssAst(root: postcss.Root): AstNode[] {
+ let inputMap = new DefaultMap((input) => ({
file: input.file ?? input.id ?? null,
code: input.css,
}))
- function toSource(node: PostCssChildNode): SourceLocation | undefined {
+ function toSource(node: postcss.ChildNode): SourceLocation | undefined {
let source = node.source
if (!source) return
@@ -144,7 +142,7 @@ export function postCssAstToCssAst(root: PostCssRoot): AstNode[] {
}
function transform(
- node: PostCssChildNode,
+ node: postcss.ChildNode,
parent: Extract['nodes'],
) {
// Declaration
diff --git a/packages/@tailwindcss-postcss/src/index.test.ts b/packages/@tailwindcss-postcss/src/index.test.ts
index 4227d6fbbf93..593ef70cff5a 100644
--- a/packages/@tailwindcss-postcss/src/index.test.ts
+++ b/packages/@tailwindcss-postcss/src/index.test.ts
@@ -391,7 +391,7 @@ describe('concurrent builds', () => {
let ast = postcss.parse(input)
for (let runner of (plugin as any).plugins) {
if (runner.Once) {
- await runner.Once(ast, { result: { opts: { from }, messages: [] } })
+ await runner.Once(ast, { postcss, result: { opts: { from }, messages: [] } })
}
}
return ast.toString()
diff --git a/packages/@tailwindcss-postcss/src/index.ts b/packages/@tailwindcss-postcss/src/index.ts
index 8a37f542c94f..5e9cf49b09d7 100644
--- a/packages/@tailwindcss-postcss/src/index.ts
+++ b/packages/@tailwindcss-postcss/src/index.ts
@@ -11,7 +11,7 @@ import { clearRequireCache } from '@tailwindcss/node/require-cache'
import { Scanner } from '@tailwindcss/oxide'
import fs from 'node:fs'
import path, { relative } from 'node:path'
-import postcss, { type AcceptedPlugin, type PluginCreator } from 'postcss'
+import type { AcceptedPlugin, PluginCreator, Postcss, Root } from 'postcss'
import { toCss, type AstNode } from '../../tailwindcss/src/ast'
import { cssAstToPostCssAst, postCssAstToCssAst } from './ast'
import fixRelativePathsPlugin from './postcss-fix-relative-paths'
@@ -23,13 +23,13 @@ interface CacheEntry {
compiler: null | ReturnType
scanner: null | Scanner
tailwindCssAst: AstNode[]
- cachedPostCssAst: postcss.Root
- optimizedPostCssAst: postcss.Root
+ cachedPostCssAst: Root
+ optimizedPostCssAst: Root
fullRebuildPaths: string[]
}
const cache = new QuickLRU({ maxSize: 50 })
-function getContextFromCache(inputFile: string, opts: PluginOptions): CacheEntry {
+function getContextFromCache(inputFile: string, opts: PluginOptions, postcss: Postcss): CacheEntry {
let key = `${inputFile}:${opts.base ?? ''}:${JSON.stringify(opts.optimize)}`
if (cache.has(key)) return cache.get(key)!
let entry = {
@@ -69,7 +69,7 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
{
postcssPlugin: 'tailwindcss',
- async Once(root, { result }) {
+ async Once(root, { result, postcss }) {
using I = new Instrumentation()
let inputFile = result.opts.from ?? ''
@@ -100,7 +100,7 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
DEBUG && I.end('Quick bail check')
}
- let context = getContextFromCache(inputFile, opts)
+ let context = getContextFromCache(inputFile, opts, postcss)
let inputBasePath = path.dirname(path.resolve(inputFile))
// Whether this is the first build or not, if it is, then we can
@@ -296,7 +296,7 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
} else {
// Convert our AST to a PostCSS AST
DEBUG && I.start('Transform Tailwind CSS AST into PostCSS AST')
- context.cachedPostCssAst = cssAstToPostCssAst(tailwindCssAst, root.source)
+ context.cachedPostCssAst = cssAstToPostCssAst(tailwindCssAst, root.source, postcss)
DEBUG && I.end('Transform Tailwind CSS AST into PostCSS AST')
}
}
@@ -335,7 +335,12 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
// We found that throwing the error will cause PostCSS to no longer watch for changes
// in some situations so we instead log the error and continue with an empty stylesheet.
console.error(error)
- root.removeAll()
+
+ if (error && typeof error === 'object' && 'message' in error) {
+ throw root.error(`${error.message}`)
+ }
+
+ throw root.error(`${error}`)
}
},
},