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

@bndynet/vue-site

v1.3.2

Published

A configurable Vue 3 site framework with sidebar navigation, markdown rendering, and theme switching.

Downloads

1,049

Readme

@bndynet/vue-site

可配置的 Vue 3 站点框架:一个依赖包、一份 site.config.ts 以及 Markdown 页面 —— 即可获得侧边栏、语法高亮代码块、亮色/暗色主题。无需手写 main.tsindex.htmlvite.config.ts

特性

  • 配置驱动的导航(使用 Lucide 图标名)
  • 通过 visible 断言进行权限控制的导航(同步或异步;隐藏条目并跳过其路由)
  • 通过 auth + 中心化的 authorize 策略实现按页面授权(导航守卫 + 登录重定向)
  • Hash 或 HTML5(web)路由历史模式,可在 site.config.ts 中配置
  • 支持 Markdown(?raw)或 Vue 页面
  • highlight.js、亮色/暗色主题 + localStorage 持久化
  • 内置多语言支持(语言切换器、LocalizedString 配置、按语言区分的页面)—— 参见国际化
  • 将项目的 README.md 作为首页
  • 完整的 TypeScript 类型

快速开始

安装

npm install @bndynet/vue-site

site.config.ts

import { defineConfig } from '@bndynet/vue-site'

export default defineConfig({
  title: 'My Project',
  favicon: '/favicon.ico',
  nav: [
    { label: 'Home', icon: 'home', page: () => import('./README.md?raw') },
    { label: 'Guide', icon: 'book-open', page: () => import('./pages/guide.md?raw') },
  ],
})

目录结构

my-site/
  package.json
  site.config.ts
  public/favicon.ico
  README.md
  pages/guide.md

CLIvue-sitevs 等价)

npx vue-site dev
npx vue-site build
npx vue-site preview

子路径部署:在 CLI 上传入 Vite 的公共 base(会覆盖 site.config 中的 env.vite.base):

npx vue-site build --base=/app/
# 或
npx vue-site build --base /app/

如果愿意,可以在 package.json 的 scripts 中加上 "dev": "vue-site dev"(或 vs dev)。

配置参考

SiteConfig

| 属性 | 说明 | |----------|-------------| | title | 站点标题(侧边栏 + 标签页)。LocalizedString | | nav | NavItem[] | | navPosition | 可选的导航位置:'top' 强制使用顶部/分层导航,'sidebar' 强制将完整导航树放在侧边栏。不传时保持基于导航深度的自动行为 | | defaultPath | 站点打开时的路径;/ 和未知路径会重定向到此。必须匹配一个已注册的路由(某个 nav 条目解析后的路径,或某个 pages 条目的 path)。默认为第一个顶级 nav 条目 | | favicon | 浏览器标签页图标 URL。可使用 public 资源路径、远程 URL 或导入后的资源 URL | | logo | Logo 的 URL 或导入的图片 | | theme | 见下方 ThemeConfig;设为 false 可禁用主题(隐藏切换器、强制使用固定的 light 调色板、不进行 localStorage 持久化) | | i18n | 多语言配置(I18nConfig)—— 参见国际化 | | footer | 页脚文本。LocalizedString | | readme | 当没有 README.md 时作为首页的原始内容 | | links | 头部链接:Lucide icon + link,可选的 titleLocalizedString) | | pages | StandalonePage[] —— 位于 nav 树之外的全屏路由(无顶部栏/侧边栏/页脚) | | auth | 中心化的授权策略(AuthConfig)—— 参见按页面授权 | | router | 历史模式(RouterConfig)—— hash(默认)或 HTML5 web;参见路由历史 | | packageRepository | 通常由 CLI 从 package.json 设置;单独使用 createSiteApp 时可省略 | | env | 开发/构建选项 —— 见下文 | | bootstrap | 可选的站点根目录相对路径(如 ./bootstrap.ts)—— 在 Vue 应用之前加载一次的模块 | | configureApp | 可选的 (app) => void \| Promise<void>,在路由安装之后、mount 之前执行(参见 configureApp 中的本地包) |

NavItem

| 属性 | 说明 | |----------|-------------| | label | 侧边栏文本。LocalizedString | | icon | Lucide 图标名 | | page | 页面内容。最简单的是一个文件路径字符串,如 './pages/AdminView.vue''./README.md'(自动加载各语言的同名文件,找不到时回退到基础文件)。也接受加载器(() => import('./Page.vue') / () => import('./page.md?raw'))或 localizedPage(...) 的返回值。见按语言区分的页面内容高级页面加载器 | | path | 路由路径(省略时从 label 的默认语言值派生;切换语言时保持稳定) | | layout | 页面内容宽度:'default' 保持标准居中阅读宽度,'wide' 使用更宽的居中画布,'full' 以无框架内边距的方式填满可用父容器。对分组和链接无效。 | | children | 嵌套分组 | | link | 渲染为超链接(内部路由路径或外部 URL),而非页面路由 | | visible | () => boolean \| Promise<boolean>,在启动时等待执行一次。返回 false 会从导航中隐藏该条目并跳过其路由(无法通过直接 URL 访问)。被隐藏的父项会隐藏其整个子树;没有剩余子项的分组会被剪除。对后续变化不具响应性。 | | auth | 由 auth.authorize 解释的授权规则(AuthRule)。会保持路由注册并通过导航守卫强制执行(因此直接访问 URL 会重定向到登录页)。需要配置 SiteConfig.auth。参见按页面授权。 |

StandalonePage

| 属性 | 说明 | |----------|-------------| | path | 路由路径,按原样使用(如 /landing) | | page | 页面内容。接受形式与 NavItem.page 相同 | | layout | 可选内容宽度。省略时保持 standalone 默认行为:全屏、无内边距画布。 | | visible | () => boolean \| Promise<boolean>,在启动时等待执行一次。返回 false 会跳过该路由注册。 | | auth | 授权规则(AuthRule),由与 NavItem.auth 相同的导航守卫强制执行。 |

页面布局

当默认阅读宽度太窄时,可以在 NavItem 页面上设置 layout

export default defineConfig({
  nav: [
    { label: 'Docs', icon: 'book', page: './pages/docs.md' },
    { label: 'Reports', icon: 'table', layout: 'wide', page: './pages/reports.vue' },
    { label: 'Dashboard', icon: 'gauge', layout: 'full', page: './pages/dashboard.vue' },
  ],
})

ThemeConfig

内置主题为 lightdark,外加始终可用的额外主题 sepiaocean(在每个站点的切换器中都会显示)。在 SiteConfig 上设置 theme: false 可完全禁用主题。

| 属性 | 默认值 | 说明 | |----------|---------|-------------| | default | light | lightdark、内置额外主题 id(sepiaocean),或某个 extraThemes[].id | | colors | — | 全局 CSS 变量覆盖 | | palettes | — | 仅针对内置 light/dark 的部分覆盖 | | extraThemes | — | 额外主题:idlabelicon,可选 basedOnpalette;复用内置 id(sepia/ocean)即可覆盖它。导入 builtinThemePalettes 获取完整默认值 |

国际化(i18n

设置 i18n 以启用多语言支持。框架会在头部添加语言切换器, 针对当前激活的语言解析每一个 LocalizedString 字段(titlenav[].labelfooterlinks[].title), 并通过 useLocale() / useLocalize() 暴露当前语言。

import { defineConfig } from '@bndynet/vue-site'

export default defineConfig({
  i18n: {
    locales: [
      { code: 'en', label: 'English' },
      { code: 'zh', label: '简体中文', icon: 'languages' },
    ],
    defaultLocale: 'en',
  },
  title: { en: 'My Site', zh: '我的站点' }, // 一个 LocalizedString
  footer: { en: '© 2026', zh: '© 2026 版权所有' },
  nav: [
    { label: { en: 'Home', zh: '首页' }, icon: 'home', page: '../README.md' },
    {
      label: { en: 'Guide', zh: '指南' },
      icon: 'book',
      // 按语言区分的内容:./pages/guide.md(基础)+ guide.zh.md,由 CLI 自动发现。
      page: './pages/guide.md',
    },
  ],
})

按语言区分的页面内容

page 指向一个文件路径字符串,框架就会为当前语言加载正确的文件 —— 无需任何额外接线:

import { defineConfig, tk } from '@bndynet/vue-site'

export default defineConfig({
  i18n: { locales: [{ code: 'en' }, { code: 'zh' }], defaultLocale: 'en' },
  nav: [
    // 加载 ../README.md;当语言为 `zh` 时自动改用 ../README.zh.md。
    { label: tk('nav.home'), icon: 'home', page: '../README.md' },
    // Vue 页面同理:Dashboard.vue + Dashboard.zh.vue。
    { label: tk('nav.dash'), icon: 'gauge', page: './pages/Dashboard.vue' },
  ],
})
  • 在基础文件旁边以 名.<code>.<ext> 命名各语言变体 —— README.zh.mdDashboard.zh.vue……
  • 没有对应文件的语言会回退到基础文件README.md)。解析顺序:精确匹配 → 主子标签(zh-TWzh)→ 基础文件。
  • 新增语言只需放入一个 名.<code> 文件 —— 无需改配置。
  • 支持 Markdown(.md)和 Vue(.vue)。基础名称不能包含点号(README.md ✓,my.page.md ✗)。

字符串形式由 vue-site CLI 在构建时解析。如果你自行嵌入该库(不使用 CLI —— 见库模式), 请改用高级页面加载器中的加载器。

高级页面加载器

一般用不到。 上面的文件路径字符串已能覆盖大多数站点。只有在你需要一个普通的单文件加载器、 各语言文件不同名,或在不使用 CLI(库模式)时,才需要用到这些。

除了字符串,page 还接受一个加载器函数localizedPage(...) 的返回值:

  • 单文件、不做多语言 —— 一个普通的动态导入:

    page: () => import('./pages/Dashboard.vue')   // Markdown 用 () => import('./guide.md?raw')
  • 显式语言映射 —— 当各语言文件不共享基础名称(字符串形式无法推断)时:

    import { localizedPage } from '@bndynet/vue-site'
    
    page: localizedPage({
      en: () => import('./pages/guide-en.md?raw'),
      zh: () => import('./pages/guide-zh.md?raw'),
    })
  • glob(库模式) —— 与字符串形式相同的自动发现,但显式写出,因此不依赖 CLI:

    page: localizedPage(import.meta.glob(['../README.md', '../README.*.md'], { query: '?raw' }))

    page: '../README.md' 正是 CLI 帮你生成的这一段。(Vue 页面去掉 { query: '?raw' } 即可。)

所有 localizedPage 形式在当前语言没有对应文件时都会回退(精确匹配 → 主子标签 → 基础文件 → 第一个)。

工作原理

  • 初始语言:已存储的选择(localStorage)> 浏览器语言(navigator.language,当开启 detectBrowser 时)> defaultLocale > 第一个条目。
  • LocalizedStringstring | Record<LocaleCode, string> | MessageRef。普通字符串会原样返回 (单语言配置照常工作),语言映射保存内联的按语言文本,而 MessageRef(用 tk('id') 构建) 引用中心化消息文件中的某个键 —— 参见中心化消息文件
  • 稳定的 URL:路由路径从默认语言的 label(或显式 path)派生,因此切换语言永远不会改变 URL。
  • 响应式:切换语言会实时更新 label、标题、页脚和页面内容(页面内容通过 localizedPage 重新加载)。 仅当 locales.length > 1 时才会出现切换器。
  • UI 字符串:框架自带内置字符串(目前为 enzh)用于主题/语言切换器和页面错误提示; 可通过 i18n.messages 按语言覆盖或扩展它们。

I18nConfig

| 属性 | 默认值 | 说明 | |----------|---------|-------------| | locales | 自动发现的 locales/*.json | { code, label, icon? }[] —— 支持的语言,按显示顺序排列。可选:省略时从自动加载的文件名派生(使用内置标签)。第一个条目为回退项 | | defaultLocale | locales[0].code | 当没有存储值且检测未命中时使用的初始语言 | | detectBrowser | true | 首次访问时从 navigator.language(s) 检测初始语言 | | storageKey | vue-site-locale | 用于存储所选语言的 localStorage 键 | | messages | 自动从 locales/<code>.json 加载 | Record<LocaleCode, Record<string, string>> —— 额外/覆盖的翻译,会合并到自动加载的文件和内置 UI 字符串之上 |

消息文件与键(tk / t

与其到处内联 { en, zh },不如把所有翻译放在纯 JSON 中 —— 每种语言一个文件 —— 并通过键引用它们。 这是零配置的:CLI 会在你的 site.config.ts 旁自动发现 locales/<code>.json。 你无需编写任何粘合代码(无需 index.ts,无需 messages 字段),甚至不必列出语言。

// locales/en.json —— 嵌套分组(推荐),会被展平为点号分隔的 id
{
  "site": { "title": "My Site" },
  "nav": { "home": "Home", "guide": "Guide" }
}
// locales/zh.json
{
  "site": { "title": "我的站点" },
  "nav": { "home": "首页", "guide": "指南" }
}

文件可以是嵌套的(如上)或扁平的{ "site.title": "My Site" })—— 嵌套分组会被 展平为点号分隔的 id,因此 tk('site.title') / t('site.title') 两种写法都能工作。

// site.config.ts —— 在配置中用 tk() 引用键,在页面中用 t()
import { defineConfig, tk } from '@bndynet/vue-site'

export default defineConfig({
  // `i18n` 可以完全省略:语言列表会从文件名(en、zh、……)派生,
  // 并带有友好的内置标签。仅当需要自定义 label/icon/顺序时才声明它。
  i18n: {
    locales: [
      { code: 'en', label: 'English' },
      { code: 'zh', label: '简体中文', icon: 'languages' },
    ],
    defaultLocale: 'en',
  },
  title: tk('site.title'),
  nav: [
    { label: tk('nav.home'), icon: 'home', page: '../README.md' },
    {
      label: tk('nav.guide'),
      icon: 'book',
      page: './pages/guide.md',
    },
  ],
})

键的解析会依次回退:当前语言的主子标签 → defaultLocaleen → id 本身,并对 {name} 占位符进行插值。tk() 与内联 { en, zh } 映射可以自由混用 —— 对共享/集中管理的文本用 tk(), 对一次性字符串用内联映射。

自动发现的细节与覆盖

  • 约定是 locales/<code>.json(如 locales/en.jsonlocales/zh.json),相对于配置目录解析。 code 即文件名(一个 LocaleCode,如 enzh-TW)。
  • 仍支持显式的 i18n.messages,并会覆盖自动加载的键(按 id);显式的 i18n.locales 控制 label/icon/顺序。两者均为可选。
  • 自动发现是 CLI 特性。如果你自行嵌入该库(不使用 vue-site CLI 而直接调用 createSiteApp), 请直接传入 i18n.messages —— 例如用显式 import 从 JSON 构建(避免在 site.config.ts 中使用 import.meta.glob,CLI 会在 Node 中预加载它)。

在你自己的页面中本地化

useLocalize() 返回 t(id, params?)(从中心目录解析消息 id,并对 {name} 进行插值)、 localize(value)(解析任意 LocalizedString —— tk() 引用、内联映射或普通字符串), 以及响应式的 locale ref。useLocale() 返回 { locale, setLocale, locales }, 用于构建自定义切换器。它们都可在由 createSiteApp 渲染的任意组件中工作。

<script setup lang="ts">
import { useLocalize } from '@bndynet/vue-site'

const { t, localize, locale } = useLocalize()
</script>

<template>
  <!-- 来自中心消息文件的键 -->
  <h1>{{ t('nav.home') }}</h1>
  <!-- 或内联,用于一次性文本 -->
  <p>{{ localize({ en: 'Hello', zh: '你好' }) }} — {{ locale }}</p>
</template>

按页面授权(auth

基于当前用户对单个页面进行门控。为任意 NavItemStandalonePage 添加 auth 规则, 并在 SiteConfig 中提供单一的 auth.authorize 策略来决定访问权限。

import { defineConfig } from '@bndynet/vue-site'

export default defineConfig({
  title: 'My Site',
  auth: {
    loginPath: '/login',
    authorize: ({ rule }) => {
      const user = getCurrentUser() // 你自己的认证状态
      if (!user) return '/login' // 未登录 -> 重定向(字符串)
      if (rule === true) return true // `auth: true` -> 任意已登录用户
      if (typeof rule === 'string') return user.roles.includes(rule)
      if (Array.isArray(rule)) return rule.some((r) => user.roles.includes(r))
      return true
    },
  },
  nav: [
    { label: 'Home', icon: 'home', page: () => import('../README.md?raw') },
    { label: 'Dashboard', icon: 'gauge', auth: true, page: () => import('./pages/Dash.vue') },
    { label: 'Admin', icon: 'shield', auth: ['admin'], page: () => import('./pages/Admin.vue') },
  ],
  pages: [
    { path: '/login', page: () => import('./pages/Login.vue') }, // 无 `auth` -> 始终可访问
  ],
})

工作原理

  • authorize每次导航时运行(一个 Vue Router beforeEach 守卫)。它接收 { rule, item, to, from }, 并返回 true(允许)、false(拒绝)或一个路径 string(重定向,例如到登录页)。
  • 返回 false 时,用户会被发送到 auth.loginPath,并把请求的路径作为 redirect 查询参数 (/login?redirect=/admin);如果未设置 loginPath,则取消该次导航。
  • authorize 也会在启动时运行一次(仅带 rule / item),以从导航菜单中隐藏未授权的条目。 与 visible 一样,此菜单过滤不具响应性 —— 它反映应用创建时的状态,因此需要通过重建应用来更新它 (例如登录后整页刷新)。
  • 受保护的路由仍保持注册,因此直接访问受保护 URL 会触发守卫(及你的登录重定向), 而不是静默地 404。
  • 登录页本身不能携带 auth 规则(且 loginPath 始终被守卫允许),以避免重定向循环。

AuthConfig

| 属性 | 说明 | |----------|-------------| | authorize | (ctx: AuthContext) => boolean \| string \| Promise<boolean \| string>true 允许,false 拒绝,string 重定向。 | | loginPath | 将被拒绝的用户发送到何处(带 ?redirect=)。可选;不设置时,拒绝将取消导航。 |

AuthRuleboolean \| string \| string[] \| ((ctx: AuthContext) => boolean \| Promise<boolean>)。 框架从不检查规则;它会将规则转发给 authorize,因此其含义完全由你决定。

visibleauth 对比

| | visible | auth | |--|-----------|--------| | 决定 | 条目/路由是否存在 | 当前用户是否可以进入 | | 时机 | 构建/启动(一次) | 导航(每次)+ 启动时的菜单过滤 | | 路由是否注册 | 否(无法通过 URL 访问) | 是(受守卫保护;可重定向到登录) | | 对登录/登出的响应 | 否 | 守卫:是;菜单过滤:否 | | 最适合 | 环境 / 功能开关 / 静态裁剪 | 登录状态、角色、登录重定向 |

visible 进行静态存在性裁剪,用 auth 进行基于用户的访问控制。它们可以组合在同一个条目上。

路由历史(router

默认情况下路由使用 hash 历史模式(#/path),它无需任何服务器配置即可在任意静态主机上工作, 且与公共 base 无关。要获得干净的 URL,请切换到 HTML5 历史模式:

export default defineConfig({
  title: 'My Site',
  router: { mode: 'web' }, // /app/admin 而不是 /app/#/admin
  nav: [/* ... */],
})

RouterConfig

| 属性 | 默认值 | 说明 | |----------|---------|-------------| | mode | hash | hash#/path,无需服务器配置)或 web(HTML5 干净 URL) | | base | import.meta.env.BASE_URL | web 模式的 base 路径。CLI 会自动从 --base / env.vite.base 设置它;仅当从自定义入口调用 createSiteApp 时才需要覆盖。 |

注意:

  • web 模式需要 SPA 回退:请配置你的主机为未知路径提供 index.html,否则深链接 / 刷新会 404。 Hash 模式无需任何配置。
  • 子路径部署:使用 CLI 时,--base=/app/ 会在 web 模式下被自动用作历史 base。 在库模式下,请从你自己的入口传入 router: { mode: 'web', base: import.meta.env.BASE_URL }

envSiteEnvConfig

| 属性 | 说明 | |----------|-------------| | port | 开发服务器端口 | | outDir | 构建输出(相对于站点根目录;默认 {folder}-dist) | | customElements | 自定义元素的标签前缀(如 ['chat-', 'i-']) | | watchPackages | 本地包 —— 参见 env.watchPackages | | vite | Vite 覆盖配置(不含 root);框架会合并别名、server.fs.allowbuild.outDir 等 |

env.watchPackages

  • 字符串 —— 仅包名(如工作区软链接 / npm link)。会跳过依赖预打包;文件监听遵循 Vite 默认行为。
  • { name, entryPath } —— name 必须匹配导入说明符(如 @scope/pkg)。entryPath 相对于 你运行 CLI 的目录(包含 site.config.* 的文件夹)。Vite 会将该包解析为你的源码入口以支持开发 HMR, 并将包目录加入 server.fs.allow
  • 如果你将 env.vite.resolve.alias 用作数组{ find, replacement }[]),CLI 仍会正确合并 watchPackages 的别名(仅对象展开会破坏这一点)。
  • 如果某个包列在此处,不要site.config.ts 中再为同一个包添加顶层值导入 —— 配置预加载在 Node 中运行并会解析到 node_modules,而应用使用的是 Vite。请改用 configureApp + 动态 import() (见下一节)。

configureApp 中使用本地包

configureApp 可以是 async 的,因此你可以在路由安装之后 await import('your-package')。 该动态导入在浏览器中、在 Vite 下运行,因此它遵循 watchPackages,且不会在 CLI 配置加载期间运行。

export default defineConfig({
  env: {
    watchPackages: [{ name: '@acme/widgets', entryPath: '../widgets/src/index.ts' }],
  },
  async configureApp(app) {
    const w = await import('@acme/widgets')
    w.register(app)
  },
})

开发服务器文件系统访问

CLI 允许 server.fs 读取站点根目录下、已安装的 vue-site 包目录、站点根目录的父目录 (用于 ../… 导入),以及 —— 当不会扩展到文件系统根目录时 —— 祖父目录(在 monorepo 中常见, 如 ../../packages/...)。如有需要,可通过 env.vite.server.fs.allow 添加更多路径。 带 { entryPath }watchPackages 条目也会为这些包树扩展 fs.allow

库模式

拥有自己的 index.html + Vite 配置:

import { createSiteApp } from '@bndynet/vue-site'
import '@bndynet/vue-site/style.css'
import config from './site.config'

const app = await createSiteApp(config)
app.mount('#app')

在你的入口中使用顶层 await(或一个 async IIFE):createSiteApp 是异步的,当 configureApp 返回 Promise 时会 await 它。如果你在配置中设置了可选的 bootstrap,该模块会在应用创建前加载; 如果省略 bootstrap,则跳过该步骤。

导出:createSiteAppdefineConfiguseThemeuseSiteConfiguseLocaleuseLocalizetkresolveLocalizedresolveFieldresolveMessagemergeCatalogflattenMessagesisMessageReflocalizedPagebuiltinMessagesthemeRefKeylocaleRefKey。类型:SiteConfigSiteEnvConfigSiteViteConfigSiteExternalLinkNavItemStandalonePagePageLayoutAuthRuleAuthContextAuthConfigRouterConfigThemeConfigThemeOptionThemePaletteVarsResolvedNavItemI18nConfigLocaleOptionLocaleCodeLocalizedStringMessageRefMessageTreeMessageCatalogPageLoaderLocalizedPageOptions

在 Vue 页面中使用主题(useTheme

@bndynet/vue-site 导入 useTheme。它返回一个响应式的 theme ref(当前激活的主题 id, 如 lightdark 或某个 extraThemes[].id),外加 setThemetoggleTheme。 根布局还会在 document.documentElement 上设置 data-theme 属性并应用 CSS 变量, 因此你可以用 var(--color-*) 来设置样式,无需 JavaScript。

theme ref 由 createSiteApp() 提供(与你的应用同一个 Vue 运行时),所以 watch(theme, …) 能可靠工作,即使其他工具可能会从嵌套的 node_modules 加载第二份 vue

<script setup lang="ts">
import { watch } from 'vue'
import { useTheme } from '@bndynet/vue-site'

const { theme, setTheme, toggleTheme } = useTheme()

watch(theme, (next, prev) => {
  if (prev !== undefined) console.log('theme:', prev, '→', next)
})
</script>

<template>
  <p>当前主题:{{ theme }}</p>
</template>

watch 仅在你的组件初始化之后主题发生变化时运行(例如用户点击主题控件之后)。 除非你传入 { immediate: true },否则它不会在初始值上运行。

升级

npm update @bndynet/vue-site

开发本仓库

git clone https://github.com/bndynet/vue-site.git && cd vue-site
npm install
npm run dev    # 监听构建 lib + 示例站点
npm run build  # `dist/` + `example/example-dist`

在其他项目中使用本地构建

已发布的入口指向 dist/。在更改 src/ 下的库源码后,需在使用方看到更新之前运行 npm run build:lib(或 build:lib:watch)。对 bin/vue-site.mjs 的更改会在下一次运行 vue-site 时生效,无需重新构建 lib。

cd /path/to/vue-site
npm install && npm run build:lib
npm link

cd /path/to/consumer
npm link @bndynet/vue-site

编辑文件后无需再次运行 npm link;软链接会保持。完成后在使用方运行 npm unlink @bndynet/vue-sitenpm install

许可证

MIT