jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

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.

7 MIN READ

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 CJS}

Pick output formats based on who consumes your library {Chọn định dạng output theo ai dùng thư viện}:

FormatConsumer {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/iifea <script> tag global {global qua thẻ <script>}

Shipping both es and cjs maximizes compatibility {Ship cả escjs 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.lib turns Vite into a library bundler — emit es + cjs and wire the exports map {build.lib biến Vite thành bundler thư viện — phát es + cjs và nối exports map}.
  • Generate .d.ts with vite-plugin-dts for TypeScript consumers {Sinh .d.ts bằng vite-plugin-dts cho người dùng TypeScript}.
  • Externalize peer deps (React) and declare peerDependencies — avoid double copies {Externalize peer dep và khai báo peerDependencies — 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}.