В Vite любой импорт файла по умолчанию обрабатывается как ресурс
сборщика. Однако в ряде случаев требуется получить не содержимое файла и
не модуль, а именно итоговый URL до ресурса. Для этого используется
специальный суффикс ?url.
Импорт с ?url заставляет Vite вернуть строку с адресом
файла:
import imageUrl from './images/logo.png?url'
console.log(imageUrl)
Результатом будет строка:
'/assets/logo.8d7f3a.png'
Во время разработки URL указывает на dev-сервер Vite, а после
production-сборки — на итоговый файл в каталоге
dist/assets.
Без ?url Vite пытается определить тип ресурса
автоматически:
import logo from './logo.svg'
Для SVG возможны разные сценарии:
Суффикс ?url отключает подобную неоднозначность и явно
сообщает:
требуется строковый URL до файла.
Пример:
import fileUrl from './document.pdf?url'
Теперь переменная содержит только путь:
'/assets/document.a1b2c3.pdf'
?urlНекоторые браузерные API требуют именно строковый путь:
import workerUrl from './worker.js?url'
const worker = new Worker(workerUrl, {
type: 'module'
})
Многие библиотеки работают исключительно с URL:
import textureUrl from './textures/wood.jpg?url'
engine.loadTexture(textureUrl)
import videoUrl from './video/demo.mp4?url'
const video = document.createElement('video')
video.src = videoUrl
video.controls = true
document.body.append(video)
import soundUrl from './audio/click.mp3?url'
const audio = new Audio(soundUrl)
audio.play()
import bgUrl from './bg.jpg?url'
document.documentElement.style.setProperty(
'--bg-image',
`url(${bgUrl})`
)
import image from './photo.png'
Vite самостоятельно решает:
?urlimport imageUrl from './photo.png?url'
Результат всегда предсказуем:
В dev-режиме Vite не выполняет полноценную production-сборку. URL формируются напрямую через сервер разработки.
Пример:
import imageUrl from './logo.png?url'
Результат:
'/src/logo.png'
или:
'http://localhost:5173/src/logo.png'
в зависимости от конфигурации.
После выполнения:
vite build
файл получает хэш:
/assets/logo.2af61d.png
Импорт автоматически обновляется:
import imageUrl from './logo.png?url'
становится:
'/assets/logo.2af61d.png'
Это обеспечивает:
?url работает не только с медиафайлами.
Пример:
import scriptUrl from './external.js?url'
Теперь можно динамически подключать скрипт:
const script = document.createElement('script')
script.src = scriptUrl
document.head.append(script)
Один из наиболее распространённых сценариев.
?urlnew Worker('./worker.js')
Такой код часто ломается после сборки.
?urlimport workerUrl from './worker.js?url'
const worker = new Worker(workerUrl, {
type: 'module'
})
Vite корректно обработает:
WebAssembly-файлы часто импортируются именно через
?url.
import wasmUrl from './math.wasm?url'
Загрузка:
const response = await fetch(wasmUrl)
const bytes = await response.arrayBuffer()
const result = await WebAssembly.instantiate(bytes)
import pdfUrl from './manual.pdf?url'
window.open(pdfUrl)
import archiveUrl from './backup.zip?url'
Создание ссылки:
const a = document.createElement('a')
a.href = archiveUrl
a.download = 'backup.zip'
a.click()
?url от
?raw?urlВозвращает путь:
import file from './text.txt?url'
Результат:
'/assets/text.a12f.txt'
?rawВозвращает содержимое файла:
import text from './text.txt?raw'
Результат:
'Hello world'
?url от
inline-импортаimport img from './small.png?inline'
Файл преобразуется в Base64.
import img from './small.png?url'
Файл остаётся отдельным ресурсом.
new URL()В современном JavaScript часто используется конструкция:
const url = new URL('./logo.png', import.meta.url)
Vite умеет обрабатывать такой код автоматически.
Однако между подходами есть различия.
new URL()const imageUrl = new URL(
'./image.png',
import.meta.url
).href
Плюсы:
Минусы:
?urlimport imageUrl from './image.png?url'
Плюсы:
Можно загружать ресурсы динамически:
const imageModule = await import(
'./images/banner.png?url'
)
console.log(imageModule.default)
TypeScript не всегда понимает импорты с суффиксами.
Иногда требуется декларация:
declare module '*?url' {
const url: string
export default url
}
Обычно Vite уже предоставляет необходимые типы через:
{
"compilerOptions": {
"types": ["vite/client"]
}
}
<script setup>
import logoUrl from './logo.png?url'
</script>
<template>
<img :src="logoUrl">
</template>
import iconUrl from './icon.svg?url'
export default function App() {
return <img src={iconUrl} />
}
<script>
import imageUrl from './image.jpg?url'
</script>
<img src={imageUrl}>
import textureUrl from './texture.png?url'
const image = new Image()
image.src = textureUrl
image.onl oad = () => {
ctx.drawImage(image, 0, 0)
}
import textureUrl from './wood.jpg?url'
const texture = new THREE.TextureLoader().load(
textureUrl
)
Иногда URL нужен в runtime-конфигурации:
import workerUrl from './worker.js?url'
export const config = {
worker: workerUrl
}
При использовании ?url файл:
dist/assets.Если импортированный URL нигде не используется:
import imageUrl from './image.png?url'
то Vite и Rollup могут удалить его из итоговой сборки.
import iconUrl from '@/assets/icon.svg?url'
Alias корректно разрешается через конфигурацию:
resolve: {
alias: {
'@': '/src'
}
}
?urlСуффикс не подходит для случаев, когда требуется:
Неверно:
import worker from './worker.js?url'
worker.postMessage()
worker — это строка URL, а не объект Worker.
Неверно:
import text from './file.txt?url'
console.log(text.includes('hello'))
Импорт возвращает путь, а не текст файла.
new WorkerНеверно:
import workerUrl from './worker.js?url'
workerUrl.postMessage('hello')
Правильно:
const worker = new Worker(workerUrl, {
type: 'module'
})
| Подход | Результат |
|---|---|
import './img.png' |
зависит от обработки |
import './img.png?url' |
URL-строка |
import './img.png?raw' |
содержимое файла |
import './img.png?inline' |
Base64 |
new URL(..., import.meta.url) |
URL через ESM API |
src/
├── assets/
│ ├── logo.svg
│ ├── bg.jpg
│ ├── worker.js
│ └── manual.pdf
├── main.js
main.js:
import logoUrl from './assets/logo.svg?url'
import bgUrl from './assets/bg.jpg?url'
import workerUrl from './assets/worker.js?url'
import manualUrl from './assets/manual.pdf?url'
document.querySelector('#logo').src = logoUrl
document.body.style.backgroundImage =
`url(${bgUrl})`
const worker = new Worker(workerUrl, {
type: 'module'
})
document.querySelector('#manual').href =
manualUrl
Во время анализа модулей Vite:
?url;После сборки импорт:
import imageUrl from './logo.png?url'
может превратиться примерно в:
const imageUrl = "/assets/logo.8f2c1d.png"
?urlРазработчик сразу показывает намерение:
import fileUrl from './file.bin?url'
Пути автоматически обновляются после hash-переименования файлов.
Большинство нативных API работают именно со строковыми URL.
Суффикс одинаково работает: