The confusing part is only four characters long: ?url. Add it to a Vite import and the value changes from executable JavaScript to the address of an asset.
import $ from 'jquery/dist/jquery.slim.js';
import jqueryUrl from 'jquery/dist/jquery.slim.js?url';
typeof $; // "function"
typeof jqueryUrl; // "string"
| Import | Value in your code | What happens at import time |
|---|---|---|
jquery.slim.js | jQuery’s API | JavaScript is loaded as a dependency |
jquery.slim.js?url | A URL string | The file is treated as an asset |
If the next line calls the imported value, you probably do not want
?url. If the next line passes it tosrc,addModule(), or another URL-based browser API, you probably do.
The 20-second fix
This fails because jqueryUrl is a string, not the jQuery function:
import jqueryUrl from 'jquery/dist/jquery.slim.js?url';
jqueryUrl('#app').hide();
// TypeError: jqueryUrl is not a function
Remove the suffix when you need to use jQuery in the current module:
import $ from 'jquery/dist/jquery.slim.js';
$('#app').hide();
For jQuery 4, prefer the package’s public slim entry point instead of reaching into dist:
import { $ } from 'jquery/slim';
$('#app').hide();
The jQuery project documents jquery/slim as the supported npm import for the slim build. Older jQuery 3 projects often use the dist/jquery.slim.js path shown above.
What Vite actually does
Vite calls ?url an explicit URL import. It opts a file into the asset pipeline and makes the module’s default export a string.
During development, that string may point to the source file. In a production build, Vite can give the asset a hashed filename or inline it as a data URL, depending on the file and configuration. The stable contract is not the exact path. The stable contract is: you receive a string that the browser can use as a URL.
Importing that URL does not execute the file in the current module. The file can still execute later if you load the URL through a browser API such as a script element or a worklet loader.
When ?url is the right tool
A Paint Worklet is the clean example because addModule() asks for a URL:
import workletUrl from './paint-worklet.js?url';
CSS.paintWorklet.addModule(workletUrl);
The same idea applies when a document needs a classic script URL. For example, you can load the jQuery distribution into an iframe rather than importing jQuery into the parent page:
import jqueryUrl from 'jquery/dist/jquery.slim.js?url';
const script = iframe.contentDocument.createElement('script');
script.src = jqueryUrl;
iframe.contentDocument.head.append(script);
Here the string is exactly what script.src needs. jQuery executes when the iframe loads the script, not when Vite imports the URL.
Do not use bare ?url for a worker graph
If a worker imports other modules, let Vite process the worker and its dependencies. Vite’s recommended worker syntax stays close to the browser standard:
const worker = new Worker(
new URL('./worker.js', import.meta.url),
{ type: 'module' },
);
Vite also supports ?worker when you want an imported constructor, and ?worker&url when an API specifically needs the URL of a processed worker.
| Need | Use | You receive |
|---|---|---|
| Call jQuery in this module | jquery/slim or jquery.slim.js | jQuery’s API |
| Pass a file to a URL-based API | ?url | URL string |
| Read a file’s contents | ?raw | Source string |
| Create a processed worker | new Worker(new URL(...)) or ?worker | Worker instance or constructor |
The rule worth remembering
?url does not mean “import this module from a URL.” It means “import the URL of this file.”
That one distinction explains both the error and the fix.
Sources
One quick signal
Did this earn your time?
Thanks. That gives me something concrete to check.


