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 |
Use the value, not the filename 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
jqueryUrl is a string, not the jQuery function. Remove the suffix and call the module import:
-import jqueryUrl from 'jquery/dist/jquery.slim.js?url';
+import $ from 'jquery/dist/jquery.slim.js';
-jqueryUrl('#app').hide();
-// TypeError: jqueryUrl is not a function
+$('#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';
// Use an already-loaded iframe that this page can access.
const frameDocument = iframe.contentDocument;
if (!frameDocument) throw new Error('The iframe document is unavailable');
const script = frameDocument.createElement('script');
const loaded = new Promise((resolve, reject) => {
script.onload = resolve;
script.onerror = () => reject(new Error('jQuery failed to load'));
});
script.src = jqueryUrl;
frameDocument.head.append(script);
await loaded;
iframe.contentWindow.jQuery('#app').hide();
Here the string is exactly what script.src needs. jQuery executes when the iframe loads the script, not when Vite imports the URL.
Wait for the iframe’s script to load before calling jQuery. Appending the script starts loading it; the next line cannot assume jQuery is ready. Register the
loadanderrorhandlers before appending, await the result, and use the iframe’s jQuery instance. This example requires an already-loaded iframe whose document the parent can access. The script element documentation describes the loading callbacks.
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
Was this useful?
Thanks for the feedback.