From 93d55bf5568347b6c98cd28726c8d61c1600f875 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Sun, 3 Nov 2024 00:41:31 +0100 Subject: [PATCH 01/11] properly wait for watchers to be cleaned Unsubscribing from a watcher returns a promise, which we didn't wait for. This makes the `Disposables#dispose` method async to ensure we properly wait. --- .../src/commands/build/index.ts | 22 ++++++++++--------- .../@tailwindcss-cli/src/utils/disposables.ts | 4 ++-- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/@tailwindcss-cli/src/commands/build/index.ts b/packages/@tailwindcss-cli/src/commands/build/index.ts index b61f3dd49b86..8db8856e1dff 100644 --- a/packages/@tailwindcss-cli/src/commands/build/index.ts +++ b/packages/@tailwindcss-cli/src/commands/build/index.ts @@ -204,7 +204,7 @@ export async function handle(args: Result>) { // Scan the entire `base` directory for full rebuilds. if (rebuildStrategy === 'full') { // Clear all watchers - cleanupWatchers() + await cleanupWatchers() // Read the new `input`. let input = args['--input'] @@ -271,8 +271,10 @@ export async function handle(args: Result>) { // disable this behavior with `--watch=always`. if (args['--watch'] !== 'always') { process.stdin.on('end', () => { - cleanupWatchers() - process.exit(0) + cleanupWatchers().then( + () => process.exit(0), + () => process.exit(1), + ) }) } @@ -322,9 +324,9 @@ async function createWatchers(dirs: string[], cb: (files: string[]) => void) { // A changed file can be watched by multiple watchers, but we only want to // handle the file once. We debounce the handle function with the collected // files to handle them in a single batch and to avoid multiple rebuilds. - function enqueueCallback() { - // Dispose all existing macrotask. - debounceQueue.dispose() + async function enqueueCallback() { + // Dispose all existing macrotasks. + await debounceQueue.dispose() // Setup a new macrotask to handle the files in batch. debounceQueue.queueMacrotask(() => { @@ -365,7 +367,7 @@ async function createWatchers(dirs: string[], cb: (files: string[]) => void) { ) // Handle the tracked files at some point in the future. - enqueueCallback() + await enqueueCallback() }) // Ensure we cleanup the watcher when we're done. @@ -373,9 +375,9 @@ async function createWatchers(dirs: string[], cb: (files: string[]) => void) { } // Cleanup - return () => { - watchers.dispose() - debounceQueue.dispose() + return async () => { + await watchers.dispose() + await debounceQueue.dispose() } } diff --git a/packages/@tailwindcss-cli/src/utils/disposables.ts b/packages/@tailwindcss-cli/src/utils/disposables.ts index cb0d982c1a78..737f3ed1e0a1 100644 --- a/packages/@tailwindcss-cli/src/utils/disposables.ts +++ b/packages/@tailwindcss-cli/src/utils/disposables.ts @@ -35,9 +35,9 @@ export class Disposables { /** * Dispose all disposables at once. */ - dispose() { + async dispose() { for (let dispose of this.#disposables) { - dispose() + await dispose() } this.#disposables.clear() From 92fdc2bd1b6dc5731d2de2be18505c03a6de548d Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Sun, 3 Nov 2024 02:12:42 +0100 Subject: [PATCH 02/11] WIP --- integrations/cli/index.test.ts | 16 ++++++----- integrations/utils.ts | 3 +- .../src/commands/build/index.ts | 28 ++++++++++++++++--- 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/integrations/cli/index.test.ts b/integrations/cli/index.test.ts index 6930175be2a4..32ca1c636766 100644 --- a/integrations/cli/index.test.ts +++ b/integrations/cli/index.test.ts @@ -17,13 +17,15 @@ const STANDALONE_BINARY = (() => { } })() -describe.each([ - ['CLI', 'pnpm tailwindcss'], +describe.each( [ - 'Standalone CLI', - path.resolve(__dirname, `../../packages/@tailwindcss-standalone/dist/${STANDALONE_BINARY}`), - ], -])('%s', (_, command) => { + ['CLI', 'pnpm tailwindcss'], + [ + 'Standalone CLI', + path.resolve(__dirname, `../../packages/@tailwindcss-standalone/dist/${STANDALONE_BINARY}`), + ], + ].slice(-1), +)('%s', (_, command) => { test( 'production build', { @@ -93,7 +95,7 @@ describe.each([ }, ) - test( + test.only( 'watch mode', { fs: { diff --git a/integrations/utils.ts b/integrations/utils.ts index 70b97c111d06..4fd288c88f5a 100644 --- a/integrations/utils.ts +++ b/integrations/utils.ts @@ -280,6 +280,7 @@ export function test( }, fs: { async write(filename: string, content: string): Promise { + console.log('Writing file:', filename) let full = path.join(root, filename) if (filename.endsWith('package.json')) { @@ -293,7 +294,7 @@ export function test( let dir = path.dirname(full) await fs.mkdir(dir, { recursive: true }) - await fs.writeFile(full, content) + await fs.writeFile(full, content, 'utf-8') }, async create(filenames: string[]): Promise { diff --git a/packages/@tailwindcss-cli/src/commands/build/index.ts b/packages/@tailwindcss-cli/src/commands/build/index.ts index 8db8856e1dff..96fc17cb1a63 100644 --- a/packages/@tailwindcss-cli/src/commands/build/index.ts +++ b/packages/@tailwindcss-cli/src/commands/build/index.ts @@ -54,7 +54,10 @@ export function options() { } satisfies Arg } +console.log('build/index.js#module') export async function handle(args: Result>) { + console.log('build/index.js#handle') + let base = path.resolve(args['--cwd']) // Resolve the output as an absolute path. @@ -166,6 +169,10 @@ export async function handle(args: Result>) { let cleanupWatchers = await createWatchers( watchDirectories(scanner), async function handle(files) { + console.log('Files changed:', files.length) + for (let file of files) { + console.log(`File changed: ${file}`) + } try { // If the only change happened to the output file, then we don't want to // trigger a rebuild because that will result in an infinite loop. @@ -181,6 +188,7 @@ export async function handle(args: Result>) { // config/plugin files, then we need to do a full rebuild because // the theme might have changed. if (resolvedFullRebuildPaths.includes(file)) { + console.log(`Full rebuild required because ${file} changed`) rebuildStrategy = 'full' // No need to check the rest of the events, because we already know we @@ -203,9 +211,6 @@ export async function handle(args: Result>) { // Scan the entire `base` directory for full rebuilds. if (rebuildStrategy === 'full') { - // Clear all watchers - await cleanupWatchers() - // Read the new `input`. let input = args['--input'] ? args['--input'] === '-' @@ -226,7 +231,12 @@ export async function handle(args: Result>) { env.DEBUG && console.timeEnd('[@tailwindcss/cli] Scan for candidates') // Setup new watchers - cleanupWatchers = await createWatchers(watchDirectories(scanner), handle) + // let newCleanupWatchers = await createWatchers(watchDirectories(scanner), handle) + + // Clear old watchers + // await cleanupWatchers() + + // cleanupWatchers = newCleanupWatchers // Re-compile the CSS env.DEBUG && console.time('[@tailwindcss/cli] Build CSS') @@ -271,6 +281,7 @@ export async function handle(args: Result>) { // disable this behavior with `--watch=always`. if (args['--watch'] !== 'always') { process.stdin.on('end', () => { + console.log('EEEEND') cleanupWatchers().then( () => process.exit(0), () => process.exit(1), @@ -309,6 +320,7 @@ function watchDirectories(scanner: Scanner) { } async function createWatchers(dirs: string[], cb: (files: string[]) => void) { + console.log('createWatchers(…)') // Track all Parcel watchers for each glob. // // When we encounter a change in a CSS file, we need to setup new watchers and @@ -336,6 +348,13 @@ async function createWatchers(dirs: string[], cb: (files: string[]) => void) { } // Setup a watcher for every directory. + console.log( + `Watching:\n${dirs + .map((dir) => dir) + .map((x) => ` - ${x}`) + .join('\n')}`, + ) + for (let dir of dirs) { let { unsubscribe } = await watcher.subscribe(dir, async (err, events) => { // Whenever an error occurs we want to let the user know about it but we @@ -376,6 +395,7 @@ async function createWatchers(dirs: string[], cb: (files: string[]) => void) { // Cleanup return async () => { + console.log('CLEANUP') await watchers.dispose() await debounceQueue.dispose() } From af7718ebd83ee0f9dfe6afacc288b0471b4a658c Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Sun, 3 Nov 2024 02:21:29 +0100 Subject: [PATCH 03/11] don't double logs --- integrations/utils.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/integrations/utils.ts b/integrations/utils.ts index 4fd288c88f5a..9cf2d4420387 100644 --- a/integrations/utils.ts +++ b/integrations/utils.ts @@ -214,8 +214,9 @@ export function test( }) options.onTestFailed(() => { - // In debug mode, messages are logged to the console immediately - if (debug) return + // In only or debug mode, messages are logged to the console + // immediately. + if (only || debug) return for (let [type, message] of combined) { if (type === 'stdout') { From feb9630e95f31e4db8aa713f30768eedc7167433 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Sun, 3 Nov 2024 02:21:37 +0100 Subject: [PATCH 04/11] re-enable --- packages/@tailwindcss-cli/src/commands/build/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/@tailwindcss-cli/src/commands/build/index.ts b/packages/@tailwindcss-cli/src/commands/build/index.ts index 96fc17cb1a63..c2b428007b69 100644 --- a/packages/@tailwindcss-cli/src/commands/build/index.ts +++ b/packages/@tailwindcss-cli/src/commands/build/index.ts @@ -231,12 +231,12 @@ export async function handle(args: Result>) { env.DEBUG && console.timeEnd('[@tailwindcss/cli] Scan for candidates') // Setup new watchers - // let newCleanupWatchers = await createWatchers(watchDirectories(scanner), handle) + let newCleanupWatchers = await createWatchers(watchDirectories(scanner), handle) // Clear old watchers - // await cleanupWatchers() + await cleanupWatchers() - // cleanupWatchers = newCleanupWatchers + cleanupWatchers = newCleanupWatchers // Re-compile the CSS env.DEBUG && console.time('[@tailwindcss/cli] Build CSS') From 1c2ebce35faedacdeda89fe250a5efd4f465428c Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Mon, 4 Nov 2024 12:09:03 -0500 Subject: [PATCH 05/11] =?UTF-8?q?Don=E2=80=99t=20add=20watcher=20for=20dir?= =?UTF-8?q?=20when=20parent=20dir=20is=20watched?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a speculative fix for CI failures on macOS --- .../src/commands/build/index.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/packages/@tailwindcss-cli/src/commands/build/index.ts b/packages/@tailwindcss-cli/src/commands/build/index.ts index c2b428007b69..e3821b374e16 100644 --- a/packages/@tailwindcss-cli/src/commands/build/index.ts +++ b/packages/@tailwindcss-cli/src/commands/build/index.ts @@ -320,6 +320,29 @@ function watchDirectories(scanner: Scanner) { } async function createWatchers(dirs: string[], cb: (files: string[]) => void) { + // Remove any directories that are children of an already watched directory. + // If we don't we may not get notified of certain filesystem events regardless + // of whether or not they are for the directory that is duplicated. + + // 1. Sort in asc by length + dirs = dirs.sort((a, z) => a.length - z.length) + + // 2. Remove any directories that are children of another directory + let toRemove = [] + + // /project-a 0 + // /project-a/src 1 + + for (let i = 0; i < dirs.length; ++i) { + for (let j = 0; j < i; ++j) { + if (!dirs[i].startsWith(`${dirs[j]}/`)) continue + + toRemove.push(dirs[i]) + } + } + + dirs = dirs.filter((dir) => !toRemove.includes(dir)) + console.log('createWatchers(…)') // Track all Parcel watchers for each glob. // From b79638f2a2683c08918ab70ba7033f27144e58ed Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Mon, 4 Nov 2024 12:09:21 -0500 Subject: [PATCH 06/11] Run CI for macOS --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d28a1e1e0660..3d8dd7284c85 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: exclude: - on-next-branch: false runner: windows-latest - - on-next-branch: false + - on-next-branch: true runner: macos-14 runs-on: ${{ matrix.runner }} From ec0dddb12b4f390325d58f5c7e6ba48208588816 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Mon, 4 Nov 2024 12:19:06 -0500 Subject: [PATCH 07/11] Cleanup logs --- integrations/utils.ts | 1 - .../src/commands/build/index.ts | 18 ------------------ 2 files changed, 19 deletions(-) diff --git a/integrations/utils.ts b/integrations/utils.ts index 9cf2d4420387..277b3bda4b29 100644 --- a/integrations/utils.ts +++ b/integrations/utils.ts @@ -281,7 +281,6 @@ export function test( }, fs: { async write(filename: string, content: string): Promise { - console.log('Writing file:', filename) let full = path.join(root, filename) if (filename.endsWith('package.json')) { diff --git a/packages/@tailwindcss-cli/src/commands/build/index.ts b/packages/@tailwindcss-cli/src/commands/build/index.ts index e3821b374e16..3a5fd120b81a 100644 --- a/packages/@tailwindcss-cli/src/commands/build/index.ts +++ b/packages/@tailwindcss-cli/src/commands/build/index.ts @@ -54,10 +54,7 @@ export function options() { } satisfies Arg } -console.log('build/index.js#module') export async function handle(args: Result>) { - console.log('build/index.js#handle') - let base = path.resolve(args['--cwd']) // Resolve the output as an absolute path. @@ -169,10 +166,6 @@ export async function handle(args: Result>) { let cleanupWatchers = await createWatchers( watchDirectories(scanner), async function handle(files) { - console.log('Files changed:', files.length) - for (let file of files) { - console.log(`File changed: ${file}`) - } try { // If the only change happened to the output file, then we don't want to // trigger a rebuild because that will result in an infinite loop. @@ -188,7 +181,6 @@ export async function handle(args: Result>) { // config/plugin files, then we need to do a full rebuild because // the theme might have changed. if (resolvedFullRebuildPaths.includes(file)) { - console.log(`Full rebuild required because ${file} changed`) rebuildStrategy = 'full' // No need to check the rest of the events, because we already know we @@ -281,7 +273,6 @@ export async function handle(args: Result>) { // disable this behavior with `--watch=always`. if (args['--watch'] !== 'always') { process.stdin.on('end', () => { - console.log('EEEEND') cleanupWatchers().then( () => process.exit(0), () => process.exit(1), @@ -343,7 +334,6 @@ async function createWatchers(dirs: string[], cb: (files: string[]) => void) { dirs = dirs.filter((dir) => !toRemove.includes(dir)) - console.log('createWatchers(…)') // Track all Parcel watchers for each glob. // // When we encounter a change in a CSS file, we need to setup new watchers and @@ -371,13 +361,6 @@ async function createWatchers(dirs: string[], cb: (files: string[]) => void) { } // Setup a watcher for every directory. - console.log( - `Watching:\n${dirs - .map((dir) => dir) - .map((x) => ` - ${x}`) - .join('\n')}`, - ) - for (let dir of dirs) { let { unsubscribe } = await watcher.subscribe(dir, async (err, events) => { // Whenever an error occurs we want to let the user know about it but we @@ -418,7 +401,6 @@ async function createWatchers(dirs: string[], cb: (files: string[]) => void) { // Cleanup return async () => { - console.log('CLEANUP') await watchers.dispose() await debounceQueue.dispose() } From 5a93b2eab740437961d66fb56aaa26cf2dd6b63a Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Mon, 4 Nov 2024 12:19:11 -0500 Subject: [PATCH 08/11] Remove `.only` --- integrations/cli/index.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/cli/index.test.ts b/integrations/cli/index.test.ts index 32ca1c636766..714675c19e68 100644 --- a/integrations/cli/index.test.ts +++ b/integrations/cli/index.test.ts @@ -95,7 +95,7 @@ describe.each( }, ) - test.only( + test( 'watch mode', { fs: { From f3ddd101daadd2876974c3aa60bf1b5b81d50456 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Mon, 4 Nov 2024 12:32:14 -0500 Subject: [PATCH 09/11] Only run cli watch mode test temporarily --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3d8dd7284c85..7d94d150f735 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -78,7 +78,7 @@ jobs: run: pnpm run test - name: Integration Tests - run: pnpm run test:integrations + run: pnpm run test:integrations cli/index.test -t 'watch mode' env: GITHUB_WORKSPACE: ${{ github.workspace }} From ecb6e7b2a9ab231ca39fdebe5b5983002cd458d8 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Mon, 4 Nov 2024 12:51:17 -0500 Subject: [PATCH 10/11] wip --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7d94d150f735..d28a1e1e0660 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: exclude: - on-next-branch: false runner: windows-latest - - on-next-branch: true + - on-next-branch: false runner: macos-14 runs-on: ${{ matrix.runner }} @@ -78,7 +78,7 @@ jobs: run: pnpm run test - name: Integration Tests - run: pnpm run test:integrations cli/index.test -t 'watch mode' + run: pnpm run test:integrations env: GITHUB_WORKSPACE: ${{ github.workspace }} From 389e9edb3b74f6f7d355a3c5909b3afed3217ccf Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Mon, 4 Nov 2024 12:51:45 -0500 Subject: [PATCH 11/11] wip --- integrations/cli/index.test.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/integrations/cli/index.test.ts b/integrations/cli/index.test.ts index 714675c19e68..6930175be2a4 100644 --- a/integrations/cli/index.test.ts +++ b/integrations/cli/index.test.ts @@ -17,15 +17,13 @@ const STANDALONE_BINARY = (() => { } })() -describe.each( +describe.each([ + ['CLI', 'pnpm tailwindcss'], [ - ['CLI', 'pnpm tailwindcss'], - [ - 'Standalone CLI', - path.resolve(__dirname, `../../packages/@tailwindcss-standalone/dist/${STANDALONE_BINARY}`), - ], - ].slice(-1), -)('%s', (_, command) => { + 'Standalone CLI', + path.resolve(__dirname, `../../packages/@tailwindcss-standalone/dist/${STANDALONE_BINARY}`), + ], +])('%s', (_, command) => { test( 'production build', {