Vite · Part 7 — Static Assets & Glob Imports
Import assets like code: the default URL form, the ?url / ?raw / ?worker queries, inlining small assets, public/ vs imported assets, and import.meta.glob for bulk imports. With an asset import explorer.
In Vite, assets are part of the module graph — you import an image the same way you import code {Trong Vite, asset là một phần đồ thị module — bạn import ảnh giống import code}. That gives you hashing, cache-busting, and dead-asset elimination for free {Điều đó cho bạn hash, cache-busting, và loại asset chết miễn phí}. The trick is knowing what each import form returns {Mẹo là biết mỗi dạng import trả về gì}.
Expand each import form to see what you get back and when to use it {Mở rộng mỗi dạng import để xem bạn nhận gì và khi nào dùng}:
1. The default: import returns a URL {Mặc định: import trả về URL}
import imgUrl from "./logo.png";
// imgUrl === "/assets/logo-a1b2c3.png" (hashed)
document.querySelector("img").src = imgUrl;
Importing an asset gives you a resolved, fingerprinted URL {Import một asset cho bạn một URL đã phân giải, gắn vân tay}. In the build it’s copied to dist/assets/ with a content hash, so caching is safe and updates bust the cache automatically {Khi build nó được copy vào dist/assets/ với hash nội dung, nên cache an toàn và cập nhật tự bust cache}. An asset no module imports never ends up in dist {Asset không module nào import sẽ không vào dist}.
2. Small assets get inlined {Asset nhỏ được inline}
Below a size threshold (default 4 KB), Vite inlines the asset as a base64 data URI to save a request {Dưới ngưỡng kích thước (mặc định 4 KB), Vite inline asset thành data URI base64 để tiết kiệm request}:
export default defineConfig({
build: { assetsInlineLimit: 4096 }, // bytes; set 0 to disable inlining
});
So a tiny icon may become a data URI (no extra request), while a large image stays a separate hashed file {Nên icon nhỏ có thể thành data URI (không request thêm), còn ảnh lớn vẫn là file hash riêng}.
3. Query suffixes change what you get {Hậu tố query đổi cái bạn nhận}
The same file imports differently depending on the query {Cùng một file import khác nhau tùy query}:
| Import | Returns {Trả về} | Use for {Dùng cho} |
|---|---|---|
"./a.png" | URL string {chuỗi URL} | <img src> etc. |
"./a.svg?url" | URL (forced) {URL (ép)} | when a plugin would otherwise transform it {khi plugin sẽ biến đổi nó} |
"./a.glsl?raw" | file as string {file dạng chuỗi} | shaders, SQL, templates {shader, SQL, template} |
"./w.js?worker" | Worker constructor {constructor Worker} | new MyWorker() |
import shaderSrc from "./fragment.glsl?raw"; // the text
import Worker from "./heavy.js?worker"; // const w = new Worker()
?worker is especially nice: it bundles the worker correctly so you skip the usual new Worker(new URL(...)) ceremony {?worker đặc biệt hay: nó bundle worker đúng cách nên bạn bỏ được nghi thức new Worker(new URL(...)) thường lệ}.
4. public/ vs imported assets {public/ vs asset import}
Two mechanisms, picked deliberately {Hai cơ chế, chọn có chủ đích}:
- Imported (
src/) — processed, hashed, part of the graph. Use for assets your code references {xử lý, hash, thuộc đồ thị. Dùng cho asset code tham chiếu}. public/— copied verbatim to the output root, referenced by absolute path {copy nguyên văn ra gốc output, tham chiếu bằng đường dẫn tuyệt đối}.
<!-- file: public/favicon.svg → served at /favicon.svg -->
<link rel="icon" href="/favicon.svg" />
Use public/ for files that must keep an exact name (robots.txt, favicon.svg, manifest.webmanifest), or that are referenced by code you don’t control {Dùng public/ cho file phải giữ tên chính xác, hoặc được tham chiếu bởi code bạn không kiểm soát}. Don’t put it there just to avoid an import — you lose hashing and dead-asset elimination {Đừng để vào đó chỉ để né import — bạn mất hash và loại asset chết}.
5. import.meta.glob — bulk imports {import.meta.glob — import hàng loạt}
When you need to import many files matching a pattern — file-based routing, auto-registering plugins, a content folder — use import.meta.glob {Khi cần import nhiều file khớp pattern — routing theo file, plugin tự đăng ký, thư mục nội dung — dùng import.meta.glob}:
// lazy by default: an object of dynamic-import functions
const pages = import.meta.glob("./pages/*.tsx");
// { "./pages/Home.tsx": () => import("./pages/Home.tsx"), ... }
for (const path in pages) {
pages[path]().then((mod) => registerRoute(path, mod.default));
}
Pass { eager: true } to import them all immediately instead of lazily {Truyền { eager: true } để import tất cả ngay thay vì lazy}:
const modules = import.meta.glob("./icons/*.svg", { eager: true, query: "?raw" });
This is a compile-time feature — Vite statically expands the glob, so the pattern must be a literal string {Đây là tính năng lúc biên dịch — Vite mở rộng glob tĩnh, nên pattern phải là chuỗi literal}. It’s how most file-based routers and “auto-import everything in this folder” setups work {Đây là cách đa số router theo file và thiết lập “tự import mọi thứ trong thư mục này” hoạt động}.
6. Exercises {Bài tập}
1. You import ./hero.png and reference the result as an <img src>. Why is the URL hashed, and what’s the benefit? {Bạn import ./hero.png và dùng kết quả làm <img src>. Vì sao URL được hash, lợi ích gì?}
Solution {Lời giải}
Vite fingerprints assets with a content hash for safe long-term caching — when the file changes, the URL changes, busting the cache automatically {Vite gắn vân tay asset bằng hash nội dung để cache dài hạn an toàn — khi file đổi, URL đổi, tự bust cache}.
2. You need the contents of a .sql file as a string in JS, not a URL. What import do you use? {Bạn cần nội dung file .sql dạng chuỗi trong JS, không phải URL. Dùng import nào?}
Solution {Lời giải}
import sql from "./query.sql?raw" — the ?raw suffix returns the file as a string {import sql from "./query.sql?raw" — hậu tố ?raw trả file dạng chuỗi}.
3. You want a file-based router that auto-registers every component in src/pages/, loaded lazily. What API? {Bạn muốn router theo file tự đăng ký mọi component trong src/pages/, tải lazy. API nào?}
Solution {Lời giải}
import.meta.glob("./pages/*.tsx") — returns a map of paths to dynamic-import functions (lazy). Add { eager: true } only if you want them all loaded immediately {trả map đường dẫn → hàm dynamic-import (lazy). Thêm { eager: true } chỉ khi muốn tải hết ngay}.
Stretch {Nâng cao}: in the explorer, compare ?url, ?raw, and ?worker for the same file and note how the return value changes from a string URL to file text to a constructor {trong explorer, so ?url, ?raw, và ?worker cho cùng file và để ý giá trị trả đổi từ chuỗi URL sang văn bản file sang constructor}.
Key takeaways {Điểm chính}
- Importing an asset returns a hashed URL by default {Import asset trả về URL đã hash mặc định}.
- Small assets (< 4 KB) are inlined as data URIs {Asset nhỏ (< 4 KB) được inline thành data URI}.
- Query suffixes:
?url,?raw(string),?worker(constructor) {Hậu tố query:?url,?raw(chuỗi),?worker(constructor)}. public/is verbatim, absolute-path, unhashed — for exact-name files {public/là nguyên văn, đường dẫn tuyệt đối, không hash — cho file tên chính xác}.import.meta.globbulk-imports by pattern (lazy, oreager) {import.meta.globimport hàng loạt theo pattern (lazy, hoặceager)}.
Next up {Tiếp theo}
Part 8 — Env vars & modes: import.meta.env, .env files, the VITE_ prefix and why it matters for security, define, and custom modes {Phần 8 — Biến môi trường & mode: import.meta.env, file .env, tiền tố VITE_ và vì sao quan trọng cho bảo mật, define, và mode tùy chỉnh}.