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"
Enter fullscreen mode Exit fullscreen mode
Import Value in your code What happens at import timejquery.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
Enter fullscreen mode Exit fullscreen mode
Remove the suffix when you need to use jQuery in the current module:
import $ from 'jquery/dist/jquery.slim.js';
$('#app').hide();
Enter fullscreen mode Exit fullscreen mode
For jQuery 4, prefer the package’s public slim entry point instead of reaching into dist:
import { $ } from 'jquery/slim';
$('#app').hide();
Enter fullscreen mode Exit fullscreen mode
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);
Enter fullscreen mode Exit fullscreen mode
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);
Enter fullscreen mode Exit fullscreen mode
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' },
);
Enter fullscreen mode Exit fullscreen mode
Vite also supports ?worker when you want an imported constructor, and ?worker&url when an API specifically needs the URL of a processed worker.
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.