@animus-ui/layergate

中文文档
0.1.87发布于 2026/10/24MITTypeScriptESM + CJS构建来源已验证近 30 天下载 4449

Load third-party stylesheets into a cascade layer, per file, with their imports inlined and their URLs kept working

原始使用文档
文档目录8 节
  1. Vite
  2. webpack
  3. CSS Modules alongside layergate
  4. What it does to a stylesheet
  5. What a layer does not change
  6. Not yet
  7. Delivering from a host stylesheet
  8. Core API

@animus-ui/layergate

For the vendor stylesheet you have to import: a date picker's, a grid library's, a package's styles.css. layergate loads it into a cascade layer below yours, so its normal rules can't fight your design system, whatever their specificity or load order.

A stylesheet outside any @layer beats every layered rule, so vendor CSS imported as usual overrides a design system or utility framework that uses layers. layergate loads that CSS itself, inlines its @imports, and puts each file's own rules in one layer below yours:

@layer vendor, app;
@layer vendor {
  /* react-grid-layout/css/styles.css, as written */
}

Your own CSS is out of scope. App stylesheets and CSS Modules stay as you configure them: layergate layers only the vendor files you declare (sheets) or opt into (dependencies), including where your CSS imports one.

Vite

import { layergate } from '@animus-ui/layergate/vite';
// vite.config.ts
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [
    layergate({
      layer: 'vendor',
      order: ['vendor', 'base', 'components', 'utilities'],
      sheets: ['react-grid-layout/css/styles.css', './styles/legacy.css'],
      dependencies: ['react-datepicker'],
      optOut: ['./styles/legacy/keep-unlayered.css'],
    }),
  ],
});
// your entry module, once, before other CSS
import 'virtual:layergate';
OptionMeaning
layerThe layer owned stylesheets go into. Default vendor.
orderThe full layer order every owned stylesheet declares first. It must include layer. Default [layer].
sheetsStylesheets virtual:layergate delivers, in this order, whether or not anything else imports them: package specifiers or paths relative to the Vite root.
dependenciesAlso own the .css a JS module imports from a package other than your app: true for every package, or package names. Default: none.
optOutFiles inside an owned stylesheet whose own rules stay outside the layer. Exact specifiers or paths, never globs.

A JS module's import of an owned stylesheet, by any spelling, gets the same single copy, and the declared sheets always come first, in their declared order, whichever owned stylesheet the app imports first. Vite's own CSS pipeline then processes each one once: PostCSS, minification, injection and extraction. A nested @import of a package resolves as a CSS import does, through its style entry. Fonts and images keep resolving, because layergate rewrites a relative url() to resolve from the delivered file. Edits to an owned file, nested imports and files under node_modules included, hot-update in place.

webpack

// webpack.config.js
const { LayergatePlugin } = require('@animus-ui/layergate/webpack');

module.exports = {
  plugins: [
    new LayergatePlugin({
      layer: 'vendor',
      order: ['vendor', 'base', 'components', 'utilities'],
      sheets: ['react-grid-layout/css/styles.css'],
      dependencies: ['react-datepicker'],
      declaredBy: require.resolve('./src/styles/app.css'),
    }),
  ],
};

The plugin adds no rule. It joins every loader chain of yours whose css-loader is explicitly global (modules: false, as Next's global chains are), just before PostCSS runs, and raises that css-loader's importLoaders by one, so every stylesheet passes through it, imported ones included. An owned stylesheet arrives finished, its @imports resolved as css-loader resolves them and inlined, so css-loader never sees a layer() import. PostCSS, Sass and css-loader keep running as before.

Any other chain is left untouched. One whose css-loader may treat a file as a CSS Module (modules unset, or auto) is reported in one line (layergate.coverage); a chain that only builds CSS Modules, such as Next's, is left alone silently.

CSS Modules alongside layergate

layergate does not decide which stylesheets are CSS Modules. If one rule handles both, split it: global CSS in a rule whose css-loader has modules: false, which layergate joins, and CSS Modules in a rule of their own, which it leaves alone.

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        exclude: /\.module\.css$/,
        use: [
          'style-loader',
          { loader: 'css-loader', options: { modules: false } },
        ],
      },
      {
        test: /\.module\.css$/,
        use: [
          'style-loader',
          { loader: 'css-loader', options: { modules: true } },
        ],
      },
    ],
  },
};

Next already builds CSS this way.

What layergate returns is never cached by webpack, so a warm start with a persistent cache finishes owned stylesheets again: about 118 ms per 200 vendor sheets in a synthetic measurement. Caching them safely needs their provenance (the files and plan they came from) kept with the cache, a possible later change. Ownership, opt-outs and the declared sheets go by realpath; url()s are rebased from where webpack reads each file, so resolve.symlinks: false resolves them as css-loader does. The declaredBy stylesheet is never layered, even when an owned stylesheet imports it.

webpack has no virtual:layergate. Pass declaredBy, the path of a stylesheet of yours: the declared sheets are delivered at its head, and a module's own import of one then loads nothing. Without it, import each declared sheet yourself; it is owned wherever it is imported.

  • A remote @import in an owned stylesheet is an error (layergate.remote): webpack cannot load it inside the layer, and an unlayered copy would escape it. Vendor the sheet locally, or opt the file out if unlayered is acceptable.
  • A chain that uses Lightning CSS instead of css-loader is not covered, and is reported (layergate.coverage).
  • In watch mode, an edit to any file an owned stylesheet read, files under node_modules included, rebuilds it. The plugin makes a RegExp or function watchOptions.ignored watch those files; a host that owns its watch options passes watchIgnored: false and asks plugin.watches(path).
  • Turbopack runs no loader on the stylesheets a stylesheet imports, so layergate cannot work there.

With Next and Animus, use the quarantine option of @animus-ui/next-plugin, which sets this up for Next's client compiler.

What it does to a stylesheet

  • Each file is decided on its own. A file's own rules go into the layer. An opted-out file's own rules stay outside it, under the conditions of the @import that loads it, and the files it imports keep their own decisions.
  • Imports keep their conditions. @import "x.css" layer(a) supports(…) screen becomes @media screen { @supports (…) { @layer a { … } } } inside the layer, so its rules land in vendor.a. An anonymous layer import gets a private layer of its own, never the shared one.
  • Text is kept as written. Selectors, declarations, !important, keyframes and the file's own layers reach the browser unchanged. Only a relative url() in a file outside the delivered file's directory changes.
  • Remote imports stay remote. Under Vite, an http(s): import is hoisted to the top, in its layer, and reported, since its contents are not inspected. Under webpack it is an error (see above).

What a layer does not change

A layer bounds normal declarations only. These are kept and reported as warnings, never stripped:

  • !important declarations, which beat your normal declarations from any layer (layergate.important);
  • animations and keyframes, whose values override normal declarations while they run (layergate.animation).

Font, keyframe and property names are global, whatever their layer.

Not yet

  • CSS Modules, Sass, Less and Stylus are left to Vite. Under webpack, Sass output is finished like any CSS, and CSS Modules are left alone.
  • An owned file can arrive more than once. A stylesheet Vite loads itself, such as an app sheet whose @import reaches an owned file, is finished by layergate too: its own rules stay unlayered and the owned file is inlined layered, so no unlayered copy escapes. But each finished stylesheet stands on its own, so the same owned file reached from two of them is delivered in both, each copy layered. One delivery per file across the document would need a document-wide registry, a possible later increment.
  • App CSS is left alone unless you list it in sheets. CSS that loads before virtual:layergate, or another tool's layer order statement that comes first, can still place your layers after vendor.
  • A .css request with a query (?inline, ?url, ?raw) is left to Vite.
  • A stylesheet with @namespace is an error (layergate.namespace): it applies to a whole stylesheet, which a file inlined into another is not.
  • A host's PostCSS runs once, over each finished stylesheet, not per file.
  • Turbopack: not supported (see webpack above).

Delivering from a host stylesheet

A tool with its own stylesheet module can deliver the declared sheets at its head instead of through virtual:layergate: pass declaredBy (the module's specifier, which an owned stylesheet a JS module imports then loads first), and prepend await plugin.stylesheetHead.call(this, id) to that module's CSS in its load hook. The head uses root-relative URLs, so assets resolve from a virtual module too; a file outside the Vite root is reached through /@fs/: in development a declared sheet's directory is added to server.fs.allow when the server starts (beside your own list, or Vite's default workspace root), and an asset it references in another directory is allowed when it is found; in a build it is emitted as an asset. Animus's quarantine option works this way.

Core API

compileUnit(roots, { layer, order, resolve, read, quarantined, owns, url, base, cache }) compiles stylesheet graphs, in order, into one finished stylesheet with the host's resolver and file reader, and returns the CSS, a source map, the files read, whether anything was layered, and the diagnostics. owns says which files start an owned graph (default: all), and url maps a referenced file to its URL text (default: relative to the first root). base is the directory generated layer names are relative to (the project root), so they are the same on every machine. cache keeps parsed files between compiles; drop a file's entry when it changes. The Vite and webpack adapters are built on it.

版本与发布记录

@animus-ui/layergate 共发布 50 个版本,首次发布于 2026/10/14;过去 12 个月发布了 50 个版本。最新版本附带 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 天前

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

@animus-ui/layergate 是什么?

Load third-party stylesheets into a cascade layer, per file, with their imports inlined and their URLs kept working

如何安装 @animus-ui/layergate?

在项目目录运行 npm install @animus-ui/layergate,或使用 pnpm add / yarn add / bun add。

@animus-ui/layergate 支持 TypeScript 吗?

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

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

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

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

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

@animus-ui/layergate 可以商用吗?

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

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

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

@animus-ui/layergate 还在维护吗?

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