Skip to content

Commit ed0ebb6

Browse files
committed
ensure @tailwindcss/cli builds recover from deleted files
1 parent 9d43777 commit ed0ebb6

1 file changed

Lines changed: 52 additions & 4 deletions

File tree

  • packages/@tailwindcss-cli/src/commands/build

packages/@tailwindcss-cli/src/commands/build/index.ts

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
223223
let inputBasePath = inputFilePath ? path.dirname(inputFilePath) : process.cwd()
224224

225225
let fullRebuildPaths: string[] = inputFilePath ? [inputFilePath] : []
226+
let backupRebuildPaths = fullRebuildPaths
226227

227228
async function createCompiler(css: string, I: Instrumentation) {
228229
DEBUG && I.start('Setup compiler')
@@ -312,6 +313,13 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
312313
@import 'tailwindcss';
313314
`
314315
clearRequireCache(resolvedFullRebuildPaths)
316+
317+
// Track current rebuild paths in case something goes wrong when
318+
// performing a full rebuild.
319+
backupRebuildPaths = fullRebuildPaths.slice()
320+
321+
// The `inputFilePath`, if provided, will be the only known full
322+
// rebuild path before the compiler is re-created.
315323
fullRebuildPaths = inputFilePath ? [inputFilePath] : []
316324

317325
// Create a new compiler, given the new `input`
@@ -376,6 +384,40 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
376384
let end = process.hrtime.bigint()
377385
if (!args['--silent']) eprintln(`Done in ${formatDuration(end - start)}`)
378386
} catch (err) {
387+
// It's important that we perform a full rebuild when any of the
388+
// dependencies tracked in `fullRebuildPaths` has been changed.
389+
//
390+
// If we remove one of those files, then a subsequent build will be
391+
// triggered, but it will fail because the dependency is gone. The
392+
// compiler itself will be in a broken state and won't be able to
393+
// register any dependencies therefore we want to restore all the
394+
// dependencies from before. If we don't do that, then we won't be
395+
// able to recover from a bug in a transitive dependency.
396+
//
397+
// E.g.:
398+
// ```css
399+
// /* input.css — known full rebuild path */
400+
// @import 'tailwindcss';
401+
// @config "./tailwind.config.js";
402+
// ```
403+
//
404+
// ```js
405+
// // tailwind.config.js
406+
// const theme = require('./my-theme.js');
407+
//
408+
// module.exports = {
409+
// theme
410+
// }
411+
// ```
412+
// In this cse `my-theme.js` is a transitive dependency of
413+
// `input.css` via `tailwind.config.js`. Removing `my-theme.js` will
414+
// result in an error, restoring `my-theme.js` should trigger a
415+
// fresh build even though the compiler didn't restore.
416+
//
417+
// Once the build error is fixed, a fresh full rebuild will happen
418+
// which in turn will fixup the full rebuild paths.
419+
fullRebuildPaths = backupRebuildPaths
420+
379421
// Catch any errors and print them to stderr, but don't exit the process
380422
// and keep watching.
381423
eprintln(
@@ -488,10 +530,16 @@ async function createWatchers(dirs: string[], cb: (files: string[]) => void) {
488530

489531
await Promise.all(
490532
events.map(async (event) => {
491-
// We currently don't handle deleted files because it doesn't influence
492-
// the CSS output. This is because we currently keep all scanned
493-
// candidates in a cache for performance reasons.
494-
if (event.type === 'delete') return
533+
// When a file is deleted, a rebuild should be triggered such that we
534+
// can figure out whether this file must trigger a fresh build or not.
535+
//
536+
// If it must trigger a fresh build, then we will temporarily end up
537+
// in a broken state, but an error will be shown to the user. Once the
538+
// user resolves the issue, the CLI will recover.
539+
if (event.type === 'delete') {
540+
files.add(event.path)
541+
return
542+
}
495543

496544
// Ignore directory changes. We only care about file changes
497545
let stats: Stats | null = null

0 commit comments

Comments
 (0)