Skip to content

Vite tries to aggressively prebundle even things that won't get used in the browser, and confuses itself #1570

@Conduitry

Description

@Conduitry

Describe 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, imports 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingvite

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions