Vite plugin fails to produce updated styles when run in watch mode
Describe the bug
When vite is run in watch mode, and styles are changed in source, vanilla extract fails to output new values in dist/style.css.
Reproduction
https://github.com/ehyland/issue--vanilla-extract-vite-watch-mode
System Info
System:
OS: macOS 13.6.4
CPU: (8) arm64 Apple M1
Memory: 125.52 MB / 8.00 GB
Shell: 5.2.26 - /opt/homebrew/bin/bash
Binaries:
Node: 20.11.1 - ~/Library/Caches/fnm_multishells/88671_1709762089443/bin/node
npm: 10.2.4 - ~/Library/Caches/fnm_multishells/88671_1709762089443/bin/npm
pnpm: 8.15.4 - ~/.pmm/package/bin/pnpm
bun: 1.0.29 - ~/.bun/bin/bun
Browsers:
Chrome: 122.0.6261.111
Safari: 16.6
npmPackages:
@vanilla-extract/css: ^1.14.1 => 1.14.1
@vanilla-extract/vite-plugin: ^4.0.4 => 4.0.4
vite: ^5.1.5 => 5.1.5
Used Package Manager
pnpm
Logs
vite v5.1.5 building for production...
watching for file changes...
build started...
✓ 3 modules transformed.
dist/index.mjs 0.04 kB │ gzip: 0.06 kB
dist/style.css 0.03 kB │ gzip: 0.05 kB
dist/index.js 0.05 kB │ gzip: 0.07 kB
built in 130ms.
build started...
✓ 1 modules transformed.
dist/index.js 0.05 kB │ gzip: 0.07 kB
dist/style.css 0.03 kB │ gzip: 0.05 kB
dist/index.mjs 0.04 kB │ gzip: 0.06 kB
built in 14ms.
Validations
- [X] Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
- [X] The provided reproduction is a minimal reproducible example of the bug.
From some debugging, my findings so far are:
vite-node isn't invalidating the changed module's existing cached result when a file changes.
This event handler doesn't seem to fire, maybe because we're in build mode rather than serve mode?
https://github.com/vanilla-extract-css/vanilla-extract/blob/cff97a7f44014a5da33e8c56b2980b991ec49dd3/packages/integration/src/compiler.ts#L137-L139
However, even if I manually invalidate the file/dep tree, this still doesn't result in new CSS.
Feel like there's something obvious I'm overlooking that will fix this, but not sure what. To be honest I don't think we tested much/at all in build --watch mode, probably because we assumed it would just work the same as a regular build, but I guess not.
@askoufis I tried to have a look as a complete outsider and I noticed a few things but I don't know enough to know if they could be a problem:
- every time there is a file change,
createCompileris called from theintegrationpackage - the first time this happens
cssCachegets populated viacssCache.set - when a build is done,
closeis called on the compiler instance
On subsequent file changes step 1 still happens but step 2 doesn't, so when the check happens in getCssForFile, cssCache is always empty
I was able to make this work with a small change. I think that the problem is that every time there is a file change, the compiler instance is recreated. Some state is lost in watch mode as a result.
I made a small change where I only called createCompiler if the compiler variable was undefined, and I removed the call to close in buildEnd, and that seemed to solve the issue, as in I get a new style.css file generated.
This: https://github.com/vanilla-extract-css/vanilla-extract/blob/1381e545a73a7b2270651153433490ef2c69d977/packages/vite-plugin/src/index.ts#L131-L155
Became this:
if (compiler === undefined) {
compiler = createCompiler({
root: config.root,
identifiers: getIdentOption(),
cssImportSpecifier: fileIdToVirtualId,
viteConfig: {
...configFile?.config,
plugins: configFile?.config.plugins?.flat().filter(
(plugin) =>
typeof plugin === "object" &&
plugin !== null &&
"name" in plugin &&
// Prevent an infinite loop where the compiler creates a new instance of the plugin,
// which creates a new compiler, which creates a new instance of the plugin, etc.
plugin.name !== "vanilla-extract" &&
// Skip Remix because it throws an error if it's not loaded with a config file.
// If it _is_ loaded with a config file, it will create an infinite loop because it
// also has a child compiler which uses the same mechanism to load the config file.
// https://github.com/remix-run/remix/pull/7990#issuecomment-1809356626
// Additionally, some internal Remix plugins rely on a `ctx` object to be initialized by
// the main Remix plugin, and may not function correctly without it. To address this, we
// filter out all Remix-related plugins.
!plugin.name.startsWith("remix")
),
},
});
}
I was originally affected by #1372 but this change also seems to solve that.
@askoufis what do you think?
I was able to make this work with a small change. I think that the problem is that every time there is a file change, the compiler instance is recreated. Some state is lost in watch mode as a result.
I made a small change where I only called
createCompilerif thecompilervariable wasundefined, and I removed the call tocloseinbuildEnd, and that seemed to solve the issue, as in I get a newstyle.cssfile generated.I was originally affected by #1372 but this change also seems to solve that.
@askoufis what do you think?
That seems like it could work, though we do need to call close at some point, though maybe closeWatcher is a better place to do it. I'll take a look.
This should be fixed by #1397.
Fixed in @vanilla-extract/[email protected]
@askoufis [email protected] doesn't fix this issue for me. Adding the if check on compiler so that it only gets assigned once does fix it however.
Did you leave it out on purpose?