@animus-ui/vite-plugin 0.1.32 → 0.1.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -60,7 +60,8 @@ export default defineConfig({
60
60
  ```
61
61
 
62
62
  The plugin is **delivery-only**: it injects `artifact.code` verbatim as an inline
63
- `<script data-animus-bootstrap>` at the start of `<head>`, ahead of every
63
+ `<script data-animus-bootstrap>` in `<head>`, after the charset and viewport
64
+ metas (so the charset stays within the first 1024 bytes) and ahead of every
64
65
  stylesheet reference, and interprets no appearance semantics. Omit the option and
65
66
  the built HTML is byte-for-byte what it was before.
66
67
 
@@ -96,6 +97,75 @@ supplies either `cspHash` or a per-request nonce. Worked App Router and Pages
96
97
  Router examples live in the
97
98
  [`@animus-ui/next-plugin` README](https://github.com/codecaaron/animus/tree/main/packages/next-plugin#appearance-bootstrap-no-fouc-color-mode).
98
99
 
100
+ ## Font preloads
101
+
102
+ A font face declared with `preload: true` gets a
103
+ `<link rel="preload" as="font" crossorigin data-animus-preload>` in `<head>`,
104
+ after the charset and viewport metas and ahead of every stylesheet link. Its
105
+ href is the URL the stylesheet's `url()` loads: the hashed asset in a build,
106
+ the dev server's URL in development. A root-relative or absolute URL that
107
+ `index.html` already preloads gets no second preload. A build warns, naming
108
+ the HTML page, when the finished page preloads the same file through another
109
+ tag: one written in the page for an `asset()` file, or one a plugin adds from
110
+ an `index.html` hook that runs after Animus's.
111
+
112
+ `fontPreloads` selects the faces that preload in place of their `preload`
113
+ flags, for an app that renders only some of a system's faces. A family name
114
+ selects every face of the family; `{ family, weight?, style? }` selects the
115
+ faces whose declared weight contains `weight` (a variable face's range
116
+ `'100 900'` contains 600) and whose style is `style` (`'normal'` when
117
+ omitted):
118
+
119
+ ```ts
120
+ animusExtract({
121
+ system: './src/ds.ts',
122
+ fontPreloads: [
123
+ { family: 'Apercu', weight: 400 },
124
+ { family: 'Apercu', weight: 'bold' },
125
+ { family: 'Suisse', weight: 700 },
126
+ ],
127
+ });
128
+ ```
129
+
130
+ `[]` preloads none. An entry no face matches reports
131
+ `animus.style.font-preload-unmatched`; a malformed one, such as a weight
132
+ range, reports `animus.style.font-preload-invalid`.
133
+
134
+ A preload competes with the critical CSS and JavaScript for first paint, so
135
+ preload WOFF2 and only the faces the first screen needs: a large TrueType
136
+ file can delay first paint by seconds on a slow link. A preloaded file that
137
+ is not WOFF2 or WOFF, or an `asset()` file over 150 KB, reports
138
+ `animus.style.font-face-preload-costly`.
139
+
140
+ ## Third-party stylesheets
141
+
142
+ A library's plain CSS outside any `@layer` beats every Animus layer. With
143
+ `quarantine`, [`@animus-ui/layergate`](../layergate/README.md) loads it into
144
+ `anm-vendor`, a layer below all of Animus's, so Animus's styles win and the
145
+ library's still apply where Animus sets nothing:
146
+
147
+ ```sh
148
+ bun add -D @animus-ui/layergate
149
+ ```
150
+
151
+ ```ts
152
+ animusExtract({
153
+ system: './src/ds.ts',
154
+ quarantine: {
155
+ sheets: ['react-grid-layout/css/styles.css'], // delivered ahead of Animus's own CSS
156
+ dependencies: ['react-datepicker'], // CSS these packages' modules import
157
+ optOut: [], // exact files whose own rules stay unlayered
158
+ },
159
+ });
160
+ ```
161
+
162
+ `sheets` arrive at the head of Animus's own stylesheet, wherever it is
163
+ imported, so no extra import is needed. Their fonts and images keep
164
+ resolving, files outside the project root included.
165
+ `!important` declarations and animations still win, and are reported. App
166
+ CSS keeps its own rules unlayered; where it imports a vendor file, that file
167
+ is layered. CSS Modules are left alone.
168
+
99
169
  ## MDX
100
170
 
101
171
  Animus reads each `.mdx` file as the JSX it renders: its imports, exports,
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,MAAM,CAAC;AAE3C,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,aAAa,EAClB,MAAM,EAAE,cAAc,GACrB,IAAI,CA4EN"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,MAAM,CAAC;AAE3C,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,aAAa,EAClB,MAAM,EAAE,cAAc,GACrB,IAAI,CA+EN"}
package/dist/context.d.ts CHANGED
@@ -29,9 +29,11 @@ export declare class PluginContext {
29
29
  pathAliasesJson: string | null;
30
30
  /** Vite's export conditions, so the loader reads the files Vite serves. */
31
31
  resolveConditions: string[];
32
+ private viteExtensionPolicy;
32
33
  /** Vite's resolved extension order; the built-in policy until
33
34
  * `configResolved`. */
34
- extensionPolicy: HostExtensionPolicy;
35
+ get extensionPolicy(): HostExtensionPolicy;
36
+ set extensionPolicy(policy: HostExtensionPolicy);
35
37
  /** Vite's asset classification, captured at `configResolved`. */
36
38
  assetClassification: HostAssetClassification;
37
39
  /** How Vite's build expands computed loads, captured at
@@ -85,6 +87,12 @@ export declare class PluginContext {
85
87
  /** Coalesces a burst of invalidations into one full reload. */
86
88
  private readonly reloadWindow;
87
89
  base: string;
90
+ /** Vite's root and `server.origin`, which a dev asset URL is served
91
+ * under. */
92
+ viteRoot: string;
93
+ serverOrigin: string;
94
+ /** Vite's resolved public directory; empty when it serves none. */
95
+ publicDir: string;
88
96
  assetUrlBySpecifier: Map<string, string>;
89
97
  /** Where dev asset() copies live, immutable and content-named; null in a
90
98
  * build, or when the dev server does not serve the directory, which
@@ -162,7 +170,16 @@ export declare class PluginContext {
162
170
  /** A build-strictness check that failed: a build throws `message`, and
163
171
  * development reports it at error level and takes the non-strict path. */
164
172
  strictFailure(message: string, cause?: unknown): void;
165
- devFsUrl(absPath: string): string;
173
+ /** The URL Vite's dev server serves `absPath` at, the form its dev CSS
174
+ * pass writes (`fileToDevUrl`): root-relative inside its root, `/@fs/`
175
+ * outside, under `server.origin` and the base. Every consumer, the
176
+ * stylesheet that pass rewrites, component CSS it does not, and a font
177
+ * preload, then names one URL, and the browser fetches the file once. */
178
+ devServedUrl(absPath: string): string;
179
+ /** A root-relative URL a face names as written, as Vite's dev CSS pass
180
+ * writes it: under `server.origin` and the base where it names a public
181
+ * file or a file under the root, else unchanged. */
182
+ devRootRelativeUrl(href: string): string;
166
183
  /** A dev asset() URL: an immutable content-named copy of `absPath`, so an
167
184
  * edit gets a new URL, or the live file where copies are not served. */
168
185
  devAssetUrl(specifier: string, absPath: string): string;
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,24 @@
1
1
  import type { PluginContext } from './context';
2
2
  import type { HtmlTagDescriptor } from 'vite';
3
3
  /**
4
- * Vite serializes one `head-prepend` bucket in array order, so the bootstrap
5
- * script precedes the layer declaration and every stylesheet reference.
4
+ * The hrefs a finished document preloads both through Animus's tag and
5
+ * through another, which the page wrote or a hook running after Animus's
6
+ * added: the browser may then fetch the file twice. Each href is compared
7
+ * as the browser resolves it against `pageUrl`, the URL the page is served
8
+ * at, and reported as Animus wrote it.
6
9
  */
7
- export declare function buildIndexHtmlTags(ctx: PluginContext): HtmlTagDescriptor[];
10
+ export declare function duplicatedFontPreloads(html: string, pageUrl: string): string[];
11
+ /**
12
+ * The hook's result: the tags written after the charset and viewport metas,
13
+ * each on its own line indented as the element it follows, so the charset
14
+ * stays in the first 1024 bytes however long the tags are; as `head-prepend`
15
+ * tags for a document without a `<head>`.
16
+ */
17
+ export declare function injectHeadTags(html: string, tags: HtmlTagDescriptor[]): string | HtmlTagDescriptor[];
18
+ /**
19
+ * Animus's head tags in document order: the bootstrap script, so it runs
20
+ * before first paint and ahead of every stylesheet, the layer declaration,
21
+ * the font preloads, then the development bridge.
22
+ */
23
+ export declare function buildIndexHtmlTags(ctx: PluginContext, html?: string): HtmlTagDescriptor[];
8
24
  //# sourceMappingURL=index-html.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index-html.d.ts","sourceRoot":"","sources":["../src/index-html.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAE9C;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,aAAa,GAAG,iBAAiB,EAAE,CAqC1E"}
1
+ {"version":3,"file":"index-html.d.ts","sourceRoot":"","sources":["../src/index-html.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AA0C9C;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACd,MAAM,EAAE,CAeV;AAsED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,iBAAiB,EAAE,GACxB,MAAM,GAAG,iBAAiB,EAAE,CAU9B;AAkCD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,aAAa,EAClB,IAAI,SAAK,GACR,iBAAiB,EAAE,CAuCrB"}
package/dist/index.cjs CHANGED
@@ -4,10 +4,10 @@ Object.defineProperties(exports, {
4
4
  });
5
5
  let _animus_ui_extract_pipeline = require("@animus-ui/extract/pipeline");
6
6
  let path = require("path");
7
+ let vite = require("vite");
7
8
  let fs = require("fs");
8
9
  let _animus_ui_extract_session = require("@animus-ui/extract/session");
9
10
  let child_process = require("child_process");
10
- let vite = require("vite");
11
11
  let crypto = require("crypto");
12
12
  //#region src/asset-copy-dirs.ts
13
13
  /**
@@ -192,7 +192,7 @@ function applyResolvedConfig(ctx, config) {
192
192
  const copyDir = (0, path.resolve)(config.cacheDir, "animus-assets", processCopyDir());
193
193
  const served = (0, vite.isFileLoadingAllowed)(config, (0, path.join)(copyDir, "asset"));
194
194
  ctx.assetCopyDir = served ? copyDir : null;
195
- ctx.servesFile = (path$9) => (0, vite.isFileLoadingAllowed)(config, path$9);
195
+ ctx.servesFile = (path$11) => (0, vite.isFileLoadingAllowed)(config, path$11);
196
196
  if (!served) ctx.warn(`[animus-extract] asset() copies in ${copyDir} are not served under server.fs; serving the live files instead, so an edited asset keeps its URL`);
197
197
  }
198
198
  ctx.outputExcludes = (0, _animus_ui_extract_pipeline.outputExclusions)(config.root, (0, _animus_ui_extract_pipeline.viteOutputTargets)(config));
@@ -204,6 +204,9 @@ function applyResolvedConfig(ctx, config) {
204
204
  ctx.log(`extension order: ${ctx.extensionPolicy.resolverVersion}`);
205
205
  ctx.logger = config.logger;
206
206
  ctx.base = config.base ?? "/";
207
+ ctx.viteRoot = config.root;
208
+ ctx.serverOrigin = config.server.origin ?? "";
209
+ ctx.publicDir = config.publicDir;
207
210
  ctx.lcssTargets = (0, _animus_ui_extract_pipeline.resolveLightningTargets)(ctx.options.targets, ctx.rootDir);
208
211
  ctx.log(`Lightning CSS targets resolved (${Object.keys(ctx.lcssTargets).length} browsers)`);
209
212
  const pairs = [];
@@ -348,9 +351,15 @@ var PluginContext = class {
348
351
  pathAliasesJson = null;
349
352
  /** Vite's export conditions, so the loader reads the files Vite serves. */
350
353
  resolveConditions = [];
354
+ viteExtensionPolicy;
351
355
  /** Vite's resolved extension order; the built-in policy until
352
356
  * `configResolved`. */
353
- extensionPolicy = _animus_ui_extract_pipeline.BUILT_IN_EXTENSION_POLICY;
357
+ get extensionPolicy() {
358
+ return this.viteExtensionPolicy ?? (0, _animus_ui_extract_pipeline.builtInExtensionPolicy)();
359
+ }
360
+ set extensionPolicy(policy) {
361
+ this.viteExtensionPolicy = policy;
362
+ }
354
363
  /** Vite's asset classification, captured at `configResolved`. */
355
364
  assetClassification = (0, _animus_ui_extract_pipeline.builtInAssetClassification)("noHost");
356
365
  /** How Vite's build expands computed loads, captured at
@@ -436,6 +445,12 @@ var PluginContext = class {
436
445
  this.devServer?.hot?.send({ type: "full-reload" });
437
446
  });
438
447
  base = "/";
448
+ /** Vite's root and `server.origin`, which a dev asset URL is served
449
+ * under. */
450
+ viteRoot = "";
451
+ serverOrigin = "";
452
+ /** Vite's resolved public directory; empty when it serves none. */
453
+ publicDir = "";
439
454
  assetUrlBySpecifier = /* @__PURE__ */ new Map();
440
455
  /** Where dev asset() copies live, immutable and content-named; null in a
441
456
  * build, or when the dev server does not serve the directory, which
@@ -520,6 +535,7 @@ var PluginContext = class {
520
535
  engine: options.engine,
521
536
  runtimeImport: options.runtimeImport,
522
537
  closedDynamicImports: options.closedDynamicImports,
538
+ fontPreloads: options.fontPreloads,
523
539
  mode: this.emissionProd ? "production" : "development"
524
540
  }, {
525
541
  build: this.build,
@@ -555,7 +571,7 @@ var PluginContext = class {
555
571
  * between sees one generation. */
556
572
  observeOutcome(session, outcome) {
557
573
  let systemPropsChanged = false;
558
- if (outcome.kind === "held") for (const [path$6, hash] of outcome.abortedOriginals) this.abortedParseHashes.set(path$6, hash);
574
+ if (outcome.kind === "held") for (const [path$7, hash] of outcome.abortedOriginals) this.abortedParseHashes.set(path$7, hash);
559
575
  else {
560
576
  this.abortedParseHashes.clear();
561
577
  const before = this.systemPropsModule;
@@ -646,13 +662,36 @@ var PluginContext = class {
646
662
  if (!report) throw new Error(message, cause === void 0 ? void 0 : { cause });
647
663
  report(message);
648
664
  }
649
- devFsUrl(absPath) {
650
- return `${this.base.endsWith("/") ? this.base.slice(0, -1) : this.base}/@fs${absPath}`;
665
+ /** The URL Vite's dev server serves `absPath` at, the form its dev CSS
666
+ * pass writes (`fileToDevUrl`): root-relative inside its root, `/@fs/`
667
+ * outside, under `server.origin` and the base. Every consumer, the
668
+ * stylesheet that pass rewrites, component CSS it does not, and a font
669
+ * preload, then names one URL, and the browser fetches the file once. */
670
+ devServedUrl(absPath) {
671
+ const base = this.base.endsWith("/") ? this.base.slice(0, -1) : this.base;
672
+ const root = this.viteRoot.endsWith("/") ? this.viteRoot : `${this.viteRoot}/`;
673
+ const path$8 = this.viteRoot !== "" && absPath.startsWith(root) ? `/${absPath.slice(root.length)}` : `/@fs${absPath}`;
674
+ return `${this.serverOrigin}${base}${path$8}`;
675
+ }
676
+ /** A root-relative URL a face names as written, as Vite's dev CSS pass
677
+ * writes it: under `server.origin` and the base where it names a public
678
+ * file or a file under the root, else unchanged. */
679
+ devRootRelativeUrl(href) {
680
+ if (!href.startsWith("/") || href.startsWith("//")) return href;
681
+ let path$6;
682
+ try {
683
+ path$6 = decodeURI(href.split(/[?#]/)[0]);
684
+ } catch {
685
+ return href;
686
+ }
687
+ if (![this.publicDir, this.viteRoot].some((dir) => dir !== "" && (0, fs.existsSync)((0, path.join)(dir, path$6)))) return href;
688
+ const base = this.base.endsWith("/") ? this.base.slice(0, -1) : this.base;
689
+ return `${this.serverOrigin}${base}${href}`;
651
690
  }
652
691
  /** A dev asset() URL: an immutable content-named copy of `absPath`, so an
653
692
  * edit gets a new URL, or the live file where copies are not served. */
654
693
  devAssetUrl(specifier, absPath) {
655
- if (this.assetCopyDir === null) return this.devFsUrl(absPath);
694
+ if (this.assetCopyDir === null) return this.devServedUrl(absPath);
656
695
  const { fileName } = (0, _animus_ui_extract_pipeline.writeAssetCopy)(absPath, this.assetCopyDir);
657
696
  const copyPath = (0, path.join)(this.assetCopyDir, fileName);
658
697
  if (!this.servesFile(copyPath)) {
@@ -660,12 +699,12 @@ var PluginContext = class {
660
699
  this.unservedAssetCopies.add(specifier);
661
700
  this.warn(`[animus-extract] the copy of asset() ${specifier} is not served under server.fs; serving the live file instead, so an edit keeps its URL`);
662
701
  }
663
- return this.devFsUrl(absPath);
702
+ return this.devServedUrl(absPath);
664
703
  }
665
704
  if (this.assetSourceBySpecifier.get(specifier) !== absPath) this.devServer?.watcher?.add(absPath);
666
705
  this.assetSourceBySpecifier.set(specifier, absPath);
667
706
  this.retainAssetCopy(absPath, fileName);
668
- return this.devFsUrl(copyPath);
707
+ return this.devServedUrl(copyPath);
669
708
  }
670
709
  /** Keeps each source's current copy and the one before it; an asset
671
710
  * edit reloads the page, so an older URL is never fetched again. A copy
@@ -773,8 +812,8 @@ var PluginContext = class {
773
812
  plan.missing,
774
813
  directories
775
814
  ]) {
776
- const added = paths.filter((path$7) => !registered.has(path$7));
777
- for (const path$8 of added) registered.add(path$8);
815
+ const added = paths.filter((path$9) => !registered.has(path$9));
816
+ for (const path$10 of added) registered.add(path$10);
778
817
  if (added.length > 0) watcher.add(added);
779
818
  }
780
819
  }
@@ -1283,11 +1322,145 @@ function invalidateStaleModules(ctx, environment, modules, analyzed) {
1283
1322
  }
1284
1323
  //#endregion
1285
1324
  //#region src/index-html.ts
1325
+ const LINK_TAG_RE = /<link\b[^>]*>/gi;
1326
+ /** Marks the preloads Animus writes, so another preload of the same file
1327
+ * can be told from Animus's own. */
1328
+ const ANIMUS_PRELOAD_MARKER = "data-animus-preload";
1329
+ const MARKER_RE = /\sdata-animus-preload(?=[\s=/>])/i;
1330
+ const REL_RE = /\srel\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i;
1331
+ const HREF_RE = /\shref\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i;
1332
+ function attribute(tag, re) {
1333
+ const match = re.exec(tag);
1334
+ return match ? match[1] ?? match[2] ?? match[3] : void 0;
1335
+ }
1336
+ /** Each `<link rel="preload">` in `html`, with its href. */
1337
+ function* preloadLinks(html) {
1338
+ for (const [tag] of html.matchAll(LINK_TAG_RE)) {
1339
+ const rel = attribute(tag, REL_RE)?.toLowerCase().split(/\s+/) ?? [];
1340
+ const href = attribute(tag, HREF_RE);
1341
+ if (rel.includes("preload") && href !== void 0) yield [tag, href];
1342
+ }
1343
+ }
1344
+ const ENTITY_RE = /&(?:#x([0-9a-f]+)|#(\d+)|(amp|lt|gt|quot|apos));/gi;
1345
+ const NAMED_ENTITIES = /* @__PURE__ */ new Map([
1346
+ ["amp", "&"],
1347
+ ["lt", "<"],
1348
+ ["gt", ">"],
1349
+ ["quot", "\""],
1350
+ ["apos", "'"]
1351
+ ]);
1352
+ /** An attribute value as the browser reads it: character references decoded. */
1353
+ function attributeText(value) {
1354
+ return value.replace(ENTITY_RE, (_, hex, decimal, named) => named === void 0 ? String.fromCodePoint(Number.parseInt(hex ?? decimal, hex ? 16 : 10)) : NAMED_ENTITIES.get(named.toLowerCase()));
1355
+ }
1356
+ /**
1357
+ * The hrefs a finished document preloads both through Animus's tag and
1358
+ * through another, which the page wrote or a hook running after Animus's
1359
+ * added: the browser may then fetch the file twice. Each href is compared
1360
+ * as the browser resolves it against `pageUrl`, the URL the page is served
1361
+ * at, and reported as Animus wrote it.
1362
+ */
1363
+ function duplicatedFontPreloads(html, pageUrl) {
1364
+ const resolved = (href) => {
1365
+ try {
1366
+ return new URL(attributeText(href), pageUrl).href;
1367
+ } catch {
1368
+ return href;
1369
+ }
1370
+ };
1371
+ const ours = /* @__PURE__ */ new Map();
1372
+ const others = /* @__PURE__ */ new Set();
1373
+ for (const [tag, href] of preloadLinks(html)) if (MARKER_RE.test(tag)) ours.set(resolved(href), href);
1374
+ else others.add(resolved(href));
1375
+ return [...ours].filter(([url]) => others.has(url)).map(([, href]) => href);
1376
+ }
1377
+ /** The hrefs the document already preloads. */
1378
+ function preloadedHrefs(html) {
1379
+ return new Set([...preloadLinks(html)].map(([, href]) => href));
1380
+ }
1381
+ /** The head elements Animus's tags go after (the charset and viewport
1382
+ * metas), the stylesheets they go before, and what holds neither. */
1383
+ const HEAD_SCAN_RE = /<!--[\s\S]*?-->|<script\b[\s\S]*?<\/script\s*>|<\/head\s*>|<style\b|<(?:meta|link)\b[^>]*>/gi;
1384
+ /** Where Animus's head tags go: after the head's charset and viewport
1385
+ * metas, so they cannot push the charset past the 1024 bytes a browser
1386
+ * sniffs for it, and before its first stylesheet; -1 without a `<head>`. */
1387
+ function headTagOffset(html) {
1388
+ const open = /<head\b[^>]*>/i.exec(html);
1389
+ if (open === null) return -1;
1390
+ const start = open.index + open[0].length;
1391
+ let offset = start;
1392
+ for (const match of html.slice(start).matchAll(HEAD_SCAN_RE)) {
1393
+ const [element] = match;
1394
+ const lower = element.toLowerCase();
1395
+ if (lower.startsWith("</head") || lower.startsWith("<style")) break;
1396
+ if (lower.startsWith("<link")) {
1397
+ if ((attribute(element, REL_RE)?.toLowerCase().split(/\s+/))?.includes("stylesheet")) break;
1398
+ } else if (lower.startsWith("<meta") && (/\scharset\s*=/i.test(element) || /\shttp-equiv\s*=\s*["']?content-type\b/i.test(element) || /\sname\s*=\s*["']?viewport\b/i.test(element))) offset = start + match.index + element.length;
1399
+ }
1400
+ return offset;
1401
+ }
1402
+ /** As `escape-html` escapes, which Vite applies to attribute values. */
1403
+ function escapeHtml(value) {
1404
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
1405
+ }
1406
+ /** A tag as Vite serializes it, `children` as written, except that an
1407
+ * undefined attribute is left out rather than written "undefined". */
1408
+ function serializeTag({ tag, attrs = {}, children }) {
1409
+ const serialized = Object.entries(attrs).map(([key, value]) => {
1410
+ if (value === true) return ` ${key}`;
1411
+ if (value === false || value === void 0) return "";
1412
+ return ` ${key}="${escapeHtml(value)}"`;
1413
+ }).join("");
1414
+ if (tag === "link" || tag === "meta" || tag === "base") return `<${tag}${serialized}>`;
1415
+ return `<${tag}${serialized}>${Array.isArray(children) ? "" : children ?? ""}</${tag}>`;
1416
+ }
1417
+ /**
1418
+ * The hook's result: the tags written after the charset and viewport metas,
1419
+ * each on its own line indented as the element it follows, so the charset
1420
+ * stays in the first 1024 bytes however long the tags are; as `head-prepend`
1421
+ * tags for a document without a `<head>`.
1422
+ */
1423
+ function injectHeadTags(html, tags) {
1424
+ const offset = headTagOffset(html);
1425
+ if (offset === -1) return tags;
1426
+ if (tags.length === 0) return html;
1427
+ const lineStart = html.lastIndexOf("\n", offset - 1) + 1;
1428
+ const indent = /^[ \t]*/.exec(html.slice(lineStart))[0];
1429
+ const inserted = tags.map((tag) => `\n${indent}${serializeTag(tag)}`).join("");
1430
+ return html.slice(0, offset) + inserted + html.slice(offset);
1431
+ }
1432
+ /** The preloads for the faces that ask for one, by the URL the stylesheet's
1433
+ * own `url()` gets, each marked as Animus's; one the document already
1434
+ * preloads is left out. */
1435
+ function fontPreloadTags(ctx, html) {
1436
+ const published = ctx.served?.fontPreloads ?? [];
1437
+ if (published.length === 0) return [];
1438
+ const existing = preloadedHrefs(html);
1439
+ return (0, _animus_ui_extract_pipeline.substituteFontPreloads)(published, ctx.assetUrlBySpecifier).map((preload) => ctx.isProd ? preload : {
1440
+ ...preload,
1441
+ href: ctx.devRootRelativeUrl(preload.href)
1442
+ }).filter(({ href }) => !existing.has(href)).map(({ href, type }) => {
1443
+ const attrs = {
1444
+ rel: "preload",
1445
+ href,
1446
+ as: "font"
1447
+ };
1448
+ if (type !== void 0) attrs.type = type;
1449
+ attrs.crossorigin = true;
1450
+ attrs[ANIMUS_PRELOAD_MARKER] = true;
1451
+ return {
1452
+ tag: "link",
1453
+ attrs,
1454
+ injectTo: "head-prepend"
1455
+ };
1456
+ });
1457
+ }
1286
1458
  /**
1287
- * Vite serializes one `head-prepend` bucket in array order, so the bootstrap
1288
- * script precedes the layer declaration and every stylesheet reference.
1459
+ * Animus's head tags in document order: the bootstrap script, so it runs
1460
+ * before first paint and ahead of every stylesheet, the layer declaration,
1461
+ * the font preloads, then the development bridge.
1289
1462
  */
1290
- function buildIndexHtmlTags(ctx) {
1463
+ function buildIndexHtmlTags(ctx, html = "") {
1291
1464
  const tags = [];
1292
1465
  const bootstrap = ctx.options.appearanceBootstrap;
1293
1466
  if (bootstrap?.code) tags.push({
@@ -1302,6 +1475,7 @@ function buildIndexHtmlTags(ctx) {
1302
1475
  children: ctx.layerDeclaration,
1303
1476
  injectTo: "head-prepend"
1304
1477
  });
1478
+ tags.push(...fontPreloadTags(ctx, html));
1305
1479
  if (!ctx.isProd) tags.push({
1306
1480
  tag: "script",
1307
1481
  attrs: {
@@ -1314,6 +1488,32 @@ function buildIndexHtmlTags(ctx) {
1314
1488
  return tags;
1315
1489
  }
1316
1490
  //#endregion
1491
+ //#region src/quarantine-plugin.ts
1492
+ /**
1493
+ * layergate's Vite plugin for the `quarantine` option, loaded on first use:
1494
+ * layergate is an optional peer.
1495
+ */
1496
+ function quarantinePlugin(options, layers) {
1497
+ const order = (0, _animus_ui_extract_pipeline.withVendorLayer)(layers);
1498
+ let plugin;
1499
+ return {
1500
+ order,
1501
+ plugin: () => plugin ??= load(options, order)
1502
+ };
1503
+ }
1504
+ async function load(options, order) {
1505
+ let layergate;
1506
+ try {
1507
+ ({layergate} = await import("@animus-ui/layergate/vite"));
1508
+ } catch (cause) {
1509
+ throw (0, _animus_ui_extract_pipeline.layergateMissing)("@animus-ui/vite-plugin", cause);
1510
+ }
1511
+ return layergate({
1512
+ ...(0, _animus_ui_extract_pipeline.layergateOptions)(options, order),
1513
+ declaredBy: VIRTUAL_CSS_ID
1514
+ });
1515
+ }
1516
+ //#endregion
1317
1517
  //#region src/virtual-modules.ts
1318
1518
  /**
1319
1519
  * Keyed on the whole option record: two instances sharing a key means the
@@ -1427,6 +1627,27 @@ if (import.meta.hot) {
1427
1627
  return null;
1428
1628
  }
1429
1629
  //#endregion
1630
+ //#region src/watch-ignored.ts
1631
+ /** A path as a glob that matches only itself. Each special character
1632
+ * goes in a bracket: the watcher turns every backslash in an ignored
1633
+ * glob into a slash, so a backslash escape would split the path. */
1634
+ function literal(path) {
1635
+ return path.replace(/\\/g, "/").replace(/[()[\]{}*?!+@|]/g, "[$&]");
1636
+ }
1637
+ /**
1638
+ * `server.watch.ignored` entries that watch installed kits. Vite always
1639
+ * ignores every `node_modules` path, where an installed kit lives, and
1640
+ * watches what a negated glob names. Each glob names a kit root and the
1641
+ * paths under it that ingestion would read: a segment holding an excluded
1642
+ * name, the kit's own `node_modules` among them, stays ignored, so the
1643
+ * watcher never walks into it. The segment test is a regex lookahead,
1644
+ * which picomatch keeps as written.
1645
+ */
1646
+ function kitWatchNegations(roots) {
1647
+ const excluded = _animus_ui_extract_pipeline.PACKAGE_SRC_EXCLUDES.map((name) => `*${literal(name)}`).join("|");
1648
+ return roots.map((root) => `!${literal(root)}*(/(?!${excluded})*)`);
1649
+ }
1650
+ //#endregion
1430
1651
  //#region src/index.ts
1431
1652
  async function sourceKitConfig(rootDir, systemPath, engine) {
1432
1653
  const kits = /* @__PURE__ */ new Map();
@@ -1438,45 +1659,73 @@ async function sourceKitConfig(rootDir, systemPath, engine) {
1438
1659
  exclude: names,
1439
1660
  include: [...kits.values()].flatMap((kit) => kit.dependencies.map((dependency) => `${kit.name} > ${dependency}`))
1440
1661
  },
1441
- ssr: { noExternal: names }
1662
+ ssr: { noExternal: names },
1663
+ roots: [...kits.values()].map((kit) => kit.root)
1442
1664
  };
1443
1665
  }
1666
+ /** The URL a build's root is served at, for resolving a page's hrefs: an
1667
+ * absolute `base`, a root-relative one on a stand-in origin, or the root
1668
+ * for a relative one, which every page resolves from its own location. */
1669
+ function servedBase(base) {
1670
+ const origin = "https://animus.invalid/";
1671
+ if (/^[a-z][a-z\d+.-]*:/i.test(base)) return new URL(base.replace(/\/?$/, "/"));
1672
+ if (base.startsWith("/")) return new URL(base.replace(/\/?$/, "/"), origin);
1673
+ return new URL(origin);
1674
+ }
1444
1675
  function animusExtract(options) {
1445
1676
  (0, _animus_ui_extract_pipeline.assertSupportedEngine)(options.engine);
1446
- (0, _animus_ui_extract_pipeline.assertKnownOptionKeys)(options, ["verify", "appearanceBootstrap"], [{
1677
+ (0, _animus_ui_extract_pipeline.assertKnownOptionKeys)(options, [
1678
+ "verify",
1679
+ "appearanceBootstrap",
1680
+ "quarantine"
1681
+ ], [{
1447
1682
  key: "root",
1448
1683
  reason: "the Vite driver derives its root from the resolved Vite config"
1449
1684
  }], {
1450
1685
  onUnknownKey: "warn",
1451
1686
  warn: (message) => console.warn(`[animus-extract] ${message}`)
1452
1687
  });
1453
- const ctx = new PluginContext(options);
1688
+ const vendor = options.quarantine ? quarantinePlugin(options.quarantine, options.layers) : null;
1689
+ const ctx = new PluginContext(vendor ? {
1690
+ ...options,
1691
+ layers: vendor.order
1692
+ } : options);
1454
1693
  return {
1455
1694
  name: "animus-extract",
1456
1695
  enforce: "pre",
1457
1696
  async config(userConfig, env) {
1697
+ const vendorConfig = await (await vendor?.plugin())?.config.call(this, userConfig, env);
1698
+ const withVendor = (own) => vendorConfig ? (0, vite.mergeConfig)(own, vendorConfig) : own;
1458
1699
  const { mode } = (0, _animus_ui_extract_pipeline.resolveMode)(options.mode, () => env.command === "build" ? "production" : "development");
1459
1700
  const define = { __ANIMUS_DEV__: mode === "development" };
1460
1701
  const root = (0, path.resolve)(userConfig.root ?? process.cwd());
1461
1702
  const kits = await sourceKitConfig(root, (0, path.resolve)(root, options.system), ctx.kitEngine());
1462
- return kits ? {
1703
+ if (!kits) return withVendor({ define });
1704
+ const { roots, ...config } = kits;
1705
+ const watched = env.command === "serve" && userConfig.server?.watch !== null ? { server: { watch: { ignored: kitWatchNegations(roots) } } } : {};
1706
+ return withVendor({
1463
1707
  define,
1464
- ...kits
1465
- } : { define };
1708
+ ...config,
1709
+ ...watched
1710
+ });
1466
1711
  },
1467
- configureServer(server) {
1712
+ async configureServer(server) {
1713
+ await (await vendor?.plugin())?.configureServer.call(this, server);
1468
1714
  ctx.devServer = server;
1469
1715
  ctx.registerSystemWatchPaths();
1470
1716
  },
1471
- configResolved(config) {
1717
+ async configResolved(config) {
1472
1718
  applyResolvedConfig(ctx, config);
1719
+ (await vendor?.plugin())?.configResolved.call(this, config);
1473
1720
  },
1474
- buildEnd() {
1721
+ async buildEnd() {
1722
+ (await vendor?.plugin())?.buildEnd.call(this);
1475
1723
  if (!ctx.builds.end(this.environment ?? ctx.builds.single)) return;
1476
1724
  cancelSystemReload(ctx);
1477
1725
  ctx.close();
1478
1726
  },
1479
1727
  async buildStart() {
1728
+ await (await vendor?.plugin())?.buildStart.call(this);
1480
1729
  await ctx.builds.open(this.environment ?? ctx.builds.single);
1481
1730
  await runBuildStart(ctx, async (specifier, importer) => {
1482
1731
  return (await this.resolve(specifier, importer))?.id ?? null;
@@ -1486,7 +1735,9 @@ function animusExtract(options) {
1486
1735
  source
1487
1736
  }) : void 0);
1488
1737
  },
1489
- async resolveId(id, importer) {
1738
+ async resolveId(id, importer, resolveOptions) {
1739
+ const owned = await (await vendor?.plugin())?.resolveId.call(this, id, importer, resolveOptions);
1740
+ if (owned) return owned;
1490
1741
  const virtual = resolveVirtualId(ctx, id);
1491
1742
  if (virtual !== null || !id.startsWith(".") || importer === void 0) return virtual;
1492
1743
  const importerPath = importer.split("?")[0];
@@ -1498,20 +1749,43 @@ function animusExtract(options) {
1498
1749
  moduleSideEffects: true
1499
1750
  };
1500
1751
  },
1501
- load(id) {
1502
- return loadVirtualModule(ctx, id);
1752
+ async load(id) {
1753
+ const layergate = await vendor?.plugin();
1754
+ const loaded = await layergate?.load.call(this, id);
1755
+ if (loaded) return loaded;
1756
+ const own = loadVirtualModule(ctx, id);
1757
+ if (layergate && id === RESOLVED_CSS_ID && own !== null) return await layergate.stylesheetHead.call(this, id) + own;
1758
+ return own;
1503
1759
  },
1504
- transform(code, id) {
1760
+ async transform(code, id) {
1761
+ if (vendor && /\.css(?:$|\?)/.test(id)) return (await vendor.plugin()).transform.call(this, code, id);
1505
1762
  return transformSource(ctx, code, id);
1506
1763
  },
1764
+ generateBundle: {
1765
+ order: "post",
1766
+ handler(_output, bundle) {
1767
+ if (!ctx.served?.fontPreloads.length) return;
1768
+ for (const file of Object.values(bundle)) {
1769
+ if (file.type !== "asset" || !file.fileName.endsWith(".html")) continue;
1770
+ const html = Buffer.from(file.source).toString("utf8");
1771
+ if (!html.includes("data-animus-preload")) continue;
1772
+ for (const href of duplicatedFontPreloads(html, new URL(file.fileName, servedBase(ctx.base)).href)) ctx.warn(`[animus-extract] ${file.fileName} preloads ${href} twice: Animus's font preload and another preload tag, written in the page or added by a plugin whose index.html hook runs after Animus's, so the browser may fetch it twice. Remove the other preload; Animus's names the URL the stylesheet's url() loads.`);
1773
+ }
1774
+ }
1775
+ },
1507
1776
  transformIndexHtml: {
1508
1777
  order: "pre",
1509
- handler() {
1510
- return buildIndexHtmlTags(ctx);
1778
+ handler(html) {
1779
+ return injectHeadTags(html, buildIndexHtmlTags(ctx, html));
1511
1780
  }
1512
1781
  },
1513
1782
  async hotUpdate(hmr) {
1514
- return handleHotUpdate(ctx, this.environment, hmr);
1783
+ const vendorModules = (await vendor?.plugin())?.hotUpdate.call(this, hmr);
1784
+ const next = vendorModules ? {
1785
+ ...hmr,
1786
+ modules: vendorModules
1787
+ } : hmr;
1788
+ return await handleHotUpdate(ctx, this.environment, next) ?? vendorModules;
1515
1789
  }
1516
1790
  };
1517
1791
  }
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
- import type { ClosedDynamicImport, DiagnosticLevels, StaticCssConfig } from '@animus-ui/extract/pipeline';
1
+ import type { AnimusQuarantineOptions, ClosedDynamicImport, FontPreloadEntry, DiagnosticLevels, StaticCssConfig } from '@animus-ui/extract/pipeline';
2
2
  import type { Plugin } from 'vite';
3
3
  export { discoverFiles } from '@animus-ui/extract/pipeline';
4
+ export type { AnimusQuarantineOptions } from '@animus-ui/extract/pipeline';
4
5
  export interface AnimusExtractOptions {
5
6
  /** Path to a module exporting a SystemInstance from `@animus-ui/system`. */
6
7
  system: string;
@@ -25,6 +26,11 @@ export interface AnimusExtractOptions {
25
26
  * still load any module.
26
27
  */
27
28
  closedDynamicImports?: ClosedDynamicImport[];
29
+ /** The font faces this app preloads, in place of each typed face's own
30
+ * `preload` flag, so a face declared once for every product preloads only
31
+ * where the app renders it: family names, or `{ family, weight?, style? }`
32
+ * (see `FontPreloadEntry`). `[]` preloads none. */
33
+ fontPreloads?: FontPreloadEntry[];
28
34
  /**
29
35
  * Substrings, or globs when `*`/`?` present. Replaces the defaults `dist`,
30
36
  * `.test.`, `.spec.`; `node_modules`, `.next`, `.animus` always apply.
@@ -107,6 +113,15 @@ export interface AnimusExtractOptions {
107
113
  code: string;
108
114
  cspHash: string;
109
115
  };
116
+ /**
117
+ * Load third-party stylesheets into `anm-vendor`, a layer below every
118
+ * Animus layer, so their normal rules never beat Animus's. Needs
119
+ * `@animus-ui/layergate`. `sheets` are delivered with Animus's stylesheet,
120
+ * ahead of it; `dependencies` also covers `.css` your modules import from
121
+ * packages; `optOut` keeps a file's own rules outside the layer. App CSS
122
+ * and CSS Modules are left alone.
123
+ */
124
+ quarantine?: AnimusQuarantineOptions;
110
125
  }
111
126
  export declare function animusExtract(options: AnimusExtractOptions): Plugin;
112
127
  export default animusExtract;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EACV,mBAAmB,EACnB,gBAAgB,EAEhB,eAAe,EAChB,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEnC,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AA4C5D,MAAM,WAAW,oBAAoB;IACnC,4EAA4E;IAC5E,MAAM,EAAE,MAAM,CAAC;IACf;;;0DAGsD;IACtD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;OAOG;IACH,oBAAoB,CAAC,EAAE,mBAAmB,EAAE,CAAC;IAC7C;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAC/B;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;+CAE2C;IAC3C,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;IAC5B;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC5B,sEAAsE;IACtE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;OAGG;IACH,IAAI,CAAC,EAAE,aAAa,GAAG,YAAY,CAAC;IACpC;;sDAEkD;IAClD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;gBAEY;IACZ,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;;OAGG;IACH,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB;;;OAGG;IACH,MAAM,CAAC,EAAE,IAAI,CAAC;IACd;;;OAGG;IACH,mBAAmB,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CACzD;AAMD,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,MAAM,CAkInE;eAEc,aAAa"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EACV,uBAAuB,EACvB,mBAAmB,EACnB,gBAAgB,EAChB,gBAAgB,EAEhB,eAAe,EAChB,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,MAAM,EAAc,MAAM,MAAM,CAAC;AAE/C,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,YAAY,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AA+C3E,MAAM,WAAW,oBAAoB;IACnC,4EAA4E;IAC5E,MAAM,EAAE,MAAM,CAAC;IACf;;;0DAGsD;IACtD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;OAOG;IACH,oBAAoB,CAAC,EAAE,mBAAmB,EAAE,CAAC;IAC7C;;;wDAGoD;IACpD,YAAY,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAClC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAC/B;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;+CAE2C;IAC3C,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;IAC5B;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC5B,sEAAsE;IACtE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;OAGG;IACH,IAAI,CAAC,EAAE,aAAa,GAAG,YAAY,CAAC;IACpC;;sDAEkD;IAClD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;gBAEY;IACZ,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;;OAGG;IACH,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB;;;OAGG;IACH,MAAM,CAAC,EAAE,IAAI,CAAC;IACd;;;OAGG;IACH,mBAAmB,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACxD;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,uBAAuB,CAAC;CACtC;AAiBD,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,MAAM,CA4MnE;eAEc,aAAa"}
@@ -0,0 +1,10 @@
1
+ import type { AnimusQuarantineOptions } from '@animus-ui/extract/pipeline';
2
+ import type { LayergatePlugin } from '@animus-ui/layergate/vite';
3
+ export interface Quarantine {
4
+ /** The layer order, `anm-vendor` first, Animus's stylesheet declares. */
5
+ order: string[];
6
+ /** layergate's plugin, whose hooks Animus's own run. */
7
+ plugin(): Promise<LayergatePlugin>;
8
+ }
9
+ export declare function quarantinePlugin(options: AnimusQuarantineOptions, layers: readonly string[] | undefined): Quarantine;
10
+ //# sourceMappingURL=quarantine-plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quarantine-plugin.d.ts","sourceRoot":"","sources":["../src/quarantine-plugin.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AAC3E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAEjE,MAAM,WAAW,UAAU;IACzB,yEAAyE;IACzE,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,wDAAwD;IACxD,MAAM,IAAI,OAAO,CAAC,eAAe,CAAC,CAAC;CACpC;AAED,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,uBAAuB,EAChC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,GACpC,UAAU,CAIZ"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * `server.watch.ignored` entries that watch installed kits. Vite always
3
+ * ignores every `node_modules` path, where an installed kit lives, and
4
+ * watches what a negated glob names. Each glob names a kit root and the
5
+ * paths under it that ingestion would read: a segment holding an excluded
6
+ * name, the kit's own `node_modules` among them, stays ignored, so the
7
+ * watcher never walks into it. The segment test is a regex lookahead,
8
+ * which picomatch keeps as written.
9
+ */
10
+ export declare function kitWatchNegations(roots: readonly string[]): string[];
11
+ //# sourceMappingURL=watch-ignored.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch-ignored.d.ts","sourceRoot":"","sources":["../src/watch-ignored.ts"],"names":[],"mappings":"AASA;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAKpE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@animus-ui/vite-plugin",
3
- "version": "0.1.32",
3
+ "version": "0.1.36",
4
4
  "description": "Animus static CSS extraction Vite plugin",
5
5
  "keywords": [
6
6
  "animus",
@@ -39,16 +39,23 @@
39
39
  "compile": "bun check"
40
40
  },
41
41
  "dependencies": {
42
- "@animus-ui/extract": "0.1.32",
42
+ "@animus-ui/extract": "0.1.36",
43
43
  "browserslist": "^4.28.8",
44
44
  "lightningcss": "^1.33.0"
45
45
  },
46
46
  "devDependencies": {
47
- "@animus-ui/assertions": "0.1.32"
47
+ "@animus-ui/assertions": "0.1.36",
48
+ "@animus-ui/layergate": "0.1.36"
48
49
  },
49
50
  "peerDependencies": {
51
+ "@animus-ui/layergate": "0.1.36",
50
52
  "vite": ">=8 <9"
51
53
  },
54
+ "peerDependenciesMeta": {
55
+ "@animus-ui/layergate": {
56
+ "optional": true
57
+ }
58
+ },
52
59
  "engines": {
53
60
  "node": ">=22.12.0"
54
61
  }