mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2026-02-01 17:26:34 +00:00
Fix source map generation during when watching files on the CLI (#19373)
Fixes #19362 We were overwriting the source map with the "decoded" map returned by the compiler but didn't wrap it in the helper intended to help inline vs file maps. This resulted in two issues: 1. `undefined` being appended to the CSS file when using `--map` 2. `undefined` being passed to `writeFile(…)` when using `--map <file>` This PR fixes both.
This commit is contained in:
parent
28670a9b91
commit
0e8f075ca2
@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Skip over arbitrary property utilities with a top-level `!` in the value ([#19243](https://github.com/tailwindlabs/tailwindcss/pull/19243))
|
||||
- Support environment API in `@tailwindcss/vite` ([#18970](https://github.com/tailwindlabs/tailwindcss/pull/18970))
|
||||
- Preserve case of theme keys from JS configs and plugins ([#19337](https://github.com/tailwindlabs/tailwindcss/pull/19337))
|
||||
- Write source maps correctly on the CLI when using `--watch` ([#19373](https://github.com/tailwindlabs/tailwindcss/pull/19373))
|
||||
- Upgrade: Handle `future` and `experimental` config keys ([#19344](https://github.com/tailwindlabs/tailwindcss/pull/19344))
|
||||
|
||||
### Added
|
||||
|
||||
@ -3,7 +3,7 @@ import os from 'node:os'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { describe } from 'vitest'
|
||||
import { candidate, css, html, js, json, test, ts, yaml } from '../utils'
|
||||
import { candidate, css, html, js, json, retryAssertion, test, ts, yaml } from '../utils'
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
@ -819,6 +819,481 @@ describe.each([
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
test(
|
||||
'watch mode + inline source maps',
|
||||
{
|
||||
fs: {
|
||||
'package.json': json`
|
||||
{
|
||||
"dependencies": {
|
||||
"tailwindcss": "workspace:^",
|
||||
"@tailwindcss/cli": "workspace:^"
|
||||
}
|
||||
}
|
||||
`,
|
||||
'ssrc/index.html': html`
|
||||
<div class="flex"></div>
|
||||
`,
|
||||
'src/index.css': css`
|
||||
@import 'tailwindcss/utilities';
|
||||
/* */
|
||||
`,
|
||||
},
|
||||
},
|
||||
async ({ spawn, expect, fs, parseSourceMap }) => {
|
||||
let process = await spawn(
|
||||
`${command} --input src/index.css --output dist/out.css --map --watch`,
|
||||
)
|
||||
await process.onStderr((m) => m.includes('Done in'))
|
||||
|
||||
await fs.expectFileToContain('dist/out.css', [candidate`flex`])
|
||||
|
||||
let originalCss = await fs.read('dist/out.css')
|
||||
let currentCss = originalCss
|
||||
|
||||
// Make sure we can find a source map
|
||||
let map = parseSourceMap(currentCss)
|
||||
|
||||
expect(map.at(1, 0)).toMatchObject({
|
||||
source: null,
|
||||
original: '(none)',
|
||||
generated: '/*! tailwi...',
|
||||
})
|
||||
|
||||
expect(map.at(2, 0)).toMatchObject({
|
||||
source:
|
||||
kind === 'CLI'
|
||||
? expect.stringContaining('utilities.css')
|
||||
: expect.stringMatching(/\/utilities-\w+\.css$/),
|
||||
original: '@tailwind...',
|
||||
generated: '.flex {...',
|
||||
})
|
||||
|
||||
expect(map.at(3, 2)).toMatchObject({
|
||||
source:
|
||||
kind === 'CLI'
|
||||
? expect.stringContaining('utilities.css')
|
||||
: expect.stringMatching(/\/utilities-\w+\.css$/),
|
||||
original: '@tailwind...',
|
||||
generated: 'display: f...',
|
||||
})
|
||||
|
||||
expect(map.at(4, 0)).toMatchObject({
|
||||
source: null,
|
||||
original: '(none)',
|
||||
generated: '}...',
|
||||
})
|
||||
|
||||
// Write to project source files
|
||||
await fs.write('src/index.html', html`
|
||||
<div class="flex underline"></div>
|
||||
`)
|
||||
|
||||
// Wait for the CSS to be rebuilt
|
||||
await retryAssertion(async () => {
|
||||
currentCss = await fs.read('dist/out.css')
|
||||
expect(currentCss).not.toEqual(originalCss)
|
||||
originalCss = currentCss
|
||||
})
|
||||
|
||||
// Make sure the source map was updated
|
||||
map = parseSourceMap(currentCss)
|
||||
|
||||
expect(map.at(1, 0)).toMatchObject({
|
||||
source: null,
|
||||
original: '(none)',
|
||||
generated: '/*! tailwi...',
|
||||
})
|
||||
|
||||
expect(map.at(2, 0)).toMatchObject({
|
||||
source:
|
||||
kind === 'CLI'
|
||||
? expect.stringContaining('utilities.css')
|
||||
: expect.stringMatching(/\/utilities-\w+\.css$/),
|
||||
original: '@tailwind...',
|
||||
generated: '.flex {...',
|
||||
})
|
||||
|
||||
expect(map.at(3, 2)).toMatchObject({
|
||||
source:
|
||||
kind === 'CLI'
|
||||
? expect.stringContaining('utilities.css')
|
||||
: expect.stringMatching(/\/utilities-\w+\.css$/),
|
||||
original: '@tailwind...',
|
||||
generated: 'display: f...',
|
||||
})
|
||||
|
||||
expect(map.at(4, 0)).toMatchObject({
|
||||
source: null,
|
||||
original: '(none)',
|
||||
generated: '}\n.underli...',
|
||||
})
|
||||
|
||||
expect(map.at(5, 0)).toMatchObject({
|
||||
source:
|
||||
kind === 'CLI'
|
||||
? expect.stringContaining('utilities.css')
|
||||
: expect.stringMatching(/\/utilities-\w+\.css$/),
|
||||
original: '@tailwind...',
|
||||
generated: '.underline...',
|
||||
})
|
||||
|
||||
expect(map.at(6, 2)).toMatchObject({
|
||||
source:
|
||||
kind === 'CLI'
|
||||
? expect.stringContaining('utilities.css')
|
||||
: expect.stringMatching(/\/utilities-\w+\.css$/),
|
||||
original: '@tailwind...',
|
||||
generated: 'text-decor...',
|
||||
})
|
||||
|
||||
expect(map.at(7, 0)).toMatchObject({
|
||||
source: null,
|
||||
original: '(none)',
|
||||
generated: '}...',
|
||||
})
|
||||
|
||||
// Write to the main CSS file
|
||||
await fs.write(
|
||||
'src/index.css',
|
||||
css`
|
||||
@import 'tailwindcss/utilities';
|
||||
@source inline("w-auto");
|
||||
`,
|
||||
)
|
||||
|
||||
// Wait for the CSS to be rebuilt
|
||||
await retryAssertion(async () => {
|
||||
currentCss = await fs.read('dist/out.css')
|
||||
expect(currentCss).not.toEqual(originalCss)
|
||||
originalCss = currentCss
|
||||
})
|
||||
|
||||
// Make sure the source map was updated
|
||||
map = parseSourceMap(currentCss)
|
||||
|
||||
expect(map.at(1, 0)).toMatchObject({
|
||||
source: null,
|
||||
original: '(none)',
|
||||
generated: '/*! tailwi...',
|
||||
})
|
||||
|
||||
expect(map.at(2, 0)).toMatchObject({
|
||||
source:
|
||||
kind === 'CLI'
|
||||
? expect.stringContaining('utilities.css')
|
||||
: expect.stringMatching(/\/utilities-\w+\.css$/),
|
||||
original: '@tailwind...',
|
||||
generated: '.flex {...',
|
||||
})
|
||||
|
||||
expect(map.at(3, 2)).toMatchObject({
|
||||
source:
|
||||
kind === 'CLI'
|
||||
? expect.stringContaining('utilities.css')
|
||||
: expect.stringMatching(/\/utilities-\w+\.css$/),
|
||||
original: '@tailwind...',
|
||||
generated: 'display: f...',
|
||||
})
|
||||
|
||||
expect(map.at(4, 0)).toMatchObject({
|
||||
source: null,
|
||||
original: '(none)',
|
||||
generated: '}\n.w-auto...',
|
||||
})
|
||||
|
||||
expect(map.at(5, 0)).toMatchObject({
|
||||
source:
|
||||
kind === 'CLI'
|
||||
? expect.stringContaining('utilities.css')
|
||||
: expect.stringMatching(/\/utilities-\w+\.css$/),
|
||||
original: '@tailwind...',
|
||||
generated: '.w-auto {...',
|
||||
})
|
||||
|
||||
expect(map.at(6, 2)).toMatchObject({
|
||||
source:
|
||||
kind === 'CLI'
|
||||
? expect.stringContaining('utilities.css')
|
||||
: expect.stringMatching(/\/utilities-\w+\.css$/),
|
||||
original: '@tailwind...',
|
||||
generated: 'width: aut...',
|
||||
})
|
||||
|
||||
expect(map.at(7, 0)).toMatchObject({
|
||||
source: null,
|
||||
original: '(none)',
|
||||
generated: '}\n.underli...',
|
||||
})
|
||||
|
||||
expect(map.at(8, 0)).toMatchObject({
|
||||
source:
|
||||
kind === 'CLI'
|
||||
? expect.stringContaining('utilities.css')
|
||||
: expect.stringMatching(/\/utilities-\w+\.css$/),
|
||||
original: '@tailwind...',
|
||||
generated: '.underline...',
|
||||
})
|
||||
|
||||
expect(map.at(9, 2)).toMatchObject({
|
||||
source:
|
||||
kind === 'CLI'
|
||||
? expect.stringContaining('utilities.css')
|
||||
: expect.stringMatching(/\/utilities-\w+\.css$/),
|
||||
original: '@tailwind...',
|
||||
generated: 'text-decor...',
|
||||
})
|
||||
|
||||
expect(map.at(10, 0)).toMatchObject({
|
||||
source: null,
|
||||
original: '(none)',
|
||||
generated: '}...',
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
test(
|
||||
'watch mode + separate source maps',
|
||||
{
|
||||
fs: {
|
||||
'package.json': json`
|
||||
{
|
||||
"dependencies": {
|
||||
"tailwindcss": "workspace:^",
|
||||
"@tailwindcss/cli": "workspace:^"
|
||||
}
|
||||
}
|
||||
`,
|
||||
'ssrc/index.html': html`
|
||||
<div class="flex"></div>
|
||||
`,
|
||||
'src/index.css': css`
|
||||
@import 'tailwindcss/utilities';
|
||||
/* */
|
||||
`,
|
||||
},
|
||||
},
|
||||
async ({ spawn, expect, fs, parseSourceMap }) => {
|
||||
let process = await spawn(
|
||||
`${command} --input src/index.css --output dist/out.css --map dist/out.css.map --watch`,
|
||||
)
|
||||
await process.onStderr((m) => m.includes('Done in'))
|
||||
|
||||
await fs.expectFileToContain('dist/out.css', [candidate`flex`])
|
||||
|
||||
// Make sure we can find a source map
|
||||
let originalCss = await fs.read('dist/out.css')
|
||||
let originalMap = await fs.read('dist/out.css.map')
|
||||
let currentCss = originalCss
|
||||
let currentMap = originalMap
|
||||
|
||||
// Make sure we can find a source map
|
||||
let map = parseSourceMap({ map: currentMap, content: currentCss })
|
||||
|
||||
expect(map.at(1, 0)).toMatchObject({
|
||||
source: null,
|
||||
original: '(none)',
|
||||
generated: '/*! tailwi...',
|
||||
})
|
||||
|
||||
expect(map.at(2, 0)).toMatchObject({
|
||||
source:
|
||||
kind === 'CLI'
|
||||
? expect.stringContaining('utilities.css')
|
||||
: expect.stringMatching(/\/utilities-\w+\.css$/),
|
||||
original: '@tailwind...',
|
||||
generated: '.flex {...',
|
||||
})
|
||||
|
||||
expect(map.at(3, 2)).toMatchObject({
|
||||
source:
|
||||
kind === 'CLI'
|
||||
? expect.stringContaining('utilities.css')
|
||||
: expect.stringMatching(/\/utilities-\w+\.css$/),
|
||||
original: '@tailwind...',
|
||||
generated: 'display: f...',
|
||||
})
|
||||
|
||||
expect(map.at(4, 0)).toMatchObject({
|
||||
source: null,
|
||||
original: '(none)',
|
||||
generated: '}...',
|
||||
})
|
||||
|
||||
// Write to project source files
|
||||
await fs.write('src/index.html', html`
|
||||
<div class="flex underline"></div>
|
||||
`)
|
||||
|
||||
// Wait for the CSS to be rebuilt
|
||||
await retryAssertion(async () => {
|
||||
currentCss = await fs.read('dist/out.css')
|
||||
currentMap = await fs.read('dist/out.css.map')
|
||||
expect(currentCss).not.toEqual(originalCss)
|
||||
expect(currentMap).not.toEqual(originalMap)
|
||||
originalCss = currentCss
|
||||
originalMap = currentMap
|
||||
})
|
||||
|
||||
// Make sure the source map was updated
|
||||
map = parseSourceMap({ map: currentMap, content: currentCss })
|
||||
|
||||
expect(map.at(1, 0)).toMatchObject({
|
||||
source: null,
|
||||
original: '(none)',
|
||||
generated: '/*! tailwi...',
|
||||
})
|
||||
|
||||
expect(map.at(2, 0)).toMatchObject({
|
||||
source:
|
||||
kind === 'CLI'
|
||||
? expect.stringContaining('utilities.css')
|
||||
: expect.stringMatching(/\/utilities-\w+\.css$/),
|
||||
original: '@tailwind...',
|
||||
generated: '.flex {...',
|
||||
})
|
||||
|
||||
expect(map.at(3, 2)).toMatchObject({
|
||||
source:
|
||||
kind === 'CLI'
|
||||
? expect.stringContaining('utilities.css')
|
||||
: expect.stringMatching(/\/utilities-\w+\.css$/),
|
||||
original: '@tailwind...',
|
||||
generated: 'display: f...',
|
||||
})
|
||||
|
||||
expect(map.at(4, 0)).toMatchObject({
|
||||
source: null,
|
||||
original: '(none)',
|
||||
generated: '}\n.underli...',
|
||||
})
|
||||
|
||||
expect(map.at(5, 0)).toMatchObject({
|
||||
source:
|
||||
kind === 'CLI'
|
||||
? expect.stringContaining('utilities.css')
|
||||
: expect.stringMatching(/\/utilities-\w+\.css$/),
|
||||
original: '@tailwind...',
|
||||
generated: '.underline...',
|
||||
})
|
||||
|
||||
expect(map.at(6, 2)).toMatchObject({
|
||||
source:
|
||||
kind === 'CLI'
|
||||
? expect.stringContaining('utilities.css')
|
||||
: expect.stringMatching(/\/utilities-\w+\.css$/),
|
||||
original: '@tailwind...',
|
||||
generated: 'text-decor...',
|
||||
})
|
||||
|
||||
expect(map.at(7, 0)).toMatchObject({
|
||||
source: null,
|
||||
original: '(none)',
|
||||
generated: '}...',
|
||||
})
|
||||
|
||||
// Write to the main CSS file
|
||||
await fs.write(
|
||||
'src/index.css',
|
||||
css`
|
||||
@import 'tailwindcss/utilities';
|
||||
@source inline("w-auto");
|
||||
`,
|
||||
)
|
||||
|
||||
// Wait for the CSS to be rebuilt
|
||||
await retryAssertion(async () => {
|
||||
currentCss = await fs.read('dist/out.css')
|
||||
currentMap = await fs.read('dist/out.css.map')
|
||||
expect(currentCss).not.toEqual(originalCss)
|
||||
expect(currentMap).not.toEqual(originalMap)
|
||||
originalCss = currentCss
|
||||
originalMap = currentMap
|
||||
})
|
||||
|
||||
// Make sure the source map was updated
|
||||
map = parseSourceMap({ map: currentMap, content: currentCss })
|
||||
|
||||
expect(map.at(1, 0)).toMatchObject({
|
||||
source: null,
|
||||
original: '(none)',
|
||||
generated: '/*! tailwi...',
|
||||
})
|
||||
|
||||
expect(map.at(2, 0)).toMatchObject({
|
||||
source:
|
||||
kind === 'CLI'
|
||||
? expect.stringContaining('utilities.css')
|
||||
: expect.stringMatching(/\/utilities-\w+\.css$/),
|
||||
original: '@tailwind...',
|
||||
generated: '.flex {...',
|
||||
})
|
||||
|
||||
expect(map.at(3, 2)).toMatchObject({
|
||||
source:
|
||||
kind === 'CLI'
|
||||
? expect.stringContaining('utilities.css')
|
||||
: expect.stringMatching(/\/utilities-\w+\.css$/),
|
||||
original: '@tailwind...',
|
||||
generated: 'display: f...',
|
||||
})
|
||||
|
||||
expect(map.at(4, 0)).toMatchObject({
|
||||
source: null,
|
||||
original: '(none)',
|
||||
generated: '}\n.w-auto...',
|
||||
})
|
||||
|
||||
expect(map.at(5, 0)).toMatchObject({
|
||||
source:
|
||||
kind === 'CLI'
|
||||
? expect.stringContaining('utilities.css')
|
||||
: expect.stringMatching(/\/utilities-\w+\.css$/),
|
||||
original: '@tailwind...',
|
||||
generated: '.w-auto {...',
|
||||
})
|
||||
|
||||
expect(map.at(6, 2)).toMatchObject({
|
||||
source:
|
||||
kind === 'CLI'
|
||||
? expect.stringContaining('utilities.css')
|
||||
: expect.stringMatching(/\/utilities-\w+\.css$/),
|
||||
original: '@tailwind...',
|
||||
generated: 'width: aut...',
|
||||
})
|
||||
|
||||
expect(map.at(7, 0)).toMatchObject({
|
||||
source: null,
|
||||
original: '(none)',
|
||||
generated: '}\n.underli...',
|
||||
})
|
||||
|
||||
expect(map.at(8, 0)).toMatchObject({
|
||||
source:
|
||||
kind === 'CLI'
|
||||
? expect.stringContaining('utilities.css')
|
||||
: expect.stringMatching(/\/utilities-\w+\.css$/),
|
||||
original: '@tailwind...',
|
||||
generated: '.underline...',
|
||||
})
|
||||
|
||||
expect(map.at(9, 2)).toMatchObject({
|
||||
source:
|
||||
kind === 'CLI'
|
||||
? expect.stringContaining('utilities.css')
|
||||
: expect.stringMatching(/\/utilities-\w+\.css$/),
|
||||
original: '@tailwind...',
|
||||
generated: 'text-decor...',
|
||||
})
|
||||
|
||||
expect(map.at(10, 0)).toMatchObject({
|
||||
source: null,
|
||||
original: '(none)',
|
||||
generated: '}...',
|
||||
})
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
test(
|
||||
|
||||
@ -321,7 +321,7 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
|
||||
|
||||
if (args['--map']) {
|
||||
DEBUG && I.start('Build Source Map')
|
||||
compiledMap = compiler.buildSourceMap() as any
|
||||
compiledMap = toSourceMap(compiler.buildSourceMap())
|
||||
DEBUG && I.end('Build Source Map')
|
||||
}
|
||||
}
|
||||
@ -346,7 +346,7 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
|
||||
|
||||
if (args['--map']) {
|
||||
DEBUG && I.start('Build Source Map')
|
||||
compiledMap = compiler.buildSourceMap() as any
|
||||
compiledMap = toSourceMap(compiler.buildSourceMap())
|
||||
DEBUG && I.end('Build Source Map')
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user