@fwkui/x-css là utility CSS engine siêu nhẹ, parse class theo cú pháp ngắn và sinh CSS runtime theo layer + media.
Mục tiêu của README này:
- Người dùng có thể tích hợp ngay.
- AI có thể suy luận đúng cú pháp để sinh class dùng được ngay.
- QA có thể kiểm thử parser theo vector cố định.
npm install @fwkui/x-cssLưu ý:
dist/index.jslà CommonJS (Node).- Trình duyệt dùng
dist/index.mjshoặcdist/index-auto.mjs.
Option A: chủ động khởi tạo
<script type="module">
import xcss from 'https://unpkg.com/@fwkui/x-css@latest/dist/index.mjs';
xcss.cssObserve(document, {
dictionaryImport: true
});
</script>Option B: auto observe khi import
<script type="module" src="https://unpkg.com/@fwkui/x-css@latest/dist/index-auto.mjs"></script>CDN thay thế:
https://cdn.jsdelivr.net/npm/@fwkui/x-css@latest/dist/index.mjs
import xcss from '@fwkui/x-css';
xcss.cssObserve(document);<button class="dF aiC jcC p10px;16px bdN bdra8px bgc#0a64e8 cWhite">
Đăng nhập
</button>Mỗi utility class theo form:
[Media]:[Layer][Property][Value][@Selector]
Thứ tự parse bắt buộc:
selector(hậu tố@..., nằm ngoài[]).media(tiền tố trước:).layer(chuỗi số liên tiếp ở đầu).property.value.
Ý nghĩa từng phần:
Media(tùy chọn): key media nhưsm,md,lg, hoặc key custom.Layer(tùy chọn): số ưu tiên cascade.Property(bắt buộc): alias thuộc dictionary hoặc CSS property hợp lệ.Value(bắt buộc với utility chuẩn): giá trị CSS, alias value hoặc arbitrary value.@Selector(tùy chọn): ví dụ@:hover,@::before.
Ngoại lệ parser (special syntax):
[AliasName]: class group alias (không dùng value trực tiếp).&...: nhánh selector đặc biệt theo parser hiện tại.
Engine nạp media theo thứ tự:
| Thứ tự | Key | Query |
|---|---|---|
| 1 | default |
Không bọc @media |
| 2 | xs |
screen and (max-width: 575px) |
| 3 | sm |
screen and (min-width: 576px) |
| 4 | md |
screen and (min-width: 768px) |
| 5 | lg |
screen and (min-width: 992px) |
| 6 | xl |
screen and (min-width: 1200px) |
| 7 | 2xl |
screen and (min-width: 1400px) |
| 8 | sma |
screen and (max-width: 768px) |
| 9 | mda |
screen and (max-width: 992px) |
| 10 | lga |
screen and (max-width: 1200px) |
| 11 | xla |
screen and (max-width: 1400px) |
Quy tắc custom breakpoint:
breakpointsđược nối vào sau danh sách mặc định.- Nếu trùng key, key khai báo sau cùng ghi đè key trước (
last write wins).
Ví dụ:
xcss.cssObserve(document, {
breakpoints: [
{ tablet: 'screen and (min-width: 768px)' }
]
});Dùng class: tablet:dB.
- Nếu không khai báo layer, engine dùng
0. - Engine tạo sẵn 24 layer:
l0 -> l23. - Nên dùng dải
0-23để giữ thứ tự ổn định. - Số layer lớn hơn có ưu tiên cascade cao hơn trong cùng media.
Mục tiêu là tách class thành tuple:
{ media, layer, property, value, selector }
Thứ tự suy luận bắt buộc:
- Tách
selector: lấy phần sau ký tự@cuối cùng, chỉ khi@nằm ngoài[]. - Tách
media: nếu còn:thì phần trước:làmedia. - Tách
layer: đọc chuỗi số liên tiếp ở đầu phần còn lại. - Tách
property/value: quét trái -> phải và dừngpropertytheo bảng quyết định. - Nếu phần còn lại bắt đầu bằng
&hoặc[thì đi vào nhánh special syntax.
| Ký tự đang xét | Điều kiện | Hành động |
|---|---|---|
a-z |
luôn đúng | vẫn là property |
- hoặc . |
ký tự kế tiếp là số | dừng property, phần còn lại là value |
- |
gặp -- và đã có ít nhất 1 ký tự property |
dừng property, bắt đầu value (CSS variable) |
- hoặc . |
không rơi vào 2 điều kiện trên | vẫn là property |
ký tự khác (A-Z, 0-9, #, !, [, (, %, ...) |
luôn đúng | dừng property, phần còn lại là value |
- Value bắt đầu bằng
!-> thêm hậu tố!important. - Value bắt đầu bằng
---> đổi thànhvar(--...). - Value dạng
[...]-> bỏ[], rồi thay;thành khoảng trắng. - Ký tự
#trong value giữ nguyên (hex color).
Pseudo-flow cho AI:
class -> selector -> media -> layer -> property -> value
if value startsWith('!') => important
if value startsWith('--') => var(value)
if value is bracketed [..] => strip brackets + replace ';' with ' '
| # | Input | Expected tuple |
|---|---|---|
| 1 | m10px |
{ media: '', layer: '', property: 'm', value: '10px', selector: '' } |
| 2 | md:w100% |
{ media: 'md', layer: '', property: 'w', value: '100%', selector: '' } |
| 3 | sm:3bgWhite |
{ media: 'sm', layer: '3', property: 'bg', value: 'White', selector: '' } |
| 4 | cBlue@:hover |
{ media: '', layer: '', property: 'c', value: 'Blue', selector: ':hover' } |
| 5 | m-10px |
{ media: '', layer: '', property: 'm', value: '-10px', selector: '' } |
| 6 | opc0.8 |
{ media: '', layer: '', property: 'opc', value: '0.8', selector: '' } |
| 7 | bgc--brand |
{ media: '', layer: '', property: 'bgc', value: '--brand', selector: '' } |
| 8 | c!#0a64e8 |
{ media: '', layer: '', property: 'c', value: '!#0a64e8', selector: '' } |
| 9 | w[calc(100%;-;10px)] |
{ media: '', layer: '', property: 'w', value: '[calc(100%;-;10px)]', selector: '' } |
| 10 | [btnPrimary] |
{ media: '', layer: '', property: '[btnPrimary]', value: '', selector: '' } |
| Sai | Đúng | Giải thích |
|---|---|---|
bdn |
bdN |
Value viết tắt dạng chữ cái phải viết hoa ký tự đầu (N = none). |
df |
dF |
F là value viết tắt của flex. |
posa |
posA |
A là value viết tắt của absolute. |
tr0.2s |
tran0.2s |
Property transition là tran, không phải tr. |
op0.8 |
opc0.8 |
op là object-position; opc mới là opacity. |
3:bgWhite |
3bgWhite |
Layer là số đứng liền trước property, không có : sau layer. |
hover:cRed |
cRed@:hover |
Selector modifier dùng hậu tố @Selector. |
tablet:dB (chưa khai báo) |
tablet:dB + breakpoints config |
Media custom phải được khai báo trước trong config. |
m--10px |
m-10px |
Số âm dùng -; -- dành cho CSS variable (bgc--brand). |
bgcbrand |
bgcBrand hoặc bgc--brand |
Cần điểm ngắt rõ ràng để parser tách đúng value. |
wcalc(100%-10px) |
w[calc(100%;-;10px)] |
Value phức tạp nên bọc [], dùng ; để biểu diễn khoảng trắng. |
!cRed |
c!Red |
! phải đứng trong phần value (sau property), không đứng đầu class. |
Danh sách đầy đủ alias xem tại DICTIONARY.md.
Một số alias dễ nhầm:
op=object-positionopc=opacitytran=transitiontr=transparent(value alias, không phải property transition)
Ví dụ:
<div class="dF aiC jcSB p12px;16px bdN bgcWhite"></div>
<div class="tran0.2s opc0.8@:hover"></div>
<div class="c!#0a64e8"></div>
<div class="w[calc(100%;-;10px)]"></div>Chuỗi thuộc tính bằng & (đúng):
fk-dF&fxdC@;lifk-md:dF&fxdCmd:[row]&[col]@;li(vớialiases.row,aliases.col)
Quy ước aliases khuyến nghị (đầy đủ declaration):
aliases: {
row: ['display:flex', 'padding:5px'],
col: ['flex-direction: column', 'margin:5px']
}Sai -> Đúng:
aliases: { row:['dF'], col:['fxdC'] }-> sai (không còn hỗ trợ).aliases: { row:['display:flex'], col:['flex-direction: column'] }-> đúng.
import { clsx } from '@fwkui/x-css';
export function Button({ primary, children }) {
return (
<button
className={clsx(
'dF aiC jcC p10px;16px bdN bdra8px tran0.2s',
primary ? 'bgc#0a64e8 cWhite' : 'bgc#e5e7eb c#111827',
'opc0.9@:hover'
)}
>
{children}
</button>
);
}Nếu bạn muốn tái sử dụng cùng một instance, dùng factory. Khuyến nghị mặc định cho app chạy thật:
- Không đặt
prefix. - Không bật
cache. - Chỉ bật
cachehoặcprefixkhi bạn chủ động chấp nhận tradeoff vận hành.
import { createSharedInstance } from '@fwkui/x-css';
export const fx = createSharedInstance();
// Browser: gọi 1 lần khi app khởi động
fx.observe(document);
// Dùng ở mọi nơi
const className = fx.clsx('dF aiC jcC p10px;16px');
// Nếu dictionaryImport là true/string và cần chắc chắn CSS đã sẵn sàng:
await fx.ready();
// SSR / debug
const cssText = fx.getCss();@fwkui/x-css có helper bán tự động để hỗ trợ migration từ chuỗi utility Tailwind sang token x-css.
Public API:
import {
assessTailwindMigrationReadiness,
classifyTailwindToken,
convertTailwindClasses,
convertTailwindToken,
getTailwindCoverageMatrix
} from '@fwkui/x-css';
const result = convertTailwindClasses(
'flex items-center justify-between gap-4 p-4 bg-white text-slate-900 rounded-lg border border-slate-200'
);
console.log(result.output);
// dF ai[center] jc[space-between] gap16px p16px bgc#ffffff c#0f172a bdra8px bdw1px bds[solid] bdcCurrentColor bdc#e2e8f0
console.log(classifyTailwindToken('md:fxd[row]'));
// 'xcss'
const readiness = assessTailwindMigrationReadiness(
'transition flex group-hover:bg-slate-50 md:fxd[row]'
);
console.log(readiness.releaseDecision);
// 'blocked'
console.log(readiness.autoApplyOutput);
// dF md:fxd[row]Contract hiện tại:
- Helper ưu tiên các utility Tailwind phổ biến cho layout, spacing, color, typography, border, shadow, width/height.
- Responsive/state variants phổ biến như
sm:,md:,hover:,focus:được chuyển sang formatx-css. - Token đầu vào được phân loại thành
tailwind,xcss,ambiguous, hoặcunknown. - Đây là migration helper, không phải promise “convert 100% Tailwind không cần review”.
- Khi publish hoặc migrate codebase thật, nên ưu tiên
mode: 'safe'thay vìlegacy. - Helper này làm việc trên raw utility string và bỏ qua config
prefix; khi migrate Tailwind, nên coiprefixlà rỗng.
Ví dụ:
const converted = convertTailwindToken('md:hover:bg-slate-50');
console.log(converted.outputs);
// ['md:bgc#f8fafc@:hover']
const safe = convertTailwindClasses('md:fxd[row] p-2.5', { mode: 'safe' });
console.log(safe.passthrough); // ['md:fxd[row]']
console.log(safe.converted); // ['p10px']
console.log(safe.ambiguous); // ['p-2.5']convertTailwindToken() và convertTailwindClasses() nhận option:
type TailwindConversionMode = 'legacy' | 'safe' | 'strict'Ý nghĩa:
legacy: giữ hành vi tương thích cũ. Token không convert được có thể được passthrough nếupreserveUnknown !== false.safe: chỉ passthrough token đã được xác nhận làx-css. Tokenambiguoushoặcunknownsẽ không được giữ nguyên mù.strict: dùng cùngpreserveUnknown: falseđể ép toàn bộ token không chắc chắn đi vàounsupported. Đây là mode phù hợp cho codemod, CI, hoặc review trước khi deploy.
Ví dụ khuyến nghị:
const result = convertTailwindClasses(source, {
mode: 'safe',
preserveUnknown: false
});convertTailwindToken() trả:
{
input: string
outputs: string[]
status: 'converted' | 'passthrough' | 'unsupported'
classification: 'tailwind' | 'xcss' | 'ambiguous' | 'unknown'
exact: boolean
warnings: Array<{ token: string; message: string }>
}convertTailwindClasses() trả:
{
input: string
output: string
details: TailwindTokenConversion[]
converted: string[]
passthrough: string[]
unsupported: string[]
ambiguous: string[]
warnings: TailwindConversionWarning[]
}Ý nghĩa thực tế:
converted: token đã map sangx-css.passthrough: token được giữ nguyên vì làx-cssthật, hoặc vì caller vẫn bật giữ token gốc.unsupported: token chưa có mapping an toàn.ambiguous: token có hình thức dễ nhầm giữa Tailwind vàx-css, cần review.
Nếu dùng framework này để migrate code chạy production, không nên dùng mỗi convertTailwindClasses() làm quyết định release.
Hãy chạy preflight bằng assessTailwindMigrationReadiness():
const report = assessTailwindMigrationReadiness(source);
if (report.releaseDecision === 'safe') {
// Chỉ còn exact conversion hoặc x-css thật
apply(report.autoApplyOutput);
}
if (report.releaseDecision === 'review') {
// Có output gần đúng như `transition -> tran0.2s`
review(report.approximateConverted);
}
if (report.releaseDecision === 'blocked') {
// Có utility không an toàn để tự động migrate
fail(report.blocked);
}Contract của report:
autoApplyOutput: chỉ chứa token exact-converted và tokenx-cssthật, theo đúng thứ tự gốc.approximateConverted: token đã convert nhưng không đủ an toàn để áp thẳng production.reviewRequired: tất cả token cần con người xem lại.blocked: token không nên auto-apply trong flow migration thật.safeToAutoApply: chỉtruekhi không còn token phải review.
Coverage machine-readable:
const matrix = getTailwindCoverageMatrix();matrix dùng được cho codemod, CI gate, dashboard coverage, hoặc rule riêng của team sản phẩm.
Khi helper sinh token x-css, nên coi các output dưới đây là canonical:
inline-flex -> dIfinline-block -> dIbinline-grid -> dIgborder -> bdw1px bds[solid] bdcCurrentColortext-transparent -> cTransparentborder-transparent -> bdcTransparentbg-[currentColor] -> bgcCurrentColor
Không nên dựa vào việc runtime parser “vẫn hiểu được” để sinh các biến thể khác như dIF, dIB, bdctransparent.
Nhóm utility đã hỗ trợ tốt:
- display / position / flex cơ bản
- spacing / size / fraction / width scale /
mx-auto/inset-x-*/inset-y-* - color cơ bản (
bg-*,text-*,border-*) - rounded / border / shadow /
appearance-none - font-size / font-weight / line-height / letter-spacing /
basis-*/order-* - object-fit / object-position cơ bản
- align / place / justify / content helpers phổ biến
- một phần responsive + selector variants (
sm,md,hover,focus,before,after,placeholder,selection)
Nhóm nên coi là cần review thủ công hoặc fallback riêng:
group-hover:*,group-focus:*,peer-*dark:*divide-*duration-*,ease-*,delay-*- transform chain phức hợp như
translate-*,scale-*,rotate-*
Khi migrate project thật:
- Chạy converter với
mode: 'safe'. - Áp dụng trực tiếp các token trong
converted. - Giữ nguyên các token trong
passthroughchỉ khi chúng làx-cssthật. - Review bắt buộc các token trong
ambiguousvàunsupported. - Chạy lại app, build, và kiểm tra giao diện thực tế sau mỗi đợt chuyển đổi.
Khuyến nghị:
- Không chạy codemod toàn repo ở
legacy moderồi deploy thẳng. - Không dùng parser
x-cssnhư bằng chứng rằng token Tailwind “đã an toàn”. - Nếu output chứa nhiều
ambiguous, hãy dừng batch hiện tại và bổ sung mapping trước khi chuyển tiếp.
Alias tương đương:
import { createSharedClsx } from '@fwkui/x-css';
const fx = createSharedClsx();Quy tắc dùng ổn định:
- Tạo shared instance đúng 1 lần ở bootstrap.
- Không khởi tạo lại instance ở mỗi lần render component.
- Mặc định để
prefixrỗng vàcachetắt. - Nếu bạn tự bật
cachehoặcprefix, giữ nguyên cấu hình đó trong suốt vòng đời app.
import xcss from '@fwkui/x-css';
xcss.cssObserve(document, {
theme: {
brand: '#0a64e8',
danger: '#ef4444'
},
breakpoints: [
{ tablet: 'screen and (min-width: 768px)' }
],
base: 'body{margin:0;font-family:system-ui,sans-serif;}',
excludePrefixes: ['bs-', 'rs-'],
excludes: ['legacy-*'],
dictionaryImport: true
});Sau đó dùng class: cBrand tablet:dB.
Gợi ý tối ưu bỏ qua parse:
- Ưu tiên
excludePrefixesđể bỏ qua nhanh theo tiền tố, ví dụbs-,rs-. - Dùng
excludeskhi cần rule chính xác hoặc wildcard (*), ví dụlegacy-*,tmp-debug. - Không nên lấy
prefixlàm cấu hình mặc định; chỉ dùng khi bạn thật sự cần tách namespace class.
Ví dụ đầu vào cho excludes:
| Cấu hình | Input class | Kết quả mong đợi |
|---|---|---|
excludes: ['container'] |
container m10px |
container giữ nguyên; chỉ m10px được parse thành CSS |
excludes: ['bs-*'] |
bs-btn m10px |
bs-btn bị bỏ qua parse; m10px vẫn parse |
excludes: ['*-debug'] |
card-debug p8px |
card-debug bị bỏ qua parse; p8px vẫn parse |
excludes: ['tmp-*', 'legacy-*'] |
tmp-a legacy-card dF |
tmp-a, legacy-card bị bỏ qua; dF vẫn parse |
excludePrefixes: ['bs-', 'rs-'] |
bs-modal rs-open h100% |
bs-modal, rs-open bị bỏ qua nhanh; h100% vẫn parse |
Ví dụ output thực tế của clsx:
| Config | Gọi clsx(...) |
Output | Ghi chú |
|---|---|---|---|
excludePrefixes: ['bs-'], excludes: ['abc*'] |
clsx('bs-a', 'abcde') |
bs-a abcde |
Cả hai bị bỏ qua, giữ nguyên class gốc. |
excludePrefixes: ['bs-'], excludes: ['abc*'] |
clsx('bs-a', 'abcde', 'm10px') |
bs-a abcde D0 |
Chỉ m10px parse thành class hash. |
excludes: ['bs-', 'abc*'] |
clsx('bs-a', 'abcde', 'm10px') |
bs-a abcde D0 |
bs- là exact match nên không bắt bs-a; bs-a vẫn giữ nguyên vì token không sinh CSS hợp lệ. |
excludes: ['abc*def'] |
clsx('abcXYZdef', 'm10px') |
abcXYZdef D0 |
Wildcard giữa chuỗi hoạt động bình thường. |
excludes: ['*-abc'] |
clsx('foo-abc', 'm10px') |
foo-abc D0 |
Wildcard cuối chuỗi hoạt động bình thường. |
Lưu ý format output:
clsxtrả chuỗi class phân tách bằng khoảng trắng, không dùng dấu phẩy.- Token không parse được hoặc bị exclude sẽ giữ nguyên ở output.
Lưu ý khi dùng cùng prefix:
- Engine kiểm tra
excludes/excludePrefixestrước, sau đó mới kiểm traprefix. - Nếu token match exclude thì giữ nguyên class gốc và không parse tiếp.
- Nếu có
prefix: 'fk-', token không bắt đầu bằngfk-sẽ giữ nguyên class gốc. prefixlà cấu hình tùy chọn, không phải khuyến nghị mặc định.
dictionaryImport:
true(mặc định): dùng ngay built-in dictionary từsrc/dictionary.ts/bundle hiện tại, không self-importdictionary.jsnội bộ nữa.false: tắt dictionary.stringURL/path: import dictionary ngoài.
Lưu ý:
dictionaryImport: truelà đồng bộ, có thể parse built-in dictionary ngay khi khởi tạo.- Chỉ trường hợp
dictionaryImportlàstringURL/path mới cần import bất đồng bộ. - Nếu cần chắc chắn dictionary ngoài đã sẵn sàng trước khi render quan trọng, dùng
await engine.ready.
cache:
- Mặc định
cacheđang tắt (loadOnInit: false). styleId(mặc địnhfwkui): id thẻ<style>runtime.version(mặc địnhv1): tham gia vào cache key để chủ động invalidate.compression(mặc địnhtrue): nén cache trước khi lưulocalStorage.debounceMs(mặc định1000): debounce chu kỳ nén + lưu cache.sizeLast(mặc định1000): seed mặc định cho bộ sinh keyD....
Khuyến nghị:
- Không bật
cachemặc định cho mọi app. - Không bật/tắt
cachelặp lại theo route, component, hoặc session ngắn. - Chỉ bật
cachekhi bạn chủ động muốn tối ưu first paint hoặc SSR/MPA hydration. - Nếu đã bật
cache, hãy cấu hình ổn định và giữ nguyên trong toàn app.
Khi compression: true:
- Ưu tiên
CompressionStream(deflate-raw + base64) nếu runtime hỗ trợ. - Tự động fallback về LZW nếu runtime không hỗ trợ stream.
Đồng bộ nhiều tab:
- Khi phát sinh key mới, engine gửi delta
{ class -> key }sang tab khác quaBroadcastChannelnếu môi trường hỗ trợ. - Nếu không có
BroadcastChannel, engine fallback về sự kiệnstorage. localStoragecache vẫn giữ vai trò snapshot đầy đủ cho lần mở tab mới hoặc reload sau đó.
Quy tắc cache key:
cacheKey = styleId + "_cache_" + version
Ví dụ mặc định:
fwkui_cache_v1
Nếu import dictionary ngoài:
const engine = xcss.css({ dictionaryImport: 'https://cdn.example.com/xcss-dict.mjs' });
await engine.ready;
const { clsx, observe } = engine.buildCss(document);
observe();Mẫu file để thay thế trực tiếp URL https://cdn.example.com/xcss-dict.mjs:
// xcss-dict.mjs
// Có thể public lên CDN của bạn rồi truyền URL vào dictionaryImport
export const SHORT_PROPERTIES = {
d: 'display',
c: 'color',
bgc: 'background-color',
bd: 'border',
w: 'width',
h: 'height',
p: 'padding',
m: 'margin',
tran: 'transition',
opc: 'opacity'
};
export const COMMON_VALUES = {
n: 'none',
b: 'block',
f: 'flex',
t: 'transparent',
i: 'inherit'
};
export const SPECIFIC_VALUES = {
d: {
f: 'flex',
b: 'block',
ib: 'inline-block'
},
bd: {
n: 'none'
},
c: {
pri: '#0a64e8',
danger: '#ef4444'
},
bgc: {
pri: '#0a64e8',
soft: '#e8f1ff'
}
};
export default {
SHORT_PROPERTIES,
COMMON_VALUES,
SPECIFIC_VALUES
};Lưu ý format:
- Nên export theo đúng mẫu trên với object tĩnh/plain object.
- Không nên tạo dictionary bằng biểu thức động, function, hoặc biến trung gian phức tạp.
export defaultlà tùy chọn; 3 named exportSHORT_PROPERTIES,COMMON_VALUES,SPECIFIC_VALUESmới là phần quan trọng nhất.
Quy trình thay link:
- Tạo file
xcss-dict.mjstheo mẫu trên. - Upload lên CDN/public URL của bạn.
- Thay
dictionaryImportbằng URL thật. - Chờ
await engine.readytrước khi render class.
Chỉ dùng helper getBootloaderScript khi bạn chủ động bật cache và muốn lấy CSS từ localStorage trước khi bundle chạy.
import { getBootloaderScript } from '@fwkui/x-css';
const styleId = 'fwkui';
const version = 'v1';
const bootloaderScript = getBootloaderScript(styleId, version, { loadOnInit: true });
const bootloaderScriptCompact = getBootloaderScript(styleId, version, {
compact: true,
loadOnInit: true
});<head>
<!-- 1) Bootloader từ cache: chạy sớm nhất để giảm FOUC -->
<script>
/* nội dung từ getBootloaderScript(styleId, version, { loadOnInit: true }) */
</script>
<!-- 2) Bundle/module của app -->
<script type="module" src="/assets/main.js"></script>
</head>Thay 2 biến ngay đầu script nếu cần:
sid = 'fwkui'-> đổi theocache.styleId.ver = 'v1'-> đổi theocache.version.
<script>
(async()=>{const sid='fwkui',ver='v1',k=sid+'_cache_'+ver;const L=s=>{if(!s)return'';const d={};let z=256;for(let i=0;i<256;i++)d[i]=String.fromCharCode(i);const c=[...s].map(ch=>ch.charCodeAt(0));let p=c[0],ph=d[p]||'',r=ph;for(let i=1;i<c.length;i++){const x=c[i];let e=d[x];if(!e)e=x===z?ph+ph[0]:'';r+=e;d[z++]=ph+e[0];ph=e;}return r};const S=()=>typeof DecompressionStream!=='undefined'&&typeof Blob!=='undefined'&&typeof Response!=='undefined';const B=b=>{if(typeof atob==='function'){const s=atob(b),u=new Uint8Array(s.length);for(let i=0;i<s.length;i++)u[i]=s.charCodeAt(i);return u;}if(typeof Buffer!=='undefined')return new Uint8Array(Buffer.from(b,'base64'));throw new Error('base64');};const D=async p=>{if(!S())return null;try{return await new Response(new Blob([B(p)]).stream().pipeThrough(new DecompressionStream('deflate-raw'))).text();}catch{return null;}};const P=async raw=>{if(!raw)return null;try{let j=JSON.parse(raw);if(j&&j.__xcss_cache_v===3&&j.compressed===true&&j.algorithm==='deflate-raw'&&j.encoding==='base64'&&typeof j.payload==='string'){const ex=await D(j.payload);if(!ex)return null;j=JSON.parse(ex);}else if(j&&j.__xcss_cache_v===2&&j.compressed===true&&typeof j.payload==='string'){const ex=L(j.payload);if(!ex)return null;j=JSON.parse(ex);}return j&&j.cssText?j:null;}catch{return null;}};try{if(typeof window==='undefined'||!window.localStorage)return;let p=await P(localStorage.getItem(k));if(!p)return;let st=document.getElementById(sid);if(!st){st=document.createElement('style');st.id=sid;document.head.appendChild(st);}let css=p.cssText.root?p.cssText.root+'\\n':'';for(const n in p.cssText)if(n!=='root')css+=(p.cssText[n]||'')+'\\n';st.textContent=css;}catch{}})();
</script>Ví dụ render HTML từ server:
import { getBootloaderScript } from '@fwkui/x-css';
const styleId = 'fwkui';
const version = 'v1';
const bootloaderScript = getBootloaderScript(styleId, version, {
compact: true,
loadOnInit: true
});
const html = `
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<script>${bootloaderScript}</script>
<script type="module" src="/assets/main.js"></script>
</head>
<body><div id="app"></div></body>
</html>`;Lưu ý:
- Đồng bộ
styleId+versiongiữa bootloader và cấu hìnhxcss.css(...). - Script tạo từ
getBootloaderScriptưu tiênDecompressionStream(cache deflate-raw) và fallback LZW. - Sau bootloader vẫn cần gọi
xcss.cssObserve(...)như bình thường. - Mặc định
loadOnInitđang tắt; chỉ bật bằng{ loadOnInit: true }khi bạn thật sự dùng cache runtime. - Dùng
{ compact: true }khi muốn script trả về ở dạng nén gọn để nhúng HTML. - Nếu dữ liệu cache dưới key hiện tại bị lỗi/không decode được, engine sẽ tự xóa key đó để lần chạy sau lưu lại dữ liệu mới.
- Cache runtime chỉ khởi tạo khi browser dùng được
localStoragewritable; nếu không có thì engine/bootloader sẽ bỏ qua load-save cache.
- SSR/MPA hoặc trang tĩnh cần giảm FOUC ngay từ first paint.
- Ứng dụng có cache CSS trong
localStoragevà muốn render gần như tức thì trước khi bundle chạy. - Khi bạn chủ động kiểm soát thứ tự script trong
<head>.
Không bắt buộc dùng khi:
- Trang không cần tối ưu first paint.
- Không dùng cache runtime.
- CSP không cho inline script (trừ khi đã cấu hình nonce/hash phù hợp).
SSR:
import { getCss } from '@fwkui/x-css';
const styles = getCss();
// <style dangerouslySetInnerHTML={{ __html: styles }} />Static extraction:
import xcss from '@fwkui/x-css';
import fs from 'node:fs';
const { clsx, getCssString } = xcss.css({
theme: { brand: '#0a64e8' }
}).buildCss();
clsx('m10px p20px cBrand dF');
fs.writeFileSync('./public/styles.css', getCssString());Bạn có thể đưa block này vào prompt system/project rules:
You are using @fwkui/x-css.
Generate class names strictly with syntax: [Media]:[Layer][Property][Value][@Selector].
Rules:
1. Value is required for normal utility classes.
2. Layer must be numeric and placed directly before Property (e.g. 3bgWhite).
3. Selector must be suffix @Selector (e.g. cBlue@:hover).
4. Use dictionary aliases from DICTIONARY.md.
5. Keep abbreviation values capitalized when needed (bdN, dF, posA).
6. For complex CSS values, use bracket notation, and use ';' as space placeholder:
w[calc(100%;-;10px)].
7. Use opc for opacity, tran for transition, op for object-position.
Before final answer:
- Validate each class can be parsed into {media, layer, property, value, selector}.
- Avoid invalid forms like bdn, tr0.2s, op0.8, 3:bgWhite, hover:cRed.Template giao việc cho AI thiết kế UI:
Thiết kế giao diện [màn hình] bằng @fwkui/x-css.
Yêu cầu:
1. Trả về HTML/JSX hoàn chỉnh.
2. Chỉ dùng class theo cú pháp [Media]:[Layer][Property][Value][@Selector].
3. Với value phức tạp, dùng [] và ';' thay cho khoảng trắng.
4. Không dùng class sai quy tắc (bdn, tr0.2s, op0.8, hover:cRed...).
5. Cuối câu trả lời thêm bảng kiểm:
- class
- parsed tuple {media, layer, property, value, selector}
- css dự kiến- Không còn class sai viết hoa value (
bdn,df,posa). - Không dùng nhầm alias (
op/opc,tr/tran). - Các value phức tạp đều bọc
[]. - Media custom đã khai báo trong
breakpoints. - Không có dạng sai layer/selector (
3:bg,hover:cRed). - Test parser với ít nhất bộ 10 test vector ở trên.
- Dictionary đầy đủ: DICTIONARY.md
- Source code: https://github.com/vuits24/fwkui
Licensed under MIT. See LICENSE.
Updated: 2026-03-18