npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@vestow/vue

v0.10.0

Published

Bộ component Vue 3 của WoodBlock: 85 component trên nền Reka UI, kèm 27 bảng cva và bộ chuỗi i18n của chính thư viện. Token và theme ở @vestow/theme.

Readme

@vestow/vue

85 component Vue 3, phần lớn dựng trên Reka UI, tô bằng Tailwind v4 và @vestow/theme. Bộ này là phần Vue của WoodBlock.

Nó mang theo hai tầng mà trước đây là package riêng, và mang vì cùng một lý do — chúng không dùng được nếu thiếu component đọc chúng:

| trong package | là gì | |---|---| | src/variants | 27 bảng cva, cn(), vòng focus, class menu, hình học chart | | src/i18n | locale controller + 126 khoá mà chính component phát ra (aria-label của Carousel, nút Đóng của Dialog, lỗi Zod) |

Thứ đứng riêng là @vestow/theme: Design Token dạng CSS thuần cộng registry chín trục. App nào chỉ cần token — kể cả app không dùng Vue — nạp mình nó là đủ.

Package export thẳng .vue source, không qua build — app mới là bên biên dịch. Đó là chủ ý: Tailwind phải quét được class thật, và app tự quyết mức tree-shake. Kéo theo vài ràng buộc ở phần cài đặt bên dưới, đọc kỹ ba mục có dấu ⚠.

Cài

pnpm add @vestow/vue @vestow/theme
pnpm add -D typescript tailwindcss @tailwindcss/vite @vitejs/plugin-vue

⚠ typescript là bắt buộc, kể cả app JavaScript thuần

@vue/compiler-sfc cần TypeScript để phân giải type trong defineProps<T>, mà component ở đây khai props bằng type import từ reka-ui. Thiếu nó thì build đổ hàng loạt với đúng một câu, lặp lại cho từng file:

[@vue/compiler-sfc] Failed to load TypeScript, which is required for
resolving imported types.

Đã đo: 297 lỗi, tất cả cùng gốc này.

⚠ Và typescript phải là 6.x, không phải 7

Cái bẫy tệ hơn, vì pnpm add -D typescript hôm nay kéo về 7.x và mọi thứ trông như đã đúng cho tới lúc build:

[@vue/compiler-sfc] No fs option provided to `compileScript` in non-Node environment.
File system access is required for resolving imported types.

Đo trên một app trống cài từ tarball: [email protected] → 298 lỗi; hạ xuống [email protected] → build xanh, không sửa gì khác.

pnpm add -D "typescript@~6.0.2"

Nguyên nhân, đo chứ không đoán. typescript@7 là bản viết lại bằng Go, và entry mặc định của nó chỉ còn hai trường:

require('typescript')  // -> { version: '7.0.2', versionMajorMinor: '7.0' }

Toàn bộ compiler API dời sang typescript/unstable/* với hình dạng khác hẳn — một lớp API/Project/Program thay cho namespace ts.* phẳng. Còn @vue/compiler-sfc gọi thẳng API cũ: ts.sys để đọc file, rồi ts.findConfigFile / ts.resolveModuleName để tìm ra reka-ui khi bạn viết defineProps<AccordionTriggerProps>. Với TS 7 thì ts.sys là undefined, và câu "non-Node environment" ở trên là compiler-sfc suy ra nhầm rằng nó đang chạy trong trình duyệt.

Cấp fs thủ công KHÔNG cứu được — đã thử, vì nó chỉ chữa được nửa đầu:

| | lỗi | |---|---| | TS 7 | 298 | | TS 7 + vue({ script: { fs } }) | 168 — ts2.findConfigFile is not a function | | TS 6 | 0 |

fs lo được import tương đối (../../lib/types); import bare (reka-ui) thì bắt buộc đi qua bộ phân giải module của TypeScript, và bộ đó không tồn tại ở TS 7.

Nên đây không phải lựa chọn của package này: chừng nào @vue/compiler-sfc chưa port sang API mới thì mọi thư viện Vue ship .vue source đều đứng ở TS 6.

Vẫn dùng được TS 7 — cài song song, cách chính thức của Microsoft

Ràng buộc thật không phải "cấm TS 7", mà là require('typescript') phải trả về API 6.x. TypeScript 7.0 (08/07/2026) phát hành có chủ ý mà không có API — bản thay thế hẹn ở 7.1 — và ghi thẳng trong thông báo phát hành: "Projects using Vue, MDX, Astro, Svelte, and others will need to continue using TypeScript 6.0 for now." Kèm theo là gói tương thích @typescript/typescript6.

Đo trên app thử ở trên, và nó chạy:

pnpm add -D "typescript@npm:@typescript/[email protected]" "typescript7@npm:[email protected]"

| | kết quả | |---|---| | vite build (compiler-sfc thấy API 6.x) | xanh, CSS đủ class | | vue-tsc --noEmit | chạy | | tsc --version | 7.0.2 — binary đến từ gói TS 7 | | tsc --noEmit trên file .ts thuần | bắt lỗi bằng TS 7 |

Tức phần TypeScript thuần của bạn chạy TS 7, chỉ riêng đường biên dịch SFC là đọc API 6.x. (Microsoft bày chiều ngược lại — typescript là 7 còn gói tương thích cấp tsc6 — nhưng với một dự án Vue thì phải để tên typescript cho bản 6, vì compiler-sfc không cho đổi chỗ nó tìm.)

Mốc cần theo dõi: TypeScript 7.1, kế hoạch phát hành stable 24/11/2026, trong đó có mục "Stabilize API". Port compiler-sfc sang API đó là việc của Vue và chưa có mốc; issue TS-native ở vuejs/language-tools đã đóng với nhãn upstream: typescript. Theo dõi phía Vue và phía TS, đừng theo dõi phía đây.

Cả hai ràng buộc TypeScript ở mục này là cái giá trực tiếp của việc export .vue source. Một package build sẵn ra .js + .d.ts thì không dính cái nào — đó cũng là đường thoát nếu ngày nào TS 6 hết đường lùi.

⚠ pnpm 11 phải cho vue-demi chạy build script

reka-ui (qua @floating-ui/vue) và @tanstack/vue-form đều dùng nó, và postinstall của nó là thứ chọn shim đúng cho Vue 3. Trong pnpm-workspace.yaml:

allowBuilds:
  vue-demi: true
  esbuild: true
  # maplibre-gl đi kèm @unovis/ts cho phần bản đồ. Không dùng bản đồ thì để chặn.
  maplibre-gl: false

Nạp CSS

Đúng hai dòng, và thứ tự có ý nghĩa:

@import "@vestow/vue/styles.css";
@source "./";

⚠ Đừng khai @source trỏ vào node_modules

@vestow/vue/styles.css đã tự khai @source "../" cho chính nó, mà Tailwind phân giải @source tương đối theo file CSS chứa nó — nên nằm trong node_modules nó vẫn trỏ đúng vào source của mình. Bạn chỉ cần khai vùng của app bạn.

Nó cũng đã @import "tailwindcss" source(none) và nạp sẵn token. Viết thêm @import "tailwindcss" ở file của bạn là nạp hai lần — đo được, dư 19 KB CSS thô.

Nghiệm thu không phải bằng mắt: build xong grep file CSS tìm ui-button và --focus-ring. Có thì @source đúng; DOM render đủ mà sạch class là dấu hiệu vùng quét bị hụt.

Dùng

// main.ts
import { createApp } from 'vue'
import { themeController } from '@vestow/theme'
import App from './App.vue'
import './style.css'

themeController.init()
createApp(App).mount('#app')
<script setup lang="ts">
import { Badge, Button, Card, CardContent } from '@vestow/vue'
</script>

<template>
  <Card>
    <CardContent>
      <Button variant="destructive" fill="soft">Xoá</Button>
      <Badge>Mới</Badge>
    </CardContent>
  </Card>
</template>

themeController.init() là thứ đọc localStorage và áp ba trục giao diện lên <html>. Không gọi thì component vẫn chạy, chỉ luôn ở bảng màu mặc định và không nhớ lựa chọn.

Hoặc: một lời gọi app.use, với preset có tên

Cùng cách gọi với PrimeVue:

import { createWoodBlock } from '@vestow/vue'
import { Solar } from '@vestow/theme/presets'

createApp(App)
  .use(createWoodBlock({
    theme: { preset: Solar },
  }))
  .mount('#app')

Tuỳ biến theo đúng cách PrimeVue làm với definePreset(Aura, {...}): một preset gốc, một object ghi đè.

import { definePreset } from '@vestow/theme'
import { Nebula } from '@vestow/theme/presets'

const MyBrand = definePreset(Nebula, {
  name: 'MyBrand',
  axes: { base: 'stone', style: 'nova' },                     // gộp NÔNG: palette 'nebula' giữ
  tokens: { base: { button: { radius: '2px' } } },            // gộp SÂU theo base / light / dark
})

app.use(createWoodBlock({ theme: { preset: MyBrand } }))

| trường | là gì | đi tới | |---|---|---| | axes | giá trị mặc định của các trục — bảng giá trị ngay dưới | themeController.setDefaults() | | tokens | ghi đè token, đè lên trục đang bật | definePreset() một tham số |

Hàm này thuần tuý: trả về preset mới, không sửa Nebula, không đụng DOM — nên mở rộng tiếp từ MyBrand được. Ghi đè một chuỗi lên một nhánh (accent: 'red' lên accent: { foreground }) chỉ đổi --accent, không xoá --accent-foreground.

Preset gốc — năm bảng màu

Đúng năm bảng màu đã đo tương phản AA:

| preset | primary | trung tính đi kèm | |---|---|---| | Hyperion | xanh lam — mặc định | slate | | Solar | cam | stone | | Nebula | tím | zinc | | Aurora | xanh ngọc | mist | | Eclipse | đơn sắc | neutral |

Giá trị của axes

| trục | giá trị | |---|---| | style — bo góc, đường kẻ, độ thoáng | vega (mặc định) · nova bo lớn, kẻ mờ · maia gần vuông · lyra vuông, kẻ đậm · mira mềm nhất · luma kẻ gần như không thấy · sera dày đặc · rhea mềm, kẻ đậm | | base — họ trung tính | slate (mặc định) · zinc · stone · mist · taupe · olive · mauve · neutral | | palette | năm id của năm preset gốc | | colorMode · chart · menuAccent · menuColor · menuAppearance | các trục còn lại của themeController |

axes có kiểu: gõ style: 'novaa' là lỗi biên dịch.

base: 'slate' nghĩa là "để bảng màu tự quyết", không phải "ép slate". slate là mặc định nên CSS không có khối nào cho nó và controller gỡ hẳn data-base — nền rơi về họ mà bảng màu mang theo. Đo được: Solar + slate ra --tint-hue: 56 (stone), không phải 265. Bảy họ còn lại thì luôn thắng họ của bảng màu, kể cả Eclipse.

Trục surface (data-surface="glass") đã gỡ ở 0.8.0, cùng useSurface, Solid / Glass và surfacePresets. Prop glass thay nó ở 0.8.0 cũng đã gỡ ở 0.9.0 — xem mục Button bên dưới.

Preset là giao diện BAN ĐẦU, không phải giao diện CỐ ĐỊNH. axes đi qua themeController.setDefaults() — hàm này KHÔNG ghi localStorage. Người dùng đã đổi bảng màu thì lựa chọn của họ thắng preset ở mọi lần tải trang sau.

Preset theo từng trục — cho bộ chọn giao diện, không phải để lồng

@vestow/theme/presets còn xuất một preset cho mỗi giá trị trục (Nova, Stone…) cùng ba mảng palettePresets / stylePresets / basePresets. Chúng sinh ra để dựng bộ chọn — lặp qua mảng, hiện name, gọi setStyle(p.axes.style) — chứ không phải để lồng definePreset(definePreset(...)). Muốn một cấu hình cố định thì viết axes như khối đầu mục này.

definePreset với một tham số vẫn là hàm cũ — ghi token thẳng lên trang. Hai cách gọi phân biệt bằng số tham số, và kiểu trả về (ThemePreset / void) khác nhau nên dùng nhầm thì tsc báo.

Plugin là vỏ mỏng: nó gọi setDefaults(axes) → definePreset(tokens) → themeController.init(), theo đúng thứ tự đó và trước mount(). Không giữ state riêng, không provide, không đăng ký component toàn cục — gọi rời ba hàm đó vẫn đúng y như trước.

Plugin KHÔNG đụng tới i18n — đó là một trục khác hẳn theme. Cần thì import và gọi tay:

import { setupZodMessages } from '@vestow/vue/form'
import { i18nController } from '@vestow/vue/i18n'

i18nController.addMessages('vi', { 'ui.close': 'Tắt' })   // ghi đè chuỗi thư viện
setupZodMessages()                                       // lỗi zod theo ngôn ngữ đang bật

i18nController.init() không bắt buộc: component đầu tiên gọi useI18n() sẽ tự gọi nó.

SSR: gọi được (mọi hàm tự bỏ qua khi không có document), nhưng controller vẫn là singleton ở tầng module — hai request trên cùng một server dùng chung state. Plugin này không giải quyết chuyện đó.

Hai trục của Button và Badge

| prop | giá trị | nghĩa | |---|---|---| | variant | default, secondary, destructive, success, warning, info, link | màu | | fill | solid (mặc định), soft, surface, outline, ghost | kiểu tô theo Radix Themes |

0.8.0: tone đã gỡ khỏi Button, Badge và Chip. tone="soft" đổi thành fill="soft" — nền nhạt hơn, không viền. Cần giữ viền thì fill="surface".

0.9.0: kính đã gỡ hẳn. Prop glass không còn trên Button, Card, PopoverContent, HoverCardContent, DialogContent, SheetContent, Sidebar, Toolbar, Toaster, SpeedDial và VideoPlayer; useGlassEffect / useGlassEnabled, các chuỗi class glass* ở @vestow/vue/variants và mục "Kính" trong PaletteMenu gỡ theo. Chỗ gọi còn sót glass không có tác dụng gì — Vue thả nó xuống DOM thành một thuộc tính thừa. Lý do: không nấc kính nào bảo đảm 4,5:1, và ngoài trang giới thiệu thì phía sau các bề mặt đó hiếm khi có gì để làm mờ.

Menu trong mờ (data-menu-appearance="translucent") thì ở lại — nó là trục của menu.

0.9.0: variant="outline" và variant="ghost" của Button đã gỡ. Viết variant="secondary" fill="outline" / fill="ghost" — diện mạo giữ nguyên: viền --input, hover --accent. Các sắc khác với fill="outline" vẫn dùng viền đậm của thang vai trò. variant="link" ở lại vì chưa có kiểu tô tương đương.

Import lẻ

exports mở sẵn ba nhánh con, dùng khi không muốn kéo cả index.ts:

import Button from '@vestow/vue/components/button/Button.vue'
import { cn } from '@vestow/vue/lib/utils'
import { useI18n } from '@vestow/vue/composables/useI18n'

// Hai tầng không-Vue, dùng được mà không kéo component nào:
import { buttonVariants } from '@vestow/vue/variants/components/button'
import { i18nController, locales } from '@vestow/vue/i18n'

Subpath cho component kéo gói nặng

Năm nhóm component cần một thư viện lớn đi kèm. Chúng không nằm ở barrel gốc, mà ở subpath riêng, và thư viện đi kèm là peer tuỳ chọn: app không dùng thì không cài.

| import từ | gồm | cài thêm | |---|---|---| | @vestow/vue/chart | AreaChart, BarChart, LineChart, PieChart, RadarChart, RadialChart, ChartContainer… | @unovis/ts @unovis/vue | | @vestow/vue/data-table | DataTable, DataTablePagination, DataTableFacetedFilter, adapter phân trang… | @tanstack/vue-table @tanstack/vue-virtual | | @vestow/vue/tree-table | TreeTable | @tanstack/vue-table | | @vestow/vue/form | Form, FormField, FormItem…, setupZodMessages | @tanstack/vue-form zod | | @vestow/vue/carousel | Carousel và các phần con | embla-carousel embla-carousel-vue |

import { Button } from '@vestow/vue'
import { BarChart } from '@vestow/vue/chart'

Barrel gốc import tĩnh mọi component nó export, nên chỉ cần một component ở đó với tới @unovis là app không cài @unovis build hỏng — kể cả khi không dùng biểu đồ. Vì vậy test/barrel.test.ts đi theo import từ index.ts và đỏ nếu chạm tới một peer tuỳ chọn. TableQuery và các kiểu tham số phân trang vẫn ở barrel gốc (DataView, useTableQuery dùng chúng) và không phụ thuộc TanStack.

0.9.0: trước đây năm nhóm này ở @vestow/vue và các thư viện kia là dependency thật. Đổi import sang subpath và tự cài thư viện đi kèm.

Peer

vue là peer dependency (^3.5.42). @vestow/theme là dependency thật — cài @vestow/vue là có luôn. Các peer tuỳ chọn ở bảng trên.

Publish

Không có bước build: files đóng gói index.ts + src/, và exports trỏ thẳng vào source ở cả chế độ phát triển lẫn sau khi publish. Đóng gói thử mà không cần registry:

pnpm -F @vestow/vue pack --pack-destination /tmp

Muốn thử cài tarball ở một project khác thì @vestow/theme cũng phải có mặt — nó là dependency thật, nên pnpm sẽ đi tìm trên registry. Trỏ nó về tarball local bằng overrides trong pnpm-workspace.yaml của project đó, hoặc dựng một registry local (npx verdaccio) rồi publish cả hai vào đó.

overrides:
  '@vestow/theme': file:/đường/dẫn/vestow-theme-0.1.1.tgz