Why jquery.slim.js?url Behaves Differently in Vite
jquery/dist/jquery.slim.js and jquery/dist/jquery.slim.js?url do completely different things in Vite. The ?url suffix switches Vite into "asset mode" — you get a string URL, not an executable module.
The core difference
// ✅ 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
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.
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 these query suffixes:
| 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,..."
These suffixes are Vite-specific. They won't work in Node.js, Webpack, or browsers directly.
Note
If you migrate from Vite to another bundler, you will need to replace all ?url, ?raw, and ?worker imports with that bundler's equivalents. These are not standard JavaScript.
The dependency gotcha
?url treats the file as a standalone asset. Vite does not process import statements inside it.
// 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'; // ✅
?url flips Vite into asset mode. One import 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