Hiệu năng
Những thiết lập giúp ứng dụng dùng Yugen UI nhẹ và nhanh, kèm số đo trên bản build của thư viện. Dung lượng tính sau minify và gzip.
Đường dẫn import và bundler
Import từ yugen-ui hay từ yugen-ui/<Thư mục> cho cùng một bản build production với phần lớn bundler. Ngoại lệ là esbuild có code splitting và Node không qua bundler.
| Nơi code chạy | yugen-ui | yugen-ui/<Thư mục> |
|---|---|---|
| Vite, Rollup, webpack, Next.js (production) | Chỉ phần ứng dụng dùng | Như import từ gốc |
esbuild với splitting: true | Các phần nạp lúc cần nằm luôn trong chunk đầu | Chỉ phần ứng dụng dùng |
| Node, không bundler (Vitest, Jest, SSR, dev server) | { Button }: 498 file, 1.6 s lần đầu | 14 file, 61 ms |
esbuild chia cả file vào chunk trước khi bỏ các export không dùng. Vì vậy import từ gốc gói, import { Calendar } kéo theo Toaster, menu của Breadcrumb và thẻ chú thích vào chunk đầu: 76.46 kB, so với 31.60 kB khi import từ yugen-ui/Calendar. Hai subpath tự chứa một phần như vậy: yugen-ui/AIKit cũng export Citation nên StreamingMarkdown lấy từ đó có thẻ chú thích trong chunk đầu, còn toast từ yugen-ui/Toast kéo theo Toaster vì cả hai nằm chung một file.
Next.js: thêm gói vào optimizePackageImports để dev server và bundle phía server chỉ nạp phần trang dùng. Cấu hình và cách biết tên nào thuộc thư mục nào có ở Bắt đầu nhanh › Đường dẫn import.
Phần nạp lúc cần
Bốn phần chỉ một số ứng dụng render có chunk riêng. Mỗi phần bắt đầu nạp ngay khi nó được render ở client.
| Phần | Bắt đầu nạp | Chunk đầu, 0.15 → 0.16 | Nạp sau |
|---|---|---|---|
Toaster của withToaster | Lần render đầu ở client (sau hydrate nếu có SSR) | { YugenProvider } 24.91 → 13.31 kB | 12.5 kB |
| Menu “…” của Breadcrumb | Khi BreadcrumbOverflow mount | { Breadcrumb } 40.17 → 13.47 kB | 28.37 kB |
| Thẻ chú thích trong StreamingMarkdown | Khi chip chú thích đầu tiên mount | { StreamingMarkdown } 49.28 → 27.34 kB | 24.25 kB |
Sortable của TagPicker (reorderable) | Khi TagPicker có reorderable mount | { TagPicker } 47.35 → 38.86 kB | 7.10 kB |
- Đo trên bản build Vite 8, React để external. Ứng dụng không render phần đó thì không bao giờ nạp nó.
- Trong lúc chờ code, Breadcrumb render đúng nút “…” và StreamingMarkdown render đúng chip, cùng kích thước và ARIA, nên bố cục không xê dịch. Bấm chuột hay nhấn phím trong lúc đó thì menu hoặc thẻ mở ngay khi code tới, và focus chuyển sang nút thật. Nạp lỗi thì lần tương tác sau thử lại. TagPicker hiện đúng các tag; kéo thả và Alt + phím mũi tên dùng được khi Sortable đã nạp, tag đang có focus vẫn giữ focus.
- HTML từ server có các bản thay thế này và không có Toaster, nên hydrate khớp (SSR & Next.js).
toast()gọi trước khi Toaster nạp xong vẫn hiện khi nó mount.<Toaster />tự đặt làm con của provider gốc thì nằm trong chunk đầu: toast đầu tiên không phải chờ chunk, và live region có trong trang từ lần vẽ đầu.- Hai component nhẹ đi mà không nạp gì sau. Nav không còn import Tooltip (Sidebar của AppShell gắn tooltip cho dạng rail):
{ Nav, NavItem }29.94 → 11.97 kB. Calendar không còn kèm locale tiếng Việt:{ Calendar }32.39 → 30.42 kB.
Dung lượng CSS
theme.css cho Tailwind quét mọi component trong gói. Lệnh css của CLI ghi một file chỉ gồm các component mà code của bạn import.
| Template Starter | CSS (gzip) |
|---|---|
yugen-ui/theme.css | 26.7 kB |
yugen-ui.css do yugen css ghi (5 thư mục component) | 13.2 kB |
Chỉ theme-core.css, không component nào | 6.74 kB |
Trên các template khác, yugen css cho 11.8 đến 20.0 kB, so với khoảng 26 kB (Tuỳ biến style › Chỉ CSS của component bạn dùng). Chạy lệnh trong predev / prebuild; yugen add tự cập nhật file, còn yugen css --check thoát với mã 1 khi file đã cũ (CLI).
Không dùng Tailwind: yugen css --precompile biên dịch cùng danh sách đó bằng các gói Tailwind của dự án và thay yugen-ui/styles.css: từ 26.75 kB xuống 12.79 đến 21.06 kB trên các template.
Keyframe nằm trong @theme nên Tailwind chỉ xuất những keyframe mà class của bạn dùng: không có keyframe nào khi chỉ có theme-core.css, một keyframe với ứng dụng chỉ dùng Button.
Theme trước lần vẽ đầu
HTML từ server không biết người dùng chọn theme nào. Hai helper đặt theme lên <html> trước khi trình duyệt vẽ trang.
yugenHtmlAttributes() trả về các attribute mà applyToDocument ghi, dùng cho <html> của root layout. yugenThemeScript() trả về một script cho <head>, áp theme đã lưu hoặc theo cài đặt hệ điều hành. Cả hai là hàm thường, import từ yugen-ui hoặc yugen-ui/YugenProvider/document, chạy được trong Server Component. Cách cài ở SSR & Next.js.
Ở client, YugenProvider ghi attribute trong layout effect, nên useEffect của component con đã thấy chúng. Không có SSR (SPA dùng Vite) thì trình duyệt vẽ index.html trước khi React chạy: dùng script inline ở Bắt đầu nhanh.
Render lại
YugenProvider có hai context. Đổi theme chỉ render lại các component đọc theme.
| Context | Chứa | Render lại khi context đổi |
|---|---|---|
| Theme | theme, brand, dir | Bề mặt render qua portal (Dialog, Menu, Popover…), biểu đồ, CodeBlock, nơi gọi useYugenTheme() và useYugen() |
| Cấu hình | locale, density, messages, defaults | Mọi component đọc props mặc định hoặc chuỗi giao diện |
| 1 800 control, jsdom | 0.15 | 0.16 |
|---|---|---|
| Đổi theme, children giữ nguyên | 66–117 ms | 1.3–1.9 ms |
Component cha render lại, messages inline cùng nội dung | 65–67 ms | 0.1–0.2 ms |
Dòng đầu cần element của ứng dụng giữ nguyên. Khi component giữ state theme cũng tạo luôn element của ứng dụng, mọi component render lại mỗi lần đổi theme (khoảng 68 ms trong cùng phép đo). Hãy để state theme trong một component nhận ứng dụng qua children: đổi theme khi đó chỉ render lại component này, React bỏ qua cả cây trừ các component đọc theme.
// ThemeRoot.tsx: state theme nằm ở đây, ứng dụng được truyền vào qua children
import * as React from "react";
import { YugenProvider, type YugenTheme } from "yugen-ui";
export const SetThemeContext = React.createContext<(theme: YugenTheme) => void>(() => {});
export function ThemeRoot({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = React.useState<YugenTheme>("light");
return (
<SetThemeContext.Provider value={setTheme}>
<YugenProvider applyToDocument theme={theme}>
{children}
</YugenProvider>
</SetThemeContext.Provider>
);
}
// main.tsx
createRoot(document.getElementById("root")!).render(
<ThemeRoot>
<App />
</ThemeRoot>,
);messagesvàdefaultsviết inline được so theo giá trị, sâu hai cấp (component, rồi từng key), nên không cầnuseMemo. Hàm hay element tạo trong lúc render là giá trị mới mỗi lần: khai báo chúng bên ngoài component.- Trong component của bạn,
useYugenTheme()trả về{ theme, brand, dir }vàuseYugenLocale()trả về locale;useYugen()render lại khi một trong hai đổi (Theming). - Component có variant (Button, Input, Badge, Card…) gộp class một lần cho mỗi tổ hợp props: render 1 000 Button ở server mất 6.35 ms thay vì 12.58 ms.
classNameđược gộp thêm ở mỗi lần render (8.13 ms).
Biểu đồ nhiều điểm
Rê chuột trên LineChart, AreaChart hay BarChart không còn tính lại cả biểu đồ, và Sparkline có chiều rộng cố định không đo gì.
| Một bước hover, jsdom | 0.15 | 0.16 |
|---|---|---|
| LineChart, 3 series 500 điểm | 9.65 ms | 1.05 ms |
| LineChart, 3 series 2 000 điểm | 39.2 ms | 1.11 ms |
| BarChart, 150 nhóm 3 cột | 10.6 ms | 2.33 ms |
- Thang đo, path, nhãn và các lớp SVG tĩnh được memo theo props, chiều rộng và các series đang ẩn. Giữ
datavàseriesổn định (state, hằng số hoặcuseMemo) để component cha render lại không dựng lại chúng. - Fill và stroke đặt trên nhóm của series thay vì từng điểm: LineChart 4 series 200 điểm có marker render 183.9 kB markup thay vì 263.3 kB.
- Trong bảng và danh sách, cho Sparkline một
widthbằng số: khi đó nó không dùng ResizeObserver. 200 Sparkline vớiwidth={120}commit một lần thay vì ba và mount trong 60–153 ms thay vì 229–514 ms.width="auto"theo phần tử cha. - Trong component của bạn,
useElementSize({ enabled: false })bỏ qua việc đo khi đã biết kích thước (Hooks). Chọn dạng biểu đồ và bảng màu: Trực quan hoá dữ liệu.