Vite · Part 11 — Library Mode + SSR & the Environment API
Build publishable libraries with Vite (lib mode, ESM + CJS, .d.ts types, externalized peer deps, the exports map) and understand SSR plus the Environment API — the multi-runtime build model. With a library & environments explorer.
Vite isn’t only for apps {Vite không chỉ cho app}. Two advanced uses round out your mastery: building publishable libraries, and understanding SSR and the new Environment API that powers modern meta-frameworks {Hai cách dùng nâng cao hoàn thiện sự thành thạo: xây thư viện publish được, và hiểu SSR cùng Environment API mới làm động lực cho meta-framework hiện đại}.
Explore library output formats, then the multi-environment SSR model {Khám phá định dạng output thư viện, rồi mô hình SSR đa-môi-trường}:
1. Library mode {Library mode}
App builds assume an index.html entry and output for a browser {Build app giả định entry index.html và output cho trình duyệt}. A library is different — you ship importable modules for other projects to consume {Một thư viện thì khác — bạn ship module import được cho dự án khác dùng}. Enable it with build.lib {Bật bằng build.lib}:
export default defineConfig({
build: {
lib: {
entry: "src/index.ts",
name: "MyLib", // for the UMD/IIFE global
formats: ["es", "cjs"], // which module formats to emit
},
rollupOptions: {
external: ["react", "react-dom"], // don't bundle peer deps
},
},
});
2. Ship ESM and CJS {Ship ESM và CJS}
Pick output formats based on who consumes your library {Chọn định dạng output theo ai dùng thư viện}:
| Format | Consumer {Người dùng} |
|---|---|
es (.mjs) | modern bundlers, native ESM {bundler hiện đại, ESM native} |
cjs (.cjs) | older Node tooling, require() {tooling Node cũ, require()} |
umd/iife | a <script> tag global {global qua thẻ <script>} |
Shipping both es and cjs maximizes compatibility {Ship cả es và cjs tối đa tương thích}. Wire them up in package.json with the exports map so each consumer gets the right file {Nối chúng trong package.json bằng exports map để mỗi người dùng nhận file đúng}:
{
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/my-lib.mjs",
"require": "./dist/my-lib.cjs"
}
}
}
3. Generate types {Sinh type}
Vite/Oxc strips types — it won’t emit .d.ts files on its own {Vite/Oxc bóc type — nó không tự phát file .d.ts}. For a TypeScript library, add vite-plugin-dts {Cho thư viện TypeScript, thêm vite-plugin-dts}:
import dts from "vite-plugin-dts";
export default defineConfig({
plugins: [dts()], // emits dist/index.d.ts
});
Without .d.ts, your TypeScript consumers get no autocomplete and no type-checking — a dealbreaker for a published library {Không có .d.ts, người dùng TypeScript không có autocomplete và type-check — điều cấm kỵ cho thư viện publish}.
4. Externalize peer dependencies {Externalize peer dependency}
This is the mistake that breaks libraries: bundling React (or any peer dep) into your library {Đây là lỗi làm hỏng thư viện: bundle React (hay peer dep nào) vào thư viện}. If you do, the consuming app ends up with two copies of React — hooks break, bundles bloat {Nếu làm vậy, app dùng sẽ có hai bản React — hook hỏng, bundle phình}.
rollupOptions: {
external: ["react", "react-dom"], // leave them for the consumer
}
// package.json — declare them as peers, not deps
{ "peerDependencies": { "react": "^18 || ^19" } }
Externalize peer deps, declare them in peerDependencies, and the consumer’s single React is the only one in the final app {Externalize peer dep, khai báo trong peerDependencies, và React duy nhất của người dùng là cái duy nhất trong app cuối}.
5. SSR in a nutshell {SSR tóm tắt}
Server-Side Rendering runs your app on the server to produce HTML, then the client hydrates it {SSR chạy app trên server để tạo HTML, rồi client hydrate nó}. Vite supports SSR dev natively — it transforms your server entry through the same pipeline {Vite hỗ trợ SSR dev native — nó biến đổi server entry qua cùng pipeline}:
// dev server: load the server entry through Vite's transform
const { render } = await server.ssrLoadModule("/src/entry-server.tsx");
const appHtml = await render(url);
You typically build two outputs: a client bundle (for hydration) and a server bundle (for rendering) {Bạn thường build hai output: bundle client (để hydrate) và bundle server (để render)}. In practice you rarely wire this by hand — a meta-framework does it {Thực tế bạn hiếm khi tự nối — meta-framework lo}.
6. The Environment API {Environment API}
Older Vite bolted SSR on as a separate, awkward API {Vite cũ gắn SSR như một API riêng, vụng về}. The Environment API (introduced in Vite 6, stabilizing through 7/8) makes client, SSR, and edge first-class peers {Environment API (giới thiệu ở Vite 6, ổn định dần qua 7/8) biến client, SSR, và edge thành ngang hàng hạng nhất}. Each environment has its own {Mỗi environment có riêng}:
- module graph {đồ thị module}
- plugin container {container plugin}
- dependency optimizer {trình tối ưu dependency}
- config (externals, resolve, etc.) {config (externals, resolve, v.v.)}
export default defineConfig({
environments: {
client: {
/* browser build */
},
ssr: {
resolve: { external: ["express"] },
},
},
});
A single vite build can produce coordinated output for all of them {Một vite build có thể tạo output phối hợp cho tất cả}. The Module Runner executes SSR/edge code with HMR in dev {Module Runner thực thi code SSR/edge với HMR khi dev}.
Most app developers consume the Environment API through a framework (Nuxt, SvelteKit, React Router, TanStack Start, the Cloudflare plugin). You’ll mostly touch it directly only if you author frameworks or runtime integrations {Đa số dev app dùng Environment API qua một framework. Bạn chỉ chạm trực tiếp khi viết framework hoặc tích hợp runtime}.
7. Exercises {Bài tập}
1. You publish a React component library and consumers report “Invalid hook call” with two React copies. What did you forget? {Bạn publish thư viện component React và người dùng báo “Invalid hook call” với hai bản React. Bạn quên gì?}
Solution {Lời giải}
Externalize React: rollupOptions.external: ["react", "react-dom"] and declare it in peerDependencies so the library doesn’t bundle its own copy {Externalize React và khai báo trong peerDependencies để thư viện không bundle bản riêng}.
2. Your TS library builds fine but consumers get no autocomplete. What’s missing from the build? {Thư viện TS của bạn build ổn nhưng người dùng không có autocomplete. Build thiếu gì?}
Solution {Lời giải}
.d.ts type declarations — add vite-plugin-dts and point package.json types/exports.types at them {Khai báo type .d.ts — thêm vite-plugin-dts và trỏ types/exports.types tới chúng}.
3. In one sentence, what problem does the Environment API solve compared to the old SSR setup? {Một câu, Environment API giải quyết vấn đề gì so với thiết lập SSR cũ?}
Solution {Lời giải}
It makes client/SSR/edge first-class, independently-configured environments (each with its own module graph and optimizer) coordinated by one build, instead of SSR being a bolted-on special case {Nó biến client/SSR/edge thành môi trường hạng nhất, cấu hình độc lập, phối hợp bởi một build, thay vì SSR là trường hợp đặc biệt gắn thêm}.
Stretch {Nâng cao}: in the explorer, turn off “externalize peer deps” and read the warning — that’s the exact cause of the double-React bug {trong explorer, tắt “externalize peer deps” và đọc cảnh báo — đó chính là nguyên nhân bug React đôi}.
Key takeaways {Điểm chính}
build.libturns Vite into a library bundler — emites+cjsand wire theexportsmap {build.libbiến Vite thành bundler thư viện — phátes+cjsvà nốiexportsmap}.- Generate
.d.tswithvite-plugin-dtsfor TypeScript consumers {Sinh.d.tsbằngvite-plugin-dtscho người dùng TypeScript}. - Externalize peer deps (React) and declare
peerDependencies— avoid double copies {Externalize peer dep và khai báopeerDependencies— tránh bản đôi}. - SSR renders on the server + hydrates on the client; Vite supports it natively {SSR render trên server + hydrate trên client; Vite hỗ trợ native}.
- The Environment API makes client/SSR/edge first-class environments — usually accessed via a framework {Environment API biến client/SSR/edge thành môi trường hạng nhất — thường truy cập qua framework}.
Next up {Tiếp theo}
Part 12 — Performance, capstone & migration: speeding up dev and build, a battle-tested capstone config, and a practical guide to migrating from Webpack or CRA to Vite {Phần 12 — Hiệu năng, capstone & di trú: tăng tốc dev và build, một config capstone thực chiến, và hướng dẫn thực tế di trú từ Webpack hoặc CRA sang Vite}.