jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

Build Chrome Extensions · Part 11 — Pro Tooling & Build (Vite + CRXJS + TS + React)

Why plain files stop scaling, and how to set up a modern extension project with Vite, CRXJS, TypeScript and React — HMR, bundling, npm packages, and a typed manifest. With an interactive build-pipeline visualizer.

Everything so far used plain .js files you load directly {Mọi thứ đến giờ dùng file .js thuần bạn tải trực tiếp}. That’s perfect for learning, but it stops scaling the moment you want TypeScript, React, npm packages, or fast iteration {Tuyệt để học, nhưng ngừng mở rộng ngay khi bạn muốn TypeScript, React, npm package, hay lặp nhanh}. This part sets up the professional toolchain {Phần này thiết lập bộ công cụ chuyên nghiệp}.

Run a build and start dev mode below to see how source becomes a loadable dist/ {Chạy build và bật dev mode bên dưới để xem source thành dist/ tải được}:


1. Why a build step? {Vì sao cần bước build?}

Recall the MV3 CSP from Part 9: no remote code, no inline scripts {Nhớ CSP của MV3 từ Phần 9: không code từ xa, không script inline}. So to use an npm package you must bundle it into a local file {Vậy để dùng npm package bạn phải đóng gói nó vào file cục bộ}. A build step also gives you TypeScript, JSX, minification, and hot reload {Bước build còn cho bạn TypeScript, JSX, minify, và hot reload}.

CRXJS is a Vite plugin built specifically for extensions {CRXJS là plugin Vite làm riêng cho extension}. It understands manifest.json, bundles every entry point, rewrites paths, and gives you HMR for the popup/options and auto-reload for content scripts {Nó hiểu manifest.json, đóng gói mọi entry point, viết lại đường dẫn, và cho bạn HMR cho popup/optionsauto-reload cho content script}.


2. Scaffolding the project {Dựng project}

npm create vite@latest my-extension -- --template react-ts
cd my-extension
npm install
npm install -D @crxjs/vite-plugin@beta

Versions move fast — check the CRXJS docs for the current install command and Vite compatibility {Phiên bản đổi nhanh — xem tài liệu CRXJS cho lệnh cài hiện tại và tương thích Vite}.


3. The manifest as typed config {Manifest như config có kiểu}

Instead of a hand-edited JSON, define the manifest in TypeScript and get autocompletion and type-checking {Thay vì JSON sửa tay, định nghĩa manifest trong TypeScript và có autocomplete cùng kiểm tra kiểu}:

// manifest.config.ts
import { defineManifest } from "@crxjs/vite-plugin";

export default defineManifest({
  manifest_version: 3,
  name: "My Extension",
  version: "1.0.0",
  action: { default_popup: "src/popup/index.html" },
  background: { service_worker: "src/background.ts", type: "module" },
  content_scripts: [
    { matches: ["https://*/*"], js: ["src/content.ts"] },
  ],
  permissions: ["storage", "activeTab"],
});
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { crx } from "@crxjs/vite-plugin";
import manifest from "./manifest.config";

export default defineConfig({
  plugins: [react(), crx({ manifest })],
});

Point entry points at your source files; CRXJS emits the final hashed paths into dist/manifest.json {Trỏ entry point vào file source; CRXJS phát đường dẫn băm cuối cùng vào dist/manifest.json}.


4. The dev loop {Vòng lặp dev}

npm run dev      # starts Vite, writes dist/ and watches

Load dist/ once as an unpacked extension (Part 1) {Tải dist/ một lần như extension chưa đóng gói (Phần 1)}. Now {Bây giờ}:

  • Edit the popup/options → HMR updates instantly, state preserved {Sửa popup/options → HMR cập nhật tức thì, giữ state}.
  • Edit a content script → CRXJS auto-reloads the affected tabs {Sửa content script → CRXJS tự tải lại các tab liên quan}.
  • Edit the service worker → it reloads automatically {Sửa service worker → nó tự tải lại}.

No more manual “reload extension” clicks for most changes {Không còn click “reload extension” thủ công cho đa số thay đổi}. Trigger the “Save an edit” button in the visualizer to see the HMR path light up {Bấm nút “Save an edit” trong trình trực quan để xem đường HMR sáng lên}.


5. Typing the chrome.* APIs {Gắn kiểu cho chrome.*}

Install the official types so chrome.* is fully typed {Cài types chính thức để chrome.* có kiểu đầy đủ}:

npm install -D @types/chrome

Now chrome.storage.local.get returns typed results, and you catch mistakes at compile time {Giờ chrome.storage.local.get trả kết quả có kiểu, và bạn bắt lỗi lúc biên dịch}. Combine with the typed storage wrapper from Part 7 for end-to-end safety {Kết hợp với wrapper storage có kiểu từ Phần 7 để an toàn đầu-cuối}.


6. A sane project structure {Cấu trúc project hợp lý}

my-extension/
├─ manifest.config.ts      # typed manifest
├─ vite.config.ts
├─ src/
│  ├─ background.ts         # service worker
│  ├─ content.ts            # content script
│  ├─ popup/                # React popup
│  │  ├─ index.html
│  │  └─ Popup.tsx
│  ├─ options/              # React options page
│  ├─ lib/storage.ts        # typed storage wrapper (Part 7)
│  └─ lib/messages.ts       # shared message types
└─ dist/                    # build output — this is what you load/zip

Share message-shape types and storage helpers in lib/ so every context agrees on the same contracts {Chia sẻ kiểu hình-dạng-tin-nhắn và helper storage trong lib/ để mọi ngữ cảnh đồng thuận cùng hợp đồng}.


7. Building for production {Build cho production}

npm run build    # minified, tree-shaken dist/

The output dist/ is exactly what you zip and upload to the Chrome Web Store (Part 12) {dist/ xuất ra chính là thứ bạn nén và tải lên Chrome Web Store (Phần 12)}. CRXJS handles code-splitting and emits the production manifest.json automatically {CRXJS lo code-splitting và phát manifest.json production tự động}.


8. Exercises {Bài tập}

1. You import { z } from "zod" in your popup and it works after bundling, but a <script src="https://cdn.../zod"> did not. Why is bundling required? {Bạn import { z } from "zod" trong popup và nó chạy sau khi bundle, nhưng <script src="https://cdn.../zod"> thì không. Vì sao cần bundle?}

Solution {Lời giải}

MV3’s CSP forbids remote code; the bundler inlines the package into a local file you ship {CSP của MV3 cấm code từ xa; bundler nội tuyến package vào file cục bộ bạn ship}.

2. Which entry points should manifest.config.ts reference — src/ or dist/? {manifest.config.ts nên tham chiếu entry point nào — src/ hay dist/?}

Solution {Lời giải}

src/ files. CRXJS transforms them and writes the correct hashed dist/ paths into the emitted manifest.json {File src/. CRXJS chuyển đổi chúng và ghi đường dẫn dist/ băm đúng vào manifest.json phát ra}.

3. After npm run build, what exactly do you upload to the Chrome Web Store? {Sau npm run build, bạn tải chính xác cái gì lên Chrome Web Store?}

Solution {Lời giải}

A zip of the dist/ folder — the built, minified output, not the source {Một zip của thư mục dist/ — kết quả đã build, minify, không phải source}.

Stretch {Nâng cao}: in the visualizer, run dev mode then “Save an edit” and note only the popup hot-reloads while the rest of dist/ stays put — that’s HMR {trong trình trực quan, chạy dev mode rồi “Save an edit” và để ý chỉ popup hot-reload trong khi phần còn lại của dist/ giữ nguyên — đó là HMR}.


Key takeaways {Điểm chính}

  • A build step is required to use npm packages under MV3’s CSP {Bước build là bắt buộc để dùng npm package dưới CSP của MV3}.
  • Vite + CRXJS bundles entry points, rewrites manifest paths, and gives HMR/auto-reload {Vite + CRXJS đóng gói entry point, viết lại đường dẫn manifest, và cho HMR/auto-reload}.
  • Define the manifest as typed config; add @types/chrome {Định nghĩa manifest như config có kiểu; thêm @types/chrome}.
  • Share message types and storage helpers in lib/ {Chia sẻ kiểu tin nhắn và helper storage trong lib/}.
  • You ship the built dist/, never the source {Bạn ship dist/ đã build, không bao giờ source}.

Next up {Tiếp theo}

Part 12 — Publish, auto-update, cross-browser & capstone: packaging and uploading to the Chrome Web Store, the review process, versioning and auto-update, porting to Firefox/Edge, and a capstone extension that ties the whole series together {Phần 12 — Phát hành, tự cập nhật, đa trình duyệt & capstone: đóng gói và tải lên Chrome Web Store, quy trình duyệt, đánh phiên bản và tự cập nhật, port sang Firefox/Edge, và một extension capstone gắn cả series lại}.