Skip to content

Commit b7edcb7

Browse files
authored
fix(css): await sass/less/styl worker disposal on teardown (fix #22274) (#22275)
1 parent 7190cca commit b7edcb7

2 files changed

Lines changed: 58 additions & 9 deletions

File tree

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import fs from 'node:fs'
2+
import os from 'node:os'
3+
import path from 'node:path'
4+
import type { ChildProcess } from 'node:child_process'
5+
import { describe, expect, test } from 'vitest'
6+
import { createServer } from '../../index'
7+
8+
const getActiveHandles = (): unknown[] => (process as any)._getActiveHandles()
9+
10+
const runningSassWorkers = (): ChildProcess[] =>
11+
getActiveHandles().filter((h): h is ChildProcess => {
12+
if (!h || (h as object).constructor?.name !== 'ChildProcess') return false
13+
const cp = h as ChildProcess & { spawnfile?: string }
14+
return (
15+
cp.exitCode == null &&
16+
typeof cp.spawnfile === 'string' &&
17+
cp.spawnfile.includes('sass')
18+
)
19+
})
20+
21+
describe('css preprocessor worker teardown', () => {
22+
test('awaits sass-embedded worker disposal on server.close()', async () => {
23+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'vite-sass-teardown-'))
24+
const scssPath = path.join(root, 'a.scss')
25+
fs.writeFileSync(scssPath, '$c: red;\nbody { color: $c; }\n')
26+
27+
const server = await createServer({
28+
root,
29+
logLevel: 'silent',
30+
configFile: false,
31+
server: { ws: false },
32+
})
33+
await server.listen()
34+
35+
try {
36+
await server.pluginContainer.transform(
37+
fs.readFileSync(scssPath, 'utf8'),
38+
scssPath,
39+
)
40+
} catch {
41+
// the optimizer can throw ERR_OUTDATED_OPTIMIZED_DEP post-transform;
42+
// not relevant here — we only need the scss processor to have run.
43+
}
44+
45+
expect(runningSassWorkers().length).toBeGreaterThan(0)
46+
47+
await server.close()
48+
49+
expect(runningSassWorkers().length).toBe(0)
50+
}, 30_000)
51+
})

packages/vite/src/node/plugins/css.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -331,8 +331,8 @@ export function cssPlugin(config: ResolvedConfig): Plugin {
331331
)
332332
},
333333

334-
buildEnd() {
335-
preprocessorWorkerController?.close()
334+
async buildEnd() {
335+
await preprocessorWorkerController?.close()
336336
},
337337

338338
load: {
@@ -2391,7 +2391,7 @@ type StylePreprocessor<Options extends StylePreprocessorInternalOptions> = {
23912391
options: Options,
23922392
resolvers: CSSAtImportResolvers,
23932393
) => StylePreprocessorResults | Promise<StylePreprocessorResults>
2394-
close: () => void
2394+
close: () => void | Promise<void>
23952395
}
23962396

23972397
export interface StylePreprocessorResults {
@@ -2612,8 +2612,8 @@ const scssProcessor = (
26122612
const normalizedErrors = new WeakSet<Error>()
26132613

26142614
return {
2615-
close() {
2616-
worker?.stop()
2615+
async close() {
2616+
await worker?.stop()
26172617
},
26182618
async process(environment, source, root, options, resolvers) {
26192619
let sassPackage = loadSassPackage(root, failedSassEmbedded ?? false)
@@ -3150,10 +3150,8 @@ const createPreprocessorWorkerController = (maxWorkers: number | undefined) => {
31503150
return scss.process(environment, source, root, opts, resolvers)
31513151
}
31523152

3153-
const close = () => {
3154-
less.close()
3155-
scss.close()
3156-
styl.close()
3153+
const close = async () => {
3154+
await Promise.all([less.close(), scss.close(), styl.close()])
31573155
}
31583156

31593157
return {

0 commit comments

Comments
 (0)