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

vuepress-theme-wings

v0.5.6

Published

Wings — 新一代 VuePress 博客 / 文档主题,轻量、快速、可组合

Readme

Wings

为 VuePress 2 而生的新一代博客 / 文档主题

轻量 · 快速 · 可组合


特性

架构:完全独立实现

不 extends 默认主题,Layout / Navbar / Sidebar / TOC / 首页 / 404 全部自带。 依赖面收敛到 vuepress + @vuepress/bundler-vite + sass:

  • 不受默认主题的 sass-palette 弃用告警与 rc 版本 API 漂移影响;
  • 样式来源唯一(--wings-* 变量),不会与默认主题的变量打架;
  • 想改一处布局,不必先弄清父主题的实现细节。

博客

  • 文章列表(分页)、标签总览(点击筛选)、年月归档(折叠);
  • 数据在构建期从 /posts/ 的 frontmatter 汇总,客户端只做筛选与渲染;
  • frontmatter 支持 date / tags / categories / description / sticky / cover;
  • 摘要自动生成,字数与阅读时长中英文分别计数。

文档站

  • 多级侧边栏,默认展开层级可配(sidebarDepth),层级指示线可关;
  • 右侧目录滚动高亮,点击平滑定位;
  • 本地搜索,⌘/Ctrl + K 唤起,索引延迟加载;
  • 上一页 / 下一页按侧边栏顺序推导,调整目录时自动跟随。

首页

Hero(标题、副标题、图片、深浅色双图)+ actions + features + 最新文章 + 友链 + 页脚, 全部 frontmatter 驱动,主题配置只作为共享默认值。

组件库

13 个全局组件,markdown 里直接写标签即可,不必 import:

WCard WSteps / WStep WTimeline / WTimelineItem WFileTree / WFileTreeItem WBadge / WBadgeList WIcon WLinkCard WBanner WQuote FriendLinks

成对标签内部的 markdown 会被正常解析 —— 主题改写了 markdown-it 的 HTML 块规则, 把 W 前缀的标签当容器处理,因此卡片里能写加粗、列表与代码块。

集成

  • 评论 —— Giscus(GitHub Discussions)与 Waline,运行时按需加载;
  • 订阅 —— RSS 2.0 / Atom / JSON Feed,默认只收录文章;
  • sitemap —— 生成 sitemap.xml 并追加进 robots.txt;
  • 分享卡片 —— Open Graph / Twitter Card / canonical / JSON-LD,默认开启;
  • Algolia DocSearch —— 与本地搜索可同站共存,按语言分组配置凭据;
  • 多语言站点模板 —— 一条配置补齐缺失语言的页面骨架与语言切换器。

其它

  • 零运行时依赖 —— 主题自身不引入第三方运行时库,样式变量化,客户端按需收集。
  • 开箱可用 —— 只填导航与侧边栏即可上线;其余能力有经过取舍的默认值。
  • 按语言 / 按路径组合 —— 导航栏与侧边栏支持多语言与路径前缀匹配,一套配置服务多个栏目。
  • 本地搜索 —— 内置基于 @vuepress/plugin-search 的搜索面板,索引延迟加载,⌘/Ctrl + K 唤起。 索引在构建期生成,只收录给人读的正文:代码块整体排除, 行内代码只去掉反引号而保留标识符(siteName 这类配置名是可搜的), 表格按行切分,组件属性只保留取值。想调整收录范围改 src/shared/search/extract.ts 即可,它由单元测试覆盖。
  • 深浅色模式 —— 跟随系统,支持手动切换并记忆选择;改动只落在一个 html.dark 作用域。
  • 可换肤 —— 所有视觉细节收敛为 CSS 变量,改一条 --wings-accent 即可换主题色。
  • 无障碍 —— 语义化标签、键盘可达、prefers-reduced-motion 与 prefers-color-scheme 均被尊重。

安装

脚手架(推荐)

npm create wings-theme@latest

npm create wings-theme 会在 npm 上找 create-wings-theme 这个包(一个薄壳, 与主题同版本发布),它转发到主题包里的脚手架 —— 只有一份实现、一套测试。

不想装薄壳也可以直接用主题包自带的入口,两条命令等价:

npx vuepress-theme-wings create-wings-theme

依次回答站点标题、描述、语言、域名,得到的是一个空站点 —— docs/posts/ 下只有列表页,没有任何文章,装完不会多出别人的内容:

├─ package.json
├─ README.md
└─ docs/
   ├─ .vuepress/config.ts   # 标题 / 描述 / 语言 / 导航已按回答填好
   ├─ index.md              # 空首页
   └─ posts/                # 只有列表页:posts.md / tags.md / archive.md

不提问的写法:

npm create wings-theme@latest -- -y \
  --title "我的站点" --description "用 Wings 搭建" --lang zh-CN \
  --hostname https://example.com

npm create wings-theme@latest -- --no-blog   # 纯文档站,不要博客
npm create wings-theme@latest -- --waline    # 顺带打开 Waline 评论

生成器也可以当库用(resolveScaffoldOptions / scaffoldFiles / writeScaffold):

import { resolveScaffoldOptions, scaffoldFiles, writeScaffold } from 'vuepress-theme-wings/scaffold'

const options = resolveScaffoldOptions({ title: '我的站点', lang: 'zh-CN' })
await writeScaffold('/path/to/site', scaffoldFiles(options))

手动安装

npm i -D [email protected] vuepress-theme-wings @vuepress/[email protected]

主题的样式是 SCSS,sass-embedded 写在主题的 dependencies 里,装主题时会一并装上, 不必自己加。

使用

import { viteBundler } from '@vuepress/bundler-vite'
import { defineUserConfig } from 'vuepress'
import { wingsTheme } from 'vuepress-theme-wings'

export default defineUserConfig({
  lang: 'zh-CN',
  title: '我的站点',
  description: '用 Wings 搭建',
  bundler: viteBundler(),
  theme: wingsTheme({
    siteName: '我的站点',
    search: 'search',
    navbar: [
      { text: '首页', link: '/' },
      { text: '指南', link: '/guide/getting-started.html' },
    ],
    sidebar: {
      '/guide/': [
        {
          text: '指南',
          children: [
            { text: '快速开始', link: '/guide/getting-started.html' },
            { text: '主题配置', link: '/guide/configuration.html' },
          ],
        },
      ],
    },
  }),
})
vuepress dev docs     # 本地开发
vuepress build docs   # 产出静态文件到 docs/.vuepress/dist

配置速览

| 配置 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | siteName | string | 站点 title | 导航栏与页脚名称 | | hostname | string | — | 站点域名。feed / sitemap / seo 省略各自的 hostname 时回落到它;带子路径时同步到 vuepress 的 base | | logo / logoDark | string | — | Logo,深色模式可单独指定;建议用透明底 PNG | | heroImage / heroImageDark | string | — | 首页 Hero 配图,深色模式可单独指定 | | accentColor | string | #0a84ff | 主题色 | | colorMode | 'auto' \| 'light' \| 'dark' | 'auto' | 深浅色模式 | | navbar | 数组 / 对象 / false | — | 导航项,可按语言分组 | | sidebar | 数组 / 对象 / false | — | 侧边栏,可按路径前缀匹配 | | toc / tocDepth | boolean / number | true / 2 | 右侧目录与深度 | | prevNext | boolean | true | 上下篇导航 | | search | 'search' \| 'docsearch' \| false | false | 搜索 | | externalLinkIcon | boolean | true | 站外链接图标提示 | | editLink | 对象 / false | — | 编辑此页 | | lastUpdated | 对象 / boolean | true | 上次更新时间(需 @vuepress/plugin-git) | | contributors | 对象 / boolean | true | 贡献者(需 @vuepress/plugin-git) | | footer | 对象 | — | 页脚 | | comment | 对象 / false | — | 评论(Giscus / Waline) | | feed | 对象 / false | — | RSS / Atom / JSON Feed | | sitemap | 对象 / false | — | sitemap.xml | | seo | 对象 / false | 开启 | 分享卡片、canonical 与结构化数据 | | searchLocales | 对象 | — | DocSearch 凭据,按语言分组 | | i18n | 对象 | — | 多语言站点模板 | | blog | 对象 / false | — | 博客能力,见下 | | friendLinks | 数组 | — | FriendLinks 组件默认读取的友链 | | components | boolean | true | 是否在 markdown 中注册全局组件 | | sidebarCollapsible | boolean | false | 侧边栏默认是否可折叠 | | sidebarDepth | number | 1 | 多级侧边栏默认展开到第几级 | | sidebarGuideLine | boolean | true | 是否展示目录层级指示线 | | faviconProxy | string | — | WLinkCard 取站点图标的代理地址 |

博客配置:

| 配置 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | blog.postsDir | string | /posts/ | 文章目录,决定从哪些页面收集 | | blog.pageSize | number | 10 | 列表页每页篇数,0 表示不分页 | | blog.limit | number | 5 | 首页最新文章数量,0 表示不展示 | | blog.excerptLength | number | 160 | 摘要截断长度 | | blog.wordsPerMinute | number | 300 | 估算阅读时长用 | | blog.sortBy | 'date' \| 'title' | 'date' | 排序字段 | | blog.nav | 数组 | 内置三栏 | 博客栏目导航 |

集成能力的配置示例:

wingsTheme({
  // 评论:四个 ID 在 giscus.app 生成
  comment: {
    provider: 'giscus',
    repo: 'owner/repo',
    repoId: 'R_kgDOxxxxxxx',
    category: 'Announcements',
    categoryId: 'DIC_kwDOxxxxxxxx',
  },
  // 订阅、sitemap 与分享卡片都需要绝对域名,写一处即可复用
  feed: { hostname: 'https://example.com', atom: true },
  sitemap: { hostname: 'https://example.com' },
  seo: { hostname: 'https://example.com', author: 'cosc' },
  // 多语言站点模板:补齐缺失语言的页面
  i18n: { enabled: true, locales: ['en', 'zh'] },
})

配置不齐时对应的能力安静地不渲染,不会中断构建 —— 可以先写好配置,再去申请各类 ID。

完整类型见 src/shared/options/types.ts, 使用说明见 docs/,集成能力见 评论、订阅与多语言。

关于 git 信息

「上次更新」与「贡献者」由主题内置的 @vuepress/plugin-git 提供,无需额外配置。 两项都通过 lastUpdated / contributors 开关控制,设为 false 时主题不会遍历 git 历史。

::: tip 部署时的 git 历史 这两个字段依赖完整的提交记录。CI 里通常只做浅克隆,记得把 fetch-depth 设为 0, 否则所有页面会显示同一个时间。 :::

项目结构

src/
├─ node/                     # Node 端:主题入口、插件装配、构建期数据处理
│  ├─ index.ts
│  ├─ resolveOptions.ts
│  ├─ resolvePlugins.ts
│  ├─ i18n.ts                # 多语言站点模板与语言对应关系
│  ├─ searchIndexPlugin.ts   # 生成客户端搜索用的 searchindex.json
│  ├─ blog/postPlugin.ts     # 从 /posts/ 汇总博客数据
│  └─ markdown/
│     ├─ sidebarPlugin.ts    # 侧边栏匹配 + 上下篇推导
│     ├─ containerPlugin.ts  # 提示 / 折叠容器
│     ├─ componentPlugin.ts  # 组件标签的 markdown 支持
│     └─ componentRule.ts    # 让 W 前缀标签成为 markdown 容器
├─ client/                   # 浏览器端:布局、组件、组合式函数、样式
│  ├─ config.ts
│  ├─ layouts/               # Layout / Home / NotFound / BlogPosts / BlogTags / BlogArchive
│  ├─ components/            # 13 个全局组件 + 内部组件
│  ├─ composables/
│  └─ styles/
├─ shared/                   # 两端共享:类型、语言包、博客 / 搜索数据层(纯函数)
│  ├─ options/               # 类型、默认值、语言包、文案解析
│  ├─ blog/                  # collect / query / types
│  └─ search/                # 索引结构 + 检索算法 + 正文提取
└─ templates/                # Html 模板
docs/                        # 主题自带的示例 / 文档站点
test/                        # 纯逻辑与架构约束测试
scripts/build.js             # 构建脚本

分层约定

三层各有明确的边界,破坏其中任何一条都不会让构建失败, 但会让站点在浏览器里静默失效:

  • shared/ 是两端共用的纯函数,不得 import Node 内置模块或 vuepress/core;
  • client/ 不得触达 node/** —— 否则 vuepress/core 会被打进浏览器包, createRequire 在浏览器里不存在,应用会在水合前崩掉;
  • 需要两端共用的逻辑,放 shared/,各自只保留「必须跑在自己那边」的那一小步 (如搜索:算法在 shared/search/,fetch 在 client/)。

这三条由 test/architecture.test.mjs 自动校验,改代码时不必靠记忆维持。

博客布局

/posts/ 下三个列表页只写 frontmatter:

---
layout: BlogPosts    # 或 BlogTags / BlogArchive
title: 文章
article: false       # 必需:否则列表页自己也会被当成一篇文章
---

开发

pnpm install
pnpm run build        # 构建主题到 lib/
pnpm run typecheck    # 类型检查
pnpm run test         # 单元测试
pnpm run docs:build   # 构建文档站点

test、docs:build 与 dev 都挂了前置钩子,会先自动构建主题产物(lib/), 新 clone 的仓库直接跑即可,不必手动先 build。

发布

打与 package.json 版本号同名的 Tag 即触发发布流水线:

node -p "require('./package.json').version"   # 先确认版本号
git tag 0.2.3
git push origin 0.2.3

流水线会依次校验「Tag 名 = 版本号」「CHANGELOG 有对应条目」,再构建、打包, 并检查 package/lib/node/index.js 与 index.d.ts 确实进了 tarball。

是否发布到 npm 取决于仓库密钥里有没有 NPM_TOKEN:

  • 有 → 自动 npm publish --access public(版本已存在时静默跳过,可安全重跑旧 Tag);
  • 没有 → 打包照常完成、流水线保持绿色,只是不上传, tarball 可在构建日志的制品中下载。

这样 fork 或内部仓库在没接凭据时,不会被一条卡在认证上的流水线拖红。

怎么把包发到 npm

发布分两半,一半在仓库里已经做好了,一半需要你去 npm 拿一次凭据。

1. 拿一个 npm token。 登录 npmjs.com → 头像 → Access Tokens → Generate New Token, 选 Automation 类型(它绕过 OTP 交互,否则 CI 会卡在验证码上)。

2. 存进仓库的密钥。 CNB 仓库 → 设置 → 密钥,新增一个名为 NPM_TOKEN 的密钥, 值就是上一步复制的 token。名字必须一字不差 —— 流水线只认这个变量。

3. 打 tag 触发发布。

# 先把 package.json 的 version 与 CHANGELOG.md 的条目改好
git tag 0.5.2 && git push origin 0.5.2

流水线会依次校验「Tag 名 = package.json 版本」「CHANGELOG 有该版本条目」, 再构建、打包,然后:

  • 有 NPM_TOKEN → npm publish --access public;
  • 没有 → 只打包,流水线照样绿色,tarball 在构建日志的制品里下载。

4. 想补发某个已打过的 tag。 配好密钥后在 CNB 上重跑那条 tag 流水线即可, 版本已存在于 npm 时会静默跳过,不会报错。

发布包里有什么:lib/(构建产物,含 lib/bin/create-wings-theme.js)、 docs/guide/ 与 docs/index.md(README 里指向的那些文档)。 docs/posts/ 与 docs/advanced/ 是主题自己的示例站点,不进包 —— 使用者装完不该在自己目录里看到别人的文章。

Stage 脚本由 sh(dash)执行,不是 bash。写 set -euo pipefail 会因为 pipefail 不是 POSIX 选项而在第一行就报 Illegal option 退出 —— 本地用 bash 测不出来,只有真正跑一次流水线才会暴露。本仓库统一只用 set -eu。

许可

MIT