Skip to content

Commit

Permalink
fix(postcss): race condition on builder instance for simultaneous plu…
Browse files Browse the repository at this point in the history
…gin invocations (#2919)
  • Loading branch information
doubleaxe authored Oct 22, 2024
1 parent 6ab0037 commit 50fc8ef
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 29 deletions.
5 changes: 5 additions & 0 deletions .changeset/fast-zebras-live.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@pandacss/postcss': patch
---

fix(postcss): race condition on builder instance for simultaneous plugin invocations
32 changes: 30 additions & 2 deletions packages/postcss/__tests__/postcss.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import postcss from 'postcss'
import { existsSync } from 'fs'
import { rm } from 'fs/promises'
import { logger } from '@pandacss/logger'
import { describe, expect, test } from 'vitest'
import { describe, expect, test, vi } from 'vitest'

import pandacss, { type PluginOptions } from '../src/index'
import pandacss, { builder, type PluginOptions } from '../src/index'

async function run(input: string, options: PluginOptions, from?: string) {
const result = await postcss([pandacss(options)]).process(input, { from: from || '/foo.css' })
Expand Down Expand Up @@ -90,4 +90,32 @@ describe('PostCSS plugin', () => {
]),
)
})

test.sequential(
'`Builder` instance race condition when postcss invokes panda processing simultaneously',
async () => {
builder.context = undefined
const setupContextSpy = vi.spyOn(builder, 'setupContext')

const setupOrder: string[] = []
const setupOriginal = builder.setup
const setupSpy = vi.spyOn(builder, 'setup').mockImplementation((...args) => {
setupOrder.push('enter')
return setupOriginal.apply(this, args).finally(() => setupOrder.push('leave'))
})

try {
const input = '@layer reset, base, tokens, recipes, utilities;'
const configPath = join(__dirname, 'samples', 'panda.config.cjs')

await Promise.all([1, 2, 3, 4].map(() => run(input, { configPath })))

expect(setupContextSpy).toHaveBeenCalledTimes(1)
expect(setupOrder).toEqual(['enter', 'leave', 'enter', 'leave', 'enter', 'leave', 'enter', 'leave'])
} finally {
setupContextSpy.mockRestore()
setupSpy.mockRestore()
}
},
)
})
64 changes: 37 additions & 27 deletions packages/postcss/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Builder, setLogStream } from '@pandacss/node'
import { createRequire } from 'module'
import path from 'path'
import type { PluginCreator } from 'postcss'
import type { PluginCreator, TransformCallback } from 'postcss'

const customRequire = createRequire(__dirname)

Expand All @@ -20,48 +20,58 @@ export const loadConfig = () => interopDefault(customRequire('@pandacss/postcss'

let stream: ReturnType<typeof setLogStream> | undefined

const builder = new Builder()
// export for unit test
export const builder = new Builder()
let builderGuard: Promise<void> | undefined

export const pandacss: PluginCreator<PluginOptions> = (options = {}) => {
const { configPath, cwd, logfile, allow } = options

if (!stream && logfile) {
stream = setLogStream({ cwd, logfile })
}
const postcssProcess: TransformCallback = async function (root, result) {
const fileName = result.opts.from

return {
postcssPlugin: PLUGIN_NAME,
plugins: [
async function (root, result) {
const fileName = result.opts.from
const skip = shouldSkip(fileName, allow)
if (skip) return

const skip = shouldSkip(fileName, allow)
if (skip) return
await builder.setup({ configPath, cwd })

await builder.setup({ configPath, cwd })
// ignore non-panda css file
if (!builder.isValidRoot(root)) return

// ignore non-panda css file
if (!builder.isValidRoot(root)) return
await builder.emit()

await builder.emit()
builder.extract()

builder.extract()
builder.registerDependency((dep) => {
result.messages.push({
...dep,
plugin: PLUGIN_NAME,
parent: result.opts.from,
})
})

builder.registerDependency((dep) => {
result.messages.push({
...dep,
plugin: PLUGIN_NAME,
parent: result.opts.from,
})
})
builder.write(root)

builder.write(root)
root.walk((node) => {
if (!node.source) {
node.source = root.source
}
})
}

root.walk((node) => {
if (!node.source) {
node.source = root.source
}
})
return {
postcssPlugin: PLUGIN_NAME,
plugins: [
function (...args) {
builderGuard = Promise.resolve(builderGuard)
.catch(() => {
/**/
})
.then(() => postcssProcess(...args))
return builderGuard
},
],
}
Expand Down

0 comments on commit 50fc8ef

Please sign in to comment.