@fwkui/x-css 1.0.21 → 1.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -252,26 +252,22 @@ export function Button({ primary, children }) {
252
252
 
253
253
  ## Shared Instance (Khởi Tạo Một Lần)
254
254
 
255
- Nếu bạn muốn tái sử dụng cùng một instance (giữ mapping key ổn định), dùng factory:
255
+ Nếu bạn muốn tái sử dụng cùng một instance, dùng factory.
256
+ Khuyến nghị mặc định cho app chạy thật:
257
+ 1. Không đặt `prefix`.
258
+ 2. Không bật `cache`.
259
+ 3. Chỉ bật `cache` hoặc `prefix` khi bạn chủ động chấp nhận tradeoff vận hành.
256
260
 
257
261
  ```ts
258
262
  import { createSharedInstance } from '@fwkui/x-css';
259
263
 
260
- export const fx = createSharedInstance({
261
- prefix: 'fk-',
262
- cache: {
263
- styleId: 'fwkui',
264
- version: 'v1',
265
- compression: true,
266
- debounceMs: 1000
267
- }
268
- });
264
+ export const fx = createSharedInstance();
269
265
 
270
266
  // Browser: gọi 1 lần khi app khởi động
271
267
  fx.observe(document);
272
268
 
273
269
  // Dùng ở mọi nơi
274
- const className = fx.clsx('fk-dF fk-aiC fk-jcC fk-p10px;16px');
270
+ const className = fx.clsx('dF aiC jcC p10px;16px');
275
271
 
276
272
  // Nếu dictionaryImport là true/string và cần chắc chắn CSS đã sẵn sàng:
277
273
  await fx.ready();
@@ -280,17 +276,225 @@ await fx.ready();
280
276
  const cssText = fx.getCss();
281
277
  ```
282
278
 
279
+ ## Tailwind Migration Helper
280
+
281
+ `@fwkui/x-css` có helper bán tự động để hỗ trợ migration từ chuỗi utility Tailwind sang token `x-css`.
282
+
283
+ Public API:
284
+
285
+ ```ts
286
+ import {
287
+ assessTailwindMigrationReadiness,
288
+ classifyTailwindToken,
289
+ convertTailwindClasses,
290
+ convertTailwindToken,
291
+ getTailwindCoverageMatrix
292
+ } from '@fwkui/x-css';
293
+
294
+ const result = convertTailwindClasses(
295
+ 'flex items-center justify-between gap-4 p-4 bg-white text-slate-900 rounded-lg border border-slate-200'
296
+ );
297
+
298
+ console.log(result.output);
299
+ // dF ai[center] jc[space-between] gap16px p16px bgc#ffffff c#0f172a bdra8px bdw1px bds[solid] bdcCurrentColor bdc#e2e8f0
300
+
301
+ console.log(classifyTailwindToken('md:fxd[row]'));
302
+ // 'xcss'
303
+
304
+ const readiness = assessTailwindMigrationReadiness(
305
+ 'transition flex group-hover:bg-slate-50 md:fxd[row]'
306
+ );
307
+
308
+ console.log(readiness.releaseDecision);
309
+ // 'blocked'
310
+
311
+ console.log(readiness.autoApplyOutput);
312
+ // dF md:fxd[row]
313
+ ```
314
+
315
+ Contract hiện tại:
316
+ 1. Helper ưu tiên các utility Tailwind phổ biến cho layout, spacing, color, typography, border, shadow, width/height.
317
+ 2. Responsive/state variants phổ biến như `sm:`, `md:`, `hover:`, `focus:` được chuyển sang format `x-css`.
318
+ 3. Token đầu vào được phân loại thành `tailwind`, `xcss`, `ambiguous`, hoặc `unknown`.
319
+ 4. Đây là migration helper, không phải promise “convert 100% Tailwind không cần review”.
320
+ 5. Khi publish hoặc migrate codebase thật, nên ưu tiên `mode: 'safe'` thay vì `legacy`.
321
+ 6. Helper này làm việc trên raw utility string và bỏ qua config `prefix`; khi migrate Tailwind, nên coi `prefix` là rỗng.
322
+
323
+ Ví dụ:
324
+
325
+ ```ts
326
+ const converted = convertTailwindToken('md:hover:bg-slate-50');
327
+
328
+ console.log(converted.outputs);
329
+ // ['md:bgc#f8fafc@:hover']
330
+
331
+ const safe = convertTailwindClasses('md:fxd[row] p-2.5', { mode: 'safe' });
332
+ console.log(safe.passthrough); // ['md:fxd[row]']
333
+ console.log(safe.converted); // ['p10px']
334
+ console.log(safe.ambiguous); // ['p-2.5']
335
+ ```
336
+
337
+ ### Mode
338
+
339
+ `convertTailwindToken()` và `convertTailwindClasses()` nhận option:
340
+
341
+ ```ts
342
+ type TailwindConversionMode = 'legacy' | 'safe' | 'strict'
343
+ ```
344
+
345
+ Ý nghĩa:
346
+ 1. `legacy`:
347
+ giữ hành vi tương thích cũ.
348
+ Token không convert được có thể được passthrough nếu `preserveUnknown !== false`.
349
+ 2. `safe`:
350
+ chỉ passthrough token đã được xác nhận là `x-css`.
351
+ Token `ambiguous` hoặc `unknown` sẽ không được giữ nguyên mù.
352
+ 3. `strict`:
353
+ dùng cùng `preserveUnknown: false` để ép toàn bộ token không chắc chắn đi vào `unsupported`.
354
+ Đây là mode phù hợp cho codemod, CI, hoặc review trước khi deploy.
355
+
356
+ Ví dụ khuyến nghị:
357
+
358
+ ```ts
359
+ const result = convertTailwindClasses(source, {
360
+ mode: 'safe',
361
+ preserveUnknown: false
362
+ });
363
+ ```
364
+
365
+ ### Kết Quả Trả Về
366
+
367
+ `convertTailwindToken()` trả:
368
+
369
+ ```ts
370
+ {
371
+ input: string
372
+ outputs: string[]
373
+ status: 'converted' | 'passthrough' | 'unsupported'
374
+ classification: 'tailwind' | 'xcss' | 'ambiguous' | 'unknown'
375
+ exact: boolean
376
+ warnings: Array<{ token: string; message: string }>
377
+ }
378
+ ```
379
+
380
+ `convertTailwindClasses()` trả:
381
+
382
+ ```ts
383
+ {
384
+ input: string
385
+ output: string
386
+ details: TailwindTokenConversion[]
387
+ converted: string[]
388
+ passthrough: string[]
389
+ unsupported: string[]
390
+ ambiguous: string[]
391
+ warnings: TailwindConversionWarning[]
392
+ }
393
+ ```
394
+
395
+ Ý nghĩa thực tế:
396
+ 1. `converted`: token đã map sang `x-css`.
397
+ 2. `passthrough`: token được giữ nguyên vì là `x-css` thật, hoặc vì caller vẫn bật giữ token gốc.
398
+ 3. `unsupported`: token chưa có mapping an toàn.
399
+ 4. `ambiguous`: token có hình thức dễ nhầm giữa Tailwind và `x-css`, cần review.
400
+
401
+ ### Readiness API Cho Sản Phẩm Thật
402
+
403
+ 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.
404
+ Hãy chạy preflight bằng `assessTailwindMigrationReadiness()`:
405
+
406
+ ```ts
407
+ const report = assessTailwindMigrationReadiness(source);
408
+
409
+ if (report.releaseDecision === 'safe') {
410
+ // Chỉ còn exact conversion hoặc x-css thật
411
+ apply(report.autoApplyOutput);
412
+ }
413
+
414
+ if (report.releaseDecision === 'review') {
415
+ // Có output gần đúng như `transition -> tran0.2s`
416
+ review(report.approximateConverted);
417
+ }
418
+
419
+ if (report.releaseDecision === 'blocked') {
420
+ // Có utility không an toàn để tự động migrate
421
+ fail(report.blocked);
422
+ }
423
+ ```
424
+
425
+ Contract của report:
426
+ 1. `autoApplyOutput`: chỉ chứa token exact-converted và token `x-css` thật, theo đúng thứ tự gốc.
427
+ 2. `approximateConverted`: token đã convert nhưng không đủ an toàn để áp thẳng production.
428
+ 3. `reviewRequired`: tất cả token cần con người xem lại.
429
+ 4. `blocked`: token không nên auto-apply trong flow migration thật.
430
+ 5. `safeToAutoApply`: chỉ `true` khi không còn token phải review.
431
+
432
+ Coverage machine-readable:
433
+
434
+ ```ts
435
+ const matrix = getTailwindCoverageMatrix();
436
+ ```
437
+
438
+ `matrix` dùng được cho codemod, CI gate, dashboard coverage, hoặc rule riêng của team sản phẩm.
439
+
440
+ ### Canonical Output
441
+
442
+ Khi helper sinh token `x-css`, nên coi các output dưới đây là canonical:
443
+ 1. `inline-flex -> dIf`
444
+ 2. `inline-block -> dIb`
445
+ 3. `inline-grid -> dIg`
446
+ 4. `border -> bdw1px bds[solid] bdcCurrentColor`
447
+ 5. `text-transparent -> cTransparent`
448
+ 6. `border-transparent -> bdcTransparent`
449
+ 7. `bg-[currentColor] -> bgcCurrentColor`
450
+
451
+ 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`.
452
+
453
+ ### Coverage Hiện Tại
454
+
455
+ Nhóm utility đã hỗ trợ tốt:
456
+ 1. display / position / flex cơ bản
457
+ 2. spacing / size / fraction / width scale / `mx-auto` / `inset-x-*` / `inset-y-*`
458
+ 3. color cơ bản (`bg-*`, `text-*`, `border-*`)
459
+ 4. rounded / border / shadow / `appearance-none`
460
+ 5. font-size / font-weight / line-height / letter-spacing / `basis-*` / `order-*`
461
+ 6. object-fit / object-position cơ bản
462
+ 7. align / place / justify / content helpers phổ biến
463
+ 8. một phần responsive + selector variants (`sm`, `md`, `hover`, `focus`, `before`, `after`, `placeholder`, `selection`)
464
+
465
+ Nhóm nên coi là cần review thủ công hoặc fallback riêng:
466
+ 1. `group-hover:*`, `group-focus:*`, `peer-*`
467
+ 2. `dark:*`
468
+ 3. `divide-*`
469
+ 4. `duration-*`, `ease-*`, `delay-*`
470
+ 5. transform chain phức hợp như `translate-*`, `scale-*`, `rotate-*`
471
+
472
+ ### Quy Trình Dùng An Toàn
473
+
474
+ Khi migrate project thật:
475
+ 1. Chạy converter với `mode: 'safe'`.
476
+ 2. Áp dụng trực tiếp các token trong `converted`.
477
+ 3. Giữ nguyên các token trong `passthrough` chỉ khi chúng là `x-css` thật.
478
+ 4. Review bắt buộc các token trong `ambiguous` và `unsupported`.
479
+ 5. Chạy lại app, build, và kiểm tra giao diện thực tế sau mỗi đợt chuyển đổi.
480
+
481
+ Khuyến nghị:
482
+ 1. Không chạy codemod toàn repo ở `legacy mode` rồi deploy thẳng.
483
+ 2. Không dùng parser `x-css` như bằng chứng rằng token Tailwind “đã an toàn”.
484
+ 3. 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.
485
+
283
486
  Alias tương đương:
284
487
 
285
488
  ```ts
286
489
  import { createSharedClsx } from '@fwkui/x-css';
287
- const fx = createSharedClsx({ prefix: 'fk-' });
490
+ const fx = createSharedClsx();
288
491
  ```
289
492
 
290
493
  Quy tắc dùng ổn định:
291
494
  1. Tạo shared instance đúng 1 lần ở bootstrap.
292
495
  2. Không khởi tạo lại instance ở mỗi lần render component.
293
- 3. Giữ nguyên `prefix/cache` trong suốt vòng đời app để key hash ổn định.
496
+ 3. Mặc định để `prefix` rỗng `cache` tắt.
497
+ 4. Nếu bạn tự bật `cache` hoặc `prefix`, giữ nguyên cấu hình đó trong suốt vòng đời app.
294
498
 
295
499
  ## Cấu Hình
296
500
 
@@ -306,25 +510,18 @@ xcss.cssObserve(document, {
306
510
  { tablet: 'screen and (min-width: 768px)' }
307
511
  ],
308
512
  base: 'body{margin:0;font-family:system-ui,sans-serif;}',
309
- prefix: 'fk-',
310
513
  excludePrefixes: ['bs-', 'rs-'],
311
514
  excludes: ['legacy-*'],
312
- dictionaryImport: true,
313
- cache: {
314
- styleId: 'fwkui',
315
- version: 'v1',
316
- compression: true,
317
- debounceMs: 1000
318
- }
515
+ dictionaryImport: true
319
516
  });
320
517
  ```
321
518
 
322
- Sau đó dùng class: `fk-cBrand fk-tablet:dB`.
519
+ Sau đó dùng class: `cBrand tablet:dB`.
323
520
 
324
521
  Gợi ý tối ưu bỏ qua parse:
325
- 1. Dùng `prefix` nếu bạn kiểm soát được class framework (nhanh sạch nhất).
326
- 2. Dùng `excludePrefixes` để bỏ qua nhanh theo tiền tố, ví dụ `bs-`, `rs-`.
327
- 3. Dùng `excludes` khi cần rule chính xác hoặc wildcard (`*`), dụ `legacy-*`, `tmp-debug`.
522
+ 1. Ưu tiên `excludePrefixes` để bỏ qua nhanh theo tiền tố, dụ `bs-`, `rs-`.
523
+ 2. Dùng `excludes` khi cần rule chính xác hoặc wildcard (`*`), ví dụ `legacy-*`, `tmp-debug`.
524
+ 3. Không nên lấy `prefix` làm cấu hình mặc định; chỉ dùng khi bạn thật sự cần tách namespace class.
328
525
 
329
526
  Ví dụ đầu vào cho `excludes`:
330
527
 
@@ -354,6 +551,7 @@ Lưu ý khi dùng cùng `prefix`:
354
551
  1. Engine kiểm tra `excludes`/`excludePrefixes` trước, sau đó mới kiểm tra `prefix`.
355
552
  2. Nếu token match exclude thì giữ nguyên class gốc và không parse tiếp.
356
553
  3. Nếu có `prefix: 'fk-'`, token không bắt đầu bằng `fk-` sẽ giữ nguyên class gốc.
554
+ 4. `prefix` là cấu hình tùy chọn, không phải khuyến nghị mặc định.
357
555
 
358
556
 
359
557
  `dictionaryImport`:
@@ -367,11 +565,18 @@ Lưu ý:
367
565
  3. 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`.
368
566
 
369
567
  `cache`:
370
- 1. `styleId` (mặc định `fwkui`): id thẻ `<style>` runtime.
371
- 2. `version` (mặc định `v1`): tham gia vào cache key để chủ động invalidate.
372
- 3. `compression` (mặc định `true`): nén cache trước khi lưu `localStorage`.
373
- 4. `debounceMs` (mặc định `1000`): debounce chu kỳ nén + lưu cache.
374
- 5. `sizeLast` (mặc định `1000`): seed mặc định cho bộ sinh key `D...`.
568
+ 1. Mặc định `cache` đang tắt (`loadOnInit: false`).
569
+ 2. `styleId` (mặc định `fwkui`): id thẻ `<style>` runtime.
570
+ 3. `version` (mặc định `v1`): tham gia vào cache key để chủ động invalidate.
571
+ 4. `compression` (mặc định `true`): nén cache trước khi lưu `localStorage`.
572
+ 5. `debounceMs` (mặc định `1000`): debounce chu kỳ nén + lưu cache.
573
+ 6. `sizeLast` (mặc định `1000`): seed mặc định cho bộ sinh key `D...`.
574
+
575
+ Khuyến nghị:
576
+ 1. Không bật `cache` mặc định cho mọi app.
577
+ 2. Không bật/tắt `cache` lặp lại theo route, component, hoặc session ngắn.
578
+ 3. Chỉ bật `cache` khi bạn chủ động muốn tối ưu first paint hoặc SSR/MPA hydration.
579
+ 4. Nếu đã bật `cache`, hãy cấu hình ổn định và giữ nguyên trong toàn app.
375
580
 
376
581
  Khi `compression: true`:
377
582
  1. Ưu tiên `CompressionStream` (deflate-raw + base64) nếu runtime hỗ trợ.
@@ -461,9 +666,9 @@ Quy trình thay link:
461
666
  3. Thay `dictionaryImport` bằng URL thật.
462
667
  4. Chờ `await engine.ready` trước khi render class.
463
668
 
464
- ## Bootloader Từ Cache (Khuyến nghị)
669
+ ## Bootloader Từ Cache (Tùy Chọn)
465
670
 
466
- Khuyến nghị dùng helper `getBootloaderScript` để chèn script vào `<head>` trước bundle/module, giúp giảm FOUC khi đã cache CSS.
671
+ Chỉ dùng helper `getBootloaderScript` khi bạn chủ động bật `cache` muốn lấy CSS từ `localStorage` trước khi bundle chạy.
467
672
 
468
673
  ### Dùng helper `getBootloaderScript`
469
674
 
@@ -473,8 +678,11 @@ import { getBootloaderScript } from '@fwkui/x-css';
473
678
  const styleId = 'fwkui';
474
679
  const version = 'v1';
475
680
 
476
- const bootloaderScript = getBootloaderScript(styleId, version);
477
- const bootloaderScriptCompact = getBootloaderScript(styleId, version, { compact: true });
681
+ const bootloaderScript = getBootloaderScript(styleId, version, { loadOnInit: true });
682
+ const bootloaderScriptCompact = getBootloaderScript(styleId, version, {
683
+ compact: true,
684
+ loadOnInit: true
685
+ });
478
686
  ```
479
687
 
480
688
  ### Cấu trúc chèn vào `<head>` (khuyến nghị)
@@ -483,7 +691,7 @@ const bootloaderScriptCompact = getBootloaderScript(styleId, version, { compact:
483
691
  <head>
484
692
  <!-- 1) Bootloader từ cache: chạy sớm nhất để giảm FOUC -->
485
693
  <script>
486
- /* nội dung từ getBootloaderScript(styleId, version) */
694
+ /* nội dung từ getBootloaderScript(styleId, version, { loadOnInit: true }) */
487
695
  </script>
488
696
 
489
697
  <!-- 2) Bundle/module của app -->
@@ -510,7 +718,10 @@ import { getBootloaderScript } from '@fwkui/x-css';
510
718
 
511
719
  const styleId = 'fwkui';
512
720
  const version = 'v1';
513
- const bootloaderScript = getBootloaderScript(styleId, version, { compact: true });
721
+ const bootloaderScript = getBootloaderScript(styleId, version, {
722
+ compact: true,
723
+ loadOnInit: true
724
+ });
514
725
 
515
726
  const html = `
516
727
  <!doctype html>
@@ -528,8 +739,10 @@ Lưu ý:
528
739
  1. Đồng bộ `styleId` + `version` giữa bootloader và cấu hình `xcss.css(...)`.
529
740
  2. Script tạo từ `getBootloaderScript` ưu tiên `DecompressionStream` (cache deflate-raw) và fallback LZW.
530
741
  3. Sau bootloader vẫn cần gọi `xcss.cssObserve(...)` như bình thường.
531
- 4. Dùng `{ compact: true }` khi muốn script trả về dạng nén gọn để nhúng HTML.
742
+ 4. Mặc định `loadOnInit` đang tắt; chỉ bật bằng `{ loadOnInit: true }` khi bạn thật sự dùng cache runtime.
743
+ 5. Dùng `{ compact: true }` khi muốn script trả về ở dạng nén gọn để nhúng HTML.
532
744
  6. 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.
745
+ 7. Cache runtime chỉ khởi tạo khi browser dùng được `localStorage` writable; nếu không có thì engine/bootloader sẽ bỏ qua load-save cache.
533
746
 
534
747
  ### Khi nào nên dùng `getBootloaderScript`
535
748
 
@@ -1,5 +1,5 @@
1
1
  import _default from './index.mjs';
2
- export { BootloaderScriptOptions, SharedXCSSInstance, XCSSConfig, clsx, createSharedClsx, createSharedInstance, getBootloaderScript, getCss, observe, ready, setClsxRoot } from './index.mjs';
2
+ export { BootloaderScriptOptions, SharedXCSSInstance, TAILWIND_COVERAGE_MATRIX, TailwindConversionMode, TailwindConversionOptions, TailwindConversionResult, TailwindConversionStatus, TailwindConversionWarning, TailwindCoverageEntry, TailwindCoverageSupport, TailwindMigrationReadinessOptions, TailwindMigrationReadinessReport, TailwindTokenClassification, TailwindTokenConversion, XCSSConfig, assessTailwindMigrationReadiness, classifyTailwindToken, clsx, convertTailwindClasses, convertTailwindToken, createSharedClsx, createSharedInstance, getBootloaderScript, getCss, getTailwindCoverageMatrix, observe, ready, setClsxRoot, tailwindToXcss } from './index.mjs';
3
3
 
4
4
 
5
5
 
@@ -1,5 +1,5 @@
1
1
  import _default from './index.js';
2
- export { BootloaderScriptOptions, SharedXCSSInstance, XCSSConfig, clsx, createSharedClsx, createSharedInstance, getBootloaderScript, getCss, observe, ready, setClsxRoot } from './index.js';
2
+ export { BootloaderScriptOptions, SharedXCSSInstance, TAILWIND_COVERAGE_MATRIX, TailwindConversionMode, TailwindConversionOptions, TailwindConversionResult, TailwindConversionStatus, TailwindConversionWarning, TailwindCoverageEntry, TailwindCoverageSupport, TailwindMigrationReadinessOptions, TailwindMigrationReadinessReport, TailwindTokenClassification, TailwindTokenConversion, XCSSConfig, assessTailwindMigrationReadiness, classifyTailwindToken, clsx, convertTailwindClasses, convertTailwindToken, createSharedClsx, createSharedInstance, getBootloaderScript, getCss, getTailwindCoverageMatrix, observe, ready, setClsxRoot, tailwindToXcss } from './index.js';
3
3
 
4
4
 
5
5