Handle BOM (#16800)
Resolves #15662 Resolves #15467 ## Test plan Added integration tests for upgrade tooling (which already worked surprisingly?) and CLI.
This commit is contained in:
parent
662c6862ac
commit
294952f170
6 changed files with 164 additions and 3 deletions
|
|
@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Ensure `@reference "…"` does not emit CSS variables ([#16774](https://github.com/tailwindlabs/tailwindcss/pull/16774))
|
||||
- Fix an issue where `@reference "…"` would sometimes omit keyframe animations ([#16774](https://github.com/tailwindlabs/tailwindcss/pull/16774))
|
||||
- Ensure `z-*!` utilities are property marked as `!important` ([#16795](https://github.com/tailwindlabs/tailwindcss/pull/16795))
|
||||
- Read UTF-8 CSS files that start with a byte-order mark (BOM) ([#16796](https://github.com/tailwindlabs/tailwindcss/pull/16796))
|
||||
|
||||
## [4.0.8] - 2025-02-21
|
||||
|
||||
|
|
|
|||
|
|
@ -1314,3 +1314,79 @@ test(
|
|||
)
|
||||
},
|
||||
)
|
||||
|
||||
test(
|
||||
'can read files with UTF-8 files with BOM',
|
||||
{
|
||||
fs: {
|
||||
'package.json': json`
|
||||
{
|
||||
"dependencies": {
|
||||
"tailwindcss": "workspace:^",
|
||||
"@tailwindcss/cli": "workspace:^"
|
||||
}
|
||||
}
|
||||
`,
|
||||
'index.css': withBOM(css`
|
||||
@reference 'tailwindcss/theme.css';
|
||||
@import 'tailwindcss/utilities';
|
||||
`),
|
||||
'index.html': withBOM(html`
|
||||
<div class="underline"></div>
|
||||
`),
|
||||
},
|
||||
},
|
||||
async ({ fs, exec, expect }) => {
|
||||
await exec('pnpm tailwindcss --input index.css --output dist/out.css')
|
||||
|
||||
expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(`
|
||||
"
|
||||
--- ./dist/out.css ---
|
||||
.underline {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
"
|
||||
`)
|
||||
},
|
||||
)
|
||||
|
||||
test(
|
||||
'fails when reading files with UTF-16 files with BOM',
|
||||
{
|
||||
fs: {
|
||||
'package.json': json`
|
||||
{
|
||||
"dependencies": {
|
||||
"tailwindcss": "workspace:^",
|
||||
"@tailwindcss/cli": "workspace:^"
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
async ({ fs, exec, expect }) => {
|
||||
await fs.write(
|
||||
'index.css',
|
||||
withBOM(css`
|
||||
@reference 'tailwindcss/theme.css';
|
||||
@import 'tailwindcss/utilities';
|
||||
`),
|
||||
'utf16le',
|
||||
)
|
||||
await fs.write(
|
||||
'index.html',
|
||||
withBOM(html`
|
||||
<div class="underline"></div>
|
||||
`),
|
||||
'utf16le',
|
||||
)
|
||||
|
||||
await expect(exec('pnpm tailwindcss --input index.css --output dist/out.css')).rejects.toThrow(
|
||||
/Invalid declaration:/,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
function withBOM(text: string): string {
|
||||
return '\uFEFF' + text
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2745,3 +2745,71 @@ test(
|
|||
`)
|
||||
},
|
||||
)
|
||||
|
||||
test(
|
||||
`can read files with BOM`,
|
||||
{
|
||||
fs: {
|
||||
'package.json': json`
|
||||
{
|
||||
"dependencies": {
|
||||
"tailwindcss": "^3",
|
||||
"@tailwindcss/upgrade": "workspace:^"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/cli": "workspace:^"
|
||||
}
|
||||
}
|
||||
`,
|
||||
'tailwind.config.js': js`
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ['./src/**/*.{html,js}'],
|
||||
}
|
||||
`,
|
||||
'src/index.html': withBOM(html`
|
||||
<div class="ring"></div>
|
||||
`),
|
||||
'src/input.css': withBOM(css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`),
|
||||
},
|
||||
},
|
||||
async ({ exec, fs, expect }) => {
|
||||
await exec('npx @tailwindcss/upgrade')
|
||||
|
||||
expect(await fs.dumpFiles('./src/**/*.{css,html}')).toMatchInlineSnapshot(`
|
||||
"
|
||||
--- ./src/index.html ---
|
||||
<div class="ring-3"></div>
|
||||
|
||||
--- ./src/input.css ---
|
||||
@import 'tailwindcss';
|
||||
|
||||
/*
|
||||
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
|
||||
so we've added these compatibility styles to make sure everything still
|
||||
looks the same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add an explicit border
|
||||
color utility to any element that depends on these defaults.
|
||||
*/
|
||||
@layer base {
|
||||
*,
|
||||
::after,
|
||||
::before,
|
||||
::backdrop,
|
||||
::file-selector-button {
|
||||
border-color: var(--color-gray-200, currentColor);
|
||||
}
|
||||
}
|
||||
"
|
||||
`)
|
||||
},
|
||||
)
|
||||
|
||||
function withBOM(text: string): string {
|
||||
return '\uFEFF' + text
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ interface TestContext {
|
|||
exec(command: string, options?: ChildProcessOptions, execOptions?: ExecOptions): Promise<string>
|
||||
spawn(command: string, options?: ChildProcessOptions): Promise<SpawnedProcess>
|
||||
fs: {
|
||||
write(filePath: string, content: string): Promise<void>
|
||||
write(filePath: string, content: string, encoding?: BufferEncoding): Promise<void>
|
||||
create(filePaths: string[]): Promise<void>
|
||||
read(filePath: string): Promise<string>
|
||||
glob(pattern: string): Promise<[string, string][]>
|
||||
|
|
@ -268,7 +268,11 @@ export function test(
|
|||
}
|
||||
},
|
||||
fs: {
|
||||
async write(filename: string, content: string | Uint8Array): Promise<void> {
|
||||
async write(
|
||||
filename: string,
|
||||
content: string | Uint8Array,
|
||||
encoding: BufferEncoding = 'utf8',
|
||||
): Promise<void> {
|
||||
let full = path.join(root, filename)
|
||||
let dir = path.dirname(full)
|
||||
await fs.mkdir(dir, { recursive: true })
|
||||
|
|
@ -286,7 +290,7 @@ export function test(
|
|||
content = content.replace(/\n/g, '\r\n')
|
||||
}
|
||||
|
||||
await fs.writeFile(full, content, 'utf-8')
|
||||
await fs.writeFile(full, content, encoding)
|
||||
},
|
||||
|
||||
async create(filenames: string[]): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -1154,4 +1154,15 @@ describe.each(['Unix', 'Windows'])('Line endings: %s', (lineEndings) => {
|
|||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('ignores BOM at the beginning of a file', () => {
|
||||
expect(parse("\uFEFF@reference 'tailwindcss';")).toEqual([
|
||||
{
|
||||
kind: 'at-rule',
|
||||
name: '@reference',
|
||||
nodes: [],
|
||||
params: "'tailwindcss'",
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ const AT_SIGN = 0x40
|
|||
const EXCLAMATION_MARK = 0x21
|
||||
|
||||
export function parse(input: string) {
|
||||
if (input[0] === '\uFEFF') input = input.slice(1)
|
||||
input = input.replaceAll('\r\n', '\n')
|
||||
|
||||
let ast: AstNode[] = []
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue