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

vite-plugin-custom-cursor

v0.0.3

Published

A Vite plugin that replaces CSS cursor declarations with custom url-based cursors via source code transformation.通过源代码替换,将CSS鼠标样式替换为自定义鼠标。

Downloads

299

Readme

vite-plugin-custom-cursor

一个通过源码替换方式实现自定义鼠标指针(cursor)的 Vite 插件。

A Vite plugin that implements custom mouse cursors via source-code replacement.

📦 安装 & Install

npm install vite-plugin-custom-cursor --save-dev
# 或 / or
pnpm add vite-plugin-custom-cursor -D
# 或 / or
yarn add vite-plugin-custom-cursor -D

🦄 快速开始 & Quick Start

vite.config.ts 中引入并配置:

Import and configure it in vite.config.ts:

import { defineConfig } from 'vite';
import customCursor from 'vite-plugin-custom-cursor';

export default defineConfig({
  plugins: [
    customCursor({
      cursors: {
        default: 'url(@/assets/cursors/default.cur), default',
        pointer: 'url(@/assets/cursors/pointer.cur), pointer',
        text: 'url(@/assets/cursors/text.cur), text',
      },
      inject: {
        body: 'url(@/assets/cursors/default.cur), default',
        'a, button': 'url(@/assets/cursors/pointer.cur), pointer !important',
      },
      replaceImportant: true,
    }),
  ],
});

上面例子里的 @/assets/... 路径别名会自动从 Vite 配置的 resolve.alias 中读取并解析。

The @/assets/... path alias in the example above is automatically resolved from Vite's resolve.alias configuration.

🎃 功能特性 & Features

  • 多状态替换:给 defaultpointertextcrosshairmove 等任意 cursor 关键字配置替换值。

  • Multi-state replacement — configure replacement values for any cursor keyword (default, pointer, text, crosshair, move, etc.).

  • 预处理器支持:默认处理 .css.less.scss.sass.styl.pcss 文件,全量替换 cursor 声明并支持注入规则。

  • Preprocessor support.css, .less, .scss, .sass, .styl, and .pcss files are processed by default with full declaration replacement and inject rules.

  • URL 回退:替换值支持 CSS 原生的逗号分隔回退链,比如 url(a.cur), url(b.svg), default,浏览器会依次尝试。

  • URL fallback — replacement values support the native CSS comma-separated fallback chain, e.g. url(a.cur), url(b.svg), default; the browser tries them in order.

  • 选择器注入:直接给指定 CSS 选择器(如 body.btna, button)注入光标声明。

  • Selector injection — directly inject cursor declarations onto specific CSS selectors (body, .btn, a, button, etc.).

  • !important 支持:可配置是否替换带 !important 的 cursor 声明,替换后自动保留 !important

  • !important support — optionally replace declarations that carry !important, preserving the flag on the new value.

  • 路径别名url(...) 里可以写路径别名(如 @/assets/cursor.cur),手动配置或自动继承 Vite 的 resolve.alias 都行。支持嵌套别名(@assets@/assets/src/assets)。别名替换后,项目根目录下的绝对路径会自动剥离成根相对路径(如 /src/assets/cursor.cur),不会把系统绝对路径暴露到 CSS 里。

  • Path aliases — use path aliases inside url(...) (e.g. @/assets/cursor.cur); configure them manually or inherit automatically from Vite's resolve.alias. Nested aliases are supported (@assets@/assets/src/assets). After alias resolution, absolute filesystem paths under the project root are stripped to root-relative URLs (e.g. /src/assets/cursor.cur), so system paths never leak into the CSS.

  • 内联 style 支持:自动替换 HTML / Vue / Svelte 文件中 style="cursor: pointer;" 内联属性里的 cursor 声明,用 inlineStyle 选项可以开关。

  • Inline style support — automatically replaces cursor declarations inside inline style="cursor: pointer;" attributes in HTML / Vue / Svelte files; toggle via inlineStyle option.

  • Vue / Svelte SFC 样式块<style> 块编译产生的虚拟 CSS 模块也会被全量处理。

  • Vue / Svelte SFC style blocks — virtual CSS modules emitted from <style> blocks are also fully processed.

  • 注释安全:CSS 注释(/* ... */)里的 cursor 声明不会被误替换。

  • Comment-safe — cursor declarations inside CSS comments (/* ... */) are not replaced.

  • 自定义属性安全--cursor 这类 CSS 自定义属性不会被误匹配。

  • Custom-property-safe — CSS custom properties like --cursor are not falsely matched.

  • 零运行时开销:纯构建期源码替换,不注入任何运行时代码。

  • Zero runtime cost — pure build-time source replacement, no runtime code is injected.

🎨 工作原理 & How It Works

插件在 Vite 的 transform 钩子(enforce: 'post')中处理源码:

The plugin processes source in Vite's transform hook (enforce: 'post'):

对于 CSS 及预处理器文件(.css.less.scss.sass.styl.pcss,以及 Vue / Svelte 编译出的虚拟 CSS 模块):

For CSS and preprocessor files (.css, .less, .scss, .sass, .styl, .pcss, and virtual CSS modules from Vue / Svelte):

  1. 先保护 CSS 注释,避免注释里的 cursor 被误替换。

  2. CSS comments are protected first so cursors inside comments are not replaced.

  3. 用正则匹配每一条 cursor: <value> [!important]; 声明(--cursor 等自定义属性不会被匹配)。

  4. A regular expression matches every cursor: <value> [!important]; declaration (--cursor and other custom properties are not matched`).

  5. 把值修剪并转小写后,在 cursors 映射表里查找。

  6. The value is trimmed and lowercased, then looked up in the cursors map.

  7. 命中就替换成配置值;如果原声明带 !importantreplaceImportanttrue,新值后面会保留 !important

  8. On a hit it is replaced with the configured value; if the original declaration carried !important and replaceImportant is true, the !important flag is preserved on the new value.

  9. 对替换值和注入值中的路径别名做解析(支持嵌套别名,多轮替换直到稳定)。如果别名解析后产生了项目根目录下的绝对文件路径,会自动把根目录前缀剥掉,变成根相对 URL(比如 /home/me/proj/src/a.cur/src/a.cur)。

  10. Path aliases inside replacement and injected values are resolved (nested aliases are supported, with multiple passes until stable). If alias resolution produces an absolute filesystem path under the project root, the root prefix is stripped to produce a root-relative URL (e.g. /home/me/proj/src/a.cur/src/a.cur).

  11. inject 中配置的选择器规则追加到文件末尾。

  12. Rules from inject are appended to the end of the file.

对于 HTML / Vue / Svelte 等模板源文件(当 inlineStyletrue 时):

For template source files (HTML / Vue / Svelte, when inlineStyle is true):

  1. 只匹配内联 style="..." 属性,<style> 块和 <script> 代码都不动(交给 Vite 自己的管道处理,<style> 块会以虚拟 CSS 模块的形式被单独处理)。

  2. Only inline style="..." attributes are matched; <style> blocks and <script> code are left to Vite's own pipeline (<style> blocks are processed separately as virtual CSS modules).

  3. 在每个 style 属性值内部执行和 CSS 文件一样的 cursor 替换逻辑。

  4. The same cursor replacement logic is applied inside each style attribute value.

  5. inject 规则不会追加到模板文件中。

  6. inject rules are never appended to template files.

替换示例 / Replacement Example

输入 CSS / Input CSS:

.btn {
  cursor: pointer;
}
.title {
  cursor: default !important;
}
/* cursor: wait;  ← 注释里的不会被替换 / inside a comment, not replaced */
:root {
  --cursor: pointer; /* ← 自定义属性不会被替换 / custom property, not matched */
}

配置 / Configuration:

customCursor({
  cursors: {
    pointer: 'url(@/assets/cursors/pointer.cur), pointer',
    default: 'url(@/assets/cursors/default.cur), default',
  },
  alias: { '@': '/src' },
  replaceImportant: true,
})

输出 CSS / Output CSS:

.btn {
  cursor: url(/src/assets/cursors/pointer.cur), pointer;
}
.title {
  cursor: url(/src/assets/cursors/default.cur), default !important;
}
/* cursor: wait;  ← 注释里的不会被替换 / inside a comment, not replaced */
:root {
  --cursor: pointer; /* ← 自定义属性不会被替换 / custom property, not matched */
}

内联 style 替换示例 / Inline Style Example

输入 HTML / Input HTML:

<a href="#" style="cursor: pointer; color: blue;">click me</a>
<div style="cursor: default;">content</div>

输出 HTML / Output HTML:

<a href="#" style="cursor: url(/src/assets/cursors/pointer.cur), pointer; color: blue;">click me</a>
<div style="cursor: url(/src/assets/cursors/default.cur), default;">content</div>

内联 style 里的其他 CSS 声明(比如 color: blue)保持不变,只有 cursor 被替换。

Other CSS declarations inside the inline style (such as color: blue) are left untouched — only cursor is replaced.

🎖 参数列表 & Options

CustomCursorOptions

| 参数 / Option | 类型 / Type | 必填 / Required | 默认值 / Default | 说明 / Description | | --- | --- | --- | --- | --- | | cursors | Record<string, string> | 否 / No | {} | cursor 替换映射表。key 是原始 cursor 关键字(如 defaultpointer),匹配时忽略大小写和首尾空格;value 是完整的替换值,支持逗号分隔的回退链,url(...) 中可以用路径别名。 | Cursor replacement map. The key is the original cursor keyword (e.g. default, pointer), matched case-insensitively after trimming. The value is the full replacement value and may contain a comma-separated fallback chain; path aliases are supported inside url(...). | | inject | Record<string, string> | 否 / No | {} | 给指定选择器注入 cursor 声明。key 是 CSS 选择器(如 body.btna, button),value 是 cursor 值,可以包含 !important 和路径别名。规则会追加到每个被处理的 CSS 文件末尾。 | Inject a cursor declaration onto specific selectors. The key is a CSS selector (e.g. body, .btn, a, button); the value is the cursor value, which may include !important and path aliases. Rules are appended to every transformed CSS file. | | replaceImportant | boolean | 否 / No | true | 是否替换带 !important 的 cursor 声明。为 true 时替换并保留 !important;为 false 时跳过这类声明。 | Whether to replace cursor declarations that carry !important. When true, the declaration is replaced and !important is preserved; when false, such declarations are left untouched. | | inlineStyle | boolean | 否 / No | true | 是否替换模板文件中内联 style="..." 属性里的 cursor 声明(如 <div style="cursor: pointer;">)。为 true 时默认处理 .html.vue.svelte 文件;inject 规则不会追加到模板文件。设为 false 可禁用。 | Whether to replace cursor declarations inside inline style="..." attributes in template files (e.g. <div style="cursor: pointer;">). When true, .html, .vue, and .svelte files are processed by default; inject rules are not appended to template files. Set to false to disable. | | alias | Record<string, string> \| Array<{ find: string \| RegExp; replacement: string }> | 否 / No | 自动继承 Vite resolve.alias | 路径别名配置,用来解析 url(...) 中的别名路径。支持对象形式 { '@': '/src' } 和数组形式 [{ find: '@', replacement: '/src' }]。支持嵌套别名(多轮解析)。不传时自动读取 Vite 配置中的 resolve.alias;传空对象 {} 可禁用别名解析。别名替换后,项目根目录下的绝对路径会被剥离成根相对 URL。 | Path alias configuration for resolving aliases inside url(...). Supports object form { '@': '/src' } and array form [{ find: '@', replacement: '/src' }]. Nested aliases are supported (multi-pass resolution). When omitted, automatically reads resolve.alias from the Vite config; pass an empty object {} to disable. After alias resolution, absolute paths under the project root are stripped to root-relative URLs. | | include | FilterPatternRegExp \| RegExp[] \| string \| string[]) | 否 / No | /\.(css\|less\|scss\|sass\|styl\|pcss\|html?\|vue\|svelte)$/ | 需要处理的文件匹配模式。 | File pattern(s) to process. | | exclude | FilterPatternRegExp \| RegExp[] \| string \| string[]) | 否 / No | undefined | 需要排除的文件匹配模式。 | File pattern(s) to exclude. |

🍥 URL 回退说明 & URL Fallback

CSS cursor 属性本身就支持逗号分隔的回退列表,浏览器会从左到右依次尝试加载,全部失败时用最后一个关键字兜底。

The CSS cursor property natively supports a comma-separated fallback list. The browser tries each URL from left to right and falls back to the final keyword if all fail.

customCursor({
  cursors: {
    default:
      'url(@/assets/cursors/default.cur), url(@/assets/cursors/default.png), url(@/assets/cursors/default.svg), default',
  },
})

上面这个例子里浏览器会依次尝试 .cur.png.svg,都失败时回退到系统默认光标。

In the example above the browser tries .cur.png.svg in order, and falls back to the system default cursor if all fail.

🍀 路径别名详解 & Path Aliases in Detail

自动继承 Vite 配置 / Automatic Inheritance from Vite Config

如果你的 vite.config.ts 里已经配了 resolve.alias,插件会自动继承,不用重复写:

If you already have resolve.alias configured in vite.config.ts, the plugin inherits it automatically — no duplicate configuration needed:

// vite.config.ts
import { defineConfig } from 'vite';
import customCursor from 'vite-plugin-custom-cursor';

export default defineConfig({
  resolve: {
    alias: {
      '@': '/src',
      '@assets': '/src/assets',
    },
  },
  plugins: [
    customCursor({
      cursors: {
        // @/assets 会被自动解析为 /src/assets / @/assets is automatically resolved to /src/assets
        default: 'url(@/assets/cursors/default.cur), default',
      },
    }),
  ],
});

手动配置 / Manual Configuration

也可以在插件选项里单独指定别名,这会合并 Vite 的自动继承:

You can also specify aliases directly on the plugin, which merges with Vite's automatic inheritance:

customCursor({
  alias: {
    '@': '/src',
    '@assets': '/src/assets',
  },
  cursors: {
    default: 'url(@assets/cursors/default.cur), default',
  },
})

数组形式(支持正则)/ Array form (supports RegExp):

customCursor({
  alias: [
    { find: /^~/, replacement: '/src' },
    { find: '@', replacement: '/src' },
  ],
})

嵌套别名 / Nested Aliases

如果一个别名的替换值里又包含了另一个别名,插件会多轮解析直到稳定,最多 10 轮(防止循环引用):

If one alias's replacement contains another alias, the plugin resolves it in multiple passes until stable (up to 10 passes, to prevent circular references):

customCursor({
  alias: {
    '@root': '/home/me/proj',
    '@src': '@root/src',
    '@assets': '@src/assets',
  },
  cursors: {
    // @assets → @src/assets → @root/src/assets → /home/me/proj/src/assets → /src/assets
    default: 'url(@assets/cursors/default.cur), default',
  },
})

别名替换后的路径处理 / Path Handling After Alias Resolution

别名替换完成后,如果结果是一个落在项目根目录下的绝对文件路径(比如 Vite 里常用 path.resolve(__dirname, 'src') 配别名,替换后会得到 /home/me/proj/src/assets/cur.cur),插件会自动把项目根目录前缀剥掉,变成根相对 URL /src/assets/cur.cur

After alias resolution, if the result is an absolute filesystem path under the project root (common when Vite aliases are configured with path.resolve(__dirname, 'src'), producing something like /home/me/proj/src/assets/cur.cur), the plugin automatically strips the project root prefix to produce a root-relative URL /src/assets/cur.cur.

已经是相对路径、根相对 URL,或者 data:https: 这类非文件系统 URL,都不会被改动。插件不会把路径转成相对于当前 CSS 文件的形式——这样输出的路径在任何文件里都是一致的,不会因为 CSS 文件所在目录不同而变化。

Paths that are already relative, root-relative web URLs, or non-filesystem URLs (data:, https:, etc.) are left untouched. The plugin does not convert paths to be relative to the current CSS file — this keeps output paths consistent regardless of where the CSS file lives.

禁用别名解析 / Disabling Alias Resolution

传空对象就行:

Pass an empty object to disable:

customCursor({
  alias: {},
})

🍁 预处理器支持 & Preprocessor Support

Less、Sass(SCSS / indented)、Stylus、PostCSS 文件默认包含在处理范围内,和普通 CSS 文件一样进行全量 cursor 替换和规则注入。

Less, Sass (SCSS / indented), Stylus, and PostCSS files are included by default and receive the same full cursor replacement and inject treatment as regular CSS.

由于插件运行在 enforce: 'post',此时预处理器已经把源码编译成了 CSS,所以不需要额外配置。如果你的预处理器输出有特殊情况,可以通过 include / exclude 调整匹配范围。

Because the plugin runs at enforce: 'post', preprocessors have already compiled the source to CSS by the time it reaches this plugin, so no extra configuration is needed. If your preprocessor output has special requirements, you can adjust the match range via include / exclude.

⚠ 注意事项 & Notes

  1. 光标文件放哪:建议把光标文件放在 public/ 目录下,用 / 开头的根相对路径引用,构建后路径不容易出错。如果用别名,确保别名指向的最终路径在构建后能被正确访问。 Cursor file paths — place cursor files under public/ and reference them with a root-relative path starting with / to avoid broken paths after the build. When using aliases, ensure the resolved final path is accessible after the build.

  2. 大小写不敏感cursors 的 key 匹配时会统一转小写,所以 cursor: POINTER; 也能命中 pointer 的配置。 Case-insensitive matching — keys in cursors are lowercased before matching, so cursor: POINTER; also matches the pointer configuration.

  3. 文件处理范围:默认处理 .css.less.scss.sass.styl.pcss.html.htm.vue.svelte 文件。CSS 及预处理器文件会全量替换 cursor 声明;模板文件只替换内联 style="..." 属性中的 cursor,<style> 块由 Vite 编译为虚拟 CSS 模块后单独处理。设 inlineStyle: false 可以禁用模板文件的内联 style 处理。 File scope — by default .css, .less, .scss, .sass, .styl, .pcss, .html, .htm, .vue, and .svelte files are processed. CSS and preprocessor files undergo full declaration replacement; template files only have inline style="..." attributes processed, while <style> blocks are compiled by Vite into virtual CSS modules and processed separately. Set inlineStyle: false to disable inline style processing in template files.

  4. !important 保留:替换后会原样保留原声明的 !important;如果希望注入的规则也带 !important,直接在 inject 的 value 里写就行。 !important preservation — the original !important is preserved after replacement. If you want injected rules to carry !important, write it directly in the inject value.

  5. 别名匹配边界:字符串别名要求别名后面紧跟 / 或字符串结束,所以 @/assets 会被解析而 @@x 不会,避免误匹配。 Alias matching boundary — string aliases require the alias to be followed by / or end-of-string, so @/assets resolves but @@x does not, preventing partial matches.

  6. 注释与自定义属性:CSS 注释里的 cursor 声明不会被替换;--cursor 等自定义属性也不会被误匹配。 Comments and custom properties — cursor declarations inside CSS comments are not replaced; custom properties like --cursor are not falsely matched.

  7. 源码替换:本插件通过正则做文本替换,不解析 CSS AST。遇到特别复杂或不规范的 CSS 写法,建议构建后检查一下输出结果。 Source replacement — this plugin performs text-based replacement via regular expressions and does not parse a CSS AST. For extremely complex or unusual CSS, inspect the build output afterwards.

🍬 作者 & Author

eogic

🎀 许可证 & License

MIT licenses


如果您喜欢这个插件,请给 vite-plugin-define-dts 一个星标,谢谢!

If you like this plugin, please give vite-plugin-define-dts a star, thanks!