Skip to content

Commit

Permalink
Fix missing PostCSS dependencies in the CLI (#9617)
Browse files Browse the repository at this point in the history
* Record and watch PostCSS dependencies in the CLI

* ensure `changedContent` gets cleared

Otherwise this list gets bigger and bigger, not only that there is a
subtle bug. The moment you save a `.css` file we want to create a new
context and start from scratch. However, since the list was never
cleared, it meant that every subsequent save to *any* file (not only
config / css files) creates a new context...

By clearing the least we should work around this problem.

* add test that verifies an odd bug

The story goes like this:

1. add `underline` to html file
  -> css contains `underline` rule
2. add `font-bold` to html file
  -> css contains `underline` and `font-bold`
3. remove `underline` from html file
  -> css still contains `underline` and `font-bold` for performance reasons
4. Save a css file (! RED FLAG)
  -> css contains `font-bold` because we started from scratch
5. add `underline` to html file
  -> css contains `underline` and `font-bold`
6. remove `underline` from html file
  -> css only contains `font-bold`... (UH OH)

This is because the moment we did step 4, every single save in any file created a new context. Every. Single. Time.

* use a property that doesn't require `autoprefixer`

* update changelog

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
  • Loading branch information
RobinMalfait and thecrypticace committed Oct 20, 2022
1 parent 4dfb1e3 commit 40f6b4f
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed

- Fix missing `supports` in types ([#9616](https://github.com/tailwindlabs/tailwindcss/pull/9616))
- Fix missing PostCSS dependencies in the CLI ([#9617](https://github.com/tailwindlabs/tailwindcss/pull/9617))

## [3.2.0] - 2022-10-19

Expand Down
25 changes: 25 additions & 0 deletions integrations/tailwindcss-cli/tests/cli.test.js
Expand Up @@ -411,6 +411,31 @@ describe('Build command', () => {
`
)

await writeInputFile(
'imported.css',
css`
@layer utilities {
.something-cool {
color: blue;
}
}
`
)

await runningProcess.onStderr(function ready(message) {
return message.includes('Done in')
})

expect(await readOutputFile('main.css')).toIncludeCss(
css`
@media (min-width: 768px) {
.md\:something-cool {
color: blue;
}
}
`
)

return runningProcess.stop()
})

Expand Down
101 changes: 101 additions & 0 deletions integrations/tailwindcss-cli/tests/integration.test.js
Expand Up @@ -605,4 +605,105 @@ describe('watcher', () => {

return runningProcess.stop()
})

test('classes are generated (and kept) when the index.html file changes (and removed when css/config files are changed)', async () => {
let runningProcess = $('node ../../lib/cli.js -i ./src/index.css -o ./dist/main.css -w')

// Start with a simple single class
await writeInputFile('index.html', html`<div class="font-bold"></div>`)
await runningProcess.onStderr(ready)
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.font-bold {
font-weight: 700;
}
`
)

// Add another class
await writeInputFile('index.html', html`<div class="flex font-bold"></div>`)
await runningProcess.onStderr(ready)
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.flex {
display: flex;
}
.font-bold {
font-weight: 700;
}
`
)

// Remove a class, because of performance reasons both classes will still be in the css file
await writeInputFile('index.html', html`<div class="font-bold"></div>`)
await runningProcess.onStderr(ready)
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.flex {
display: flex;
}
.font-bold {
font-weight: 700;
}
`
)

// Save the index.css file, this should trigger a fresh context
await writeInputFile(
'index.css',
css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
)
await runningProcess.onStderr(ready)

// Only 1 class should stay, because we started from scratch
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.font-bold {
font-weight: 700;
}
`
)

// Add another class
await writeInputFile('index.html', html`<div class="flex font-bold"></div>`)
await runningProcess.onStderr(ready)
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.flex {
display: flex;
}
.font-bold {
font-weight: 700;
}
`
)

// Remove a class, because of performance reasons both classes will still be in the css file
await writeInputFile('index.html', html`<div class="font-bold"></div>`)
await runningProcess.onStderr(ready)

// If everything goes right, then both classes should still be here (because of the performance
// improvement). If we didn't solve the bug where from now on every save is a fresh context
// then this only has 1 class. So let's hope there are 2!
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.flex {
display: flex;
}
.font-bold {
font-weight: 700;
}
`
)

return runningProcess.stop()
})
})
20 changes: 20 additions & 0 deletions src/cli/build/plugin.js
Expand Up @@ -328,6 +328,26 @@ export async function createProcessor(args, cliConfigPath) {

return readInput()
.then((css) => processor.process(css, { ...postcssOptions, from: input, to: output }))
.then((result) => {
if (!state.watcher) {
return result
}

env.DEBUG && console.time('Recording PostCSS dependencies')
for (let message of result.messages) {
if (message.type === 'dependency') {
state.contextDependencies.add(message.file)
}
}
env.DEBUG && console.timeEnd('Recording PostCSS dependencies')

// TODO: This needs to be in a different spot
env.DEBUG && console.time('Watch new files')
state.watcher.refreshWatchedFiles()
env.DEBUG && console.timeEnd('Watch new files')

return result
})
.then((result) => {
if (!output) {
process.stdout.write(result.css)
Expand Down
2 changes: 1 addition & 1 deletion src/cli/build/watching.js
Expand Up @@ -62,7 +62,7 @@ export function createWatcher(args, { state, rebuild }) {
extension: path.extname(file).slice(1),
})

chain = chain.then(() => rebuild(changedContent))
chain = chain.then(() => rebuild(changedContent.splice(0)))

return chain
}
Expand Down

0 comments on commit 40f6b4f

Please sign in to comment.