Stale CSS after editing a Sass partial: which caching layer is actually responsible?
0 reputation · 09 Oct 2020, 21:37 UTC
0 reputation · 09 Oct 2020, 21:37 UTC
A project compiles Sass through a build tool (for example webpack with sass-loader, Vite, or a framework asset pipeline) on top of Dart Sass. The goal is to reliably diagnose why editing a partial pulled in via @use or @import sometimes leaves the emitted CSS unchanged until some cache is cleared.
The uncertainty is which layer caches at all. Dart Sass itself does not keep a persistent compilation cache comparable to the legacy Ruby Sass .sass-cache directory, so the staleness presumably comes from the surrounding toolchain keying on the entry file rather than the full dependency graph. Custom importers or loadPaths resolution may also mean some files never register as watched dependencies.
Assume current Dart Sass and a recent build-tool version; the legacy Ruby Sass :cache_location behavior is out of scope unless the setup turns out to be legacy.
Which specific signals distinguish a build-tool cache miss from a watcher dependency-tracking gap when a partial edit produces no output change? Does compiling the entry directly with the Dart Sass CLI conclusively rule Sass itself in or out? Is there any documented, toolchain-independent contract for cache invalidation, or must each integration be verified on its own versions?
Dart Sass itself is almost never the culprit: it has no persistent compilation cache comparable to the legacy Ruby Sass .sass-cache directory. When editing a partial pulled in via @use or @import produces no change in the emitted CSS, the responsible layer is nearly always one of two things in the surrounding toolchain: a build-tool cache keyed on the entry file (or on stale mtimes), or a watcher dependency-tracking gap where the partial was never registered as a dependency of the entry, so no rebuild is triggered at all.
The cleanest discriminator is whether a rebuild runs when you save the partial:
loadPaths entry, so the loader never reported it via the bundler's dependency API (e.g. this.addDependency() in webpack loaders); or the partial lives outside the watched directories/globs.A second useful signal: touch the entry file (add a whitespace change). If that produces correct CSS, the toolchain can compile the new partial content fine — it simply wasn't invalidated by the partial edit. That points at watcher/dependency registration. If even an entry edit yields stale output, suspect a persistent cache directory or content-hash keying that doesn't include partials.
Compiling the entry directly is a strong test but not a fully conclusive one:
sass entry.scss /tmp/out.css
# edit the partial, then:
sass entry.scss /tmp/out2.css
diff /tmp/out.css /tmp/out2.cssIf the CLI output changes, Sass's compilation is correct and the staleness is in the build-tool layer. The caveat: the CLI only reproduces your setup if it resolves the same files. If your build uses custom importers, package resolution (pkg: URLs), or loader-injected loadPaths, replicate them with --load-path flags, or the CLI may compile a different file than the bundler does and the comparison is meaningless.
No. There is no shared specification for how Sass integrations must key or invalidate caches. Each integration — sass-loader, Vite's CSS pipeline, framework asset pipelines — implements its own scheme (mtime, content hash, resolved-graph tracking) and its own watcher registration. The one real contract is narrower: the Sass compiler API reports the files it loaded, and a correct integration is expected to register those as watch dependencies. Integrations that resolve files outside that API (custom importers doing their own filesystem work) can silently break it. So behavior must be verified per integration and per version.
webpack --watch with stats, vite --debug) and save the partial. No change event → watcher gap.node_modules/.cache) once; if the problem disappears until the next partial edit, the cache keying is at fault.One caveat worth flagging for review: exact cache locations, debug flags, and watcher behavior vary by version, so confirm flag names against the versions installed rather than assuming the examples above apply verbatim. If the setup turns out to involve a custom importer, that is the first place to look — it is the most common way partials escape both the cache key and the watch graph simultaneously.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.