@cascivo/vite-plugin 0.0.0 → 0.1.0

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
@@ -57,6 +57,27 @@ cascivo layers:
57
57
  cascivo.theme, cascivo.blocks, cascivo.override;
58
58
  ```
59
59
 
60
+ ## SSR: `cascivoSsr()`
61
+
62
+ The published `@cascivo/react` bundle ships per-component CSS as static
63
+ side-effect imports. Bundlers resolve these; a bare server-side ESM loader (Node
64
+ native, workerd) does not and throws `Unknown file extension ".css"` during SSR.
65
+ `cascivoSsr()` marks every `@cascivo/*` package `ssr.noExternal`, so Vite
66
+ processes their CSS imports instead of the runtime loading them raw:
67
+
68
+ ```ts
69
+ // vite.config.ts — for TanStack Start, vite-ssr, Remix on Vite, workerd, …
70
+ import { cascivoSsr } from '@cascivo/vite-plugin'
71
+
72
+ export default defineConfig({
73
+ plugins: [cascivoSsr()],
74
+ })
75
+ ```
76
+
77
+ You still import `@cascivo/react/styles.css` + a theme once in your root entry.
78
+ `cascivoSsr()` composes with `cascivoLayers()` in the same `plugins` array. Full
79
+ recipe: [docs/USING-WITH-VITE-SSR.md](https://github.com/cascivo/cascivo/blob/main/docs/USING-WITH-VITE-SSR.md).
80
+
60
81
  ## Scope
61
82
 
62
83
  - **Vite only.** A webpack/Next.js loader is not included.
@@ -66,7 +87,8 @@ cascivo layers:
66
87
 
67
88
  ## API
68
89
 
69
- - `cascivoLayers({ imports })` — the Vite plugin.
90
+ - `cascivoLayers({ imports })` — the vendor CSS-layering plugin.
91
+ - `cascivoSsr()` — sets `ssr.noExternal` for every `@cascivo/*` package (SSR/workerd).
70
92
  - `wrapCssInLayer(source, layer?)` — the underlying pure transform, exported for testing.
71
93
 
72
94
  ## Install
package/dist/index.d.mts CHANGED
@@ -22,6 +22,11 @@ interface VitePlugin {
22
22
  code: string;
23
23
  map: null;
24
24
  } | null | undefined;
25
+ config?: () => {
26
+ ssr: {
27
+ noExternal: RegExp[];
28
+ };
29
+ };
25
30
  }
26
31
  interface CascivoLayersOptions {
27
32
  /**
@@ -46,6 +51,27 @@ declare function wrapCssInLayer(source: string, layer?: string): string;
46
51
  * own CSS pipeline processes it.
47
52
  */
48
53
  declare function cascivoLayers(options: CascivoLayersOptions): VitePlugin;
54
+ /**
55
+ * Matches every `@cascivo/*` package specifier (`@cascivo/react`,
56
+ * `@cascivo/react/styles.css`, `@cascivo/charts`, …).
57
+ */
58
+ declare const CASCIVO_PACKAGE_RE: RegExp;
59
+ /**
60
+ * Vite plugin that marks all `@cascivo/*` packages `ssr.noExternal`.
61
+ *
62
+ * The published `@cascivo/react` bundle ships per-component CSS as static
63
+ * side-effect imports (`import './button.css'`). A bundler resolves those at
64
+ * build time, but a bare server-side ESM loader (Node native, workerd) does not
65
+ * and throws `Unknown file extension ".css"`. Setting `ssr.noExternal` tells
66
+ * Vite to process these packages — CSS imports included — during SSR instead of
67
+ * leaving them for the runtime to load raw.
68
+ *
69
+ * The returned partial config is deep-merged by Vite (which concatenates
70
+ * `ssr.noExternal` arrays), so this composes with any existing `noExternal` the
71
+ * user set. Use for TanStack Start, vite-ssr, Remix on Vite, Astro SSR, and
72
+ * workerd/Cloudflare targets. See docs/USING-WITH-VITE-SSR.md.
73
+ */
74
+ declare function cascivoSsr(): VitePlugin;
49
75
  //#endregion
50
- export { CascivoLayersOptions, VitePlugin, cascivoLayers, cascivoLayers as default, wrapCssInLayer };
76
+ export { CASCIVO_PACKAGE_RE, CascivoLayersOptions, VitePlugin, cascivoLayers, cascivoLayers as default, cascivoSsr, wrapCssInLayer };
51
77
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;AAiBA;;;;;;;;;;;;AAG+D;AAG/D;;UANiB,UAAA;EACf,IAAA;EACA,OAAA;EACA,SAAA,IAAa,IAAA,UAAc,EAAA;IAAiB,IAAA;IAAc,GAAA;EAAA;AAAA;AAAA,UAG3C,oBAAA;EAuDY;;;;;;;EA/C3B,OAAA,EAAS,MAAM;AAAA;;;;;;;iBAoBD,cAAA,CAAe,MAAA,UAAgB,KAAgB;;;;;;iBA2B/C,aAAA,CAAc,OAAA,EAAS,oBAAA,GAAuB,UAAU"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;AAiBA;;;;;;;;;;;;;;;UAAiB,UAAA;EACf,IAAA;EACA,OAAA;EACA,SAAA,IAAa,IAAA,UAAc,EAAA;IAAiB,IAAA;IAAc,GAAA;EAAA;EAC1D,MAAA;IAAiB,GAAA;MAAO,UAAA,EAAY,MAAM;IAAA;EAAA;AAAA;AAAA,UAG3B,oBAAA;;;;;;;AAuDuD;EA/CtE,OAAA,EAAS,MAAM;AAAA;;;AAkE8B;AAiB/C;;;iBA/DgB,cAAA,CAAe,MAAA,UAAgB,KAAgB;AA+DvB;;;;;AAAA,iBApCxB,aAAA,CAAc,OAAA,EAAS,oBAAA,GAAuB,UAAU;;;;;cAmB3D,kBAAA,EAAkB,MAAgB;;;;;;;;;;;;;;;;iBAiB/B,UAAA,IAAc,UAAU"}
package/dist/index.mjs CHANGED
@@ -56,7 +56,35 @@ function cascivoLayers(options) {
56
56
  }
57
57
  };
58
58
  }
59
+ /**
60
+ * Matches every `@cascivo/*` package specifier (`@cascivo/react`,
61
+ * `@cascivo/react/styles.css`, `@cascivo/charts`, …).
62
+ */
63
+ const CASCIVO_PACKAGE_RE = /^@cascivo\//;
64
+ /**
65
+ * Vite plugin that marks all `@cascivo/*` packages `ssr.noExternal`.
66
+ *
67
+ * The published `@cascivo/react` bundle ships per-component CSS as static
68
+ * side-effect imports (`import './button.css'`). A bundler resolves those at
69
+ * build time, but a bare server-side ESM loader (Node native, workerd) does not
70
+ * and throws `Unknown file extension ".css"`. Setting `ssr.noExternal` tells
71
+ * Vite to process these packages — CSS imports included — during SSR instead of
72
+ * leaving them for the runtime to load raw.
73
+ *
74
+ * The returned partial config is deep-merged by Vite (which concatenates
75
+ * `ssr.noExternal` arrays), so this composes with any existing `noExternal` the
76
+ * user set. Use for TanStack Start, vite-ssr, Remix on Vite, Astro SSR, and
77
+ * workerd/Cloudflare targets. See docs/USING-WITH-VITE-SSR.md.
78
+ */
79
+ function cascivoSsr() {
80
+ return {
81
+ name: "cascivo:ssr-no-external",
82
+ config() {
83
+ return { ssr: { noExternal: [CASCIVO_PACKAGE_RE] } };
84
+ }
85
+ };
86
+ }
59
87
  //#endregion
60
- export { cascivoLayers, cascivoLayers as default, wrapCssInLayer };
88
+ export { CASCIVO_PACKAGE_RE, cascivoLayers, cascivoLayers as default, cascivoSsr, wrapCssInLayer };
61
89
 
62
90
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @cascivo/vite-plugin — layer JS-imported vendor CSS.\n *\n * The native recipe (`@import url('lib.css') layer(vendor)` from a CSS file) is\n * the primary, tooling-free way to tame third-party stylesheets — see\n * docs/THIRD-PARTY-CSS.md. What native CSS cannot do is layer a stylesheet\n * imported from JavaScript (`import 'lib/styles.css'`), which is how most JS\n * libraries ship. This plugin closes exactly that gap: it wraps configured\n * node_modules `.css` imports into a low-priority `@layer` at build time so the\n * cascivo layers always win.\n */\n\n/**\n * The subset of Vite's `Plugin` interface this package implements. Declared\n * structurally so the package doesn't require `vite`'s types to be resolvable;\n * the returned object is assignable to Vite's `Plugin`.\n */\nexport interface VitePlugin {\n name: string\n enforce?: 'pre' | 'post'\n transform?: (code: string, id: string) => { code: string; map: null } | null | undefined\n}\n\nexport interface CascivoLayersOptions {\n /**\n * Map a stylesheet's module id (matched against the tail of the resolved id,\n * e.g. `\"recharts/styles.css\"`) to the cascade layer it should live in\n * (typically `\"vendor\"`). The layer must be declared before the cascivo\n * layers in your app's order statement — the scaffold from `cascivo create`\n * already includes a `vendor` slot.\n */\n imports: Record<string, string>\n}\n\nconst IMPORT_RE = /@import\\s+[^;]+;/g\nconst CHARSET_RE = /^\\s*@charset\\s+[^;]+;/i\n\n/** Add `layer(<layer>)` to an `@import` statement that isn't already layered. */\nfunction layerizeImport(stmt: string, layer: string): string {\n if (/\\blayer\\b/.test(stmt)) return stmt\n const head = stmt.match(/@import\\s+(?:url\\([^)]*\\)|\"[^\"]*\"|'[^']*')/i)\n if (!head) return stmt\n return stmt.replace(head[0], `${head[0]} layer(${layer})`)\n}\n\n/**\n * Wrap a stylesheet's rules in `@layer <layer> { … }`. Because an `@import` may\n * not appear inside a layer block, top-level `@import`s are hoisted out and\n * rewritten to carry `layer(<layer>)` themselves; a leading `@charset` is kept\n * first. Pure string-in/string-out.\n */\nexport function wrapCssInLayer(source: string, layer = 'vendor'): string {\n let charset = ''\n let body = source\n const cm = body.match(CHARSET_RE)\n if (cm) {\n charset = `${cm[0].trim()}\\n`\n body = body.slice(cm[0].length)\n }\n const imports: string[] = []\n body = body.replace(IMPORT_RE, (stmt) => {\n imports.push(layerizeImport(stmt.trim(), layer))\n return ''\n })\n const importsBlock = imports.length > 0 ? `${imports.join('\\n')}\\n` : ''\n return `${charset}${importsBlock}@layer ${layer} {\\n${body.trim()}\\n}\\n`\n}\n\n/** Normalize a module id: drop the query/hash and Windows backslashes. */\nfunction normalizeId(id: string): string {\n return (id.split('?')[0] ?? id).replace(/\\\\/g, '/')\n}\n\n/**\n * Vite plugin that wraps configured node_modules stylesheets in a cascade\n * `@layer`. Runs `enforce: 'pre'` so it transforms the raw CSS before Vite's\n * own CSS pipeline processes it.\n */\nexport function cascivoLayers(options: CascivoLayersOptions): VitePlugin {\n const entries = Object.entries(options?.imports ?? {})\n return {\n name: 'cascivo:vendor-layers',\n enforce: 'pre',\n transform(code, id) {\n const clean = normalizeId(id)\n if (!clean.endsWith('.css') || !clean.includes('node_modules')) return null\n const match = entries.find(([key]) => clean.endsWith(key))\n if (!match) return null\n return { code: wrapCssInLayer(code, match[1]), map: null }\n },\n }\n}\n\nexport default cascivoLayers\n"],"mappings":";AAkCA,MAAM,YAAY;AAClB,MAAM,aAAa;;AAGnB,SAAS,eAAe,MAAc,OAAuB;CAC3D,IAAI,YAAY,KAAK,IAAI,GAAG,OAAO;CACnC,MAAM,OAAO,KAAK,MAAM,6CAA6C;CACrE,IAAI,CAAC,MAAM,OAAO;CAClB,OAAO,KAAK,QAAQ,KAAK,IAAI,GAAG,KAAK,GAAG,SAAS,MAAM,EAAE;AAC3D;;;;;;;AAQA,SAAgB,eAAe,QAAgB,QAAQ,UAAkB;CACvE,IAAI,UAAU;CACd,IAAI,OAAO;CACX,MAAM,KAAK,KAAK,MAAM,UAAU;CAChC,IAAI,IAAI;EACN,UAAU,GAAG,GAAG,EAAE,CAAC,KAAK,EAAE;EAC1B,OAAO,KAAK,MAAM,GAAG,EAAE,CAAC,MAAM;CAChC;CACA,MAAM,UAAoB,CAAC;CAC3B,OAAO,KAAK,QAAQ,YAAY,SAAS;EACvC,QAAQ,KAAK,eAAe,KAAK,KAAK,GAAG,KAAK,CAAC;EAC/C,OAAO;CACT,CAAC;CACD,MAAM,eAAe,QAAQ,SAAS,IAAI,GAAG,QAAQ,KAAK,IAAI,EAAE,MAAM;CACtE,OAAO,GAAG,UAAU,aAAa,SAAS,MAAM,MAAM,KAAK,KAAK,EAAE;AACpE;;AAGA,SAAS,YAAY,IAAoB;CACvC,QAAQ,GAAG,MAAM,GAAG,CAAC,CAAC,MAAM,GAAA,CAAI,QAAQ,OAAO,GAAG;AACpD;;;;;;AAOA,SAAgB,cAAc,SAA2C;CACvE,MAAM,UAAU,OAAO,QAAQ,SAAS,WAAW,CAAC,CAAC;CACrD,OAAO;EACL,MAAM;EACN,SAAS;EACT,UAAU,MAAM,IAAI;GAClB,MAAM,QAAQ,YAAY,EAAE;GAC5B,IAAI,CAAC,MAAM,SAAS,MAAM,KAAK,CAAC,MAAM,SAAS,cAAc,GAAG,OAAO;GACvE,MAAM,QAAQ,QAAQ,MAAM,CAAC,SAAS,MAAM,SAAS,GAAG,CAAC;GACzD,IAAI,CAAC,OAAO,OAAO;GACnB,OAAO;IAAE,MAAM,eAAe,MAAM,MAAM,EAAE;IAAG,KAAK;GAAK;EAC3D;CACF;AACF"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @cascivo/vite-plugin — layer JS-imported vendor CSS.\n *\n * The native recipe (`@import url('lib.css') layer(vendor)` from a CSS file) is\n * the primary, tooling-free way to tame third-party stylesheets — see\n * docs/THIRD-PARTY-CSS.md. What native CSS cannot do is layer a stylesheet\n * imported from JavaScript (`import 'lib/styles.css'`), which is how most JS\n * libraries ship. This plugin closes exactly that gap: it wraps configured\n * node_modules `.css` imports into a low-priority `@layer` at build time so the\n * cascivo layers always win.\n */\n\n/**\n * The subset of Vite's `Plugin` interface this package implements. Declared\n * structurally so the package doesn't require `vite`'s types to be resolvable;\n * the returned object is assignable to Vite's `Plugin`.\n */\nexport interface VitePlugin {\n name: string\n enforce?: 'pre' | 'post'\n transform?: (code: string, id: string) => { code: string; map: null } | null | undefined\n config?: () => { ssr: { noExternal: RegExp[] } }\n}\n\nexport interface CascivoLayersOptions {\n /**\n * Map a stylesheet's module id (matched against the tail of the resolved id,\n * e.g. `\"recharts/styles.css\"`) to the cascade layer it should live in\n * (typically `\"vendor\"`). The layer must be declared before the cascivo\n * layers in your app's order statement — the scaffold from `cascivo create`\n * already includes a `vendor` slot.\n */\n imports: Record<string, string>\n}\n\nconst IMPORT_RE = /@import\\s+[^;]+;/g\nconst CHARSET_RE = /^\\s*@charset\\s+[^;]+;/i\n\n/** Add `layer(<layer>)` to an `@import` statement that isn't already layered. */\nfunction layerizeImport(stmt: string, layer: string): string {\n if (/\\blayer\\b/.test(stmt)) return stmt\n const head = stmt.match(/@import\\s+(?:url\\([^)]*\\)|\"[^\"]*\"|'[^']*')/i)\n if (!head) return stmt\n return stmt.replace(head[0], `${head[0]} layer(${layer})`)\n}\n\n/**\n * Wrap a stylesheet's rules in `@layer <layer> { … }`. Because an `@import` may\n * not appear inside a layer block, top-level `@import`s are hoisted out and\n * rewritten to carry `layer(<layer>)` themselves; a leading `@charset` is kept\n * first. Pure string-in/string-out.\n */\nexport function wrapCssInLayer(source: string, layer = 'vendor'): string {\n let charset = ''\n let body = source\n const cm = body.match(CHARSET_RE)\n if (cm) {\n charset = `${cm[0].trim()}\\n`\n body = body.slice(cm[0].length)\n }\n const imports: string[] = []\n body = body.replace(IMPORT_RE, (stmt) => {\n imports.push(layerizeImport(stmt.trim(), layer))\n return ''\n })\n const importsBlock = imports.length > 0 ? `${imports.join('\\n')}\\n` : ''\n return `${charset}${importsBlock}@layer ${layer} {\\n${body.trim()}\\n}\\n`\n}\n\n/** Normalize a module id: drop the query/hash and Windows backslashes. */\nfunction normalizeId(id: string): string {\n return (id.split('?')[0] ?? id).replace(/\\\\/g, '/')\n}\n\n/**\n * Vite plugin that wraps configured node_modules stylesheets in a cascade\n * `@layer`. Runs `enforce: 'pre'` so it transforms the raw CSS before Vite's\n * own CSS pipeline processes it.\n */\nexport function cascivoLayers(options: CascivoLayersOptions): VitePlugin {\n const entries = Object.entries(options?.imports ?? {})\n return {\n name: 'cascivo:vendor-layers',\n enforce: 'pre',\n transform(code, id) {\n const clean = normalizeId(id)\n if (!clean.endsWith('.css') || !clean.includes('node_modules')) return null\n const match = entries.find(([key]) => clean.endsWith(key))\n if (!match) return null\n return { code: wrapCssInLayer(code, match[1]), map: null }\n },\n }\n}\n\n/**\n * Matches every `@cascivo/*` package specifier (`@cascivo/react`,\n * `@cascivo/react/styles.css`, `@cascivo/charts`, …).\n */\nexport const CASCIVO_PACKAGE_RE = /^@cascivo\\//\n\n/**\n * Vite plugin that marks all `@cascivo/*` packages `ssr.noExternal`.\n *\n * The published `@cascivo/react` bundle ships per-component CSS as static\n * side-effect imports (`import './button.css'`). A bundler resolves those at\n * build time, but a bare server-side ESM loader (Node native, workerd) does not\n * and throws `Unknown file extension \".css\"`. Setting `ssr.noExternal` tells\n * Vite to process these packages — CSS imports included — during SSR instead of\n * leaving them for the runtime to load raw.\n *\n * The returned partial config is deep-merged by Vite (which concatenates\n * `ssr.noExternal` arrays), so this composes with any existing `noExternal` the\n * user set. Use for TanStack Start, vite-ssr, Remix on Vite, Astro SSR, and\n * workerd/Cloudflare targets. See docs/USING-WITH-VITE-SSR.md.\n */\nexport function cascivoSsr(): VitePlugin {\n return {\n name: 'cascivo:ssr-no-external',\n config() {\n return { ssr: { noExternal: [CASCIVO_PACKAGE_RE] } }\n },\n }\n}\n\nexport default cascivoLayers\n"],"mappings":";AAmCA,MAAM,YAAY;AAClB,MAAM,aAAa;;AAGnB,SAAS,eAAe,MAAc,OAAuB;CAC3D,IAAI,YAAY,KAAK,IAAI,GAAG,OAAO;CACnC,MAAM,OAAO,KAAK,MAAM,6CAA6C;CACrE,IAAI,CAAC,MAAM,OAAO;CAClB,OAAO,KAAK,QAAQ,KAAK,IAAI,GAAG,KAAK,GAAG,SAAS,MAAM,EAAE;AAC3D;;;;;;;AAQA,SAAgB,eAAe,QAAgB,QAAQ,UAAkB;CACvE,IAAI,UAAU;CACd,IAAI,OAAO;CACX,MAAM,KAAK,KAAK,MAAM,UAAU;CAChC,IAAI,IAAI;EACN,UAAU,GAAG,GAAG,EAAE,CAAC,KAAK,EAAE;EAC1B,OAAO,KAAK,MAAM,GAAG,EAAE,CAAC,MAAM;CAChC;CACA,MAAM,UAAoB,CAAC;CAC3B,OAAO,KAAK,QAAQ,YAAY,SAAS;EACvC,QAAQ,KAAK,eAAe,KAAK,KAAK,GAAG,KAAK,CAAC;EAC/C,OAAO;CACT,CAAC;CACD,MAAM,eAAe,QAAQ,SAAS,IAAI,GAAG,QAAQ,KAAK,IAAI,EAAE,MAAM;CACtE,OAAO,GAAG,UAAU,aAAa,SAAS,MAAM,MAAM,KAAK,KAAK,EAAE;AACpE;;AAGA,SAAS,YAAY,IAAoB;CACvC,QAAQ,GAAG,MAAM,GAAG,CAAC,CAAC,MAAM,GAAA,CAAI,QAAQ,OAAO,GAAG;AACpD;;;;;;AAOA,SAAgB,cAAc,SAA2C;CACvE,MAAM,UAAU,OAAO,QAAQ,SAAS,WAAW,CAAC,CAAC;CACrD,OAAO;EACL,MAAM;EACN,SAAS;EACT,UAAU,MAAM,IAAI;GAClB,MAAM,QAAQ,YAAY,EAAE;GAC5B,IAAI,CAAC,MAAM,SAAS,MAAM,KAAK,CAAC,MAAM,SAAS,cAAc,GAAG,OAAO;GACvE,MAAM,QAAQ,QAAQ,MAAM,CAAC,SAAS,MAAM,SAAS,GAAG,CAAC;GACzD,IAAI,CAAC,OAAO,OAAO;GACnB,OAAO;IAAE,MAAM,eAAe,MAAM,MAAM,EAAE;IAAG,KAAK;GAAK;EAC3D;CACF;AACF;;;;;AAMA,MAAa,qBAAqB;;;;;;;;;;;;;;;;AAiBlC,SAAgB,aAAyB;CACvC,OAAO;EACL,MAAM;EACN,SAAS;GACP,OAAO,EAAE,KAAK,EAAE,YAAY,CAAC,kBAAkB,EAAE,EAAE;EACrD;CACF;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cascivo/vite-plugin",
3
- "version": "0.0.0",
3
+ "version": "0.1.0",
4
4
  "private": false,
5
5
  "description": "Vite plugin — wrap JS-imported third-party stylesheets into a low-priority CSS @layer",
6
6
  "keywords": [
package/readme.body.md CHANGED
@@ -39,6 +39,27 @@ cascivo layers:
39
39
  cascivo.theme, cascivo.blocks, cascivo.override;
40
40
  ```
41
41
 
42
+ ## SSR: `cascivoSsr()`
43
+
44
+ The published `@cascivo/react` bundle ships per-component CSS as static
45
+ side-effect imports. Bundlers resolve these; a bare server-side ESM loader (Node
46
+ native, workerd) does not and throws `Unknown file extension ".css"` during SSR.
47
+ `cascivoSsr()` marks every `@cascivo/*` package `ssr.noExternal`, so Vite
48
+ processes their CSS imports instead of the runtime loading them raw:
49
+
50
+ ```ts
51
+ // vite.config.ts — for TanStack Start, vite-ssr, Remix on Vite, workerd, …
52
+ import { cascivoSsr } from '@cascivo/vite-plugin'
53
+
54
+ export default defineConfig({
55
+ plugins: [cascivoSsr()],
56
+ })
57
+ ```
58
+
59
+ You still import `@cascivo/react/styles.css` + a theme once in your root entry.
60
+ `cascivoSsr()` composes with `cascivoLayers()` in the same `plugins` array. Full
61
+ recipe: [docs/USING-WITH-VITE-SSR.md](https://github.com/cascivo/cascivo/blob/main/docs/USING-WITH-VITE-SSR.md).
62
+
42
63
  ## Scope
43
64
 
44
65
  - **Vite only.** A webpack/Next.js loader is not included.
@@ -48,5 +69,6 @@ cascivo layers:
48
69
 
49
70
  ## API
50
71
 
51
- - `cascivoLayers({ imports })` — the Vite plugin.
72
+ - `cascivoLayers({ imports })` — the vendor CSS-layering plugin.
73
+ - `cascivoSsr()` — sets `ssr.noExternal` for every `@cascivo/*` package (SSR/workerd).
52
74
  - `wrapCssInLayer(source, layer?)` — the underlying pure transform, exported for testing.