KahWee - Web Development, AI Tools & Tech Trends

Expert takes on AI tools like Claude and Sora, modern web development with React and Vite, and tech trends. By KahWee.

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: