Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import type { ChildProcess } from 'node:child_process'
import { describe, expect, test } from 'vitest'
import { createServer } from '../../index'

const getActiveHandles = (): unknown[] => (process as any)._getActiveHandles()

const runningSassWorkers = (): ChildProcess[] =>
getActiveHandles().filter((h): h is ChildProcess => {
if (!h || (h as object).constructor?.name !== 'ChildProcess') return false
const cp = h as ChildProcess & { spawnfile?: string }
return (
cp.exitCode == null &&
typeof cp.spawnfile === 'string' &&
cp.spawnfile.includes('sass')
)
})

describe('css preprocessor worker teardown', () => {
test('awaits sass-embedded worker disposal on server.close()', async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'vite-sass-teardown-'))
const scssPath = path.join(root, 'a.scss')
fs.writeFileSync(scssPath, '$c: red;\nbody { color: $c; }\n')

const server = await createServer({
root,
logLevel: 'silent',
configFile: false,
server: { ws: false },
})
await server.listen()

try {
await server.pluginContainer.transform(
fs.readFileSync(scssPath, 'utf8'),
scssPath,
)
} catch {
// the optimizer can throw ERR_OUTDATED_OPTIMIZED_DEP post-transform;
// not relevant here — we only need the scss processor to have run.
}

expect(runningSassWorkers().length).toBeGreaterThan(0)

await server.close()

expect(runningSassWorkers().length).toBe(0)
}, 30_000)
})
16 changes: 7 additions & 9 deletions packages/vite/src/node/plugins/css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,8 +331,8 @@ export function cssPlugin(config: ResolvedConfig): Plugin {
)
},

buildEnd() {
preprocessorWorkerController?.close()
async buildEnd() {
await preprocessorWorkerController?.close()
},

load: {
Expand Down Expand Up @@ -2391,7 +2391,7 @@ type StylePreprocessor<Options extends StylePreprocessorInternalOptions> = {
options: Options,
resolvers: CSSAtImportResolvers,
) => StylePreprocessorResults | Promise<StylePreprocessorResults>
close: () => void
close: () => void | Promise<void>
}

export interface StylePreprocessorResults {
Expand Down Expand Up @@ -2612,8 +2612,8 @@ const scssProcessor = (
const normalizedErrors = new WeakSet<Error>()

return {
close() {
worker?.stop()
async close() {
await worker?.stop()
},
async process(environment, source, root, options, resolvers) {
let sassPackage = loadSassPackage(root, failedSassEmbedded ?? false)
Expand Down Expand Up @@ -3150,10 +3150,8 @@ const createPreprocessorWorkerController = (maxWorkers: number | undefined) => {
return scss.process(environment, source, root, opts, resolvers)
}

const close = () => {
less.close()
scss.close()
styl.close()
const close = async () => {
await Promise.all([less.close(), scss.close(), styl.close()])
}

return {
Expand Down
Loading