@animus-ui/vite-plugin

中文文档
0.1.87发布于 2026/10/24MITTypeScriptCommonJS构建来源已验证近 30 天下载 6839

Animus static CSS extraction Vite plugin

原始使用文档
文档目录11 节
  1. Install
  2. Setup
  3. What It Does
  4. Appearance bootstrap
  5. Content-Security-Policy
  6. Font preloads
  7. Split stylesheets
  8. Third-party stylesheets
  9. MDX
  10. Important
  11. License

@animus-ui/vite-plugin

Static CSS extraction plugin for Vite. Transforms @animus-ui/system builder chains into static CSS at build time — zero runtime style injection.

Install

npm install @animus-ui/vite-plugin @animus-ui/system

Setup

// vite.config.ts
import { animusExtract } from '@animus-ui/vite-plugin';
import react from '@vitejs/plugin-react';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [react(), animusExtract({ system: './src/ds.ts' })],
});

The system option points to the file that exports your built system instance. The plugin loads this file in a subprocess to serialize the prop config and theme data for the Rust extraction pipeline.

TypeScript apps get the plugin's virtual modules (virtual:animus/styles.css, virtual:animus/font-preloads) typed with /// <reference types="@animus-ui/vite-plugin/client" /> or "types": ["@animus-ui/vite-plugin/client"] in tsconfig.json.

What It Does

  1. Build time: Analyzes all files importing from @animus-ui/system, extracts static styles into CSS with @layer ordering
  2. Dev server: Runs extraction on startup, holds results in memory, serves CSS via virtual module
  3. Transforms: Resolves __TRANSFORM__ placeholders using your system's named transform functions
  4. Global styles: Emits global styles (reset, base) from your exported createGlobalStyles() configuration

Appearance bootstrap

Optional. Restores a persisted color mode before first paint, so a dark-persisted page never flashes a light frame — with zero runtime and a real CSP story. The restoration is a generated, dependency-free inline snippet (not a component, not a listener), and its script-src hash is computed at build time from the exact bytes generated. Runtime theme switchers have to choose between unsafe-inline and a hand-maintained hash that goes stale; here the artifact IS the hash source, so a theme edit can never strand a stale hash without also changing the code it authorizes.

Generate the artifact in your Vite config — it is build tooling, and nothing under src/ may import it:

// vite.config.ts
import { createAppearanceBootstrap } from '@animus-ui/system/bootstrap';
import { animusExtract } from '@animus-ui/vite-plugin';
import { defineConfig } from 'vite';

import { theme } from './src/ds';

const appearanceBootstrap = createAppearanceBootstrap(theme);

export default defineConfig({
  plugins: [animusExtract({ system: './src/ds.ts', appearanceBootstrap })],
});

The plugin is delivery-only: it injects artifact.code verbatim as an inline <script data-animus-bootstrap> in <head>, after the charset and viewport metas (so the charset stays within the first 1024 bytes) and ahead of every stylesheet reference, and interprets no appearance semantics. Omit the option and the built HTML is byte-for-byte what it was before.

Content-Security-Policy

The artifact's second field, cspHash, authorizes that inline script. Two rules:

  • Derive it from the artifact at build time. createAppearanceBootstrap returns the hash of the exact bytes it just generated. Renaming a color mode or changing storageKey changes those bytes and therefore the hash — a hand-copied literal in a config file silently becomes stale, and a stale hash means a blocked script and a flash of the wrong mode.

  • Single-quote it in script-src. The value is returned ready to use:

    Content-Security-Policy: script-src 'sha256-…'
    

    Unquoted, sha256-… parses as a host source, matches nothing, and silently fails to authorize the script.

Never fall back to unsafe-inline, and never use a build-time constant nonce.

// Build the header from the artifact — never from a copied string.
// The hash is single-quoted in the source list, per the rule above.
const csp = `script-src 'self' '${appearanceBootstrap.cspHash}'`;

Next.js is deliberately not automatic: CSP nonces need request-time control the bundler does not have, so the application places artifact.code itself and supplies either cspHash or a per-request nonce. Worked App Router and Pages Router examples live in the @animus-ui/next-plugin README.

Font preloads

A font face declared with preload: true gets a <link rel="preload" as="font" crossorigin data-animus-preload> in <head>, after the charset and viewport metas and ahead of every stylesheet link. Its href is the URL the stylesheet's url() loads: the hashed asset in a build, the dev server's URL in development. A root-relative or absolute URL that index.html already preloads gets no second preload. A build warns, naming the HTML page, when the finished page preloads the same file through another tag: one written in the page for an asset() file, or one a plugin adds from an index.html hook that runs after Animus's.

fontPreloads selects the faces that preload in place of their preload flags, for an app that renders only some of a system's faces. A family name selects every face of the family; { family, weight?, style? } selects the faces whose declared weight contains weight (a variable face's range '100 900' contains 600) and whose style is style ('normal' when omitted):

animusExtract({
  system: './src/ds.ts',
  fontPreloads: [
    { family: 'Apercu', weight: 400 },
    { family: 'Apercu', weight: 'bold' },
    { family: 'Suisse', weight: 700 },
  ],
});

An entry's fetchpriority ('high', 'low' or 'auto') is written on the preloads of the faces it selects, in index.html and in virtual:animus/font-preloads; a file several entries select carries the strongest one given. A preload competes with the critical CSS and JavaScript, so give 'high' only to the face the first screen paints with:

fontPreloads: [{ family: 'Apercu', weight: 400, fetchpriority: 'high' }],

[] preloads none. An entry no face matches reports animus.style.font-preload-unmatched; a malformed one, such as a weight range, reports animus.style.font-preload-invalid.

A preload competes with the critical CSS and JavaScript for first paint, so preload WOFF2 and only the faces the first screen needs: a large TrueType file can delay first paint by seconds on a slow link. A preloaded file that is not WOFF2 or WOFF, or an asset() file over 150 KB, reports animus.style.font-face-preload-costly.

Split stylesheets

stylesheets: 'split' builds the stylesheet as three files instead of one, each a consecutive slice of the single sheet, in its order:

  1. foundation: theme variables and color modes, @property registrations, and anm-global (resets, @font-face, keyframes), with any quarantine sheets;
  2. components: anm-base, anm-variants, anm-compounds, anm-states;
  3. utilities: anm-system, anm-custom.

Each page links them in that order exactly where Vite places the single sheet among the app's own CSS, as ordinary render-blocking stylesheets: app CSS bundled with it keeps its place before or after the three files. They hold the single sheet's rules in its order, so the cascade is unchanged; Vite minifies each file on its own, which leaves its bytes the same apart from the newline ending each file. Their URLs are the ones Vite gives its own links, through base and experimental.renderBuiltUrl. Each file is named by its content, so a browser keeps its cached copy of a file an edit leaves alone: an edit to a component's styles changes only the components file. The three files compress slightly worse than one (about 0.1% more gzip on a large app).

animusExtract({ system: './src/ds.ts', stylesheets: 'split' });

A split build needs a page Vite builds from HTML that links the stylesheet, which is where the links go: one with no page, or whose stylesheet only a lazy chunk loads, fails. The dev server keeps one sheet. 'single' (the default) is one sheet the bundle imports.

Third-party stylesheets

A library's plain CSS outside any @layer beats every Animus layer. With quarantine, @animus-ui/layergate loads it into anm-vendor, a layer below all of Animus's, so Animus's styles win and the library's still apply where Animus sets nothing:

bun add -D @animus-ui/layergate
animusExtract({
  system: './src/ds.ts',
  quarantine: {
    sheets: ['react-grid-layout/css/styles.css'], // delivered ahead of Animus's own CSS
    dependencies: ['react-datepicker'], // CSS these packages' modules import
    optOut: [], // exact files whose own rules stay unlayered
  },
});

sheets arrive at the head of Animus's own stylesheet, wherever it is imported, so no extra import is needed. Their fonts and images keep resolving, files outside the project root included. !important declarations and animations still win, and are reported. App CSS keeps its own rules unlayered; where it imports a vendor file, that file is layered. CSS Modules are left alone.

MDX

Animus reads each .mdx file as the JSX it renders: its imports, exports, tags, props and {expressions}. It needs no MDX compiler. A component a tag names, or one your provider maps markdown to (# x → your heading), counts as rendered. Markdown and code examples render nothing.

MDX renders components; it declares no styles. Animus reports any reference to its builder in an .mdx file as SOURCE_MDX_STYLE_DECLARATION, and a strict build fails on it. That covers any import from @animus-ui/system, and any member named like a builder method (styles, variant, compound, states, system, props, extend, asElement, asComponent, asClass), read, called or destructured. So {Box.styles} is reported too. Declare components in .ts or .tsx files and import them.

Important

  • Do not add React resolve aliases to vite.config.ts — they break the extraction transform pipeline
  • After system config changes, restart the dev server (the subprocess runs at buildStart)
  • Run bun run clean:light if styles seem stale (clears .vite cache)
  • The virtual module ids are published contract, and Vite gives the first resolveId/load answer for an id to the whole config, so two plugin instances in one Vite config share one bridge and one stylesheet — the second instance's modules resolve to the first's. The per-instance key only separates independently built bundles that share a document.

License

MIT

版本与发布记录

@animus-ui/vite-plugin 共发布 112 个版本,首次发布于 2026/03/26;过去 12 个月发布了 112 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.1.87latest2026/10/2414 天前
0.1.862026/10/2315 天前
0.1.852026/10/2315 天前
0.1.842026/10/2216 天前
0.1.832026/10/2217 天前
0.1.822026/10/2217 天前

其他发布通道:next → 0.1.1-beta.2。

关于 @animus-ui/vite-plugin 的常见问题

@animus-ui/vite-plugin 是什么?

Animus static CSS extraction Vite plugin

如何安装 @animus-ui/vite-plugin?

在项目目录运行 npm install @animus-ui/vite-plugin,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:vite(>=8 <9)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@animus-ui/vite-plugin 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@animus-ui/vite-plugin 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@animus-ui/vite-plugin 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22.12.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@animus-ui/vite-plugin 可以商用吗?

@animus-ui/vite-plugin 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@animus-ui/vite-plugin 的下载量有多少?

根据 npm 官方下载统计,@animus-ui/vite-plugin 在 2026/10/06 – 2026/11/04 这 30 天内被下载 6,839 次,日均约 228 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@animus-ui/vite-plugin 还在维护吗?

最新版本 0.1.87 发布于 2026/10/24(14 天前);至今共发布 112 个版本,其中过去 12 个月发布了 112 个;首次发布于 2026/03/26。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。