vite-plugin-svelte: Circular filesystem watch events cascade, including self-referential, leading to RangeError: Maximum call stack size exceeded
Describe the bug
I have encountered crash of dev script.
RangeError: Maximum call stack size exceeded
Initial investigation lead me to emitChangeEventOnDependants function in src/utils/watch.js, where I’ve discovered
self-referential loops, where supposed dependant was same as filename.
Unfortunately, blocking self-referential event emitting revealed another problem - circular emission of virtual change event.
It is challenging to create an isolated reproduction. Which may or may not be related to how vite-plugin-svelte-cache choses to build dependency tree.
However, it is clear that self-referential, and circular references may exist, and therefore may overwhelm nodejs process with storm of emited events.
While it is not as good as reproduction, I hope, you would find this information instrumental.
Reproduction URL
https://github.com/sveltejs/vite-plugin-svelte
Reproduction
No response
Logs
No response
System Info
System:
OS: macOS 13.5.2
CPU: (8) arm64 Apple M2
Memory: 208.14 MB / 24.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 20.8.0 - ~/.asdf/installs/nodejs/20.8.0/bin/node
npm: 10.1.0 - ~/.asdf/plugins/nodejs/shims/npm
Browsers:
Chrome: 117.0.5938.149
Safari: 16.6
npmPackages:
@sveltejs/adapter-auto: ^2.0.0 => 2.1.0
@sveltejs/adapter-static: ^2.0.3 => 2.0.3
@sveltejs/kit: ^1.20.4 => 1.25.1
svelte: ^4.0.5 => 4.2.1
vite: ^4.4.2 => 4.4.10
About this issue
- Original URL
- State: closed
- Created 9 months ago
- Reactions: 1
- Comments: 22 (8 by maintainers)
@RowanAldean , I had same story, in your project’s folder you may find file
node_modules/@sveltejs/vite-plugin-svelte/src/utils/watch.json line27you may seewatcher.emit('change', dependant);comment this line to avoid crashes.P.S. I have tailwind installed. What would you say about your setup? Are you use anything beyond vanilla setup? Style preprocessors?
Sure, I wanted Tailwind utilities in my scopes, but I get your point, thank you.
“This explains why we don’t see more reports of it happening, you really shouldn’t do this.”
I’m leaning towards throwing an error if we detect the file itself in preprocess dependencies, its a sign of something seriously wrong.
yeah, so whats happening here is not pretty at all… in the project above, these are the dependencies for src/routes/+page.svelte returned by preprocess:
including the file we just preprocessed in the dependencies output is obviously super wrong, so step 1 is filtering that from the returned dependencies before passing them on. This means that the direct circle from +page.svelte to +page.svelte no longer happens.
but notice how it includes all the others too? change any of those files and +page.svelte gets preprocessed/compiled again just in case the change actually affected the output. We have no way to tell which of these dependencies are real and which are just there because tailwind postcss decided to go nuclear.
I assume it returns similar dependencies for all the other .svelte files too, essentially leading to a “compile every svelte file as soon as you edit something” scenario. Great!
yes, tailwind with it’s postcss directory dependencies could cause this. I’d recommend not using theirsee below@applyfeature in svelte style blocks.in general I’d recommend unocss instead of it. But given it’s popularity and use in 3rd party libraries we have to make it work somehow. still leaning towards keeping it as simple as possible which might mean just avoiding emitting the event in case it’s a circle.
@dominikg I wonder if all this jazz is connected with Tailwind recompilation or CSS purging…
… as Tailwind generates CSS classes depending on js files (configs, extensions, plugins), and basically change to any file may cause change of CSS styles, including those inside svelte components
which leads to update of components, which leads to virtual event, which may lead to recompilation of tailwind… and so on, and so forth…
@andriytyurnikov Thanks, i’ll try this and edit comment with result(s).
I’m using
svelte-preprocessorto support a globalapp.scssviaprependDatain mysvelte.config.js.I also moved to using yarn to handle all the dependency hell (if that is relevant 😉)
Also using tailwind for skeletonUI and recently for shadcn-svelte which is what forced me to do this whole updating everything as I had a bunch of dependency issues.
Also your solution seems painful as this would need to be done anytime I rebuild
node_modulesfrompackage.json😢update: @andriytyurnikov above solution works as a workaround to not interrupt developer workflow - hello future googler probably struggling with this pain in the ass watcher 👋
I just upgraded my entire project to svelte 4 (meaning sveltekit, vite and the rest all updated). Watcher failing and same error as described, I can’t save a file without a crash that bubbles up from:
Here’s a full stack trace:
Much of what you have discussed is far beyond my technical understanding of the sveltekit/svelte ecosystem so i’ll pass on trying to understand for now as i’m looking to just build! However I hope the above is of some value (and proof this is happening to others too 👀)
TLDR; Things are complex and there are 3 paths.
Details: Sad irony is that vite relies on 3rd party watchers (chokidar?), I remember reading their discussion about choosing new one.
I don’t mean to be pushy, yet I would like to reiterate for clarity ( and disagreements, questions and critique are totally welcome): My understanding is that current implementation optimistically assumes:
watcher,vite-plugin-svelte-cacheorvite-plugin-sveltesuch behaviour is fundamentally unsafe.Also, consider this - circular dependency prevention mechanism based on reference checks is only possible on a level, where full dependency graph is known. With this in mind - having more then one dependency-tracking context is fundamentally vulnerable to cascades as well, as such contexts would be able of triggering each-other.
It is not obvious how exactly watcher (and code triggers event on watcher, which is not the same as communicating dependency tree to vite!) is supposed resolve such an issue: watcher receives “event”, and it is not aware that event is “virtual”, so watcher tries hard to do his job, regardless of dependency tree - because allowing or disallowing circular references is not it’s job - maybe you building a language friendly to circular behaviour.
Apparently, watchers leave issue of dependencies beyond their scope. And, again, it is not surprising, given the need of access to dependency tree for comparison-based implementation. Yet insufficient watcher resiliency is indeed surprising.