jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

Vite · Part 9 — Plugins

The Rollup-compatible plugin API: the hooks that matter (resolveId, load, transform), enforce (pre/post) and apply (serve/build) ordering, and writing your own virtual-module plugin. With a plugin hook timeline.

7 MIN READ

Almost everything Vite does beyond plain JS/CSS goes through a plugin — frameworks, legacy support, PWA, SVG-as-component, and your own custom transforms {Hầu hết mọi thứ Vite làm ngoài JS/CSS thuần đều qua plugin — framework, hỗ trợ legacy, PWA, SVG-as-component, và biến đổi tùy chỉnh của bạn}. The good news: Vite plugins are Rollup plugins with a few extra hooks, so the knowledge transfers across the whole ecosystem {Tin tốt: plugin Vite là plugin Rollup với vài hook thêm, nên kiến thức chuyển giao khắp hệ sinh thái}.

Run a module through the plugin pipeline and watch each hook fire in order {Chạy một module qua pipeline plugin và xem mỗi hook kích hoạt theo thứ tự}:


1. A plugin is an object {Plugin là một object}

The minimal shape — a name and one or more hook functions {Hình dạng tối thiểu — một name và một hoặc nhiều hàm hook}:

import type { Plugin } from "vite";

function myPlugin(): Plugin {
  return {
    name: "my-plugin", // required, used in errors/warnings
    transform(code, id) {
      // rewrite a module's code
      return null; // null = leave unchanged
    },
  };
}

// vite.config.ts
export default defineConfig({ plugins: [myPlugin()] });

You add it to plugins like any other {Bạn thêm vào plugins như bất kỳ cái nào khác}. Most plugins are functions returning this object so they can take options {Đa số plugin là hàm trả object này để nhận tùy chọn}.


2. The hooks that matter {Các hook quan trọng}

Vite runs hooks as it processes each module {Vite chạy hook khi xử lý từng module}. The ones you’ll actually use {Những cái bạn thực sự dùng}:

HookWhen {Khi}Use for {Dùng cho}
config / configResolvedstartup {khởi động}read/tweak config {đọc/chỉnh config}
configureServerdev only {chỉ dev}add dev-server middleware {thêm middleware dev-server}
resolveIdper module {mỗi module}claim/redirect an import id {nhận/chuyển hướng id import}
loadper module {mỗi module}provide source for an id {cấp source cho một id}
transformper module {mỗi module}rewrite a module’s code {viết lại code module}
handleHotUpdatedev only {chỉ dev}customize HMR behavior {tùy chỉnh hành vi HMR}
renderChunk / generateBundlebuild only {chỉ build}edit output / emit files {sửa output / phát file}

resolveIdloadtransform is the core trio for every module {resolveIdloadtransform là bộ ba cốt lõi cho mỗi module}. transform is the workhorse {transform là người làm chính}.


3. enforce and apply — ordering & scope {enforceapply — thứ tự & phạm vi}

Plugin order can matter {Thứ tự plugin có thể quan trọng}. Vite runs plugins in three groups {Vite chạy plugin theo ba nhóm}:

function myPlugin(): Plugin {
  return {
    name: "my-plugin",
    enforce: "pre", // "pre" → before core | undefined → with core | "post" → after
    apply: "build", // "build" | "serve" | a function — limit when it runs
    transform(code, id) {
      /* ... */
    },
  };
}
  • enforce: "pre" — run before Vite’s core plugins (e.g. to transform source before it’s parsed) {chạy trước plugin core của Vite}.
  • enforce: "post" — run after core (e.g. to post-process output) {chạy sau core}.
  • apply: "serve" | "build" — only run in dev or only in build {chỉ chạy khi dev hoặc chỉ khi build}.

4. Writing a virtual-module plugin {Viết plugin virtual-module}

The most useful pattern to learn: a virtual module — an importable module that doesn’t exist on disk, whose source you generate at build time {Pattern hữu ích nhất để học: một virtual module — module import được nhưng không tồn tại trên đĩa, source do bạn sinh lúc build}:

function buildInfoPlugin(): Plugin {
  const id = "virtual:build-info";
  const resolvedId = "\0" + id; // the \0 prefix marks it virtual (convention)

  return {
    name: "build-info",
    resolveId(source) {
      if (source === id) return resolvedId; // "I own this import"
    },
    load(thisId) {
      if (thisId === resolvedId) {
        return `export const builtAt = ${JSON.stringify(new Date().toISOString())};`;
      }
    },
  };
}
// anywhere in your app
import { builtAt } from "virtual:build-info";

resolveId claims the id; load returns the generated source {resolveId nhận id; load trả source được sinh}. The \0 prefix is the convention that tells Vite (and other plugins) “this isn’t a real file, don’t try to read it from disk” {Tiền tố \0 là quy ước nói với Vite (và plugin khác) “đây không phải file thật, đừng đọc từ đĩa”}. This is how vite/client, env injection, and many integrations work {Đây là cách vite/client, chèn env, và nhiều tích hợp hoạt động}.


5. A transform example {Ví dụ transform}

transform lets you rewrite real files {transform cho bạn viết lại file thật}. A toy plugin that replaces a token {Plugin đồ chơi thay một token}:

function bannerPlugin(): Plugin {
  return {
    name: "banner",
    transform(code, id) {
      if (!id.endsWith(".ts")) return null;
      return { code: `/* generated ${Date.now()} */\n${code}`, map: null };
    },
  };
}

Return null to leave a module unchanged {Trả null để giữ module không đổi}; return { code, map } to rewrite it (provide a source map when you can) {trả { code, map } để viết lại (cung cấp source map khi có thể)}.


6. Compatibility note (Vite 8) {Lưu ý tương thích (Vite 8)}

Because Vite 8 uses Rolldown (which keeps the Rollup-compatible plugin API), the vast majority of existing Vite and Rollup plugins work unchanged {Vì Vite 8 dùng Rolldown (giữ API plugin tương thích Rollup), đại đa số plugin Vite và Rollup hiện có chạy không cần đổi}. When you write a plugin, target the documented Vite/Rollup hooks and you’re future-proof {Khi viết plugin, nhắm các hook Vite/Rollup được tài liệu hóa và bạn an toàn tương lai}.


7. Exercises {Bài tập}

1. Which trio of hooks handles a single module from import string to final code, and which is the “workhorse”? {Bộ ba hook nào xử lý một module từ chuỗi import tới code cuối, và cái nào là “người làm chính”?}

Solution {Lời giải}

resolveIdloadtransform; transform is the workhorse that rewrites module code {resolveIdloadtransform; transform là người làm chính viết lại code module}.

2. You want import config from "virtual:app-config" to work without any such file on disk. Which two hooks do you implement? {Bạn muốn import config from "virtual:app-config" chạy mà không có file đó trên đĩa. Hai hook nào?}

Solution {Lời giải}

resolveId (claim the id, return a \0-prefixed resolved id) and load (return the generated source) {resolveId (nhận id, trả id có tiền tố \0) và load (trả source sinh ra)}.

3. Your plugin must transform files before Vite’s core plugins see them, and only during dev. What do you set? {Plugin của bạn phải biến đổi file trước plugin core của Vite, và chỉ khi dev. Đặt gì?}

Solution {Lời giải}

enforce: "pre" and apply: "serve" {enforce: "pre"apply: "serve"}.

Stretch {Nâng cao}: in the timeline, note which hooks are “dev only” vs “build only” — those are the ones gated by apply and the serve/build distinction {trong timeline, để ý hook nào “chỉ dev” vs “chỉ build” — đó là những cái bị giới hạn bởi apply và phân biệt serve/build}.


Key takeaways {Điểm chính}

  • A Vite plugin is an object with a name and hooks — and it’s a Rollup plugin {Plugin Vite là object có name và hook — và là plugin Rollup}.
  • resolveIdloadtransform processes each module; transform is the workhorse {resolveIdloadtransform xử lý mỗi module; transform là người làm chính}.
  • enforce (pre/post) controls order; apply (serve/build) controls when it runs {enforce điều khiển thứ tự; apply điều khiển khi nào chạy}.
  • Virtual modules (resolveId + load, \0 prefix) expose generated code as an import {Virtual module phơi code sinh ra dưới dạng import}.
  • Rolldown keeps the Rollup API, so most plugins just work {Rolldown giữ API Rollup, nên đa số plugin chạy luôn}.

Next up {Tiếp theo}

Part 10 — Production build with Rolldown: what vite build produces, chunk splitting and manualChunks, the new advancedChunks, asset handling, and reading the build output {Phần 10 — Build production với Rolldown: vite build tạo gì, tách chunk và manualChunks, advancedChunks mới, xử lý asset, và đọc output build}.