Skip to content

Commit 6bf8941

Browse files
authored
fix(cli): avoid clearing cache early to prevent race condition in watch mode (#5054)
1 parent b55b462 commit 6bf8941

File tree

3 files changed

+89
-74
lines changed

3 files changed

+89
-74
lines changed

packages-engine/cli/src/index.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ async function initializeConfig(options: CliOptions) {
8181
}
8282

8383
async function parseEntries(options: ResolvedCliOptions, cache: Map<string, FileEntryItem[]>) {
84-
cache.clear()
84+
const newCache = new Map<string, FileEntryItem[]>()
8585

8686
for (const entry of options.entries) {
8787
const { outFile, rewrite } = entry
@@ -91,9 +91,9 @@ async function parseEntries(options: ResolvedCliOptions, cache: Map<string, File
9191
const singleKey = outFile.replace(/(\.css)?$/, '-merged.css')
9292

9393
const addToCache = (file: string, code: string, key: string) => {
94-
const existing = cache.get(key) || []
94+
const existing = newCache.get(key) || []
9595
existing.push({ id: file, code, rewrite })
96-
cache.set(key, existing)
96+
newCache.set(key, existing)
9797
}
9898

9999
for (const file of otherFiles) {
@@ -118,6 +118,11 @@ async function parseEntries(options: ResolvedCliOptions, cache: Map<string, File
118118
// false: discard CSS files
119119
}
120120
}
121+
122+
cache.clear()
123+
for (const [key, value] of newCache) {
124+
cache.set(key, value)
125+
}
121126
}
122127

123128
export async function build(_options: CliOptions) {

packages-engine/cli/test/cli.test.ts

Lines changed: 67 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import process from 'node:process'
21
import fs from 'fs-extra'
32
import { resolve } from 'pathe'
43
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
@@ -98,31 +97,6 @@ describe('cli', () => {
9897
expect(transform).toMatchSnapshot()
9998
})
10099

101-
it.skipIf(process.version.startsWith('v20'))('uno.css exclude initialized class after changing file', async () => {
102-
const { output, testDir } = await runCli({
103-
'views/index.html': '<div class="bg-blue"></div>',
104-
}, { args: ['--preset', 'wind3', '-w'] })
105-
106-
expect(output).toContain('.bg-blue')
107-
108-
const changedContent = '<div class="bg-red"></div>'
109-
const absolutePathOfFile = resolve(testDir!, 'views/index.html')
110-
await fs.writeFile(absolutePathOfFile, changedContent)
111-
112-
// polling until update
113-
114-
while (true) {
115-
await sleep(100)
116-
const output = await readFile(testDir!)
117-
if (output.includes('.bg-red')) {
118-
expect(output).toContain('.bg-red')
119-
break
120-
}
121-
}
122-
123-
(await getWatcher()).close()
124-
})
125-
126100
it('supports unocss.config.js cli options', async () => {
127101
const testDir = getTestDir()
128102
const outFiles = ['./uno1.css', './test/uno2.css']
@@ -173,7 +147,73 @@ describe('cli', () => {
173147
expect(output2).toContain('.bg-red')
174148
})
175149

176-
it.skipIf(process.version.startsWith('v20'))('supports uno.config.ts changed rebuild', async () => {
150+
it('should correctly deduplicate files of different types containing @media', async () => {
151+
const { output, transform } = await runCli(
152+
{
153+
'views/index1.html': '<div class="lg:p-8"></div>',
154+
'views/index2.html': '<div class="md:p-4"></div>',
155+
'views/index3.html': '<div class="box"></div>',
156+
'views/index.css': '.box { @apply pd-6 sm:p-2; }',
157+
'unocss.config.js': `
158+
import { defineConfig, presetWind3, transformerDirectives } from 'unocss'
159+
export default defineConfig({
160+
presets: [presetWind3()],
161+
transformers: [transformerDirectives()]
162+
})
163+
`.trim(),
164+
},
165+
{ transformFile: 'views/index.css', args: ['--rewrite'] },
166+
)
167+
168+
expect(output).toMatchSnapshot()
169+
expect(transform).toMatchSnapshot()
170+
})
171+
172+
it('@unocss-skip uno.css', async () => {
173+
const { output } = await runCli({
174+
'views/index.html': `
175+
<div class="p-4"></div>
176+
// @unocss-skip-start
177+
<div class="bg-red text-white"></div>
178+
// @unocss-skip-end
179+
<div className="w-10"></div>
180+
`,
181+
})
182+
183+
expect(output).toContain('.p-4')
184+
expect(output).toContain('.w-10')
185+
expect(output).not.toContain('.bg-red')
186+
expect(output).not.toContain('.text-white')
187+
})
188+
})
189+
190+
describe.skipIf(process.version.startsWith('v20'))('cli watch mode', () => {
191+
it('uno.css exclude initialized class after changing file', async () => {
192+
const { output, testDir } = await runCli({
193+
'views/index.html': '<div class="bg-blue"></div>',
194+
}, { args: ['-w'] })
195+
196+
expect(output).toContain('.bg-blue')
197+
198+
const changedContent = '<div class="bg-red"></div>'
199+
const absolutePathOfFile = resolve(testDir!, 'views/index.html')
200+
await fs.writeFile(absolutePathOfFile, changedContent)
201+
202+
// polling until update
203+
204+
while (true) {
205+
await sleep(100)
206+
const output = await readFile(testDir!)
207+
if (output.includes('.bg-red')) {
208+
expect(output).toContain('.bg-red')
209+
break
210+
}
211+
}
212+
213+
(await getWatcher()).close()
214+
})
215+
216+
it('supports uno.config.ts changed rebuild', async () => {
177217
const { output, testDir } = await runCli({
178218
'views/index.html': '<div class="bg-foo"></div>',
179219
'uno.config.ts': `
@@ -213,43 +253,4 @@ describe('cli', () => {
213253

214254
(await getWatcher()).close()
215255
})
216-
217-
it('should correctly deduplicate files of different types containing @media', async () => {
218-
const { output, transform } = await runCli(
219-
{
220-
'views/index1.html': '<div class="lg:p-8"></div>',
221-
'views/index2.html': '<div class="md:p-4"></div>',
222-
'views/index3.html': '<div class="box"></div>',
223-
'views/index.css': '.box { @apply pd-6 sm:p-2; }',
224-
'unocss.config.js': `
225-
import { defineConfig, presetWind3, transformerDirectives } from 'unocss'
226-
export default defineConfig({
227-
presets: [presetWind3()],
228-
transformers: [transformerDirectives()]
229-
})
230-
`.trim(),
231-
},
232-
{ transformFile: 'views/index.css', args: ['--rewrite'] },
233-
)
234-
235-
expect(output).toMatchSnapshot()
236-
expect(transform).toMatchSnapshot()
237-
})
238-
239-
it('@unocss-skip uno.css', async () => {
240-
const { output } = await runCli({
241-
'views/index.html': `
242-
<div class="p-4"></div>
243-
// @unocss-skip-start
244-
<div class="bg-red text-white"></div>
245-
// @unocss-skip-end
246-
<div className="w-10"></div>
247-
`,
248-
})
249-
250-
expect(output).toContain('.p-4')
251-
expect(output).toContain('.w-10')
252-
expect(output).not.toContain('.bg-red')
253-
expect(output).not.toContain('.text-white')
254-
})
255256
})

packages-engine/cli/test/utils.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import process from 'node:process'
12
import fs from 'fs-extra'
23
import { resolve } from 'pathe'
34
import { startCli } from '../src/cli-start'
45

6+
const isCI = !!process.env.CI
7+
58
export const tempDir = resolve('_temp')
69
export const cli = resolve(__dirname, '../src/cli.ts')
710

@@ -49,22 +52,28 @@ export default defineConfig({
4952
`.trim()
5053
}
5154

55+
const fileName = options?.outFile || 'uno.css'
56+
5257
await initOutputFiles(testDir, files)
5358
const process = runAsyncChildProcess(testDir, 'views/**/*', ...options?.args ?? [])
5459

5560
if (options?.args?.includes('-w')) {
5661
while (true) {
57-
await sleep(50)
58-
const outFilePath = resolve(testDir, options?.outFile || 'uno.css')
59-
if (fs.existsSync(outFilePath))
60-
break
62+
await sleep(isCI ? 1000 : 100)
63+
const outFilePath = resolve(testDir, fileName)
64+
65+
if (fs.existsSync(outFilePath)) {
66+
const content = await readFile(testDir, fileName)
67+
if (content.length > 0)
68+
break
69+
}
6170
}
6271
}
6372
else {
6473
await process
6574
}
6675

67-
const output = await readFile(testDir, options?.outFile)
76+
const output = await readFile(testDir, fileName)
6877

6978
if (options?.transformFile) {
7079
const transform = await readFile(testDir, options.transformFile)

0 commit comments

Comments
 (0)