vanilla-extract icon indicating copy to clipboard operation
vanilla-extract copied to clipboard

Vite plugin fails to produce updated styles when run in watch mode

Open ehyland opened this issue 1 year ago • 1 comments

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

ehyland avatar Mar 06 '24 22:03 ehyland

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 avatar Mar 11 '24 23:03 askoufis

@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:

  1. every time there is a file change, createCompiler is called from the integration package
  2. the first time this happens cssCache gets populated via cssCache.set
  3. when a build is done, close is 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

szszoke avatar Apr 29 '24 10:04 szszoke

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?

szszoke avatar Apr 29 '24 10:04 szszoke

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.

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.

askoufis avatar Apr 29 '24 11:04 askoufis

This should be fixed by #1397.

askoufis avatar Apr 29 '24 11:04 askoufis

@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?

szszoke avatar Apr 29 '24 15:04 szszoke