Why jquery.slim.js?url Behaves Differently in Vite
In a Vite project, jquery/dist/jquery.slim.js and jquery/dist/jquery.slim.js?url are fundamentally different imports. The ?url suffix switches Vite into "asset mode," returning a string URL instead of an executable module.
The core difference
At the top level, you're toggling between "run this JavaScript" and "just give me its path."
// ✅ Normal import: executes the module
import $ from 'jquery/dist/jquery.slim.js';
console.log(typeof $); // "function" - it's the jQuery API
// ❌ URL import: asset URL only
import jquerySlimUrl from 'jquery/dist/jquery.slim.js?url';
console.log(typeof jquerySlimUrl); // "string" - just a path
console.log(jquerySlimUrl); // "/assets/jquery.slim-a1b2c3d4.js"
| Import Type | What You Get | Code Executed? | Use Case |
|---|---|---|---|
import $ |
Module exports | ✅ Yes | Normal usage |
import url from '...?url' |
string |
❌ No | Workers, iframes |
Important
?url is not a filter or modifier—it completely changes what Vite does with the file.
Without ?url:
- Vite runs the file through the JS pipeline
- Dependencies are resolved and bundled
- You get the module's exports
With ?url:
- Vite copies the file to your build output as-is
- No bundling, no dependency resolution
- You get a string path to that file
Why ?url "breaks" your code
If your code looks like this:
// 🚫 WRONG: This gives you a string, not jQuery
import $ from 'jquery/dist/jquery.slim.js?url';
$('#app').hide();
// TypeError: $ is not a function
// Because $ === "/assets/jquery.slim-a1b2c3d4.js"
The fix is simple—remove ?url:
// ✅ CORRECT: This gives you the jQuery function
import $ from 'jquery/dist/jquery.slim.js';
$('#app').hide(); // Works
Tip
If you see TypeError: x is not a function after an import, check if you accidentally added ?url to the import path.
What ?url is actually for
?url exists for cases where you need a file's URL, not its code. These are legitimate use cases:
1. CSS Paint Worklets
import workletUrl from './checkered-pattern.js?url';
// Worklet API requires a URL, not code
CSS.paintWorklet.addModule(workletUrl);
2. Audio Worklets
import processorUrl from './audio-processor.js?url';
await audioContext.audioWorklet.addModule(processorUrl);
const node = new AudioWorkletNode(audioContext, 'my-processor');
3. Injecting scripts into iframes
import jqueryUrl from 'jquery/dist/jquery.slim.js?url';
const script = document.createElement('script');
script.src = jqueryUrl; // Need URL here, not code
iframe.contentDocument.head.appendChild(script);
4. Web Workers (alternative syntax)
// Option A: Vite's ?worker suffix (recommended)
import MyWorker from './worker.js?worker';
const worker = new MyWorker();
// Option B: Manual with ?url
import workerUrl from './worker.js?url';
const worker = new Worker(workerUrl, { type: 'module' });
All Vite import suffixes
Vite recognizes several query suffixes that change import behavior:
| Suffix | Returns | Bundled? | Example Use |
|---|---|---|---|
| (none) | Module exports | ✅ Yes | Normal imports |
?url |
string (URL) |
❌ No | Worklets, iframes |
?raw |
string (contents) |
❌ No | GLSL shaders, SQL |
?worker |
Worker class |
✅ Yes | Web Workers |
?worker&url |
string (Worker URL) |
✅ Yes | Worker URL only |
?inline |
string (data URI) |
❌ No | Inline assets |
// ?raw - get file contents as string
import vertexShader from './shader.vert?raw';
gl.shaderSource(shader, vertexShader);
// ?worker - get a Worker constructor
import AnalyticsWorker from './analytics.js?worker';
const worker = new AnalyticsWorker();
// ?inline - get base64 data URI
import iconDataUri from './icon.svg?inline';
img.src = iconDataUri; // "data:image/svg+xml;base64,..."
Note
These suffixes are Vite-specific. They won't work in Node.js, Webpack, or browsers directly.
The dependency gotcha
When you use ?url, Vite treats the file as a standalone asset. It does not process any import statements inside that file.
// worker.js
import { helper } from './utils.js'; // This import exists
// main.js
import workerUrl from './worker.js?url';
// ⚠️ worker.js is copied as-is
// ⚠️ The import for utils.js is NOT resolved
// ⚠️ Worker will fail at runtime with "Failed to resolve module"
| Import Method | Dependencies Resolved? | Tree-shaken? |
|---|---|---|
| Normal import | ✅ Yes | ✅ Yes |
?url import |
❌ No | ❌ No |
?worker import |
✅ Yes | ✅ Yes |
Warning
If your file has import statements, use ?worker instead of ?url + manual new Worker(). Vite will bundle the worker and its dependencies correctly.
Quick reference
Do you need...
- The module's API (functions, classes, values) → Normal import
- A URL to pass to browser APIs →
?url - File contents as a string →
?raw - A Web Worker with bundled dependencies →
?worker
// I need jQuery's $ function
import $ from 'jquery'; // ✅
// I need to inject jQuery into an iframe
import jqUrl from 'jquery?url'; // ✅
// I need GLSL shader source code
import frag from './shader.frag?raw'; // ✅
// I need a worker that imports other modules
import MyWorker from './worker.js?worker'; // ✅
Once you internalize that ?url flips Vite into "asset mode," the difference stops being mysterious: one gives you the module, the other gives you the address.
Related posts:
- Switching from Webpack to Vite in 2025 - Why Vite's architecture makes it faster