diff --git a/README.md b/README.md
index 03b76b0..cc6a126 100644
--- a/README.md
+++ b/README.md
@@ -97,6 +97,41 @@ const sheet = css.make({
});
```
+### css.extend
+
+```tsx
+import { css } from "@swan-io/css";
+
+const input = css.extend({
+ colors: {
+ red: "#fa2c37",
+ blue: "#2c7fff",
+ green: "#00c950",
+ },
+});
+
+type CustomInput = typeof input;
+
+declare module "@swan-io/css" {
+ export interface Input extends CustomInput {}
+}
+```
+
+```tsx
+import "./theme";
+
+import { createRoot } from "react-dom/client";
+// …
+```
+
+```tsx
+const sheet = css.make(({ colors }) => ({
+ box: {
+ backgroundColor: colors.blue,
+ },
+}));
+```
+
### cx
Concatenate the generated classes from left to right, with subsequent styles overwriting the property values of earlier ones.
@@ -119,6 +154,18 @@ const Component = ({ inline }: { inline: boolean }) => (
);
```
+## CSS extraction
+
+```tsx
+import swanCss from "@swan-io/css/vite-plugin";
+import react from "@vitejs/plugin-react-swc";
+import { defineConfig } from "vite";
+
+export default defineConfig(({ command }) => ({
+ plugins: [react(), command === "build" && swanCss()],
+}));
+```
+
## Links
- ⚖️ [**License**](./LICENSE)
diff --git a/example/.gitignore b/example/.gitignore
index 3209677..b219d5d 100644
--- a/example/.gitignore
+++ b/example/.gitignore
@@ -1,5 +1,6 @@
*.local
.DS_Store
+.vite-inspect
dist
dist-ssr
node_modules
diff --git a/example/package.json b/example/package.json
index bc6182e..cc1d2a4 100644
--- a/example/package.json
+++ b/example/package.json
@@ -17,6 +17,7 @@
"@types/react-dom": "19.1.7",
"@vitejs/plugin-react-swc": "4.0.0",
"typescript": "5.9.2",
- "vite": "7.1.2"
+ "vite": "7.1.2",
+ "vite-plugin-inspect": "11.3.2"
}
}
diff --git a/example/src/App.tsx b/example/src/App.tsx
index a72c3ba..09a7a73 100644
--- a/example/src/App.tsx
+++ b/example/src/App.tsx
@@ -1,13 +1,36 @@
-import { css } from "@swan-io/css";
+import { css, cx } from "@swan-io/css";
+import { useState } from "react";
import { useSheetLogger } from "./useSheetLogger";
-const sheet = css.make({
+const sheet = css.make(({ colors }) => ({
title: {
- color: "red",
+ color: colors.red,
+ ":hover": {
+ color: colors.blue,
+ },
},
-});
+ extra: {
+ color: colors.green,
+ },
+}));
export const App = () => {
+ const [checked, setChecked] = useState(false);
+
useSheetLogger(); // log sheet on "l" keypress
- return
Hello world
;
+
+ const handleOnChange = (event: React.ChangeEvent) => {
+ setChecked(event.target.checked);
+ };
+
+ return (
+ <>
+ Hello world
+
+
+
+ Add extra className
+
+ >
+ );
};
diff --git a/example/src/index.tsx b/example/src/index.tsx
index 46d7811..666df1c 100644
--- a/example/src/index.tsx
+++ b/example/src/index.tsx
@@ -1,3 +1,5 @@
+import "./theme";
+
import { createRoot } from "react-dom/client";
import { App } from "./App";
diff --git a/example/src/theme.ts b/example/src/theme.ts
new file mode 100644
index 0000000..af024bd
--- /dev/null
+++ b/example/src/theme.ts
@@ -0,0 +1,15 @@
+import { css } from "@swan-io/css";
+
+const input = css.extend({
+ colors: {
+ red: "#fa2c37",
+ blue: "#2c7fff",
+ green: "#00c950",
+ },
+});
+
+type CustomInput = typeof input;
+
+declare module "@swan-io/css" {
+ export interface Input extends CustomInput {}
+}
diff --git a/example/src/useSheetLogger.ts b/example/src/useSheetLogger.ts
index c7d868a..a9e1eba 100644
--- a/example/src/useSheetLogger.ts
+++ b/example/src/useSheetLogger.ts
@@ -6,8 +6,9 @@ export const useSheetLogger = () => {
if (event.code === "KeyL") {
const id = "swan-stylesheet";
- const sheet = document.querySelector(
- `style[id="${id}"]`,
+ const sheet = (
+ document.querySelector(`link[id="${id}"]`) ??
+ document.querySelector(`style[id="${id}"]`)
)?.sheet;
if (sheet != null) {
diff --git a/example/vite.config.ts b/example/vite.config.ts
index 794d7c6..5947907 100644
--- a/example/vite.config.ts
+++ b/example/vite.config.ts
@@ -1,7 +1,13 @@
+import swanCss from "@swan-io/css/vite-plugin";
import react from "@vitejs/plugin-react-swc";
import { defineConfig } from "vite";
+import inspect from "vite-plugin-inspect";
export default defineConfig(({ command }) => ({
build: { sourcemap: true },
- plugins: [react()],
+ plugins: [
+ react(),
+ command === "build" && inspect({ build: true }),
+ command === "build" && swanCss(),
+ ],
}));
diff --git a/package.json b/package.json
index ade2b19..392e749 100644
--- a/package.json
+++ b/package.json
@@ -10,10 +10,21 @@
"url": "git+https://github.com/swan-io/css.git"
},
"packageManager": "pnpm@10.14.0",
- "source": "src/index.ts",
- "main": "dist/index.js",
- "module": "dist/index.mjs",
- "types": "dist/index.d.ts",
+ "main": "./dist/index.js",
+ "module": "./dist/index.mjs",
+ "types": "./dist/index.d.ts",
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "import": "./dist/index.mjs",
+ "default": "./dist/index.js"
+ },
+ "./vite-plugin": {
+ "types": "./dist/vite-plugin.d.ts",
+ "import": "./dist/vite-plugin.mjs",
+ "default": "./dist/vite-plugin.js"
+ }
+ },
"files": [
"dist"
],
@@ -43,7 +54,8 @@
"pnpm": {
"onlyBuiltDependencies": [
"@swc/core",
- "esbuild"
+ "esbuild",
+ "oxc-resolver"
]
},
"prettier": {
@@ -51,10 +63,23 @@
"prettier-plugin-organize-imports"
]
},
+ "peerDependencies": {
+ "vite": ">=5"
+ },
+ "peerDependenciesMeta": {
+ "vite": {
+ "optional": true
+ }
+ },
"dependencies": {
"@emotion/hash": "^0.9.2",
"@react-native/normalize-colors": "^0.81.0",
"csstype": "^3.1.3",
+ "magic-string": "^0.30.17",
+ "node-html-parser": "^7.0.1",
+ "oxc-parser": "^0.82.1",
+ "oxc-resolver": "^11.6.1",
+ "oxc-walker": "^0.4.0",
"postcss-value-parser": "^4.2.0"
},
"devDependencies": {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 1044794..366e9ce 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -17,9 +17,27 @@ importers:
csstype:
specifier: ^3.1.3
version: 3.1.3
+ magic-string:
+ specifier: ^0.30.17
+ version: 0.30.17
+ node-html-parser:
+ specifier: ^7.0.1
+ version: 7.0.1
+ oxc-parser:
+ specifier: ^0.82.1
+ version: 0.82.1
+ oxc-resolver:
+ specifier: ^11.6.1
+ version: 11.6.1
+ oxc-walker:
+ specifier: ^0.4.0
+ version: 0.4.0(oxc-parser@0.82.1)
postcss-value-parser:
specifier: ^4.2.0
version: 4.2.0
+ vite:
+ specifier: '>=5'
+ version: 7.1.2(@types/node@24.3.0)
devDependencies:
'@types/node':
specifier: ^24.3.0
@@ -88,6 +106,9 @@ importers:
vite:
specifier: 7.1.2
version: 7.1.2(@types/node@24.3.0)
+ vite-plugin-inspect:
+ specifier: 11.3.2
+ version: 11.3.2(vite@7.1.2(@types/node@24.3.0))
packages:
@@ -103,6 +124,15 @@ packages:
resolution: {integrity: sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==}
engines: {node: '>=6.9.0'}
+ '@emnapi/core@1.4.5':
+ resolution: {integrity: sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==}
+
+ '@emnapi/runtime@1.4.5':
+ resolution: {integrity: sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==}
+
+ '@emnapi/wasi-threads@1.0.4':
+ resolution: {integrity: sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==}
+
'@emotion/hash@0.9.2':
resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==}
@@ -269,6 +299,9 @@ packages:
'@jridgewell/gen-mapping@0.3.13':
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
+ '@jridgewell/remapping@2.3.5':
+ resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
+
'@jridgewell/resolve-uri@3.1.2':
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
engines: {node: '>=6.0.0'}
@@ -279,6 +312,196 @@ packages:
'@jridgewell/trace-mapping@0.3.30':
resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==}
+ '@napi-rs/wasm-runtime@1.0.3':
+ resolution: {integrity: sha512-rZxtMsLwjdXkMUGC3WwsPwLNVqVqnTJT6MNIB6e+5fhMcSCPP0AOsNWuMQ5mdCq6HNjs/ZeWAEchpqeprqBD2Q==}
+
+ '@oxc-parser/binding-android-arm64@0.82.1':
+ resolution: {integrity: sha512-Vph9abEKcjDm1qypjgvvHzrMcjIC5Nhi5kVO/GQ9WTRIbVEq5yS7vWp3VYh6TQ405DxAX2z8g2o67Ovdh3r1hA==}
+ engines: {node: '>=20.0.0'}
+ cpu: [arm64]
+ os: [android]
+
+ '@oxc-parser/binding-darwin-arm64@0.82.1':
+ resolution: {integrity: sha512-0biUTb+VBpbNG3begg5e2FW9DlOW/7wLLr/sF9JLXLKiCQnYMTTQ/FTkHMqxkDJBON+FTiHpnvp4aS2eUv3lkA==}
+ engines: {node: '>=20.0.0'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@oxc-parser/binding-darwin-x64@0.82.1':
+ resolution: {integrity: sha512-VGIJSzPsWAK501FOOW44TspbZ8eWIOhY1FfBNIsn0JTN3Ve9uk/waSVN/lysQzMJOX3S3mIhtrVGdKQ3fum8GQ==}
+ engines: {node: '>=20.0.0'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@oxc-parser/binding-freebsd-x64@0.82.1':
+ resolution: {integrity: sha512-5oLRbNxkNQz8bRuvr07xOTJIzsmaRg6pLHxH2HqlRvhhpo9sXJN4yROSGKgoQSFCSWZyDylg18Aw5cxvSxfJgw==}
+ engines: {node: '>=20.0.0'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@oxc-parser/binding-linux-arm-gnueabihf@0.82.1':
+ resolution: {integrity: sha512-lhbZaoDoxbxNp83GOvcXw02R+qJk3ckUPmHmbxkTCjSD/BttrjTLsZ30HJdH2rDB9kmBtfsbg3/Mnz/bU2D6ow==}
+ engines: {node: '>=20.0.0'}
+ cpu: [arm]
+ os: [linux]
+
+ '@oxc-parser/binding-linux-arm-musleabihf@0.82.1':
+ resolution: {integrity: sha512-0dLHzsC22O+9/diTCjDrTDSEaTUsnvbvq2jTGpHNaVg7903Jieb3Eftqut3MpBea9764a/IZkGoKP3btCdQnYQ==}
+ engines: {node: '>=20.0.0'}
+ cpu: [arm]
+ os: [linux]
+
+ '@oxc-parser/binding-linux-arm64-gnu@0.82.1':
+ resolution: {integrity: sha512-ftux8M4nPbYj/6lEO9PbfZ8knacHx/o4JfwueDbcrWOf53otJ9jHNITbZooIgl7zwGCW+JSZgJis2Ts4u9feQw==}
+ engines: {node: '>=20.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@oxc-parser/binding-linux-arm64-musl@0.82.1':
+ resolution: {integrity: sha512-dvtBGwTsW2bUHB0c6lVj0KIm7NT00xcsATWTCXiwGoDIGl/FPJctudpj+nMwXyjdPk3rlKRDjJ3pHcd7pYR1DQ==}
+ engines: {node: '>=20.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@oxc-parser/binding-linux-riscv64-gnu@0.82.1':
+ resolution: {integrity: sha512-y2A2lXUyppruU5AE6MforoqAvptwRGSRV8rO32Xv+vdflJH3rKxqKhWxW2bfRc14YhKRbQUcwPaW4nEqPPvwag==}
+ engines: {node: '>=20.0.0'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@oxc-parser/binding-linux-s390x-gnu@0.82.1':
+ resolution: {integrity: sha512-2oajEj8l0TGyWawVl+cuFjn7mcVBCR2fTO2EFsCf9WH7KEG/gyU86G5XDLN6tnl1E3Gqe88A09s0J8UUj+qUKA==}
+ engines: {node: '>=20.0.0'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@oxc-parser/binding-linux-x64-gnu@0.82.1':
+ resolution: {integrity: sha512-r0XCtdH36uXlwH504O2zRXqjhD8NjBQaPgInnwGMFskgPKPQBJIAYqWqrBzTEOJEPQEuhksfjsGNn7TAOQKdNQ==}
+ engines: {node: '>=20.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ '@oxc-parser/binding-linux-x64-musl@0.82.1':
+ resolution: {integrity: sha512-CW6Rw6RME+cp1AmS2GFT7wdg/7nCxL/pHGbwhq7RumO6ITBKzicd6YH2rkQrMYy0x8ZTzSdS4YjbPBHT78E4jA==}
+ engines: {node: '>=20.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ '@oxc-parser/binding-wasm32-wasi@0.82.1':
+ resolution: {integrity: sha512-0Vy/d8iBwFxrXldnc5GfXFmF8Pxgrqv14d/htz5u2kb02bhCCWIk+GjI2gKEcQfOgl2Bn4oOK3tL5rUrFNPRPg==}
+ engines: {node: '>=14.0.0'}
+ cpu: [wasm32]
+
+ '@oxc-parser/binding-win32-arm64-msvc@0.82.1':
+ resolution: {integrity: sha512-qfWIjAPt7ljozHkIH8sa155yH4rLrG8w36GRRhSZ1ltQT6HFgNZO56HSyZShSw3++3zBb5AkHVVnBWvBTo5zjQ==}
+ engines: {node: '>=20.0.0'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@oxc-parser/binding-win32-x64-msvc@0.82.1':
+ resolution: {integrity: sha512-xiMVlP38bsq/7FHR6e+pZQ8XJetPhNToPy5mNh227pIybSWWjcdPTHo0LAJmIrsqrx5+/msIkZ+Wm/E+SXBkww==}
+ engines: {node: '>=20.0.0'}
+ cpu: [x64]
+ os: [win32]
+
+ '@oxc-project/types@0.82.1':
+ resolution: {integrity: sha512-MCPtxtmHRmCqMI+DZyADBtW7QrFQ6OtQvHVAu576LWu6Y5zshLNabDc6RDJE/+uKVdypd9ZU1r05J/547VooPQ==}
+
+ '@oxc-resolver/binding-android-arm-eabi@11.6.1':
+ resolution: {integrity: sha512-Ma/kg29QJX1Jzelv0Q/j2iFuUad1WnjgPjpThvjqPjpOyLjCUaiFCCnshhmWjyS51Ki1Iol3fjf1qAzObf8GIA==}
+ cpu: [arm]
+ os: [android]
+
+ '@oxc-resolver/binding-android-arm64@11.6.1':
+ resolution: {integrity: sha512-xjL/FKKc5p8JkFWiH7pJWSzsewif3fRf1rw2qiRxRvq1uIa6l7Zoa14Zq2TNWEsqDjdeOrlJtfWiPNRnevK0oQ==}
+ cpu: [arm64]
+ os: [android]
+
+ '@oxc-resolver/binding-darwin-arm64@11.6.1':
+ resolution: {integrity: sha512-u0yrJ3NHE0zyCjiYpIyz4Vmov21MA0yFKbhHgixDU/G6R6nvC8ZpuSFql3+7C8ttAK9p8WpqOGweepfcilH5Bw==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@oxc-resolver/binding-darwin-x64@11.6.1':
+ resolution: {integrity: sha512-2lox165h1EhzxcC8edUy0znXC/hnAbUPaMpYKVlzLpB2AoYmgU4/pmofFApj+axm2FXpNamjcppld8EoHo06rw==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@oxc-resolver/binding-freebsd-x64@11.6.1':
+ resolution: {integrity: sha512-F45MhEQ7QbHfsvZtVNuA/9obu3il7QhpXYmCMfxn7Zt9nfAOw4pQ8hlS5DroHVp3rW35u9F7x0sixk/QEAi3qQ==}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@oxc-resolver/binding-linux-arm-gnueabihf@11.6.1':
+ resolution: {integrity: sha512-r+3+MTTl0tD4NoWbfTIItAxJvuyIU7V0fwPDXrv7Uj64vZ3OYaiyV+lVaeU89Bk/FUUQxeUpWBwdKNKHjyRNQw==}
+ cpu: [arm]
+ os: [linux]
+
+ '@oxc-resolver/binding-linux-arm-musleabihf@11.6.1':
+ resolution: {integrity: sha512-TBTZ63otsWZ72Z8ZNK2JVS0HW1w9zgOixJTFDNrYPUUW1pXGa28KAjQ1yGawj242WLAdu3lwdNIWtkxeO2BLxQ==}
+ cpu: [arm]
+ os: [linux]
+
+ '@oxc-resolver/binding-linux-arm64-gnu@11.6.1':
+ resolution: {integrity: sha512-SjwhNynjSG2yMdyA0f7wz7Yvo3ppejO+ET7n2oiI7ApCXrwxMzeRWjBzQt+oVWr2HzVOfaEcDS9rMtnR83ulig==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@oxc-resolver/binding-linux-arm64-musl@11.6.1':
+ resolution: {integrity: sha512-f4EMidK6rosInBzPMnJ0Ri4RttFCvvLNUNDFUBtELW/MFkBwPTDlvbsmW0u0Mk/ruBQ2WmRfOZ6tT62kWMcX2Q==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@oxc-resolver/binding-linux-ppc64-gnu@11.6.1':
+ resolution: {integrity: sha512-1umENVKeUsrWnf5IlF/6SM7DCv8G6CoKI2LnYR6qhZuLYDPS4PBZ0Jow3UDV9Rtbv5KRPcA3/uXjI88ntWIcOQ==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@oxc-resolver/binding-linux-riscv64-gnu@11.6.1':
+ resolution: {integrity: sha512-Hjyp1FRdJhsEpIxsZq5VcDuFc8abC0Bgy8DWEa31trCKoTz7JqA7x3E2dkFbrAKsEFmZZ0NvuG5Ip3oIRARhow==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@oxc-resolver/binding-linux-riscv64-musl@11.6.1':
+ resolution: {integrity: sha512-ODJOJng6f3QxpAXhLel3kyWs8rPsJeo9XIZHzA7p//e+5kLMDU7bTVk4eZnUHuxsqsB8MEvPCicJkKCEuur5Ag==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@oxc-resolver/binding-linux-s390x-gnu@11.6.1':
+ resolution: {integrity: sha512-hCzRiLhqe1ZOpHTsTGKp7gnMJRORlbCthawBueer2u22RVAka74pV/+4pP1tqM07mSlQn7VATuWaDw9gCl+cVg==}
+ cpu: [s390x]
+ os: [linux]
+
+ '@oxc-resolver/binding-linux-x64-gnu@11.6.1':
+ resolution: {integrity: sha512-JansPD8ftOzMYIC3NfXJ68tt63LEcIAx44Blx6BAd7eY880KX7A0KN3hluCrelCz5aQkPaD95g8HBiJmKaEi2w==}
+ cpu: [x64]
+ os: [linux]
+
+ '@oxc-resolver/binding-linux-x64-musl@11.6.1':
+ resolution: {integrity: sha512-R78ES1rd4z2x5NrFPtSWb/ViR1B8wdl+QN2X8DdtoYcqZE/4tvWtn9ZTCXMEzUp23tchJ2wUB+p6hXoonkyLpA==}
+ cpu: [x64]
+ os: [linux]
+
+ '@oxc-resolver/binding-wasm32-wasi@11.6.1':
+ resolution: {integrity: sha512-qAR3tYIf3afkij/XYunZtlz3OH2Y4ni10etmCFIJB5VRGsqJyI6Hl+2dXHHGJNwbwjXjSEH/KWJBpVroF3TxBw==}
+ engines: {node: '>=14.0.0'}
+ cpu: [wasm32]
+
+ '@oxc-resolver/binding-win32-arm64-msvc@11.6.1':
+ resolution: {integrity: sha512-QqygWygIuemGkaBA48POOTeinbVvlamqh6ucm8arGDGz/mB5O00gXWxed12/uVrYEjeqbMkla/CuL3fjL3EKvw==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@oxc-resolver/binding-win32-ia32-msvc@11.6.1':
+ resolution: {integrity: sha512-N2+kkWwt/bk0JTCxhPuK8t8JMp3nd0n2OhwOkU8KO4a7roAJEa4K1SZVjMv5CqUIr5sx2CxtXRBoFDiORX5oBg==}
+ cpu: [ia32]
+ os: [win32]
+
+ '@oxc-resolver/binding-win32-x64-msvc@11.6.1':
+ resolution: {integrity: sha512-DfMg3cU9bJUbN62Prbp4fGCtLgexuwyEaQGtZAp8xmi1Ii26uflOGx0FJkFTF6lVMSFoIRFvIL8gsw5/ZdHrMw==}
+ cpu: [x64]
+ os: [win32]
+
'@pkgjs/parseargs@0.11.0':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
@@ -477,6 +700,9 @@ packages:
peerDependencies:
'@testing-library/dom': '>=7.21.4'
+ '@tybys/wasm-util@0.10.0':
+ resolution: {integrity: sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==}
+
'@types/aria-query@5.0.4':
resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==}
@@ -575,6 +801,10 @@ packages:
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
engines: {node: '>=12'}
+ ansis@4.1.0:
+ resolution: {integrity: sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w==}
+ engines: {node: '>=14'}
+
any-promise@1.3.0:
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
@@ -588,9 +818,19 @@ packages:
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+ birpc@2.5.0:
+ resolution: {integrity: sha512-VSWO/W6nNQdyP520F1mhf+Lc2f8pjGQOtoHHm7Ze8Go1kX7akpVIrtTa0fn+HB0QJEDVacl6aO08YE0PgXfdnQ==}
+
+ boolbase@1.0.0:
+ resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
+
brace-expansion@2.0.2:
resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
+ bundle-name@4.1.0:
+ resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==}
+ engines: {node: '>=18'}
+
bundle-require@5.1.0:
resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@@ -635,6 +875,13 @@ packages:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
+ css-select@5.2.2:
+ resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==}
+
+ css-what@6.2.2:
+ resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==}
+ engines: {node: '>= 6'}
+
csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
@@ -651,6 +898,18 @@ packages:
resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
engines: {node: '>=6'}
+ default-browser-id@5.0.0:
+ resolution: {integrity: sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==}
+ engines: {node: '>=18'}
+
+ default-browser@5.2.1:
+ resolution: {integrity: sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==}
+ engines: {node: '>=18'}
+
+ define-lazy-prop@3.0.0:
+ resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==}
+ engines: {node: '>=12'}
+
dequal@2.0.3:
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
engines: {node: '>=6'}
@@ -658,6 +917,19 @@ packages:
dom-accessibility-api@0.5.16:
resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==}
+ dom-serializer@2.0.0:
+ resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
+
+ domelementtype@2.3.0:
+ resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
+
+ domhandler@5.0.3:
+ resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
+ engines: {node: '>= 4'}
+
+ domutils@3.2.2:
+ resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
+
eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
@@ -667,6 +939,13 @@ packages:
emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
+ entities@4.5.0:
+ resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
+ engines: {node: '>=0.12'}
+
+ error-stack-parser-es@1.0.5:
+ resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==}
+
es-module-lexer@1.7.0:
resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
@@ -712,10 +991,28 @@ packages:
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
hasBin: true
+ he@1.2.0:
+ resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
+ hasBin: true
+
+ is-docker@3.0.0:
+ resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ hasBin: true
+
is-fullwidth-code-point@3.0.0:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
+ is-inside-container@1.0.0:
+ resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==}
+ engines: {node: '>=14.16'}
+ hasBin: true
+
+ is-wsl@3.1.0:
+ resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==}
+ engines: {node: '>=16'}
+
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
@@ -756,6 +1053,9 @@ packages:
resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
hasBin: true
+ magic-regexp@0.10.0:
+ resolution: {integrity: sha512-Uly1Bu4lO1hwHUW0CQeSWuRtzCMNO00CmXtS8N6fyvB3B979GOEEeAkiTUDsmbYLAbvpUS/Kt5c4ibosAzVyVg==}
+
magic-string@0.30.17:
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
@@ -785,10 +1085,40 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
+ napi-postinstall@0.3.3:
+ resolution: {integrity: sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow==}
+ engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
+ hasBin: true
+
+ node-html-parser@7.0.1:
+ resolution: {integrity: sha512-KGtmPY2kS0thCWGK0VuPyOS+pBKhhe8gXztzA2ilAOhbUbxa9homF1bOyKvhGzMLXUoRds9IOmr/v5lr/lqNmA==}
+
+ nth-check@2.1.1:
+ resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
+
object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
+ ohash@2.0.11:
+ resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==}
+
+ open@10.2.0:
+ resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==}
+ engines: {node: '>=18'}
+
+ oxc-parser@0.82.1:
+ resolution: {integrity: sha512-2bBrazc/0wpA/+XECTwLA6dvp2Swp9vm/psgvwDz4CcxwfvYyQN0/ghw9km0LFpPIDSrxhltJSsfajhb2NZq0A==}
+ engines: {node: '>=20.0.0'}
+
+ oxc-resolver@11.6.1:
+ resolution: {integrity: sha512-WQgmxevT4cM5MZ9ioQnEwJiHpPzbvntV5nInGAKo9NQZzegcOonHvcVcnkYqld7bTG35UFHEKeF7VwwsmA3cZg==}
+
+ oxc-walker@0.4.0:
+ resolution: {integrity: sha512-x5TJAZQD3kRnRBGZ+8uryMZUwkTYddwzBftkqyJIcmpBOXmoK/fwriRKATjZroR2d+aS7+2w1B0oz189bBTwfw==}
+ peerDependencies:
+ oxc-parser: '>=0.72.0'
+
package-json-from-dist@1.0.1:
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
@@ -807,6 +1137,9 @@ packages:
resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==}
engines: {node: '>= 14.16'}
+ perfect-debounce@1.0.0:
+ resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==}
+
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@@ -895,6 +1228,10 @@ packages:
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
engines: {node: '>= 14.18.0'}
+ regexp-tree@0.1.27:
+ resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==}
+ hasBin: true
+
resolve-from@5.0.0:
resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
engines: {node: '>=8'}
@@ -904,6 +1241,10 @@ packages:
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
+ run-applescript@7.0.0:
+ resolution: {integrity: sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==}
+ engines: {node: '>=18'}
+
scheduler@0.26.0:
resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==}
@@ -1008,6 +1349,9 @@ packages:
ts-interface-checker@0.1.13:
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
+ tslib@2.8.1:
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
tsup@8.5.0:
resolution: {integrity: sha512-VmBp77lWNQq6PfuMqCHD3xWl22vEoWsKajkF8t+yMBawlUS8JzEI+vOVMeuNZIuMML8qXRizFKi9oD5glKQVcQ==}
engines: {node: '>=18'}
@@ -1027,6 +1371,9 @@ packages:
typescript:
optional: true
+ type-level-regexp@0.1.17:
+ resolution: {integrity: sha512-wTk4DH3cxwk196uGLK/E9pE45aLfeKJacKmcEgEOA/q5dnPGNxXt0cfYdFxb57L+sEpf1oJH4Dnx/pnRcku9jg==}
+
typescript@5.9.2:
resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==}
engines: {node: '>=14.17'}
@@ -1038,11 +1385,39 @@ packages:
undici-types@7.10.0:
resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==}
+ unplugin-utils@0.2.5:
+ resolution: {integrity: sha512-gwXJnPRewT4rT7sBi/IvxKTjsms7jX7QIDLOClApuZwR49SXbrB1z2NLUZ+vDHyqCj/n58OzRRqaW+B8OZi8vg==}
+ engines: {node: '>=18.12.0'}
+
+ unplugin@2.3.6:
+ resolution: {integrity: sha512-+/MdXl8bLTXI2lJF22gUBeCFqZruEpL/oM9f8wxCuKh9+Mw9qeul3gTqgbKpMeOFlusCzc0s7x2Kax2xKW+FQg==}
+ engines: {node: '>=18.12.0'}
+
+ vite-dev-rpc@1.1.0:
+ resolution: {integrity: sha512-pKXZlgoXGoE8sEKiKJSng4hI1sQ4wi5YT24FCrwrLt6opmkjlqPPVmiPWWJn8M8byMxRGzp1CrFuqQs4M/Z39A==}
+ peerDependencies:
+ vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1 || ^7.0.0-0
+
+ vite-hot-client@2.1.0:
+ resolution: {integrity: sha512-7SpgZmU7R+dDnSmvXE1mfDtnHLHQSisdySVR7lO8ceAXvM0otZeuQQ6C8LrS5d/aYyP/QZ0hI0L+dIPrm4YlFQ==}
+ peerDependencies:
+ vite: ^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0
+
vite-node@3.2.4:
resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
hasBin: true
+ vite-plugin-inspect@11.3.2:
+ resolution: {integrity: sha512-nzwvyFQg58XSMAmKVLr2uekAxNYvAbz1lyPmCAFVIBncCgN9S/HPM+2UM9Q9cvc4JEbC5ZBgwLAdaE2onmQuKg==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ '@nuxt/kit': '*'
+ vite: ^6.0.0 || ^7.0.0-0
+ peerDependenciesMeta:
+ '@nuxt/kit':
+ optional: true
+
vite@7.1.2:
resolution: {integrity: sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -1130,6 +1505,9 @@ packages:
webidl-conversions@4.0.2:
resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
+ webpack-virtual-modules@0.6.2:
+ resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
+
whatwg-url@7.1.0:
resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==}
@@ -1163,6 +1541,10 @@ packages:
utf-8-validate:
optional: true
+ wsl-utils@0.1.0:
+ resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==}
+ engines: {node: '>=18'}
+
snapshots:
'@babel/code-frame@7.27.1':
@@ -1175,6 +1557,22 @@ snapshots:
'@babel/runtime@7.28.3': {}
+ '@emnapi/core@1.4.5':
+ dependencies:
+ '@emnapi/wasi-threads': 1.0.4
+ tslib: 2.8.1
+ optional: true
+
+ '@emnapi/runtime@1.4.5':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@emnapi/wasi-threads@1.0.4':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
'@emotion/hash@0.9.2': {}
'@esbuild/aix-ppc64@0.25.9':
@@ -1269,6 +1667,11 @@ snapshots:
'@jridgewell/sourcemap-codec': 1.5.5
'@jridgewell/trace-mapping': 0.3.30
+ '@jridgewell/remapping@2.3.5':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.30
+
'@jridgewell/resolve-uri@3.1.2': {}
'@jridgewell/sourcemap-codec@1.5.5': {}
@@ -1278,6 +1681,121 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
+ '@napi-rs/wasm-runtime@1.0.3':
+ dependencies:
+ '@emnapi/core': 1.4.5
+ '@emnapi/runtime': 1.4.5
+ '@tybys/wasm-util': 0.10.0
+ optional: true
+
+ '@oxc-parser/binding-android-arm64@0.82.1':
+ optional: true
+
+ '@oxc-parser/binding-darwin-arm64@0.82.1':
+ optional: true
+
+ '@oxc-parser/binding-darwin-x64@0.82.1':
+ optional: true
+
+ '@oxc-parser/binding-freebsd-x64@0.82.1':
+ optional: true
+
+ '@oxc-parser/binding-linux-arm-gnueabihf@0.82.1':
+ optional: true
+
+ '@oxc-parser/binding-linux-arm-musleabihf@0.82.1':
+ optional: true
+
+ '@oxc-parser/binding-linux-arm64-gnu@0.82.1':
+ optional: true
+
+ '@oxc-parser/binding-linux-arm64-musl@0.82.1':
+ optional: true
+
+ '@oxc-parser/binding-linux-riscv64-gnu@0.82.1':
+ optional: true
+
+ '@oxc-parser/binding-linux-s390x-gnu@0.82.1':
+ optional: true
+
+ '@oxc-parser/binding-linux-x64-gnu@0.82.1':
+ optional: true
+
+ '@oxc-parser/binding-linux-x64-musl@0.82.1':
+ optional: true
+
+ '@oxc-parser/binding-wasm32-wasi@0.82.1':
+ dependencies:
+ '@napi-rs/wasm-runtime': 1.0.3
+ optional: true
+
+ '@oxc-parser/binding-win32-arm64-msvc@0.82.1':
+ optional: true
+
+ '@oxc-parser/binding-win32-x64-msvc@0.82.1':
+ optional: true
+
+ '@oxc-project/types@0.82.1': {}
+
+ '@oxc-resolver/binding-android-arm-eabi@11.6.1':
+ optional: true
+
+ '@oxc-resolver/binding-android-arm64@11.6.1':
+ optional: true
+
+ '@oxc-resolver/binding-darwin-arm64@11.6.1':
+ optional: true
+
+ '@oxc-resolver/binding-darwin-x64@11.6.1':
+ optional: true
+
+ '@oxc-resolver/binding-freebsd-x64@11.6.1':
+ optional: true
+
+ '@oxc-resolver/binding-linux-arm-gnueabihf@11.6.1':
+ optional: true
+
+ '@oxc-resolver/binding-linux-arm-musleabihf@11.6.1':
+ optional: true
+
+ '@oxc-resolver/binding-linux-arm64-gnu@11.6.1':
+ optional: true
+
+ '@oxc-resolver/binding-linux-arm64-musl@11.6.1':
+ optional: true
+
+ '@oxc-resolver/binding-linux-ppc64-gnu@11.6.1':
+ optional: true
+
+ '@oxc-resolver/binding-linux-riscv64-gnu@11.6.1':
+ optional: true
+
+ '@oxc-resolver/binding-linux-riscv64-musl@11.6.1':
+ optional: true
+
+ '@oxc-resolver/binding-linux-s390x-gnu@11.6.1':
+ optional: true
+
+ '@oxc-resolver/binding-linux-x64-gnu@11.6.1':
+ optional: true
+
+ '@oxc-resolver/binding-linux-x64-musl@11.6.1':
+ optional: true
+
+ '@oxc-resolver/binding-wasm32-wasi@11.6.1':
+ dependencies:
+ '@napi-rs/wasm-runtime': 1.0.3
+ optional: true
+
+ '@oxc-resolver/binding-win32-arm64-msvc@11.6.1':
+ optional: true
+
+ '@oxc-resolver/binding-win32-ia32-msvc@11.6.1':
+ optional: true
+
+ '@oxc-resolver/binding-win32-x64-msvc@11.6.1':
+ optional: true
+
'@pkgjs/parseargs@0.11.0':
optional: true
@@ -1414,6 +1932,11 @@ snapshots:
dependencies:
'@testing-library/dom': 10.4.1
+ '@tybys/wasm-util@0.10.0':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
'@types/aria-query@5.0.4': {}
'@types/chai@5.2.2':
@@ -1519,6 +2042,8 @@ snapshots:
ansi-styles@6.2.1: {}
+ ansis@4.1.0: {}
+
any-promise@1.3.0: {}
aria-query@5.3.0:
@@ -1529,10 +2054,18 @@ snapshots:
balanced-match@1.0.2: {}
+ birpc@2.5.0: {}
+
+ boolbase@1.0.0: {}
+
brace-expansion@2.0.2:
dependencies:
balanced-match: 1.0.2
+ bundle-name@4.1.0:
+ dependencies:
+ run-applescript: 7.0.0
+
bundle-require@5.1.0(esbuild@0.25.9):
dependencies:
esbuild: 0.25.9
@@ -1572,6 +2105,16 @@ snapshots:
shebang-command: 2.0.0
which: 2.0.2
+ css-select@5.2.2:
+ dependencies:
+ boolbase: 1.0.0
+ css-what: 6.2.2
+ domhandler: 5.0.3
+ domutils: 3.2.2
+ nth-check: 2.1.1
+
+ css-what@6.2.2: {}
+
csstype@3.1.3: {}
debug@4.4.1:
@@ -1580,16 +2123,47 @@ snapshots:
deep-eql@5.0.2: {}
+ default-browser-id@5.0.0: {}
+
+ default-browser@5.2.1:
+ dependencies:
+ bundle-name: 4.1.0
+ default-browser-id: 5.0.0
+
+ define-lazy-prop@3.0.0: {}
+
dequal@2.0.3: {}
dom-accessibility-api@0.5.16: {}
+ dom-serializer@2.0.0:
+ dependencies:
+ domelementtype: 2.3.0
+ domhandler: 5.0.3
+ entities: 4.5.0
+
+ domelementtype@2.3.0: {}
+
+ domhandler@5.0.3:
+ dependencies:
+ domelementtype: 2.3.0
+
+ domutils@3.2.2:
+ dependencies:
+ dom-serializer: 2.0.0
+ domelementtype: 2.3.0
+ domhandler: 5.0.3
+
eastasianwidth@0.2.0: {}
emoji-regex@8.0.0: {}
emoji-regex@9.2.2: {}
+ entities@4.5.0: {}
+
+ error-stack-parser-es@1.0.5: {}
+
es-module-lexer@1.7.0: {}
esbuild@0.25.9:
@@ -1657,8 +2231,20 @@ snapshots:
package-json-from-dist: 1.0.1
path-scurry: 1.11.1
+ he@1.2.0: {}
+
+ is-docker@3.0.0: {}
+
is-fullwidth-code-point@3.0.0: {}
+ is-inside-container@1.0.0:
+ dependencies:
+ is-docker: 3.0.0
+
+ is-wsl@3.1.0:
+ dependencies:
+ is-inside-container: 1.0.0
+
isexe@2.0.0: {}
jackspeak@3.4.3:
@@ -1687,6 +2273,16 @@ snapshots:
lz-string@1.5.0: {}
+ magic-regexp@0.10.0:
+ dependencies:
+ estree-walker: 3.0.3
+ magic-string: 0.30.17
+ mlly: 1.7.4
+ regexp-tree: 0.1.27
+ type-level-regexp: 0.1.17
+ ufo: 1.6.1
+ unplugin: 2.3.6
+
magic-string@0.30.17:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
@@ -1716,8 +2312,78 @@ snapshots:
nanoid@3.3.11: {}
+ napi-postinstall@0.3.3: {}
+
+ node-html-parser@7.0.1:
+ dependencies:
+ css-select: 5.2.2
+ he: 1.2.0
+
+ nth-check@2.1.1:
+ dependencies:
+ boolbase: 1.0.0
+
object-assign@4.1.1: {}
+ ohash@2.0.11: {}
+
+ open@10.2.0:
+ dependencies:
+ default-browser: 5.2.1
+ define-lazy-prop: 3.0.0
+ is-inside-container: 1.0.0
+ wsl-utils: 0.1.0
+
+ oxc-parser@0.82.1:
+ dependencies:
+ '@oxc-project/types': 0.82.1
+ optionalDependencies:
+ '@oxc-parser/binding-android-arm64': 0.82.1
+ '@oxc-parser/binding-darwin-arm64': 0.82.1
+ '@oxc-parser/binding-darwin-x64': 0.82.1
+ '@oxc-parser/binding-freebsd-x64': 0.82.1
+ '@oxc-parser/binding-linux-arm-gnueabihf': 0.82.1
+ '@oxc-parser/binding-linux-arm-musleabihf': 0.82.1
+ '@oxc-parser/binding-linux-arm64-gnu': 0.82.1
+ '@oxc-parser/binding-linux-arm64-musl': 0.82.1
+ '@oxc-parser/binding-linux-riscv64-gnu': 0.82.1
+ '@oxc-parser/binding-linux-s390x-gnu': 0.82.1
+ '@oxc-parser/binding-linux-x64-gnu': 0.82.1
+ '@oxc-parser/binding-linux-x64-musl': 0.82.1
+ '@oxc-parser/binding-wasm32-wasi': 0.82.1
+ '@oxc-parser/binding-win32-arm64-msvc': 0.82.1
+ '@oxc-parser/binding-win32-x64-msvc': 0.82.1
+
+ oxc-resolver@11.6.1:
+ dependencies:
+ napi-postinstall: 0.3.3
+ optionalDependencies:
+ '@oxc-resolver/binding-android-arm-eabi': 11.6.1
+ '@oxc-resolver/binding-android-arm64': 11.6.1
+ '@oxc-resolver/binding-darwin-arm64': 11.6.1
+ '@oxc-resolver/binding-darwin-x64': 11.6.1
+ '@oxc-resolver/binding-freebsd-x64': 11.6.1
+ '@oxc-resolver/binding-linux-arm-gnueabihf': 11.6.1
+ '@oxc-resolver/binding-linux-arm-musleabihf': 11.6.1
+ '@oxc-resolver/binding-linux-arm64-gnu': 11.6.1
+ '@oxc-resolver/binding-linux-arm64-musl': 11.6.1
+ '@oxc-resolver/binding-linux-ppc64-gnu': 11.6.1
+ '@oxc-resolver/binding-linux-riscv64-gnu': 11.6.1
+ '@oxc-resolver/binding-linux-riscv64-musl': 11.6.1
+ '@oxc-resolver/binding-linux-s390x-gnu': 11.6.1
+ '@oxc-resolver/binding-linux-x64-gnu': 11.6.1
+ '@oxc-resolver/binding-linux-x64-musl': 11.6.1
+ '@oxc-resolver/binding-wasm32-wasi': 11.6.1
+ '@oxc-resolver/binding-win32-arm64-msvc': 11.6.1
+ '@oxc-resolver/binding-win32-ia32-msvc': 11.6.1
+ '@oxc-resolver/binding-win32-x64-msvc': 11.6.1
+
+ oxc-walker@0.4.0(oxc-parser@0.82.1):
+ dependencies:
+ estree-walker: 3.0.3
+ magic-regexp: 0.10.0
+ oxc-parser: 0.82.1
+
package-json-from-dist@1.0.1: {}
path-key@3.1.1: {}
@@ -1731,6 +2397,8 @@ snapshots:
pathval@2.0.1: {}
+ perfect-debounce@1.0.0: {}
+
picocolors@1.1.1: {}
picomatch@4.0.3: {}
@@ -1791,6 +2459,8 @@ snapshots:
readdirp@4.1.2: {}
+ regexp-tree@0.1.27: {}
+
resolve-from@5.0.0: {}
rollup@4.46.2:
@@ -1819,6 +2489,8 @@ snapshots:
'@rollup/rollup-win32-x64-msvc': 4.46.2
fsevents: 2.3.3
+ run-applescript@7.0.0: {}
+
scheduler@0.26.0: {}
shebang-command@2.0.0:
@@ -1914,6 +2586,9 @@ snapshots:
ts-interface-checker@0.1.13: {}
+ tslib@2.8.1:
+ optional: true
+
tsup@8.5.0(@swc/core@1.13.3)(postcss@8.5.6)(typescript@5.9.2):
dependencies:
bundle-require: 5.1.0(esbuild@0.25.9)
@@ -1943,12 +2618,36 @@ snapshots:
- tsx
- yaml
+ type-level-regexp@0.1.17: {}
+
typescript@5.9.2: {}
ufo@1.6.1: {}
undici-types@7.10.0: {}
+ unplugin-utils@0.2.5:
+ dependencies:
+ pathe: 2.0.3
+ picomatch: 4.0.3
+
+ unplugin@2.3.6:
+ dependencies:
+ '@jridgewell/remapping': 2.3.5
+ acorn: 8.15.0
+ picomatch: 4.0.3
+ webpack-virtual-modules: 0.6.2
+
+ vite-dev-rpc@1.1.0(vite@7.1.2(@types/node@24.3.0)):
+ dependencies:
+ birpc: 2.5.0
+ vite: 7.1.2(@types/node@24.3.0)
+ vite-hot-client: 2.1.0(vite@7.1.2(@types/node@24.3.0))
+
+ vite-hot-client@2.1.0(vite@7.1.2(@types/node@24.3.0)):
+ dependencies:
+ vite: 7.1.2(@types/node@24.3.0)
+
vite-node@3.2.4(@types/node@24.3.0):
dependencies:
cac: 6.7.14
@@ -1970,6 +2669,21 @@ snapshots:
- tsx
- yaml
+ vite-plugin-inspect@11.3.2(vite@7.1.2(@types/node@24.3.0)):
+ dependencies:
+ ansis: 4.1.0
+ debug: 4.4.1
+ error-stack-parser-es: 1.0.5
+ ohash: 2.0.11
+ open: 10.2.0
+ perfect-debounce: 1.0.0
+ sirv: 3.0.1
+ unplugin-utils: 0.2.5
+ vite: 7.1.2(@types/node@24.3.0)
+ vite-dev-rpc: 1.1.0(vite@7.1.2(@types/node@24.3.0))
+ transitivePeerDependencies:
+ - supports-color
+
vite@7.1.2(@types/node@24.3.0):
dependencies:
esbuild: 0.25.9
@@ -2036,6 +2750,8 @@ snapshots:
webidl-conversions@4.0.2: {}
+ webpack-virtual-modules@0.6.2: {}
+
whatwg-url@7.1.0:
dependencies:
lodash.sortby: 4.7.0
@@ -2064,3 +2780,7 @@ snapshots:
strip-ansi: 7.1.0
ws@8.18.3: {}
+
+ wsl-utils@0.1.0:
+ dependencies:
+ is-wsl: 3.1.0
diff --git a/src/css.ts b/src/css.ts
new file mode 100644
index 0000000..3b51178
--- /dev/null
+++ b/src/css.ts
@@ -0,0 +1,318 @@
+import { caches } from "./cx";
+import { hash } from "./hash";
+import { hyphenateName } from "./hyphenateName";
+import { normalizeValue } from "./normalizeValue";
+import {
+ preprocessAtomicStyle,
+ preprocessKeyframes,
+ preprocessResetStyle,
+} from "./preprocess";
+import type { FlatStyle, Input, Keyframes, Style } from "./types";
+import { appendString, forEach } from "./utils";
+
+const getSheet = (id: string): CSSStyleSheet | null => {
+ if (typeof document === "undefined") {
+ return null;
+ }
+
+ const current =
+ document.querySelector(`link[id="${id}"]`) ??
+ document.querySelector(`style[id="${id}"]`);
+
+ if (current != null) {
+ return current.sheet;
+ }
+
+ const element = document.createElement("style");
+ element.setAttribute("id", id);
+ document.head.appendChild(element);
+
+ return element.sheet;
+};
+
+const getMediaRule = (
+ sheet: CSSStyleSheet | null,
+ index: number,
+ media: string,
+): {
+ cssRules: CSSRuleList | [];
+ toString: () => string;
+ insertRule: (rule: string) => void;
+} => {
+ const cssRules = new Set();
+
+ const toString = () =>
+ cssRules.size > 0
+ ? `@media ${media}{${[...cssRules].join("")}}`
+ : `@media ${media}{}`; // Keep an empty media sheet to preserve the index (hydratation)
+
+ if (sheet == null) {
+ return {
+ cssRules: [],
+ toString,
+ insertRule: (rule) => {
+ cssRules.add(rule);
+ },
+ };
+ }
+
+ if (sheet.cssRules[index] == null) {
+ try {
+ sheet.insertRule(`@media ${media}{}`, index);
+ } catch (error) {
+ if (process.env.NODE_ENV === "development") {
+ console.error(error);
+ }
+
+ return {
+ cssRules: [],
+ toString,
+ insertRule: (rule) => {
+ cssRules.add(rule);
+ },
+ };
+ }
+ }
+
+ const mediaRule = sheet.cssRules[index] as CSSMediaRule;
+
+ return {
+ cssRules: mediaRule.cssRules,
+ toString,
+ insertRule: (rule) => {
+ try {
+ mediaRule.insertRule(rule, mediaRule.cssRules.length);
+ cssRules.add(rule);
+ } catch (error) {
+ if (process.env.NODE_ENV === "development") {
+ console.error(error);
+ }
+ }
+ },
+ };
+};
+
+const stringifyRule = (key: string, value: string | number): string => {
+ if (key === "appearance") {
+ return `-webkit-appearance:${value};appearance:${value}`;
+ }
+ if (key === "lineClamp") {
+ return `-webkit-line-clamp:${value};line-clamp:${value}`;
+ }
+
+ return `${hyphenateName(key)}:${normalizeValue(key, value)}`;
+};
+
+const getClassName = (rule: CSSStyleRule): string => {
+ const selector = rule.selectorText;
+ const end = selector.indexOf(":");
+ return end > -1 ? selector.substring(1, end) : selector.substring(1);
+};
+
+const sheet = getSheet("swan-stylesheet");
+
+const keyframesSheet = getMediaRule(sheet, 0, "all");
+const resetSheet = getMediaRule(sheet, 1, "all");
+const atomicSheet = getMediaRule(sheet, 2, "all");
+const hoverSheet = getMediaRule(sheet, 3, "(hover:hover)");
+const focusSheet = getMediaRule(sheet, 4, "all");
+const activeSheet = getMediaRule(sheet, 5, "all");
+
+const keyframesNames = new Set();
+
+// Rehydrate keyframes sheet
+for (const rule of keyframesSheet.cssRules) {
+ if (rule instanceof CSSKeyframesRule) {
+ keyframesNames.add(rule.name);
+ }
+}
+
+// Rehydrate reset sheet
+for (const rule of resetSheet.cssRules) {
+ if (rule instanceof CSSStyleRule) {
+ caches.reset.add(getClassName(rule));
+ }
+}
+
+// Rehydrate atomic sheet
+for (const rule of atomicSheet.cssRules) {
+ if (rule instanceof CSSStyleRule) {
+ caches.atomic.set(getClassName(rule), rule.style[0]);
+ }
+}
+
+// Rehydrate hover sheet
+for (const rule of hoverSheet.cssRules) {
+ if (rule instanceof CSSStyleRule) {
+ caches.hover.set(getClassName(rule), rule.style[0]);
+ }
+}
+
+// Rehydrate focus sheet
+for (const rule of focusSheet.cssRules) {
+ if (rule instanceof CSSStyleRule) {
+ caches.focus.set(getClassName(rule), rule.style[0]);
+ }
+}
+
+// Rehydrate active sheet
+for (const rule of activeSheet.cssRules) {
+ if (rule instanceof CSSStyleRule) {
+ caches.active.set(getClassName(rule), rule.style[0]);
+ }
+}
+
+const insertKeyframes = (keyframes: Keyframes): string | undefined => {
+ let body = "";
+
+ forEach(keyframes, (key, value) => {
+ const rules: string[] = [];
+
+ forEach(value, (key, value) => {
+ rules.push(stringifyRule(key, value));
+ });
+
+ body += `${key}{${rules.join(";")}}`;
+ });
+
+ const name = "k-" + hash(body);
+
+ if (!keyframesNames.has(name)) {
+ keyframesSheet.insertRule(`@keyframes ${name}{${body}}`);
+ keyframesNames.add(name);
+ }
+
+ return name;
+};
+
+const insertResetRule = (style: FlatStyle): string => {
+ const rules: string[] = [];
+
+ forEach(style, (key, value) => {
+ rules.push(stringifyRule(key, value));
+ });
+
+ const body = rules.join(";");
+ const className = "r-" + hash(body);
+
+ if (!caches.reset.has(className)) {
+ resetSheet.insertRule(`.${className}{${body}}`);
+ caches.reset.add(className);
+ }
+
+ return className;
+};
+
+const insertAtomicRules = (style: Style): string => {
+ let classNames = "";
+
+ forEach(style, (key, value) => {
+ switch (key) {
+ case ":hover": {
+ forEach(value as FlatStyle, (key, value) => {
+ const rule = stringifyRule(key, value);
+ const className = "h-" + hash(rule);
+
+ if (!caches.hover.has(className)) {
+ hoverSheet.insertRule(`.${className}:hover{${rule}}`);
+ caches.hover.set(className, key);
+ }
+
+ classNames = appendString(classNames, className);
+ });
+
+ break;
+ }
+
+ case ":focus": {
+ forEach(value as FlatStyle, (key, value) => {
+ const rule = stringifyRule(key, value);
+ const className = "f-" + hash(rule);
+
+ if (!caches.focus.has(className)) {
+ focusSheet.insertRule(`.${className}:focus-visible{${rule}}`);
+ caches.focus.set(className, key);
+ }
+
+ classNames = appendString(classNames, className);
+ });
+
+ break;
+ }
+
+ case ":active": {
+ forEach(value as FlatStyle, (key, value) => {
+ const rule = stringifyRule(key, value);
+ const className = "a-" + hash(rule);
+
+ if (!caches.active.has(className)) {
+ activeSheet.insertRule(`.${className}:active{${rule}}`);
+ caches.active.set(className, key);
+ }
+
+ classNames = appendString(classNames, className);
+ });
+
+ break;
+ }
+
+ default: {
+ const rule = stringifyRule(key, value as string | number);
+ const className = "x-" + hash(rule);
+
+ if (!caches.atomic.has(className)) {
+ atomicSheet.insertRule(`.${className}{${rule}}`);
+ caches.atomic.set(className, key);
+ }
+
+ classNames = appendString(classNames, className);
+ }
+ }
+ });
+
+ return classNames;
+};
+
+const _input: Input = {
+ keyframes: (keyframes) => insertKeyframes(preprocessKeyframes(keyframes)),
+};
+
+export const css = {
+ extend: >(input: T) => {
+ forEach(input, (key, value) => {
+ // @ts-expect-error keep initial object instance reference
+ _input[key] = value;
+ });
+
+ return input;
+ },
+ make: (
+ styles: Record | ((input: Input) => Record),
+ ): Record => {
+ const output = {} as Record;
+
+ forEach(
+ typeof styles === "function" ? styles(_input) : styles,
+ (key, value) => {
+ output[key] =
+ key[0] === "$"
+ ? insertResetRule(preprocessResetStyle(value))
+ : insertAtomicRules(preprocessAtomicStyle(value));
+ },
+ );
+
+ return output;
+ },
+};
+
+export const getCssMakeInput = () => _input;
+
+export const getCssFileContent = () =>
+ [
+ keyframesSheet.toString(),
+ resetSheet.toString(),
+ atomicSheet.toString(),
+ hoverSheet.toString(),
+ focusSheet.toString(),
+ activeSheet.toString(),
+ ].join("\n");
diff --git a/src/cx.ts b/src/cx.ts
new file mode 100644
index 0000000..d068625
--- /dev/null
+++ b/src/cx.ts
@@ -0,0 +1,106 @@
+import type { ClassNames } from "./types";
+import { appendString, forEach } from "./utils";
+
+export const caches = {
+ reset: new Set(),
+ atomic: new Map(),
+ hover: new Map(),
+ focus: new Map(),
+ active: new Map(),
+};
+
+const extractClassNames = (items: ClassNames, acc: string[]): string[] => {
+ for (const item of items) {
+ if (typeof item === "string") {
+ for (const part of item.split(" ")) {
+ if (part !== "") {
+ acc.push(part);
+ }
+ }
+ } else if (Array.isArray(item)) {
+ extractClassNames(item, acc);
+ }
+ }
+
+ return acc;
+};
+
+const appendClassNames = (
+ acc: string,
+ classNames: Record,
+): string => {
+ let output = acc;
+
+ forEach(classNames, (_, value) => {
+ output = appendString(output, value);
+ });
+
+ return output;
+};
+
+export const cx = (...items: ClassNames): string => {
+ const classNames = extractClassNames(items, []);
+
+ let output = "";
+
+ let cacheKey: string | undefined = undefined;
+ let reset: string | undefined = undefined;
+
+ const atomic: Record = {};
+ const hover: Record = {};
+ const focus: Record = {};
+ const active: Record = {};
+
+ for (const className of classNames) {
+ cacheKey = caches.atomic.get(className);
+
+ if (cacheKey != null) {
+ atomic[cacheKey] = className;
+ continue;
+ }
+
+ cacheKey = caches.hover.get(className);
+
+ if (cacheKey != null) {
+ hover[cacheKey] = className;
+ continue;
+ }
+
+ cacheKey = caches.focus.get(className);
+
+ if (cacheKey != null) {
+ focus[cacheKey] = className;
+ continue;
+ }
+
+ cacheKey = caches.active.get(className);
+
+ if (cacheKey != null) {
+ active[cacheKey] = className;
+ continue;
+ }
+
+ if (caches.reset.has(className)) {
+ if (reset == null) {
+ reset = className;
+ } else if (reset !== className) {
+ if (process.env.NODE_ENV === "development") {
+ console.warn("`cx` only accepts one reset style.");
+ }
+ }
+ } else {
+ output = appendString(output, className);
+ }
+ }
+
+ if (reset != null) {
+ output = appendString(output, reset);
+ }
+
+ output = appendClassNames(output, atomic);
+ output = appendClassNames(output, hover);
+ output = appendClassNames(output, focus);
+ output = appendClassNames(output, active);
+
+ return output;
+};
diff --git a/src/index.ts b/src/index.ts
index 0ac147d..4118fd5 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,3 +1,3 @@
-import { createSheet } from "./sheet";
-
-export const { css, cx } = createSheet();
+export { css } from "./css";
+export { cx } from "./cx";
+export type { Input } from "./types";
diff --git a/src/normalizeValue.ts b/src/normalizeValue.ts
index df252e7..5742f29 100644
--- a/src/normalizeValue.ts
+++ b/src/normalizeValue.ts
@@ -64,25 +64,15 @@ const isWebColor = (color: string): boolean =>
color === "inherit" ||
color.indexOf("var(") === 0;
-const hexLookupTable = "0123456789abcdef";
-
const rgbToHex = (r: number, g: number, b: number) => {
- const r1 = r >> 4;
- const r2 = r & 0xf;
- const g1 = g >> 4;
- const g2 = g & 0xf;
- const b1 = b >> 4;
- const b2 = b & 0xf;
-
- return r1 === r2 && g1 === g2 && b1 === b2
- ? "#" + hexLookupTable[r1] + hexLookupTable[g1] + hexLookupTable[b1]
- : "#" +
- hexLookupTable[r1] +
- hexLookupTable[r2] +
- hexLookupTable[g1] +
- hexLookupTable[g2] +
- hexLookupTable[b1] +
- hexLookupTable[b2];
+ const hex =
+ r.toString(16).padStart(2, "0") +
+ g.toString(16).padStart(2, "0") +
+ b.toString(16).padStart(2, "0");
+
+ return hex[0] === hex[1] && hex[2] === hex[3] && hex[4] === hex[5]
+ ? "#" + hex[0] + hex[2] + hex[4]
+ : "#" + hex;
};
export const normalizeValue = (key: string, value: string | number): string => {
diff --git a/src/preprocess.ts b/src/preprocess.ts
index ca0c0d6..4df07ca 100644
--- a/src/preprocess.ts
+++ b/src/preprocess.ts
@@ -163,14 +163,25 @@ export const preprocessAtomicStyle = (style: Style): Style => {
let active: FlatStyle | undefined = undefined;
forEach(style, (key, value) => {
- if (key === ":hover") {
- hover = preprocessStyle(value as FlatStyle);
- } else if (key === ":focus") {
- focus = preprocessStyle(value as FlatStyle);
- } else if (key === ":active") {
- active = preprocessStyle(value as FlatStyle);
- } else {
- preprocessRule(output, key, value as ValueOf);
+ switch (key) {
+ case ":hover": {
+ hover = preprocessStyle(value as FlatStyle);
+ break;
+ }
+
+ case ":focus": {
+ focus = preprocessStyle(value as FlatStyle);
+ break;
+ }
+
+ case ":active": {
+ active = preprocessStyle(value as FlatStyle);
+ break;
+ }
+
+ default: {
+ preprocessRule(output, key, value as ValueOf);
+ }
}
});
diff --git a/src/sheet.ts b/src/sheet.ts
deleted file mode 100644
index 1e94e53..0000000
--- a/src/sheet.ts
+++ /dev/null
@@ -1,372 +0,0 @@
-import { hash } from "./hash";
-import { hyphenateName } from "./hyphenateName";
-import { normalizeValue } from "./normalizeValue";
-import {
- preprocessAtomicStyle,
- preprocessKeyframes,
- preprocessResetStyle,
-} from "./preprocess";
-import type { ClassNames, FlatStyle, Input, Keyframes, Style } from "./types";
-import { forEach } from "./utils";
-
-const getSheet = (id: string): CSSStyleSheet | null => {
- if (typeof document === "undefined") {
- return null;
- }
-
- const current = document.querySelector(`style[id="${id}"]`);
-
- if (current != null) {
- return current.sheet;
- }
-
- const element = document.createElement("style");
- element.setAttribute("id", id);
- document.head.appendChild(element);
-
- return element.sheet;
-};
-
-const getMediaRule = (
- sheet: CSSStyleSheet | null,
- index: number,
- media: string,
-): {
- cssRules: CSSRuleList | [];
- insertRule: (rule: string) => void;
-} => {
- if (sheet == null) {
- return {
- cssRules: [],
- insertRule: () => {},
- };
- }
-
- if (sheet.cssRules[index] == null) {
- try {
- sheet.insertRule(`@media ${media}{}`, index);
- } catch (error) {
- if (process.env.NODE_ENV === "development") {
- console.error(error);
- }
-
- return {
- cssRules: [],
- insertRule: () => {},
- };
- }
- }
-
- const mediaRule = sheet.cssRules[index] as CSSMediaRule;
-
- return {
- cssRules: mediaRule.cssRules,
- insertRule: (rule) => {
- try {
- mediaRule.insertRule(rule, mediaRule.cssRules.length);
- } catch (error) {
- if (process.env.NODE_ENV === "development") {
- console.error(error);
- }
- }
- },
- };
-};
-
-const stringifyRule = (key: string, value: string | number): string => {
- if (key === "appearance") {
- return `-webkit-appearance:${value};appearance:${value}`;
- }
- if (key === "lineClamp") {
- return `-webkit-line-clamp:${value};line-clamp:${value}`;
- }
-
- return `${hyphenateName(key)}:${normalizeValue(key, value)}`;
-};
-
-const extractClassNames = (items: ClassNames, acc: string[]): string[] => {
- for (const item of items) {
- if (typeof item === "string") {
- for (const part of item.split(" ")) {
- if (part !== "") {
- acc.push(part);
- }
- }
- } else if (Array.isArray(item)) {
- extractClassNames(item, acc);
- }
- }
-
- return acc;
-};
-
-const appendString = (acc: string, value: string): string =>
- acc + (acc ? " " + value : value);
-
-const appendClassNames = (
- acc: string,
- classNames: Record,
-): string => {
- let output = acc;
-
- forEach(classNames, (_, value) => {
- output = appendString(output, value);
- });
-
- return output;
-};
-
-const getClassName = (rule: CSSStyleRule): string => {
- const selector = rule.selectorText;
- const end = selector.indexOf(":");
- return end > -1 ? selector.substring(1, end) : selector.substring(1);
-};
-
-export const createSheet = () => {
- const sheet = getSheet("swan-stylesheet");
-
- const keyframesSheet = getMediaRule(sheet, 0, "all");
- const resetSheet = getMediaRule(sheet, 1, "all");
- const atomicSheet = getMediaRule(sheet, 2, "all");
- const hoverSheet = getMediaRule(sheet, 3, "(hover:hover)");
- const focusSheet = getMediaRule(sheet, 4, "all");
- const activeSheet = getMediaRule(sheet, 5, "all");
-
- const keyframesNames = new Set();
- const resetClassNames = new Set();
- const atomicClassNames = new Map();
- const hoverClassNames = new Map();
- const focusClassNames = new Map();
- const activeClassNames = new Map();
-
- // Rehydrate keyframes sheet
- for (const rule of keyframesSheet.cssRules) {
- if (rule instanceof CSSKeyframesRule) {
- keyframesNames.add(rule.name);
- }
- }
-
- // Rehydrate reset sheet
- for (const rule of resetSheet.cssRules) {
- if (rule instanceof CSSStyleRule) {
- resetClassNames.add(getClassName(rule));
- }
- }
-
- // Rehydrate atomic sheet
- for (const rule of atomicSheet.cssRules) {
- if (rule instanceof CSSStyleRule) {
- atomicClassNames.set(getClassName(rule), rule.style[0]);
- }
- }
-
- // Rehydrate hover sheet
- for (const rule of hoverSheet.cssRules) {
- if (rule instanceof CSSStyleRule) {
- hoverClassNames.set(getClassName(rule), rule.style[0]);
- }
- }
-
- // Rehydrate focus sheet
- for (const rule of focusSheet.cssRules) {
- if (rule instanceof CSSStyleRule) {
- focusClassNames.set(getClassName(rule), rule.style[0]);
- }
- }
-
- // Rehydrate active sheet
- for (const rule of activeSheet.cssRules) {
- if (rule instanceof CSSStyleRule) {
- activeClassNames.set(getClassName(rule), rule.style[0]);
- }
- }
-
- const insertKeyframes = (keyframes: Keyframes): string | undefined => {
- let body = "";
-
- forEach(keyframes, (key, value) => {
- const rules: string[] = [];
-
- forEach(value, (key, value) => {
- rules.push(stringifyRule(key, value));
- });
-
- body += `${key}{${rules.join(";")}}`;
- });
-
- const name = "k-" + hash(body);
-
- if (!keyframesNames.has(name)) {
- keyframesSheet.insertRule(`@keyframes ${name}{${body}}`);
- keyframesNames.add(name);
- }
-
- return name;
- };
-
- const insertResetRule = (style: FlatStyle): string => {
- const rules: string[] = [];
-
- forEach(style, (key, value) => {
- rules.push(stringifyRule(key, value));
- });
-
- const body = rules.join(";");
- const className = "r-" + hash(body);
-
- if (!resetClassNames.has(className)) {
- resetSheet.insertRule(`.${className}{${body}}`);
- resetClassNames.add(className);
- }
-
- return className;
- };
-
- const insertAtomicRules = (style: Style): string => {
- let classNames = "";
-
- forEach(style, (key, value) => {
- if (key === ":hover") {
- forEach(value as FlatStyle, (key, value) => {
- const rule = stringifyRule(key, value);
- const className = "h-" + hash(rule);
-
- if (!hoverClassNames.has(className)) {
- hoverSheet.insertRule(`.${className}:hover{${rule}}`);
- hoverClassNames.set(className, key);
- }
-
- classNames = appendString(classNames, className);
- });
- } else if (key === ":focus") {
- forEach(value as FlatStyle, (key, value) => {
- const rule = stringifyRule(key, value);
- const className = "f-" + hash(rule);
-
- if (!focusClassNames.has(className)) {
- focusSheet.insertRule(`.${className}:focus-visible{${rule}}`);
- focusClassNames.set(className, key);
- }
-
- classNames = appendString(classNames, className);
- });
- } else if (key === ":active") {
- forEach(value as FlatStyle, (key, value) => {
- const rule = stringifyRule(key, value);
- const className = "a-" + hash(rule);
-
- if (!activeClassNames.has(className)) {
- activeSheet.insertRule(`.${className}:active{${rule}}`);
- activeClassNames.set(className, key);
- }
-
- classNames = appendString(classNames, className);
- });
- } else {
- const rule = stringifyRule(key, value as string | number);
- const className = "x-" + hash(rule);
-
- if (!atomicClassNames.has(className)) {
- atomicSheet.insertRule(`.${className}{${rule}}`);
- atomicClassNames.set(className, key);
- }
-
- classNames = appendString(classNames, className);
- }
- });
-
- return classNames;
- };
-
- const input: Input = {
- keyframes: (keyframes) => insertKeyframes(preprocessKeyframes(keyframes)),
- };
-
- return {
- css: {
- make: (
- styles: Record | ((input: Input) => Record),
- ): Record => {
- const output = {} as Record;
-
- forEach(
- typeof styles === "function" ? styles(input) : styles,
- (key, value) => {
- output[key] =
- key[0] === "$"
- ? insertResetRule(preprocessResetStyle(value))
- : insertAtomicRules(preprocessAtomicStyle(value));
- },
- );
-
- return output;
- },
- },
- cx: (...items: ClassNames): string => {
- const classNames = extractClassNames(items, []);
-
- let output = "";
-
- let cacheKey: string | undefined = undefined;
- let reset: string | undefined = undefined;
-
- const atomic: Record = {};
- const hover: Record = {};
- const focus: Record = {};
- const active: Record = {};
-
- for (const className of classNames) {
- cacheKey = atomicClassNames.get(className);
-
- if (cacheKey != null) {
- atomic[cacheKey] = className;
- continue;
- }
-
- cacheKey = hoverClassNames.get(className);
-
- if (cacheKey != null) {
- hover[cacheKey] = className;
- continue;
- }
-
- cacheKey = focusClassNames.get(className);
-
- if (cacheKey != null) {
- focus[cacheKey] = className;
- continue;
- }
-
- cacheKey = activeClassNames.get(className);
-
- if (cacheKey != null) {
- active[cacheKey] = className;
- continue;
- }
-
- if (resetClassNames.has(className)) {
- if (reset == null) {
- reset = className;
- } else if (reset !== className) {
- if (process.env.NODE_ENV === "development") {
- console.warn("`cx` only accepts one reset style.");
- }
- }
- } else {
- output = appendString(output, className);
- }
- }
-
- if (reset != null) {
- output = appendString(output, reset);
- }
-
- output = appendClassNames(output, atomic);
- output = appendClassNames(output, hover);
- output = appendClassNames(output, focus);
- output = appendClassNames(output, active);
-
- return output;
- },
- };
-};
diff --git a/src/utils.ts b/src/utils.ts
index afa6819..590a943 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -1,5 +1,8 @@
import type { ValueOf } from "./types";
+export const appendString = (acc: string, value: string): string =>
+ acc + (acc ? " " + value : value);
+
export const forEach = >(
object: T,
callback: (key: keyof T, value: NonNullable>) => void,
diff --git a/src/vite-plugin.ts b/src/vite-plugin.ts
new file mode 100644
index 0000000..85134c9
--- /dev/null
+++ b/src/vite-plugin.ts
@@ -0,0 +1,430 @@
+import MagicString from "magic-string";
+import HTMLParser from "node-html-parser";
+import { createHash } from "node:crypto";
+import fs from "node:fs";
+import path from "node:path";
+import type { CallExpression, ImportDeclaration } from "oxc-parser";
+import { ResolverFactory } from "oxc-resolver";
+import { parseAndWalk } from "oxc-walker";
+import type { Plugin, ResolvedConfig } from "vite";
+
+type PluginOptions = {
+ fileName?: string;
+};
+
+const findSpecifier = (
+ { specifiers }: ImportDeclaration,
+ specifierName: "css" | "cx",
+) =>
+ specifiers.find(
+ (specifier) =>
+ specifier.type === "ImportSpecifier" &&
+ specifier.imported.type === "Identifier" &&
+ specifier.imported.name === specifierName,
+ );
+
+const isCssMethod = (
+ { callee }: CallExpression,
+ importName: string,
+ methodName: "extend" | "make",
+) =>
+ importName !== "" &&
+ callee.type === "MemberExpression" &&
+ callee.object.type === "Identifier" &&
+ callee.object.name === importName &&
+ callee.property.type === "Identifier" &&
+ callee.property.name === methodName;
+
+const normalizeConfig = (config: ResolvedConfig) => {
+ const { build, configFile: file, resolve } = config;
+ const input = build.rollupOptions.input ?? "index.html"; // fallback to vite's default
+
+ // https://vite.dev/config/shared-options.html#resolve-extensions
+ const supportedExts = new Set([".mjs", ".js", ".mts", ".ts", ".jsx", ".tsx"]);
+
+ const root = path.isAbsolute(config.root)
+ ? config.root
+ : path.resolve(
+ file != null ? path.dirname(file) : process.cwd(),
+ config.root,
+ );
+
+ return {
+ assetsDir: build.assetsDir,
+ root,
+ extensions: resolve.extensions.filter((ext) => supportedExts.has(ext)),
+
+ alias: resolve.alias.reduce>(
+ (acc, { find, replacement }) =>
+ find === "string"
+ ? { ...acc, [find]: [...(acc[find] ?? []), replacement] }
+ : acc,
+ {},
+ ),
+
+ inputs: (typeof input === "string"
+ ? [input]
+ : Array.isArray(input)
+ ? input
+ : Object.values(input)
+ ).map((input) => path.resolve(root, input)),
+ };
+};
+
+const plugin = async (options: PluginOptions = {}): Promise => {
+ const { css, getCssMakeInput, getCssFileContent } = await import("./css");
+ const { caches } = await import("./cx");
+
+ const packageName = "@swan-io/css";
+ const packageAliases = new Set([packageName]);
+
+ let assetsDir = "assets";
+ let cxVirtualModuleCode = "";
+ let emittedFileName = "";
+
+ const cxVirtualModuleId = "virtual:@swan-io/cx";
+ const cxResolvedVirtualModuleId = "\0" + cxVirtualModuleId;
+
+ return {
+ name: packageName,
+
+ configResolved: (resolvedConfig) => {
+ const config = normalizeConfig(resolvedConfig);
+ const { alias, extensions, inputs, root } = config;
+
+ assetsDir = config.assetsDir;
+
+ if (alias[packageName] != null) {
+ alias[packageName].forEach((item) => packageAliases.add(item));
+ }
+
+ const resolve = new ResolverFactory({ alias, extensions });
+ const resolvedFiles = new Set();
+
+ const visit = (file: string) => {
+ if (resolvedFiles.has(file)) {
+ return;
+ }
+
+ resolvedFiles.add(file);
+
+ const code = fs.readFileSync(file, "utf-8");
+ const dir = path.dirname(file);
+ const ext = path.extname(file);
+
+ if (ext === ".html" || ext === ".htm") {
+ const html = HTMLParser.parse(code);
+
+ const imports = [...html.querySelectorAll(`script[type="module"]`)]
+ .map((item) => item.getAttribute("src"))
+ .filter((src) => src != null)
+ .filter((src) => extensions.includes(path.extname(src)))
+ .map((src) =>
+ path.resolve(
+ root,
+ path.isAbsolute(src) ? path.join(dir, src) : src,
+ ),
+ );
+
+ // Depth-first: visit each import before continuing
+ for (const item of imports) {
+ visit(item);
+ }
+ } else if (extensions.includes(ext)) {
+ const imports = new Set();
+
+ let cssImportName = "";
+
+ parseAndWalk(code, file, (node) => {
+ switch (node.type) {
+ case "ExportNamedDeclaration": {
+ if (
+ typeof node.source != null &&
+ typeof node.source?.value === "string"
+ ) {
+ imports.add(node.source.value);
+ }
+
+ break;
+ }
+
+ case "ExportAllDeclaration": {
+ imports.add(node.source.value);
+ break;
+ }
+
+ case "ImportExpression": {
+ if (
+ node.source.type === "Literal" &&
+ typeof node.source.value === "string"
+ ) {
+ imports.add(node.source.value);
+ }
+
+ break;
+ }
+
+ case "ImportDeclaration": {
+ imports.add(node.source.value);
+
+ if (packageAliases.has(node.source.value)) {
+ const specifier = findSpecifier(node, "css");
+
+ if (specifier != null) {
+ cssImportName = specifier.local.name;
+ }
+ }
+
+ break;
+ }
+
+ case "CallExpression": {
+ if (isCssMethod(node, cssImportName, "extend")) {
+ const arg = node.arguments[0];
+
+ if (arg?.type === "ObjectExpression") {
+ css.extend(
+ new Function(
+ `return ${code.slice(arg.start, arg.end)};`,
+ )(),
+ );
+ }
+
+ break;
+ }
+
+ if (isCssMethod(node, cssImportName, "make")) {
+ const arg = node.arguments[0];
+
+ if (arg?.type === "ObjectExpression") {
+ css.make(
+ new Function(
+ `return ${code.slice(arg.start, arg.end)};`,
+ )(),
+ );
+ } else if (
+ arg?.type === "ArrowFunctionExpression" ||
+ arg?.type === "FunctionExpression"
+ ) {
+ css.make(
+ new Function(
+ "input",
+ `return (${code.slice(arg.start, arg.end)})(input);`,
+ )(getCssMakeInput()),
+ );
+ }
+ }
+ }
+ }
+ });
+
+ for (const specifier of imports) {
+ try {
+ const resolved = resolve.sync(dir, specifier).path;
+
+ if (resolved != null) {
+ visit(resolved);
+ }
+ } catch {
+ // ignore unresolved
+ }
+ }
+ }
+ };
+
+ for (const input of inputs) {
+ visit(input);
+ }
+
+ const cxId = path.join(__dirname, "./cx.mjs");
+ const cxCode = fs.readFileSync(cxId, "utf-8");
+
+ const magicString = new MagicString(cxCode);
+
+ const toStringMap = (map: Map): string =>
+ `new Map([${[...map.entries()]
+ .reduce((acc, [key, value]) => {
+ return value != null ? [...acc, `["${key}", "${value}"]`] : acc;
+ }, [])
+ .join(",")}])`;
+
+ parseAndWalk(cxCode, cxId, (node) => {
+ if (node.type === "VariableDeclaration") {
+ const declaration = node.declarations[0];
+
+ if (
+ declaration != null &&
+ declaration.id.type === "Identifier" &&
+ declaration.id.name === "caches"
+ ) {
+ magicString.overwrite(
+ node.start,
+ node.end,
+ `
+var caches = {
+ reset: new Set([${[...caches.reset].map((item) => `"${item}"`).join(",")}]),
+ atomic: ${toStringMap(caches.atomic)},
+ hover: ${toStringMap(caches.hover)},
+ focus: ${toStringMap(caches.focus)},
+ active: ${toStringMap(caches.active)},
+};
+`.trim(),
+ );
+ }
+ }
+ });
+
+ cxVirtualModuleCode = magicString.toString();
+ },
+
+ resolveId(id) {
+ if (id === cxVirtualModuleId) {
+ return cxResolvedVirtualModuleId;
+ }
+ },
+
+ load(id) {
+ if (id === cxResolvedVirtualModuleId) {
+ return cxVirtualModuleCode;
+ }
+ },
+
+ transform(code, id) {
+ let cssImportName = "";
+ let cxImportName = "";
+
+ const magicString = new MagicString(code);
+
+ parseAndWalk(code, id, (node) => {
+ switch (node.type) {
+ case "ImportDeclaration": {
+ if (packageAliases.has(node.source.value)) {
+ const cssLocalName = findSpecifier(node, "css")?.local.name;
+ const cxLocalName = findSpecifier(node, "cx")?.local.name;
+
+ if (cssLocalName != null) {
+ cssImportName = cssLocalName;
+ magicString.overwrite(node.start, node.end, "");
+ }
+
+ if (cxLocalName != null && cxLocalName !== "") {
+ cxImportName = cxLocalName;
+
+ magicString.overwrite(
+ node.start,
+ node.end,
+ `import { ${cxLocalName === "cx" ? "cx" : `cx as ${cxLocalName}`} } from "${cxVirtualModuleId}";`,
+ );
+ }
+ }
+
+ break;
+ }
+
+ case "CallExpression": {
+ if (isCssMethod(node, cssImportName, "extend")) {
+ const arg = node.arguments[0];
+
+ if (arg?.type === "ObjectExpression") {
+ const result = css.extend(
+ new Function(
+ `return ${magicString.slice(arg.start, arg.end)};`,
+ )(),
+ );
+
+ magicString.overwrite(
+ node.start,
+ node.end,
+ JSON.stringify(result, null, 2),
+ );
+ } else {
+ magicString.overwrite(node.start, node.end, "{}");
+ }
+
+ break;
+ }
+
+ if (isCssMethod(node, cssImportName, "make")) {
+ const arg = node.arguments[0];
+
+ if (arg?.type === "ObjectExpression") {
+ const result = css.make(
+ new Function(
+ `return ${magicString.slice(arg.start, arg.end)};`,
+ )(),
+ );
+
+ magicString.overwrite(
+ node.start,
+ node.end,
+ JSON.stringify(result, null, 2),
+ );
+ } else if (
+ arg?.type === "ArrowFunctionExpression" ||
+ arg?.type === "FunctionExpression"
+ ) {
+ const result = css.make(
+ new Function(
+ "input",
+ `return (${magicString.slice(arg.start, arg.end)})(input);`,
+ )(getCssMakeInput()),
+ );
+
+ magicString.overwrite(
+ node.start,
+ node.end,
+ JSON.stringify(result, null, 2),
+ );
+ } else {
+ magicString.overwrite(node.start, node.end, "{}");
+ }
+ }
+ }
+ }
+ });
+
+ if (cssImportName !== "" || cxImportName !== "") {
+ return {
+ code: magicString.toString(),
+ map: magicString.generateMap({ hires: true }),
+ };
+ }
+ },
+
+ generateBundle() {
+ const source = getCssFileContent();
+
+ const hash = createHash("sha256")
+ .update(source)
+ .digest("hex")
+ .slice(0, 8);
+
+ const fileName = options.fileName ?? "styles";
+ emittedFileName = path.join(assetsDir, `${fileName}-${hash}.css`);
+
+ this.emitFile({
+ type: "asset",
+ source,
+ fileName: emittedFileName,
+ });
+ },
+
+ transformIndexHtml(html) {
+ if (emittedFileName !== "") {
+ const attrs = {
+ rel: "stylesheet",
+ id: "swan-stylesheet",
+ crossorigin: true,
+ href: "/" + emittedFileName,
+ };
+
+ return {
+ html,
+ tags: [{ tag: "link", injectTo: "head", attrs }],
+ };
+ }
+ },
+ };
+};
+
+export default plugin;
diff --git a/tsup.config.ts b/tsup.config.ts
index eafcf7a..408807d 100644
--- a/tsup.config.ts
+++ b/tsup.config.ts
@@ -1,16 +1,18 @@
import { defineConfig } from "tsup";
const config = {
- entry: ["src/index.ts"],
+ entry: ["src/index.ts", "src/vite-plugin.ts"],
target: "es2019",
tsconfig: "./tsconfig.build.json",
bundle: true,
clean: false,
+ dts: false,
sourcemap: false,
splitting: false,
};
export default defineConfig([
{ ...config, format: "cjs", dts: true },
- { ...config, format: "esm", dts: false },
+ { ...config, format: "esm" },
+ { ...config, entry: ["src/cx.ts"], format: "esm" },
]);