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 |
[!TIP]
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.
Top comments (0)