В проектах на JavaScript и TypeScript статическими ресурсами считаются файлы, которые не являются исполняемым кодом:
Vite умеет импортировать такие ресурсы напрямую:
import logo fr om './logo.png'
import styles fr om './style.css'
import workerUrl from './worker.js?worker'
Во время сборки Vite преобразует подобные импорты в специальные URL, модули или объекты. Однако TypeScript изначально не знает, как интерпретировать такие файлы. Без дополнительных деклараций появляются ошибки:
Cannot find module './logo.png'
Для устранения подобных ошибок используются типы статических ресурсов.
Vite поставляется со встроенным набором деклараций типов для большинства популярных форматов файлов. Подключаются они через пакет:
vite/client
Обычно это делается в файле:
vite-env.d.ts
Пример:
/// <reference types="vite/client" />
После подключения становятся доступны типы для:
Vite рекомендует хранить пользовательские декларации типов в отдельном файле:
src/vite-env.d.ts
Пример структуры проекта:
src/
├── assets/
├── components/
├── vite-env.d.ts
└── main.ts
Содержимое:
/// <reference types="vite/client" />
Этот файл автоматически подхватывается TypeScript, если входит в
область действия tsconfig.json.
Импорт PNG-файла:
import logo from './logo.png'
Тип переменной:
string
После сборки значение превращается в URL:
'/assets/logo.a1b2c3.png'
Пример использования:
const image = document.createElement('img')
image.src = logo
document.body.append(image)
import photo from './photo.jpg'
или:
import photo from './photo.jpeg'
Тип:
string
import animation from './animation.gif'
Тип:
string
import banner from './banner.webp'
import image from './image.avif'
По умолчанию SVG импортируется как URL:
import icon from './icon.svg'
Тип:
string
Пример:
const img = document.createElement('img')
img.src = icon
Во многих проектах SVG импортируется как React-компонент через плагины:
import Logo from './logo.svg'
Без дополнительных типов TypeScript выдаст ошибку.
Для корректной типизации создаётся декларация:
declare module '*.svg' {
import * as React from 'react'
const ReactComponent: React.FC<
React.SVGProps<SVGSVGElement>
>
export default ReactComponent
}
Теперь SVG можно использовать как компонент:
<Logo width={120} height={120} />
Vite поддерживает CSS Modules из коробки.
Файл:
Button.module.css
Импорт:
import styles from './Button.module.css'
Тип объекта:
{
readonly [key: string]: string
}
Использование:
button.className = styles.primary
По умолчанию TypeScript знает только о строковых ключах:
styles.primary
styles.secondary
Но конкретные имена классов неизвестны.
Для строгой типизации применяются генераторы:
Пример автоматически созданного файла:
declare const styles: {
readonly primary: string
readonly secondary: string
}
export default styles
Теперь TypeScript проверяет корректность классов:
styles.primary
Ошибка:
styles.primray
Обычный CSS импортируется как side effect:
import './global.css'
Значение не возвращается.
Типизация не требуется.
Vite поддерживает:
.scss;.sass.Пример:
import styles from './layout.module.scss'
Тип аналогичен CSS Modules:
{
readonly [key: string]: string
}
import styles from './theme.module.less'
import styles from './app.module.styl'
Vite поддерживает JSON напрямую.
import config from './config.json'
TypeScript автоматически выводит тип:
{
api: string
timeout: number
}
Пример:
{
"api": "/api",
"timeout": 5000
}
Использование:
console.log(config.api)
Для работы JSON должен быть включён параметр:
{
"compilerOptions": {
"resolveJsonModule": true
}
}
В современных шаблонах Vite он обычно уже активирован.
Специальный суффикс:
?raw
Позволяет импортировать содержимое файла как строку.
Пример:
import text from './article.md?raw'
Тип:
string
Использование:
console.log(text)
Суффикс:
?url
Пример:
import fileUrl from './document.pdf?url'
Тип:
string
После сборки:
'/assets/document.123abc.pdf'
Vite поддерживает Web Workers.
import Worker from './worker.ts?worker'
Тип:
WorkerConstructor
Использование:
const worker = new Worker()
import Worker from './worker.ts?worker&inline'
import SharedWorker from './worker.ts?sharedworker'
Импорт:
import init from './module.wasm'
Тип обычно:
string
или специальный loader-тип, если используется плагин.
Если TypeScript не знает о формате файла, создаётся собственная декларация.
Пример для .txt:
declare module '*.txt' {
const content: string
export default content
}
Теперь можно писать:
import text from './notes.txt'
declare module '*.md' {
const content: string
export default content
}
Некоторые плагины преобразуют markdown в HTML:
declare module '*.md' {
const html: string
export default html
}
Иногда markdown-файл экспортирует структуру:
declare module '*.md' {
export const attributes: Record<string, string>
const body: string
export default body
}
Пример:
declare module '*.bin' {
const file: ArrayBuffer
export default file
}
Типы ресурсов обычно размещаются в:
src/types/
или:
src/@types/
Пример:
src/
├── @types/
│ ├── assets.d.ts
│ └── markdown.d.ts
TypeScript должен видеть декларации:
{
"include": [
"src",
"src/@types"
]
}
Иногда декларации не работают из-за неправильного
exclude:
{
"exclude": [
"src/types"
]
}
В таком случае TypeScript игнорирует объявления модулей.
Основной механизм типизации статических ресурсов:
declare module '*.ext'
Пример:
declare module '*.shader' {
const shader: string
export default shader
}
Модуль может экспортировать несколько значений:
declare module '*.data' {
export const version: string
const content: string
export default content
}
Vite поддерживает динамический импорт групп файлов.
Пример:
const modules = import.meta.glob('./pages/*.ts')
Тип:
Record<
string,
() => Promise<unknown>
>
Можно явно указать тип модуля:
const pages = import.meta.glob<{
default: string
}>('./content/*.md')
Теперь TypeScript знает структуру импорта.
const modules = import.meta.glob(
'./modules/*.ts',
{ eager: true }
)
Тип:
Record<string, unknown>
С generic:
const modules = import.meta.glob<{
setup(): void
}>('./modules/*.ts', {
eager: true
})
Пример React-компонентов:
const pages = import.meta.glob<{
default: React.ComponentType
}>('./pages/*.tsx')
const articles = import.meta.glob<{
metadata: {
title: string
date: string
}
default: string
}>('./articles/*.md')
В старых версиях Vite использовался:
import.meta.globEager()
В новых версиях предпочтителен:
import.meta.glob('*', {
eager: true
})
Современный JavaScript поддерживает import assertions.
Пример:
import config from './config.json' assert {
type: 'json'
}
TypeScript использует встроенные JSON-типы.
Маленькие файлы могут встраиваться как base64.
Пример:
import icon from './icon.png'
После сборки:
data:image/png;base64,...
Тип остаётся:
string
Vite поддерживает:
new URL('./image.png', import.meta.url)
Тип:
URL
Пример:
const imageUrl = new URL(
'./image.png',
import.meta.url
).href
Файл:
VITE_API_URL=https://api.site.com
Использование:
import.meta.env.VITE_API_URL
Типизация:
interface ImportMetaEnv {
readonly VITE_API_URL: string
}
interface ImportMeta {
readonly env: ImportMetaEnv
}
/// <reference types="vite/client" />
declare module '*.svg' {
const src: string
export default src
}
declare module '*.md' {
const content: string
export default content
}
interface ImportMetaEnv {
readonly VITE_API_URL: string
readonly VITE_APP_TITLE: string
}
interface ImportMeta {
readonly env: ImportMetaEnv
}
Причины:
Появляется при дублировании деклараций:
declare module '*.svg'
в нескольких файлах одновременно.
Ошибка:
styles.primary
Причина:
Крупные проекты обычно разделяют декларации:
src/@types/
├── assets.d.ts
├── css.d.ts
├── env.d.ts
├── markdown.d.ts
└── workers.d.ts
Пример assets.d.ts:
declare module '*.png' {
const src: string
export default src
}
declare module '*.jpg' {
const src: string
export default src
}
declare module '*.webp' {
const src: string
export default src
}
В монорепозиториях декларации часто выносятся в отдельный пакет:
packages/
├── types/
├── ui/
└── app/
Пакет типов:
@company/types-vite
Подключение:
{
"compilerOptions": {
"types": [
"@company/types-vite"
]
}
}
Некоторые ресурсы доступны только в браузере.
Например:
import image from './image.png'
В Node.js такой импорт без bundler-а не работает.
Поэтому типизация Vite относится именно к среде сборщика, а не к стандартному TypeScript-runtime.
Во время dev-сервера Vite использует:
esbuild
Он обрабатывает импорты ресурсов и преобразует их в браузерно-совместимый формат.
Во время production-сборки применяется:
Rollup
Типы при этом остаются исключительно задачей TypeScript и не влияют на итоговый JavaScript-код.