Vite tries to aggressively prebundle even things that won't get used in the browser, and confuses itself
See original GitHub issueDescribe the bug
Any import()
s in an isomorphic part of the app are aggressively prebundled by Vite, even if they’re never ultimately used in the browser (and even if they are inside an if (!browser) { }
). This is a problem because when something cannot successfully be prebundled (e.g., if it uses Node APIs with no browser analogue), something within Vite crashes(?) or gets into a bad state, and - even if the offending import()
is removed - Vite then does not perform other prebundling that actually is necessary until the server is restarted. This can lead to, for example, import
s of CJS modules being left as import ... from '/node_modules/foo/...'
, which means the browser is being served CJS files when it is expecting ESM files.
Logs The logs from Vite when it’s failing to prebundle the package look like, e.g.:
> node_modules/@sentry/node/esm/parsers.js:2:9: error: No matching export in "browser-external:fs" for import "readFile"
2 │ import { readFile } from 'fs';
╵ ~~~~~~~~
> node_modules/@sentry/node/esm/integrations/utils/http.js:3:9: error: No matching export in "browser-external:url" for import "URL"
3 │ import { URL } from 'url';
╵ ~~~
> node_modules/@sentry/node/esm/integrations/modules.js:2:9: error: No matching export in "browser-external:fs" for import "existsSync"
2 │ import { existsSync, readFileSync } from 'fs';
╵ ~~~~~~~~~~
> node_modules/@sentry/node/esm/integrations/modules.js:2:21: error: No matching export in "browser-external:fs" for import "readFileSync"
2 │ import { existsSync, readFileSync } from 'fs';
╵ ~~~~~~~~~~~~
> node_modules/@sentry/node/esm/integrations/modules.js:3:9: error: No matching export in "browser-external:path" for import "dirname"
3 │ import { dirname, join } from 'path';
╵ ~~~~~~~
> node_modules/@sentry/node/esm/integrations/modules.js:3:18: error: No matching export in "browser-external:path" for import "join"
3 │ import { dirname, join } from 'path';
╵ ~~~~
The logs from the browser when it’s attempting to load the CJS version of a module as a ESM when Vite gives up and serves that instead look like:
Uncaught (in promise) ReferenceError: exports is not defined
<anonymous> http://localhost:3000/node_modules/decoders/index.js:3
To Reproduce
My reproduction uses two of the libraries I was seeing this with in a real project. @sentry/node
is the library that can’t (and shouldn’t) be bundled for the browser. decoders
is the library that’s available only in CJS and that needs to be prebundled for the browser to understand it.
Clone https://github.com/Conduitry-Repros/kit-1570 and run npm install
.
Start up the server with npm run dev
and check the browser console, where you should see the results of console.log(await import('decoders'));
.
Then, open src/routes/index.svelte
and uncomment the indicated line. Refresh the browser if necessary. In the Vite console, you should now see errors about not being able to process @sentry/node
. In the browser console, you should see errors that result from attempting to load CJS as ESM. If you look in the network tab, you’ll see that http://localhost:3000/node_modules/decoders/index.js is being requested (the raw CJS version from the package) rather than the http://localhost:3000/node_modules/.vite/decoders.js you should be getting.
This broken state persists even if you re-comment the import('@sentry/node')
. You don’t get the prebundled version of decoders
again until you restart the server.
Expected behavior A clear and concise description of what you expected to happen.
Stacktraces n/a
Information about your SvelteKit Installation:
Diagnostics
- The
envinfo
command hangs for me, I’m guessing because of WSL. This is a separate issue to be looked into, probably. - Firefox 88.0.1, but this shouldn’t be relevant
- No adapter, for now. This is in dev mode.
Severity
Fairly severe, as it was causing some very confusing behavior. I do have a workaround for now though, which is to use a helper function that I call instead of import()
:
export default async function serverImport(id) {
const { createRequire } = await import('module');
const require = createRequire('/absolute/path/package.json');
return require(id);
}
This relies on a known absolute path to resolve relative to, which is fine in my case as I’m using Docker. Note that I cannot use import.meta.url
as that is a path that looks absolute but is in fact relative to the root of my project. It’s /src/whatever/...
, not a real path in my whole filesystem.
Additional context I don’t really know enough to be able to tell whether this is a Vite bug or us calling Vite in the wrong way. It failing to prebundle certain dependencies makes sense. Some dependencies just won’t work that way. However, it aggressively attempting to prebundle things before they’re even requested by the browser seems questionable. And it then getting into a state where it won’t prebundle anything else is definitely a problem.
Issue Analytics
- State:
- Created 2 years ago
- Reactions:5
- Comments:12 (6 by maintainers)
Top GitHub Comments
Would it make sense that this is also happening in reverse. ie. my node build is failing from a client only dep. Node is having an issue with a dynamic import of a video player I’m using.
👍
optimizeDeps.exclude
worked for me. I’m no longer seeing the issue I mentioned above. I’m guessing that this is because of the fix for theprocess.env.
->({}).
stuff in SSR that’s happened in Vite in the meantime.I’m fine with this being closed on our end - we can use the Vite issue for tracking more graceful handling of these errors.