mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2026-01-25 16:44:12 +00:00
Re-throw errors from PostCSS nodes (#18373)
Fixes #18370 --------- Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
This commit is contained in:
parent
c2aab49c77
commit
5b8136e838
@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Do not migrate `variant = 'outline'` during upgrades ([#18922](https://github.com/tailwindlabs/tailwindcss/pull/18922))
|
||||
- Show Lightning CSS warnings (if any) when optimizing/minifying ([#18918](https://github.com/tailwindlabs/tailwindcss/pull/18918))
|
||||
- Use `default` export condition for `@tailwindcss/vite` ([#18948](https://github.com/tailwindlabs/tailwindcss/pull/18948))
|
||||
- Re-throw errors from PostCSS nodes ([#18373](https://github.com/tailwindlabs/tailwindcss/pull/18373))
|
||||
|
||||
## [4.1.13] - 2025-09-03
|
||||
|
||||
|
||||
@ -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`
|
||||
<div class="flex underline"></div>
|
||||
`)
|
||||
|
||||
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 ---
|
||||
.underline {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
"
|
||||
`)
|
||||
|
||||
await process.onStderr((message) => message.includes('Waiting for file changes...'))
|
||||
|
||||
// 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;
|
||||
}
|
||||
"
|
||||
`)
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@ -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(postcss, ast)
|
||||
|
||||
expect(transformedAst.toString()).toMatchInlineSnapshot(`
|
||||
"@charset "UTF-8";
|
||||
|
||||
@ -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<Source, Input>((src) => {
|
||||
return new Input(src.code, {
|
||||
export function cssAstToPostCssAst(
|
||||
postcss: postcss.Postcss,
|
||||
ast: AstNode[],
|
||||
source?: postcss.Source,
|
||||
): postcss.Root {
|
||||
let inputMap = new DefaultMap<Source, postcss.Input>((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, Source>((input) => ({
|
||||
export function postCssAstToCssAst(root: postcss.Root): AstNode[] {
|
||||
let inputMap = new DefaultMap<postcss.Input, Source>((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<AstNode, { nodes: AstNode[] }>['nodes'],
|
||||
) {
|
||||
// Declaration
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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<typeof compileAst>
|
||||
scanner: null | Scanner
|
||||
tailwindCssAst: AstNode[]
|
||||
cachedPostCssAst: postcss.Root
|
||||
optimizedPostCssAst: postcss.Root
|
||||
cachedPostCssAst: Root
|
||||
optimizedPostCssAst: Root
|
||||
fullRebuildPaths: string[]
|
||||
}
|
||||
const cache = new QuickLRU<string, CacheEntry>({ maxSize: 50 })
|
||||
|
||||
function getContextFromCache(inputFile: string, opts: PluginOptions): CacheEntry {
|
||||
function getContextFromCache(postcss: Postcss, inputFile: string, opts: PluginOptions): CacheEntry {
|
||||
let key = `${inputFile}:${opts.base ?? ''}:${JSON.stringify(opts.optimize)}`
|
||||
if (cache.has(key)) return cache.get(key)!
|
||||
let entry = {
|
||||
@ -83,7 +83,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 ?? ''
|
||||
@ -114,7 +114,7 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
|
||||
DEBUG && I.end('Quick bail check')
|
||||
}
|
||||
|
||||
let context = getContextFromCache(inputFile, opts)
|
||||
let context = getContextFromCache(postcss, inputFile, opts)
|
||||
let inputBasePath = path.dirname(path.resolve(inputFile))
|
||||
|
||||
// Whether this is the first build or not, if it is, then we can
|
||||
@ -310,7 +310,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(postcss, tailwindCssAst, root.source)
|
||||
DEBUG && I.end('Transform Tailwind CSS AST into PostCSS AST')
|
||||
}
|
||||
}
|
||||
@ -349,7 +349,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}`)
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user