Skip to content

fix(webpack): persistent cache#5017

Merged
zyyv merged 2 commits intounocss:mainfrom
Jack-sh1:fix/webpack-persistent-cache
Apr 8, 2026
Merged

fix(webpack): persistent cache#5017
zyyv merged 2 commits intounocss:mainfrom
Jack-sh1:fix/webpack-persistent-cache

Conversation

@Jack-sh1
Copy link
Copy Markdown
Contributor

@Jack-sh1 Jack-sh1 commented Dec 8, 2025

Should be tested with:

  1. First build (no cache) - all modules processed
  2. Second build (partial cache) - some modules from cache
  3. Third build (full cache) - all modules from cache

All builds should generate identical CSS with all classes extracted.

Fixes

Closes #419

@Jack-sh1 Jack-sh1 requested review from antfu and zyyv as code owners December 8, 2025 20:46
@netlify
Copy link
Copy Markdown

netlify bot commented Dec 8, 2025

Deploy Preview for unocss ready!

Built without sensitive environment variables

Name Link
🔨 Latest commit 708825d
🔍 Latest deploy log https://app.netlify.com/projects/unocss/deploys/6954bc22639d4c00087533ec
😎 Deploy Preview https://deploy-preview-5017--unocss.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@zyyv
Copy link
Copy Markdown
Member

zyyv commented Dec 9, 2025

Please clean up your code changes and stay focused on doing one thing at a time.

- Hook into `finishModules` to scan modules when webpack cache is enabled
- Manually read file content to bypass loader skipping on cache hits
Fixes unocss#419
@Jack-sh1 Jack-sh1 force-pushed the fix/webpack-persistent-cache branch from 05842d9 to d4aae6e Compare December 9, 2025 13:12
@Jack-sh1
Copy link
Copy Markdown
Contributor Author

Jack-sh1 commented Dec 9, 2025

Apologies for the noise. I've cleaned up the unrelated changes/formatting and reverted the unnecessary files. The PR now only contains the fix for the MDX parser issue.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Dec 31, 2025

Open in StackBlitz

commit: 708825d

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses webpack persistent cache issues (#419) by ensuring that CSS classes are properly extracted from modules restored from cache. The fix adds a finishModules hook that manually reads and processes file content when webpack's persistent cache is enabled, preventing the loss of CSS extraction data between builds.

  • Adds a new finishModules hook to handle modules restored from webpack's persistent cache
  • Implements file reading logic to extract CSS classes from cached modules
  • Adds Buffer type import for type safety when handling file content

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@@ -1,4 +1,5 @@
import type { UserConfigDefaults } from '@unocss/core'
import type { Buffer } from 'node:buffer'
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Buffer type is a global type in Node.js and doesn't need to be explicitly imported from node:buffer. This import can be removed as Buffer is already available in the global scope.

Suggested change
import type { Buffer } from 'node:buffer'

Copilot uses AI. Check for mistakes.
Comment on lines +122 to +129
const content = await new Promise<string | Buffer | undefined>((resolve, reject) => {
compiler.inputFileSystem!.readFile(resource, (err, data) => {
if (err)
reject(err)
else
resolve(data)
})
}) // let it throw if error
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error handling in this code will silently fail for file read errors. The comment on line 129 says "let it throw if error", but if a file read fails, the entire Promise.all on line 137 will reject, potentially causing the compilation to fail or behave unexpectedly. Consider wrapping the file read in a try-catch block and logging errors instead of letting them propagate, or at least handle them gracefully to avoid breaking the entire compilation process.

Suggested change
const content = await new Promise<string | Buffer | undefined>((resolve, reject) => {
compiler.inputFileSystem!.readFile(resource, (err, data) => {
if (err)
reject(err)
else
resolve(data)
})
}) // let it throw if error
let content: string | Buffer | undefined
try {
content = await new Promise<string | Buffer | undefined>((resolve, reject) => {
compiler.inputFileSystem!.readFile(resource, (err, data) => {
if (err)
reject(err)
else
resolve(data)
})
})
}
catch (error) {
compilation.warnings.push(new Error(`[@unocss/webpack] Failed to read file "${resource}": ${String(error)}`))
return
}

Copilot uses AI. Check for mistakes.
if (compiler.options.cache) {
compilation.hooks.finishModules.tapPromise(PLUGIN_NAME, async (modules) => {
const { RESOLVED_ID_RE } = await ctx.getVMPRegexes()
const promises: Promise<any>[] = []
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type Promise<any>[] loses type safety. Consider using Promise<void>[] instead since the promises are only used for awaiting and their resolved values are not used.

Suggested change
const promises: Promise<any>[] = []
const promises: Promise<void>[] = []

Copilot uses AI. Check for mistakes.
Comment on lines +106 to +131
compilation.hooks.finishModules.tapPromise(PLUGIN_NAME, async (modules) => {
const { RESOLVED_ID_RE } = await ctx.getVMPRegexes()
const promises: Promise<any>[] = []

for (const module of modules) {
const resource = (module as any).resource
if (resource && isCssId(resource))
continue
if (resource && !RESOLVED_ID_RE.test(resource) && filter('', resource)) {
// For Webpack 5 persistent cache, we need to manually read the file content
// if the module is restored from cache, as loaders might be skipped.
promises.push(
(async () => {
if (!compiler.inputFileSystem)
return

const content = await new Promise<string | Buffer | undefined>((resolve, reject) => {
compiler.inputFileSystem!.readFile(resource, (err, data) => {
if (err)
reject(err)
else
resolve(data)
})
}) // let it throw if error
if (content != null)
await ctx.extract(content.toString(), resource)
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The finishModules hook processes all modules, including those that were just transformed. This means modules that went through the transform hook (lines 51-64) will have extract called twice - once during transform and once in finishModules. This duplicate processing could lead to incorrect CSS generation. Consider checking if a module was actually restored from cache before re-processing it, or track which modules have already been extracted to avoid duplicates.

Copilot uses AI. Check for mistakes.
Comment on lines +106 to +138
compilation.hooks.finishModules.tapPromise(PLUGIN_NAME, async (modules) => {
const { RESOLVED_ID_RE } = await ctx.getVMPRegexes()
const promises: Promise<any>[] = []

for (const module of modules) {
const resource = (module as any).resource
if (resource && isCssId(resource))
continue
if (resource && !RESOLVED_ID_RE.test(resource) && filter('', resource)) {
// For Webpack 5 persistent cache, we need to manually read the file content
// if the module is restored from cache, as loaders might be skipped.
promises.push(
(async () => {
if (!compiler.inputFileSystem)
return

const content = await new Promise<string | Buffer | undefined>((resolve, reject) => {
compiler.inputFileSystem!.readFile(resource, (err, data) => {
if (err)
reject(err)
else
resolve(data)
})
}) // let it throw if error
if (content != null)
await ctx.extract(content.toString(), resource)
})(),
)
}
}

await Promise.all(promises)
})
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new persistent cache handling logic lacks test coverage. Consider adding tests that verify the behavior across the three scenarios mentioned in the PR description: first build (no cache), second build (partial cache), and third build (full cache). This would ensure the cache handling works correctly and prevents regressions.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @antfu @zyyv, friendly ping — this PR has been open for a while. Is there anything else needed from my side? Happy to make adjustments if needed.

@Jack-sh1
Copy link
Copy Markdown
Contributor Author

Jack-sh1 commented Apr 7, 2026

Hi @antfu @zyyv, friendly ping — this PR has been open for a while. Is there anything else needed from my side? Happy to make adjustments if needed.

@zyyv zyyv changed the title Fix/webpack persistent cache fix(webpack): persistent cache Apr 8, 2026
@zyyv
Copy link
Copy Markdown
Member

zyyv commented Apr 8, 2026

@Jack-sh1 I apologize for the long delay in submitting this pull request, as I am not familiar with webpack. I am willing to merge and release this pull request as an attempt.

@zyyv zyyv added this pull request to the merge queue Apr 8, 2026
Merged via the queue into unocss:main with commit ff7e7f2 Apr 8, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[@unocss/webpack] CSS generation is inconsistent with persistent caching enabled

3 participants