├── packages ├── playground │ ├── src │ │ ├── vite-env.d.ts │ │ ├── main.tsx │ │ ├── prismjs │ │ │ ├── prismjs.ts │ │ │ └── prism.css │ │ ├── hooks │ │ │ ├── toggleDarkMode │ │ │ │ ├── toggleDarkMode.module.css.d.ts │ │ │ │ ├── toggleDarkMode.module.css │ │ │ │ └── useToggleDarkMode.tsx │ │ │ └── useWasmWorker.ts │ │ ├── App.module.css.d.ts │ │ ├── worker.ts │ │ ├── index.css │ │ ├── App.module.css │ │ └── App.tsx │ ├── public │ │ ├── github.png │ │ └── sitemap.xml │ ├── tsconfig.node.json │ ├── .gitignore │ ├── tsconfig.json │ ├── vite.config.ts │ ├── index.html │ └── package.json ├── node │ ├── .npmignore │ ├── samples │ │ ├── test.css │ │ ├── test.css.ts │ │ └── index.ts │ ├── src │ │ └── lib.rs │ ├── release │ │ └── index.js │ ├── Cargo.toml │ ├── package.json │ └── README.md ├── lib │ ├── Cargo.toml │ ├── package.json │ └── src │ │ ├── keyframes.rs │ │ ├── path.rs │ │ ├── lib.rs │ │ ├── supports.rs │ │ ├── utils.rs │ │ ├── media.rs │ │ └── value.rs └── wasm │ ├── src │ └── lib.rs │ ├── package.json │ └── Cargo.toml ├── .prettierrc.js ├── .eslintrc.js ├── .gitignore ├── .github ├── workflows │ ├── check.yml │ ├── release-node.yml │ └── release-playground.yml └── CONTRIBUTING.md ├── .vscode └── settings.json ├── LICENSE ├── package.json └── README.md /packages/playground/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /packages/node/.npmignore: -------------------------------------------------------------------------------- 1 | samples 2 | .github 3 | src 4 | release 5 | release.sh 6 | target 7 | Cargo.lock 8 | Cargo.toml -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | trailingComma: "es5", 3 | tabWidth: 2, 4 | semi: true, 5 | singleQuote: false, 6 | }; 7 | -------------------------------------------------------------------------------- /packages/playground/public/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activeguild/css-to-vanilla-extract/HEAD/packages/playground/public/github.png -------------------------------------------------------------------------------- /packages/playground/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "esnext", 5 | "moduleResolution": "node" 6 | }, 7 | "include": ["vite.config.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-undef 2 | module.exports = { 3 | root: true, 4 | parser: "@typescript-eslint/parser", 5 | plugins: ["@typescript-eslint"], 6 | extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], 7 | }; 8 | -------------------------------------------------------------------------------- /packages/node/samples/test.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | } 4 | 5 | @media (min-width: 768px) { 6 | .container { 7 | width: 750px; 8 | } 9 | } 10 | 11 | @media (min-width: 992px) { 12 | .container { 13 | width: 970px; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/playground/public/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | https://css-to-vanilla-extract.netlify.app/ 5 | 2022-06-27 6 | 7 | -------------------------------------------------------------------------------- /packages/node/samples/test.css.ts: -------------------------------------------------------------------------------- 1 | import { style } from "@vanilla-extract/css" 2 | 3 | export const container = style({ 4 | display: "flex", 5 | "@media": { 6 | "(min-width: 768px)": { 7 | width: "750px", 8 | width: "970px", 9 | }, 10 | }, 11 | }); 12 | 13 | -------------------------------------------------------------------------------- /packages/playground/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App"; 4 | import "./index.css"; 5 | 6 | const rootEl = document.getElementById("root"); 7 | rootEl && 8 | ReactDOM.createRoot(rootEl).render( 9 | 10 | 11 | 12 | ); 13 | -------------------------------------------------------------------------------- /packages/playground/src/prismjs/prismjs.ts: -------------------------------------------------------------------------------- 1 | import "prismjs/prism.js"; 2 | import "prismjs/components/prism-javascript.min.js"; 3 | import "prismjs/components/prism-typescript.min.js"; 4 | import "prismjs/plugins/toolbar/prism-toolbar.min.js"; 5 | import "prismjs/plugins/copy-to-clipboard/prism-copy-to-clipboard.min.js"; 6 | import "prismjs/themes/prism-okaidia.min.css"; 7 | import "./prism.css"; 8 | -------------------------------------------------------------------------------- /packages/playground/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /packages/playground/src/hooks/toggleDarkMode/toggleDarkMode.module.css.d.ts: -------------------------------------------------------------------------------- 1 | declare const classNames: { 2 | readonly toggle: "toggle"; 3 | readonly toggleInput: "toggleInput"; 4 | readonly toggleDark: "toggleDark"; 5 | readonly toggleLight: "toggleLight"; 6 | readonly toggleContainer: "toggleContainer"; 7 | readonly dark: "dark"; 8 | readonly light: "light"; 9 | readonly toggleCircle: "toggleCircle"; 10 | }; 11 | export = classNames; 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | target 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | dist 13 | dist-node 14 | node_modules 15 | 16 | .wireit 17 | # Local Netlify folder 18 | .netlify 19 | -------------------------------------------------------------------------------- /packages/playground/src/App.module.css.d.ts: -------------------------------------------------------------------------------- 1 | declare const classNames: { 2 | readonly header: "header"; 3 | readonly headerInnder: "headerInnder"; 4 | readonly headerDesc: "headerDesc"; 5 | readonly headerIcon: "headerIcon"; 6 | readonly headerDarkMode: "headerDarkMode"; 7 | readonly container: "container"; 8 | readonly containerItem: "containerItem"; 9 | readonly error: "error"; 10 | readonly light: "light"; 11 | readonly dark: "dark"; 12 | }; 13 | export = classNames; 14 | -------------------------------------------------------------------------------- /packages/playground/src/worker.ts: -------------------------------------------------------------------------------- 1 | import init, { from_code } from "../../wasm/dist/wasm_css_to_vanilla_extract"; 2 | import "../../wasm/dist/wasm_css_to_vanilla_extract_bg.wasm?init"; 3 | 4 | init() 5 | .then(() => { 6 | onmessage = (e) => { 7 | try { 8 | postMessage({ code: from_code(e.data) }); 9 | } catch (error) { 10 | postMessage({ error }); 11 | } 12 | }; 13 | 14 | postMessage({ loaded: true }); 15 | }) 16 | .catch((e) => postMessage({ error: e })); 17 | -------------------------------------------------------------------------------- /packages/lib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "css_to_vanilla_extract" 3 | version = "0.0.48-alphaa" 4 | authors = ["j1ngzoue "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | grass = "0.11.0" 9 | cssparser = "0.29.6" 10 | clap = "3.2.16" 11 | swc_css_parser = "0.113.0" 12 | swc_common = "0.26.0" 13 | swc_css_ast = "0.104.0" 14 | convert_case = "0.5.0" 15 | regex = "1.6" 16 | 17 | [profile.release] 18 | # Tell `rustc` to optimize for small code size. 19 | lto = true 20 | codegen-units = 1 21 | opt-level = "z" 22 | -------------------------------------------------------------------------------- /packages/node/src/lib.rs: -------------------------------------------------------------------------------- 1 | use neon::prelude::*; 2 | 3 | extern crate css_to_vanilla_extract; 4 | 5 | #[neon::main] 6 | fn main(mut cx: ModuleContext) -> NeonResult<()> { 7 | cx.export_function("css_to_vanilla_extract", css_to_vanilla_extract)?; 8 | Ok(()) 9 | } 10 | 11 | fn css_to_vanilla_extract(mut cx: FunctionContext) -> JsResult { 12 | let file = cx.argument::(0).unwrap(); 13 | let path = file.value(&mut cx); 14 | css_to_vanilla_extract::from_path(&path); 15 | 16 | return Ok(cx.boolean(true)); 17 | } 18 | -------------------------------------------------------------------------------- /packages/node/release/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const index = require("./index.node"); 4 | const { program } = require("commander"); 5 | const path = require("path"); 6 | const pc = require("picocolors"); 7 | 8 | program.parse(process.argv); 9 | 10 | if (program.args[0]) { 11 | let fileName = null; 12 | try { 13 | fileName = path.resolve(path.resolve(), program.args[0]); 14 | } catch (event) { 15 | console.log(pc.yellow("Unable to resolve the path of the argument.")); 16 | } 17 | index.css_to_vanilla_extract(fileName); 18 | } else { 19 | console.log(pc.yellow("Argument is required.")); 20 | } 21 | -------------------------------------------------------------------------------- /packages/wasm/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate console_error_panic_hook; 2 | 3 | use wasm_bindgen::prelude::*; 4 | extern crate css_to_vanilla_extract; 5 | 6 | // When the `wee_alloc` feature is enabled, use `wee_alloc` as the global 7 | // allocator. 8 | #[cfg(feature = "wee_alloc")] 9 | #[global_allocator] 10 | static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; 11 | 12 | #[wasm_bindgen] 13 | pub fn from_code(code: JsValue) -> Result { 14 | match css_to_vanilla_extract::from_code(&code.as_string().unwrap()) { 15 | Ok(value) => Ok(JsValue::from_str(&value)), 16 | Err(error) => Err(JsValue::from_str(&error)), 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"], 20 | "references": [{ "path": "./tsconfig.node.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /packages/node/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "css-to-vanilla-extract" 3 | version = "0.0.48-alphaa" 4 | license = "MIT" 5 | edition = "2021" 6 | exclude = ["index.node"] 7 | 8 | [lib] 9 | crate-type = ["cdylib"] 10 | 11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 12 | 13 | [dependencies] 14 | css_to_vanilla_extract = { version = "0.0.48-alphaa", path = "../lib" } 15 | 16 | [dependencies.neon] 17 | version = "1.0.0-alpha.1" 18 | default-features = false 19 | features = ["napi-6"] 20 | 21 | [profile.release] 22 | # Tell `rustc` to optimize for small code size. 23 | lto = true 24 | codegen-units = 1 25 | opt-level = "z" 26 | -------------------------------------------------------------------------------- /packages/wasm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "css-to-vanilla-extract-wasm", 3 | "version": "0.0.48-alphaa", 4 | "description": "Generate vanilla-extract typescript file from the CSS (SCSS/SASS) file. for wasm.", 5 | "scripts": { 6 | "build": "wasm-pack build --target web --out-dir ./dist" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/activeguild/css-to-vanilla-extract.git" 11 | }, 12 | "author": "j1ngzoue", 13 | "license": "MIT", 14 | "bugs": { 15 | "url": "https://github.com/activeguild/css-to-vanilla-extract/issues" 16 | }, 17 | "homepage": "https://github.com/activeguild/css-to-vanilla-extract#readme" 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: push 4 | 5 | jobs: 6 | calculate-size: 7 | runs-on: macos-latest 8 | steps: 9 | - uses: actions/checkout@master 10 | - uses: actions/setup-node@v1 11 | with: 12 | node-version: "16.x" 13 | - name: caching 14 | uses: google/wireit@setup-github-actions-caching/v1 15 | - name: install dependencies 16 | run: npm ci 17 | - uses: actions-rs/toolchain@v1 18 | with: 19 | profile: minimal 20 | toolchain: stable 21 | - uses: Swatinem/rust-cache@v1 22 | with: 23 | working-directory: packages/node 24 | - name: test 25 | run: npm run test 26 | -------------------------------------------------------------------------------- /packages/playground/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | import sassDts from "vite-plugin-sass-dts"; 4 | import wasm from "vite-plugin-wasm"; 5 | import { visualizer } from "rollup-plugin-visualizer"; 6 | // https://vitejs.dev/config/ 7 | export default defineConfig(({ mode }) => { 8 | return { 9 | build: { 10 | experimentalCodeSplitting: true, 11 | minify: "terser", 12 | terserOptions: { 13 | compress: { 14 | passes: 2, 15 | }, 16 | }, 17 | }, 18 | plugins: [ 19 | react(), 20 | wasm(), 21 | sassDts(), 22 | visualizer({ gzipSize: true, open: true }), 23 | ], 24 | }; 25 | }); 26 | -------------------------------------------------------------------------------- /packages/lib/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "css-to-vanilla-extract-lib", 3 | "version": "0.0.48-alphaa", 4 | "description": "Generate vanilla-extract typescript file from the CSS (SCSS/SASS) file.", 5 | "scripts": { 6 | "build": "cargo build", 7 | "test": "cargo test -- --nocapture", 8 | "test:watch": "cargo watch -x fmt -x clippy -x test" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/activeguild/css-to-vanilla-extract.git" 13 | }, 14 | "author": "j1ngzoue", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/activeguild/css-to-vanilla-extract/issues" 18 | }, 19 | "homepage": "https://github.com/activeguild/css-to-vanilla-extract#readme" 20 | } 21 | -------------------------------------------------------------------------------- /packages/playground/src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --base: #e6e8e9; 3 | --base-dark: #313131; 4 | --vs-dark: #1e1e1e; 5 | --vs: #fefffe; 6 | } 7 | 8 | body { 9 | margin: 0; 10 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 11 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 12 | sans-serif; 13 | -webkit-font-smoothing: antialiased; 14 | -moz-osx-font-smoothing: grayscale; 15 | } 16 | 17 | body.dark { 18 | background-color: var(--base-dark); 19 | } 20 | 21 | body.light { 22 | background-color: var(--base); 23 | } 24 | 25 | code { 26 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 27 | monospace; 28 | } 29 | 30 | h1 { 31 | font-size: 1em; 32 | } 33 | 34 | .light span.mtk4 { 35 | color: #ee0000; 36 | } 37 | -------------------------------------------------------------------------------- /.github/workflows/release-node.yml: -------------------------------------------------------------------------------- 1 | name: automatic release 2 | on: 3 | push: 4 | tags: 5 | - "v*" 6 | jobs: 7 | release: 8 | name: release-node 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: checkout 12 | uses: actions/checkout@v2 13 | - name: Setup Node.js environment 14 | uses: actions/setup-node@v2 15 | with: 16 | node-version: 16.x 17 | registry-url: "https://registry.npmjs.org" 18 | - name: caching 19 | uses: google/wireit@setup-github-actions-caching/v1 20 | - name: install dependencies 21 | run: npm ci 22 | - name: add target 23 | run: rustup target add wasm32-wasi 24 | - name: release 25 | run: npm run publish:node 26 | env: 27 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 28 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "esbenp.prettier-vscode", 3 | "editor.formatOnPaste": true, 4 | "editor.formatOnSave": true, 5 | "editor.formatOnType": true, 6 | "editor.codeActionsOnSave": ["source.fixAll.eslint"], 7 | "typescript.tsdk": "node_modules/typescript/lib", 8 | "[rust]": { 9 | "editor.defaultFormatter": "rust-lang.rust-analyzer", 10 | "editor.formatOnSave": true 11 | }, 12 | "rust-analyzer.cargo.allFeatures": true, 13 | "rust-analyzer.checkOnSave.command": "clippy", 14 | "rust-analyzer.checkOnSave.extraArgs": [ 15 | "--", 16 | "-A", 17 | "clippy::needless_return" 18 | ], 19 | "rust-analyzer.linkedProjects": [ 20 | "./packages/lib/Cargo.toml", 21 | "./packages/wasm/Cargo.toml", 22 | "./packages/node/Cargo.toml" 23 | ], 24 | "[typescriptreact]": { 25 | "editor.defaultFormatter": "vscode.typescript-language-features" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 j1ngzoue 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/node/samples/index.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-var-requires 2 | const { vanillaExtractPlugin } = require("@vanilla-extract/esbuild-plugin"); 3 | 4 | // Style selectors must target the '&' character (along with any modifiers), e.g. `${parent} &` or `${parent} &:hover`. 5 | // This is to ensure that each style block only affects the styling of a single class. 6 | // If your selector is targeting another class, you should move it to the style definition for that class, e.g. given we have styles for 'parent' and 'child' elements, instead of adding a selector of `& ${child}`) to 'parent', you should add `${parent} &` to 'child'). 7 | // If your selector is targeting something global, use the 'globalStyle' function instead, e.g. if you wanted to write `& h1`, you should instead write 'globalStyle(`${parent} h1`, { ... })' 8 | 9 | // eslint-disable-next-line @typescript-eslint/no-var-requires 10 | require("esbuild") 11 | .build({ 12 | entryPoints: ["./samples/test.css.ts"], 13 | bundle: true, 14 | plugins: [vanillaExtractPlugin()], 15 | outfile: "./samples/dist/index.js", 16 | }) 17 | .catch(() => process.exit(1)); 18 | -------------------------------------------------------------------------------- /.github/workflows/release-playground.yml: -------------------------------------------------------------------------------- 1 | name: automatic release 2 | on: 3 | push: 4 | tags: 5 | - "v*" 6 | jobs: 7 | release: 8 | name: release-playground 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: checkout 12 | uses: actions/checkout@v2 13 | - name: Setup Node.js environment 14 | uses: actions/setup-node@v2 15 | with: 16 | node-version: 16.x 17 | registry-url: "https://registry.npmjs.org" 18 | - name: caching 19 | uses: google/wireit@setup-github-actions-caching/v1 20 | - name: install dependencies 21 | run: npm ci 22 | - name: install netlify-cli 23 | run: npm i -D netlify-cli 24 | - name: add target 25 | run: rustup target add wasm32-wasi 26 | - name: install wasm-pack 27 | run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh 28 | - name: build - playground 29 | run: npm run build:playground 30 | - name: publish - playground 31 | run: npx netlify-cli deploy --dir=packages/playground/dist --build --prod 32 | env: 33 | NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} 34 | NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} 35 | -------------------------------------------------------------------------------- /packages/wasm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasm-css-to-vanilla-extract" 3 | version = "0.0.48-alphaa" 4 | authors = ["j1ngzoue "] 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "rlib"] 9 | 10 | [features] 11 | default = ["console_error_panic_hook"] 12 | 13 | [dependencies] 14 | wasm-bindgen = "0.2.82" 15 | css_to_vanilla_extract = { version = "0.0.48-alphaa", path = "../lib" } 16 | getrandom = { version = "0.2.7", features = ["js"] } 17 | 18 | # The `console_error_panic_hook` crate provides better debugging of panics by 19 | # logging them with `console.error`. This is great for development, but requires 20 | # all the `std::fmt` and `std::panicking` infrastructure, so isn't great for 21 | # code size when deploying. 22 | console_error_panic_hook = { version = "0.1.6", optional = true } 23 | 24 | # `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size 25 | # compared to the default allocator's ~10K. It is slower than the default 26 | # allocator, however. 27 | # 28 | # Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now. 29 | wee_alloc = { version = "0.4.5", optional = true } 30 | 31 | [dev-dependencies] 32 | wasm-bindgen-test = "0.3.32" 33 | 34 | [profile.release] 35 | # Tell `rustc` to optimize for small code size. 36 | lto = true 37 | codegen-units = 1 38 | opt-level = "z" 39 | -------------------------------------------------------------------------------- /packages/playground/src/hooks/useWasmWorker.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import WasmWorker from "../worker?worker"; 3 | 4 | type Message = { 5 | loaded?: boolean; 6 | error?: string; 7 | code?: string; 8 | }; 9 | 10 | export const useWasmWorker = () => { 11 | const [worker, setWorker] = useState(null); 12 | const [wasmLoaded, setWasmLoaded] = useState(false); 13 | const [receiveMessage, setReceiveMessage] = useState(""); 14 | const [receiveErrorMessage, setReceiveErrorMessage] = useState(""); 15 | 16 | useEffect(() => { 17 | const _worker = new WasmWorker(); 18 | 19 | const handle = (e: MessageEvent) => { 20 | if (e.data.loaded) { 21 | setWasmLoaded(true); 22 | } else if (e.data.error) { 23 | setReceiveErrorMessage(e.data.error); 24 | } else if (e.data.code) { 25 | setReceiveMessage(e.data.code); 26 | setReceiveErrorMessage("Success."); 27 | } 28 | }; 29 | 30 | _worker.addEventListener("message", handle, false); 31 | 32 | setWorker(_worker); 33 | 34 | return () => { 35 | _worker.removeEventListener("message", handle); 36 | _worker.terminate(); 37 | }; 38 | }, []); 39 | 40 | return { 41 | worker: wasmLoaded ? worker : null, 42 | receiveMessage, 43 | receiveErrorMessage, 44 | }; 45 | }; 46 | -------------------------------------------------------------------------------- /packages/playground/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 12 | 16 | 20 | 24 | 25 | 26 | 27 | 32 | 33 | CSS to vanilla-extract playground 34 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /packages/lib/src/keyframes.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | utils::{ 3 | wrap_keyframes, wrap_properties_with_colon, wrap_properties_with_comma, wrap_property, 4 | }, 5 | value::get_component_value, 6 | }; 7 | 8 | pub fn get_keyframes( 9 | keyframes_name: &swc_css_ast::KeyframesName, 10 | block: &Option, 11 | ) -> String { 12 | // [sample] 13 | // globalKeyframes('rotate', { 14 | // '0%': { transform: 'rotate(0deg)' }, 15 | // '100%': { transform: 'rotate(360deg)' } 16 | // }); 17 | let keyframes_name = match &keyframes_name { 18 | swc_css_ast::KeyframesName::CustomIdent(custom_ident) => custom_ident.value.to_string(), 19 | swc_css_ast::KeyframesName::Str(str) => str.value.to_string(), 20 | }; 21 | 22 | let mut component_values = String::new(); 23 | 24 | if let Some(block) = &block { 25 | for component_value in &block.value { 26 | let component = get_component_value(component_value); 27 | 28 | let mut rule = String::new(); 29 | for key_value in component[0].key_value_pairs.clone().into_iter() { 30 | rule.push_str(&wrap_property(key_value.key, key_value.value, Some(4))); 31 | } 32 | 33 | component_values.push_str(&wrap_properties_with_colon( 34 | component[0].clone().ve, 35 | rule, 36 | Some(2), 37 | )); 38 | } 39 | } 40 | 41 | wrap_keyframes(wrap_properties_with_comma( 42 | keyframes_name, 43 | component_values, 44 | Some(0), 45 | )) 46 | } 47 | -------------------------------------------------------------------------------- /packages/playground/src/App.module.css: -------------------------------------------------------------------------------- 1 | .header { 2 | background: rgb(255, 116, 163); 3 | background: linear-gradient( 4 | 90deg, 5 | rgba(255, 116, 163, 1) 0%, 6 | rgba(73, 218, 239, 1) 100% 7 | ); 8 | display: flex; 9 | justify-content: center; 10 | flex-direction: column; 11 | color: white; 12 | height: 60px; 13 | } 14 | .headerInnder { 15 | display: flex; 16 | align-items: center; 17 | justify-content: space-between; 18 | padding: 0 12px; 19 | margin: 0; 20 | font-size: 1em; 21 | } 22 | .headerDesc { 23 | font-size: 0.8em; 24 | } 25 | .headerIcon { 26 | display: flex; 27 | align-items: center; 28 | margin-left: 12px; 29 | cursor: pointer; 30 | text-decoration: none; 31 | color: inherit; 32 | } 33 | .headerIcon > img { 34 | margin-left: 12px; 35 | height: 100%; 36 | } 37 | .headerDarkMode { 38 | text-align: right; 39 | margin: 2px -2px 0 0; 40 | } 41 | .container { 42 | display: grid; 43 | grid-template-columns: 50% 50%; 44 | } 45 | .containerItem { 46 | padding: 12px 12px 0 12px; 47 | height: calc(80vh + 12px); 48 | } 49 | .containerItem + .containerItem { 50 | padding-left: 0; 51 | } 52 | .containerItem + .containerItem > pre { 53 | height: 100%; 54 | } 55 | .error { 56 | /* 100vh - header - editor-container */ 57 | height: calc(100vh - 80vh - 96px); 58 | grid-row: 2; 59 | grid-column: 1 / span 2; 60 | margin: 12px; 61 | padding: 12px; 62 | overflow: auto; 63 | } 64 | 65 | .light.error { 66 | background-color: var(--vs); 67 | color: black; 68 | } 69 | .dark.error { 70 | background-color: var(--vs-dark); 71 | color: white; 72 | } 73 | -------------------------------------------------------------------------------- /packages/playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "playground", 3 | "version": "0.0.48-alphaa", 4 | "description": "Generate vanilla-extract typescript file from the CSS (SCSS/SASS) file. for playground.", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "analyze": "vite build", 10 | "preview": "vite preview", 11 | "lint": "eslint . --ext '.js,.jsx,.ts,.tsx' --ignore-path .gitignore", 12 | "lint:fix": "eslint . --fix --ext '.js,.jsx,.ts,.tsx' --ignore-path .gitignore", 13 | "prettier": "prettier . --write --ignore-path .gitignore" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/activeguild/css-to-vanilla-extract.git" 18 | }, 19 | "author": "j1ngzoue", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/activeguild/css-to-vanilla-extract/issues" 23 | }, 24 | "homepage": "https://github.com/activeguild/css-to-vanilla-extract#readme", 25 | "dependencies": { 26 | "@monaco-editor/react": "^4.5.0", 27 | "modern-css-reset": "^1.4.0", 28 | "monaco-editor": "^0.38.0", 29 | "prismjs": "^1.29.0", 30 | "react": "^18.2.0", 31 | "react-dom": "^18.2.0" 32 | }, 33 | "devDependencies": { 34 | "@types/prismjs": "^1.26.0", 35 | "@types/react": "^18.2.4", 36 | "@types/react-dom": "^18.2.3", 37 | "@vitejs/plugin-react": "^4.0.0", 38 | "rollup-plugin-visualizer": "^5.9.0", 39 | "terser": "^5.17.1", 40 | "typescript": "^5.0.4", 41 | "vite": "^4.3.4", 42 | "vite-plugin-sass-dts": "^1.3.4", 43 | "vite-plugin-wasm": "^3.2.2" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "css-to-vanilla-extract", 3 | "version": "0.0.48-alpha", 4 | "description": "Generate vanilla-extract typescript file from the CSS (SCSS/SASS) file. for node.", 5 | "main": "index.js", 6 | "bin": { 7 | "css-to-vanilla-extract": "dist/index.js" 8 | }, 9 | "scripts": { 10 | "build": "npm run clean && cargo-cp-artifact -nc ./dist/index.node -- cargo build --message-format=json-render-diagnostics && cp release/index.js ./dist", 11 | "build-release": "npm run build", 12 | "test-dir": "node ./dist/index.js ./samples", 13 | "test-file": "node ./dist/index.js ./samples/test.css", 14 | "test-css": "node -r esbuild-register ./samples/index.ts", 15 | "clean": "rm -rf ./dist" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/activeguild/css-to-vanilla-extract.git" 20 | }, 21 | "keywords": [ 22 | "css", 23 | "scss", 24 | "sass", 25 | "convert", 26 | "vanilla-extract", 27 | "typescript", 28 | "react", 29 | "generate", 30 | "mygrate", 31 | "mygration" 32 | ], 33 | "author": "j1ngzoue", 34 | "license": "MIT", 35 | "bugs": { 36 | "url": "https://github.com/activeguild/css-to-vanilla-extract/issues" 37 | }, 38 | "homepage": "https://github.com/activeguild/css-to-vanilla-extract#readme", 39 | "dependencies": { 40 | "@vanilla-extract/css": "^1.11.0", 41 | "commander": "^10.0.1", 42 | "picocolors": "^1.0.0" 43 | }, 44 | "devDependencies": { 45 | "@types/node": "^18.16.3", 46 | "@vanilla-extract/esbuild-plugin": "^2.2.2", 47 | "cargo-cp-artifact": "^0.1.8", 48 | "esbuild": "^0.17.18", 49 | "esbuild-register": "^3.4.2" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/playground/src/hooks/toggleDarkMode/toggleDarkMode.module.css: -------------------------------------------------------------------------------- 1 | .toggle { 2 | touch-action: pan-x; 3 | display: inline-block; 4 | position: relative; 5 | cursor: pointer; 6 | background-color: transparent; 7 | border: 0; 8 | padding: 0; 9 | -webkit-touch-callout: none; 10 | -webkit-user-select: none; 11 | -ms-user-select: none; 12 | user-select: none; 13 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 14 | -webkit-tap-highlight-color: transparent; 15 | } 16 | 17 | .toggleInput { 18 | border: 0; 19 | clip: rect(0 0 0 0); 20 | height: 1px; 21 | margin: -1px; 22 | overflow: hidden; 23 | padding: 0; 24 | position: absolute; 25 | width: 1px; 26 | } 27 | 28 | .toggleDark, 29 | .toggleLight { 30 | position: absolute; 31 | width: 10px; 32 | height: 10px; 33 | top: 0; 34 | bottom: 0; 35 | margin-top: auto; 36 | margin-bottom: auto; 37 | line-height: 0; 38 | opacity: 0; 39 | transition: opacity 0.25s ease; 40 | } 41 | .toggleDark { 42 | left: 8px; 43 | } 44 | .toggleLight { 45 | opacity: 1; 46 | right: 10px; 47 | } 48 | 49 | .toggleLight span, 50 | .toggleDark span { 51 | align-items: center; 52 | display: flex; 53 | height: 10px; 54 | justify-content: center; 55 | position: relative; 56 | width: 10px; 57 | } 58 | 59 | .toggleContainer { 60 | width: 46px; 61 | height: 20px; 62 | padding: 0; 63 | border-radius: 30px; 64 | transition: all 0.2s ease; 65 | } 66 | 67 | .dark .toggleContainer { 68 | background-color: #4d4d4d; 69 | } 70 | 71 | .light .toggleContainer { 72 | background-color: var(--base); 73 | } 74 | 75 | .toggleCircle { 76 | transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0ms; 77 | position: absolute; 78 | top: 1px; 79 | left: 1px; 80 | width: 18px; 81 | height: 18px; 82 | border-radius: 50%; 83 | box-sizing: border-box; 84 | transition: all 0.25s ease; 85 | } 86 | 87 | .dark .toggleCircle { 88 | border: 1px solid #4d4d4d; 89 | background-color: #fafafa; 90 | } 91 | 92 | .light .toggleCircle { 93 | border: 1px solid #8a8a8a; 94 | background-color: #8a8a8a; 95 | } 96 | 97 | .dark .toggleDark { 98 | opacity: 1; 99 | } 100 | .dark .toggleLight { 101 | opacity: 0; 102 | } 103 | .dark .toggleCircle { 104 | left: 27px; 105 | } 106 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "css-to-vanilla-extract-workspace", 3 | "version": "0.0.48-alpha", 4 | "description": "Generate vanilla-extract typescript file from the CSS (SCSS/SASS) file.", 5 | "scripts": { 6 | "build:wasm": "wireit", 7 | "build:node": "wireit", 8 | "dev:playground": "wireit", 9 | "build:playground": "wireit", 10 | "publish:node": "wireit", 11 | "test": "wireit", 12 | "lint": "eslint . --ext '.js,.jsx,.ts,.tsx' --ignore-path .gitignore", 13 | "lint:fix": "eslint . --fix --ext '.js,.jsx,.ts,.tsx' --ignore-path .gitignore", 14 | "prettier": "prettier . --write --ignore-path .gitignore" 15 | }, 16 | "wireit": { 17 | "build:wasm": { 18 | "command": "npm run build --workspace=packages/wasm", 19 | "output": [ 20 | "./packages/wasm/dist" 21 | ], 22 | "dependencies": [ 23 | "test" 24 | ] 25 | }, 26 | "build:playground": { 27 | "command": "npm run build --workspace=packages/playground", 28 | "dependencies": [ 29 | "build:wasm" 30 | ] 31 | }, 32 | "dev:playground": { 33 | "command": "npm run dev --workspace=packages/playground", 34 | "dependencies": [ 35 | "build:wasm" 36 | ] 37 | }, 38 | "test": { 39 | "command": "npm run test --workspaces --if-present" 40 | }, 41 | "build:node": { 42 | "command": "npm run build-release --workspace=packages/node", 43 | "output": [ 44 | "./packages/node/dist" 45 | ], 46 | "dependencies": [ 47 | "test" 48 | ] 49 | }, 50 | "publish:node": { 51 | "command": "npm publish --workspace=packages/node", 52 | "dependencies": [ 53 | "build:node" 54 | ] 55 | } 56 | }, 57 | "repository": { 58 | "type": "git", 59 | "url": "git+https://github.com/activeguild/css-to-vanilla-extract.git" 60 | }, 61 | "workspaces": [ 62 | "packages/*" 63 | ], 64 | "author": "j1ngzoue", 65 | "license": "MIT", 66 | "bugs": { 67 | "url": "https://github.com/activeguild/css-to-vanilla-extract/issues" 68 | }, 69 | "homepage": "https://github.com/activeguild/css-to-vanilla-extract#readme", 70 | "private": true, 71 | "devDependencies": { 72 | "@typescript-eslint/eslint-plugin": "^5.59.6", 73 | "eslint": "^8.41.0", 74 | "eslint-config-prettier": "^8.8.0", 75 | "prettier": "^2.8.8", 76 | "wireit": "^0.9.5" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /packages/lib/src/path.rs: -------------------------------------------------------------------------------- 1 | use std::{env, path::Path}; 2 | 3 | type ResultPath = Result; 4 | 5 | pub fn resolve_paths(path: String) -> Result, String> { 6 | let mut target = env::current_dir().unwrap(); 7 | let mut _paths: Vec<(ResultPath, ResultPath)> = vec![]; 8 | 9 | target.push(path); 10 | if !target.exists() { 11 | return Err(format!("The specified path does not exist.{:?}\n", target)); 12 | } 13 | 14 | each_resolve_path(&mut _paths, &target) 15 | } 16 | 17 | pub fn each_resolve_path( 18 | paths: &mut Vec<(ResultPath, ResultPath)>, 19 | target: &Path, 20 | ) -> Result, String> { 21 | if target.is_dir() { 22 | let files = match target.read_dir() { 23 | Ok(value) => value, 24 | Err(error) => return Err(format!("{:?}", error)), 25 | }; 26 | for dir_entry in files { 27 | let path = match dir_entry { 28 | Ok(value) => value.path(), 29 | Err(error) => { 30 | eprintln!("{:?}", &error); 31 | continue; 32 | } 33 | }; 34 | 35 | if path.is_dir() { 36 | if each_resolve_path(paths, &path).is_err() { 37 | eprintln!("Unable to resolve path :{:?}", &path); 38 | } 39 | continue; 40 | } 41 | 42 | if resolve_path(&path, paths).is_err() { 43 | eprintln!("Unable to resolve path :{:?}", &path); 44 | continue; 45 | } 46 | } 47 | return Ok(paths.clone()); 48 | } 49 | resolve_path(target, paths) 50 | } 51 | 52 | fn resolve_path( 53 | path: &Path, 54 | paths: &mut Vec<(ResultPath, ResultPath)>, 55 | ) -> Result, String> { 56 | let mut output_path = path.to_path_buf(); 57 | let mut output_file_name = String::new(); 58 | 59 | if let Some(file_name) = output_path.file_name() { 60 | if let Some(file_name_as_str) = file_name.to_str() { 61 | // [Note] index.css => index.css.ts, index.scss => index.scss.ts 62 | let formatted_file_name_as_str = &format!("{}.ts", file_name_as_str); 63 | output_file_name.push_str(formatted_file_name_as_str); 64 | } else { 65 | return Err(String::new()); 66 | } 67 | } else { 68 | return Err(String::new()); 69 | } 70 | 71 | output_path.pop(); 72 | output_path.push(output_file_name); 73 | let output_path_as_str = output_path.into_os_string().into_string(); 74 | if let Err(error) = output_path_as_str { 75 | return Err(format!("{:?}", error)); 76 | } 77 | 78 | let path_as_str = path.to_path_buf().into_os_string().into_string(); 79 | if let Err(error) = path_as_str { 80 | return Err(format!("{:?}", error)); 81 | } 82 | 83 | paths.push((output_path_as_str, path_as_str)); 84 | 85 | Ok(paths.clone()) 86 | } 87 | -------------------------------------------------------------------------------- /packages/lib/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::{BufWriter, Write}; 3 | use std::thread; 4 | 5 | mod keyframes; 6 | mod media; 7 | mod path; 8 | mod supports; 9 | mod utils; 10 | mod value; 11 | use path::resolve_paths; 12 | 13 | use utils::{is_css_request, parse_css}; 14 | use value::ast_to_vanilla_extract; 15 | 16 | pub fn from_code(code: &str) -> Result { 17 | let options = grass::Options::default(); 18 | let code = match grass::from_string(code.to_string(), &options) { 19 | Ok(value) => value, 20 | Err(error) => return Err(error.to_string()), 21 | }; 22 | 23 | match parse_css(&code) { 24 | Ok(value) => Ok(ast_to_vanilla_extract(value)), 25 | Err(error) => Err(format!("{:?}", error)), 26 | } 27 | } 28 | 29 | pub fn from_path(path: &str) { 30 | let resolved_paths = match resolve_paths(path.to_string()) { 31 | Ok(value) => value, 32 | Err(error) => { 33 | eprintln!("{:?}", error); 34 | return; 35 | } 36 | }; 37 | 38 | for resolved_path in resolved_paths { 39 | let handle = thread::spawn(|| { 40 | let output_path_as_str = match resolved_path.0 { 41 | Ok(value) => value, 42 | Err(error) => { 43 | eprintln!("{:?}", error); 44 | return; 45 | } 46 | }; 47 | let path_as_str = match resolved_path.1 { 48 | Ok(value) => value, 49 | Err(error) => { 50 | eprintln!("{:?}", error); 51 | return; 52 | } 53 | }; 54 | if !is_css_request(&path_as_str) { 55 | eprintln!("It is not a css file.({:?})", path_as_str); 56 | return; 57 | } 58 | 59 | let options = grass::Options::default(); 60 | let code = match grass::from_path(&path_as_str, &options) { 61 | Ok(value) => value, 62 | Err(error) => { 63 | eprintln!("{:?}", error); 64 | return; 65 | } 66 | }; 67 | 68 | let parsed_css = match parse_css(&code) { 69 | Ok(value) => value, 70 | Err(error) => { 71 | eprintln!("{:?}", error); 72 | return; 73 | } 74 | }; 75 | 76 | let ve: String = ast_to_vanilla_extract(parsed_css); 77 | 78 | let file = match File::create(output_path_as_str) { 79 | Ok(value) => value, 80 | Err(error) => { 81 | eprintln!("{:?}", error); 82 | return; 83 | } 84 | }; 85 | 86 | let mut writer = BufWriter::new(file); 87 | let result = writer.write(ve.as_bytes()); 88 | 89 | if let Err(error) = result { 90 | eprintln!("{:?}", error); 91 | } 92 | }); 93 | handle.join().unwrap(); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /packages/playground/src/hooks/toggleDarkMode/useToggleDarkMode.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import styles from "./toggleDarkMode.module.css"; 3 | 4 | type DarkMode = "dark" | "light"; 5 | 6 | export const useToggleDarkMode = () => { 7 | const [darkMode, setDarkMode] = useState(() => { 8 | const isDark = (value: unknown): value is DarkMode => 9 | value === 'dark' || value === 'light' 10 | 11 | const darkModeFromLocalStorage = localStorage.getItem("darkMode") 12 | if (isDark(darkModeFromLocalStorage)) { 13 | return darkModeFromLocalStorage 14 | } 15 | 16 | return window.matchMedia("(prefers-color-scheme: dark)").matches ? 'dark' : 'light' 17 | }); 18 | 19 | const toggle = () => { 20 | setDarkMode((old) => { 21 | const newDarkMode = old === 'light' ? 'dark' : "light"; 22 | localStorage.setItem("darkMode", newDarkMode); 23 | return newDarkMode 24 | }); 25 | }; 26 | 27 | const renderToggleDarkMode = () => ( 28 | 29 | 30 | 31 | {/* Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. */} 32 | 33 | 34 | 35 | {/* Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. */} 36 | 37 | 38 | 39 | 40 | 45 | 46 | ); 47 | 48 | return { isDark: darkMode === 'dark', darkMode, renderToggleDarkMode }; 49 | }; 50 | 51 | -------------------------------------------------------------------------------- /packages/playground/src/App.tsx: -------------------------------------------------------------------------------- 1 | import Editor, { OnChange } from "@monaco-editor/react"; 2 | import "modern-css-reset/dist/reset.min.css"; 3 | import Prism from "prismjs"; 4 | import { useEffect } from "react"; 5 | import styles from "./App.module.css"; 6 | import { useWasmWorker } from "./hooks/useWasmWorker"; 7 | import { useToggleDarkMode } from "./hooks/toggleDarkMode/useToggleDarkMode"; 8 | import "./prismjs/prismjs"; 9 | 10 | const EDITOR_DEFAULT_VALUE = `.foo { 11 | background-color: blue; 12 | } 13 | @media (min-width: 1200px) { 14 | input { 15 | font-size: 5rem; 16 | } 17 | .foo { 18 | font-size: 5rem; 19 | color: red; 20 | } 21 | .bar { 22 | font-size: 10rem; 23 | } 24 | } 25 | @font-face { 26 | font-family: "Roboto"; 27 | src: url("https://fonts.googleapis.com/css2?family=Roboto:wght@100&display=swap"); 28 | } 29 | 30 | @keyframes slidein { 31 | from { 32 | transform: translateX(0%); 33 | } 34 | 35 | to { 36 | transform: translateX(100%); 37 | } 38 | } 39 | 40 | .fizz .buzz { 41 | background-color: blue; 42 | } 43 | .fizz .buzz { 44 | font-size: 5rem; 45 | } 46 | `; 47 | const GITHUB_URL = "https://github.com/activeguild/css-to-vanilla-extract"; 48 | 49 | function App() { 50 | const { worker, receiveMessage, receiveErrorMessage } = useWasmWorker(); 51 | const { isDark, renderToggleDarkMode } = useToggleDarkMode(); 52 | 53 | const handleChange: OnChange = (value) => { 54 | worker?.postMessage(value || ""); 55 | }; 56 | 57 | useEffect(() => { 58 | worker?.postMessage(EDITOR_DEFAULT_VALUE); 59 | }, [worker]); 60 | 61 | useEffect(() => { 62 | document.body.className = isDark ? "dark" : "light" 63 | }, [isDark]) 64 | 65 | setTimeout(() => { 66 | Prism.highlightAll() 67 | }, 100) 68 | 69 | return ( 70 | 71 | 72 | 73 | 74 | 75 | CSS to vanilla-extract playground 76 | 77 | 78 | Supported css and sass and scss. 79 | 80 | 81 | 82 | 83 | 84 | View on GitHub 85 | 86 | 87 | 88 | 89 | {renderToggleDarkMode()} 90 | 91 | 92 | 93 | 94 | 95 | 96 | 104 | 105 | 106 | 107 | 116 | 117 | 118 | {receiveErrorMessage} 119 | 120 | 121 | ); 122 | } 123 | 124 | export default App; 125 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CSS-to-vanilla-extract ⚡ Welcome 😀 2 | 3 | 4 | 5 | 6 | 7 | Generate [vanilla-extract](https://vanilla-extract.style/) typescript file from the CSS (SCSS/SASS) file. 8 | 9 | [playground](https://css-to-vanilla-extract.netlify.app/) 10 | 11 | The following APIs are covered. 12 | 13 | - [styling-api/#style](https://vanilla-extract.style/documentation/styling-api/#style) 14 | - [styling-api/#globalstyle](https://vanilla-extract.style/documentation/styling-api/#globalstyle) 15 | - [styling-api/#globalfontface](https://vanilla-extract.style/documentation/styling-api/#globalfontface) 16 | - [styling-api/#globalkeyframes](https://vanilla-extract.style/documentation/styling-api/#globalkeyframes) 17 | 18 | ## Motivation 19 | 20 | - Generate style definitions received from designers without any errors. 21 | - Cost-effective migration of existing projects using css modules. 22 | 23 | ## Install 24 | 25 | ```bash 26 | npm i -D css-to-vanilla-extract 27 | ``` 28 | 29 | ## Usage 30 | 31 | Once installed, you can run it to convert css (scss/sass) files to vanilla-extract ts files. 32 | For example: 33 | 34 | ``` 35 | npx css-to-vanilla-extract sample/test.css 36 | ``` 37 | 38 | Output:sample/test.css.ts 39 | 40 | ## Sample 41 | 42 | ### Input 43 | 44 | ```css 45 | .foo { 46 | background-color: blue; 47 | } 48 | @media (min-width: 1200px) { 49 | input { 50 | font-size: 5rem; 51 | } 52 | .foo { 53 | font-size: 5rem; 54 | color: red; 55 | } 56 | .bar { 57 | font-size: 10rem; 58 | } 59 | } 60 | @font-face { 61 | font-family: "Roboto"; 62 | src: url("https://fonts.googleapis.com/css2?family=Roboto:wght@100&display=swap"); 63 | } 64 | 65 | @keyframes slidein { 66 | from { 67 | transform: translateX(0%); 68 | } 69 | 70 | to { 71 | transform: translateX(100%); 72 | } 73 | } 74 | 75 | .fizz .buzz { 76 | background-color: blue; 77 | } 78 | 79 | .fizz .buzz { 80 | font-size: 5rem; 81 | } 82 | ``` 83 | 84 | ### Output 85 | 86 | ```ts 87 | import { 88 | globalFontFace, 89 | globalKeyframes, 90 | globalStyle, 91 | style, 92 | } from "@vanilla-extract/css"; 93 | 94 | globalFontFace("Roboto", { 95 | src: "url(https://fonts.googleapis.com/css2?family=Roboto:wght@100&display=swap)", 96 | }); 97 | 98 | globalKeyframes("slidein", { 99 | from: { 100 | transform: "translateX(0%)", 101 | }, 102 | to: { 103 | transform: "translateX(100%)", 104 | }, 105 | }); 106 | 107 | export const fizz = style({}); 108 | 109 | export const bar = style({ 110 | "@media": { 111 | "(min-width: 1200px)": { 112 | fontSize: "10rem", 113 | }, 114 | }, 115 | }); 116 | 117 | export const buzz = style({ 118 | selectors: { 119 | [`${fizz} &`]: { 120 | backgroundColor: "blue", 121 | fontSize: "5rem", 122 | }, 123 | }, 124 | }); 125 | 126 | export const foo = style({ 127 | backgroundColor: "blue", 128 | "@media": { 129 | "(min-width: 1200px)": { 130 | color: "red", 131 | fontSize: "5rem", 132 | }, 133 | }, 134 | }); 135 | 136 | globalStyle("input", { 137 | "@media": { 138 | "(min-width: 1200px)": { 139 | fontSize: "5rem", 140 | }, 141 | }, 142 | }); 143 | ``` 144 | 145 | ## Principles of conduct 146 | 147 | Please see [the principles of conduct](https://github.com/activeguild/css-to-vanilla-extract/blob/master/.github/CONTRIBUTING.md) when building a site. 148 | 149 | ## License 150 | 151 | This library is licensed under the [MIT license](https://github.com/activeguild/css-to-vanilla-extract/blob/master/LICENSE). 152 | -------------------------------------------------------------------------------- /packages/node/README.md: -------------------------------------------------------------------------------- 1 | CSS-to-vanilla-extract ⚡ Welcome 😀 2 | 3 | 4 | 5 | 6 | 7 | Generate [vanilla-extract](https://vanilla-extract.style/) typescript file from the CSS (SCSS/SASS) file. 8 | 9 | [playground](https://css-to-vanilla-extract.netlify.app/) 10 | 11 | The following APIs are covered. 12 | 13 | - [styling-api/#style](https://vanilla-extract.style/documentation/styling-api/#style) 14 | - [styling-api/#globalstyle](https://vanilla-extract.style/documentation/styling-api/#globalstyle) 15 | - [styling-api/#globalfontface](https://vanilla-extract.style/documentation/styling-api/#globalfontface) 16 | - [styling-api/#globalkeyframes](https://vanilla-extract.style/documentation/styling-api/#globalkeyframes) 17 | 18 | ## Motivation 19 | 20 | - Generate style definitions received from designers without any errors. 21 | - Cost-effective migration of existing projects using css modules. 22 | 23 | ## Install 24 | 25 | ```bash 26 | npm i -D css-to-vanilla-extract 27 | ``` 28 | 29 | ## Usage 30 | 31 | Once installed, you can run it to convert css (scss/sass) files to vanilla-extract ts files. 32 | For example: 33 | 34 | ``` 35 | npx css-to-vanilla-extract sample/test.css 36 | ``` 37 | 38 | Output:sample/test.css.ts 39 | 40 | ## Sample 41 | 42 | ### Input 43 | 44 | ```css 45 | .foo { 46 | background-color: blue; 47 | } 48 | @media (min-width: 1200px) { 49 | input { 50 | font-size: 5rem; 51 | } 52 | .foo { 53 | font-size: 5rem; 54 | color: red; 55 | } 56 | .bar { 57 | font-size: 10rem; 58 | } 59 | } 60 | @font-face { 61 | font-family: "Roboto"; 62 | src: url("https://fonts.googleapis.com/css2?family=Roboto:wght@100&display=swap"); 63 | } 64 | 65 | @keyframes slidein { 66 | from { 67 | transform: translateX(0%); 68 | } 69 | 70 | to { 71 | transform: translateX(100%); 72 | } 73 | } 74 | 75 | .fizz .buzz { 76 | background-color: blue; 77 | } 78 | 79 | .fizz .buzz { 80 | font-size: 5rem; 81 | } 82 | ``` 83 | 84 | ### Output 85 | 86 | ```ts 87 | import { 88 | globalFontFace, 89 | globalKeyframes, 90 | globalStyle, 91 | style, 92 | } from "@vanilla-extract/css"; 93 | 94 | globalFontFace("Roboto", { 95 | src: "url(https://fonts.googleapis.com/css2?family=Roboto:wght@100&display=swap)", 96 | }); 97 | 98 | globalKeyframes("slidein", { 99 | from: { 100 | transform: "translateX(0%)", 101 | }, 102 | to: { 103 | transform: "translateX(100%)", 104 | }, 105 | }); 106 | 107 | export const fizz = style({}); 108 | 109 | export const bar = style({ 110 | "@media": { 111 | "(min-width: 1200px)": { 112 | fontSize: "10rem", 113 | }, 114 | }, 115 | }); 116 | 117 | export const buzz = style({ 118 | selectors: { 119 | [`${fizz} &`]: { 120 | backgroundColor: "blue", 121 | fontSize: "5rem", 122 | }, 123 | }, 124 | }); 125 | 126 | export const foo = style({ 127 | backgroundColor: "blue", 128 | "@media": { 129 | "(min-width: 1200px)": { 130 | color: "red", 131 | fontSize: "5rem", 132 | }, 133 | }, 134 | }); 135 | 136 | globalStyle("input", { 137 | "@media": { 138 | "(min-width: 1200px)": { 139 | fontSize: "5rem", 140 | }, 141 | }, 142 | }); 143 | ``` 144 | 145 | ## Principles of conduct 146 | 147 | Please see [the principles of conduct](https://github.com/activeguild/css-to-vanilla-extract/blob/master/.github/CONTRIBUTING.md) when building a site. 148 | 149 | ## License 150 | 151 | This library is licensed under the [MIT license](https://github.com/activeguild/css-to-vanilla-extract/blob/master/LICENSE). 152 | -------------------------------------------------------------------------------- /.github/ CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a code of conduct, please follow it in all your interactions with the project. 7 | 8 | ## Pull Request Process 9 | 10 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a 11 | build. 12 | 2. Update the README.md with details of changes to the interface, this includes new environment 13 | variables, exposed ports, useful file locations and container parameters. 14 | 3. You may merge the Pull Request in once you have the sign-off of one other developers, or if you 15 | do not have permission to do that, you may request the second reviewer to merge it for you. 16 | 17 | ## Code of Conduct 18 | 19 | ### Our Pledge 20 | 21 | In the interest of fostering an open and welcoming environment, we as 22 | contributors and maintainers pledge to making participation in our project and 23 | our community a harassment-free experience for everyone, regardless of age, body 24 | size, disability, ethnicity, gender identity and expression, level of experience, 25 | nationality, personal appearance, race, religion, or sexual identity and 26 | orientation. 27 | 28 | ### Our Standards 29 | 30 | Examples of behavior that contributes to creating a positive environment 31 | include: 32 | 33 | - Using welcoming and inclusive language 34 | - Being respectful of differing viewpoints and experiences 35 | - Gracefully accepting constructive criticism 36 | - Focusing on what is best for the community 37 | - Showing empathy towards other community members 38 | 39 | Examples of unacceptable behavior by participants include: 40 | 41 | - The use of sexualized language or imagery and unwelcome sexual attention or 42 | advances 43 | - Trolling, insulting/derogatory comments, and personal or political attacks 44 | - Public or private harassment 45 | - Publishing others' private information, such as a physical or electronic 46 | address, without explicit permission 47 | - Other conduct which could reasonably be considered inappropriate in a 48 | professional setting 49 | 50 | ### Our Responsibilities 51 | 52 | Project maintainers are responsible for clarifying the standards of acceptable 53 | behavior and are expected to take appropriate and fair corrective action in 54 | response to any instances of unacceptable behavior. 55 | 56 | Project maintainers have the right and responsibility to remove, edit, or 57 | reject comments, commits, code, wiki edits, issues, and other contributions 58 | that are not aligned to this Code of Conduct, or to ban temporarily or 59 | permanently any contributor for other behaviors that they deem inappropriate, 60 | threatening, offensive, or harmful. 61 | 62 | ### Scope 63 | 64 | This Code of Conduct applies both within project spaces and in public spaces 65 | when an individual is representing the project or its community. Examples of 66 | representing a project or community include using an official project e-mail 67 | address, posting via an official social media account, or acting as an appointed 68 | representative at an online or offline event. Representation of a project may be 69 | further defined and clarified by project maintainers. 70 | 71 | ### Enforcement 72 | 73 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 74 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All 75 | complaints will be reviewed and investigated and will result in a response that 76 | is deemed necessary and appropriate to the circumstances. The project team is 77 | obligated to maintain confidentiality with regard to the reporter of an incident. 78 | Further details of specific enforcement policies may be posted separately. 79 | 80 | Project maintainers who do not follow or enforce the Code of Conduct in good 81 | faith may face temporary or permanent repercussions as determined by other 82 | members of the project's leadership. 83 | 84 | ### Attribution 85 | 86 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, 87 | available at [version/2/1/code_of_conduct/][version] 88 | 89 | [homepage]: https://www.contributor-covenant.org/ 90 | [version]: https://www.contributor-covenant.org/version/2/1/code_of_conduct/ 91 | -------------------------------------------------------------------------------- /packages/playground/src/prismjs/prism.css: -------------------------------------------------------------------------------- 1 | .language-css .token.string, 2 | .style .token.string, 3 | .token.entity, 4 | .token.operator, 5 | .token.url, 6 | .token.variable { 7 | color: black; 8 | } 9 | 10 | .token.property, 11 | .token.tag, 12 | .token.constant, 13 | .token.symbol, 14 | .token.deleted { 15 | color: #ee0000; 16 | } 17 | 18 | .token.punctuation { 19 | color: black; 20 | } 21 | 22 | .token.atrule, 23 | .token.attr-value, 24 | .token.class-name, 25 | .token.function { 26 | color: black; 27 | } 28 | 29 | .light .token.attr-name, 30 | .light .token.builtin, 31 | .light .token.char, 32 | .light .token.inserted, 33 | .light .token.selector, 34 | .light .token.string { 35 | color: #098658; 36 | } 37 | 38 | .light .token.keyword { 39 | color: #0000ff; 40 | } 41 | 42 | .dark .language-css .token.string, 43 | .dark .style .token.string, 44 | .dark .token.entity, 45 | .dark .token.operator, 46 | .dark .token.url, 47 | .dark .token.variable { 48 | color: white; 49 | } 50 | 51 | .dark .token.punctuation { 52 | color: white; 53 | } 54 | 55 | .dark .dark .token.atrule, 56 | .dark .token.attr-value, 57 | .dark .token.class-name, 58 | .dark .token.function { 59 | color: white; 60 | } 61 | 62 | .dark .token.property, 63 | .dark .token.tag, 64 | .dark .token.constant, 65 | .dark .token.symbol, 66 | .dark .token.deleted { 67 | color: #d9c9de; 68 | } 69 | 70 | div.code-toolbar { 71 | position: relative; 72 | } 73 | 74 | div.code-toolbar > .toolbar { 75 | position: absolute; 76 | z-index: 10; 77 | top: 0; 78 | right: 0; 79 | transition: opacity 0.3s ease-in-out; 80 | opacity: 1; 81 | } 82 | 83 | div.code-toolbar > .toolbar > .toolbar-item { 84 | display: inline-block; 85 | } 86 | 87 | div.code-toolbar > .toolbar > .toolbar-item > a { 88 | cursor: pointer; 89 | } 90 | 91 | div.code-toolbar > .toolbar > .toolbar-item > button { 92 | background: none; 93 | border: 0; 94 | color: inherit; 95 | font: inherit; 96 | line-height: normal; 97 | overflow: visible; 98 | padding: 0; 99 | cursor: pointer; 100 | -webkit-user-select: none; /* for button */ 101 | -moz-user-select: none; 102 | -ms-user-select: none; 103 | } 104 | 105 | div.code-toolbar > .toolbar > .toolbar-item > a, 106 | div.code-toolbar > .toolbar > .toolbar-item > button, 107 | div.code-toolbar > .toolbar > .toolbar-item > span { 108 | color: #bbb; 109 | font-size: 1.2em; 110 | padding: 0.2em 0.5em; 111 | background: #f5f2f0; 112 | background: rgba(224, 224, 224, 0.2); 113 | box-shadow: 0 2px 0 0 rgba(0, 0, 0, 0.2); 114 | border-radius: 0.5em; 115 | margin-right: 19px; 116 | margin-top: 4px; 117 | } 118 | 119 | div.code-toolbar > .toolbar > .toolbar-item > a:hover, 120 | div.code-toolbar > .toolbar > .toolbar-item > a:focus, 121 | div.code-toolbar > .toolbar > .toolbar-item > button:hover, 122 | div.code-toolbar > .toolbar > .toolbar-item > button:focus, 123 | div.code-toolbar > .toolbar > .toolbar-item > span:hover, 124 | div.code-toolbar > .toolbar > .toolbar-item > span:focus { 125 | color: lightgreen; 126 | text-decoration: none; 127 | } 128 | 129 | .language-typescript { 130 | margin: 0 !important; 131 | max-height: calc(80vh); 132 | height: calc(80vh); 133 | } 134 | 135 | /* .language-typescript::-webkit-scrollbar-track { 136 | border-radius: 10px; 137 | background: red; 138 | } */ 139 | 140 | pre[class*="language-"], 141 | code[class*="language-"] { 142 | background-color: var(--vs); 143 | font-family: Menlo, Monaco, "Courier New", monospace; 144 | font-weight: normal; 145 | text-shadow: none; 146 | border-radius: 0%; 147 | color: black; 148 | } 149 | 150 | .dark pre[class*="language-"], 151 | .dark code[class*="language-"] { 152 | background-color: var(--vs-dark); 153 | color: white; 154 | } 155 | 156 | ::-webkit-scrollbar { 157 | width: 14px; 158 | border-width: 100px; 159 | } 160 | 161 | .dark ::-webkit-scrollbar-track { 162 | background-color: var(--vs-dark); 163 | box-shadow: inset 1px 0 0 #343434; 164 | } 165 | 166 | .light ::-webkit-scrollbar-track { 167 | background-color: var(--vs); 168 | box-shadow: inset 1px 0 0 #d8d8d7; 169 | } 170 | 171 | .dark ::-webkit-scrollbar-thumb { 172 | background-color: #474747; 173 | } 174 | 175 | .light ::-webkit-scrollbar-thumb { 176 | background-color: #8a8a8a; 177 | } 178 | 179 | pre[class*="language-"] > code { 180 | border-left: none; 181 | } 182 | -------------------------------------------------------------------------------- /packages/lib/src/supports.rs: -------------------------------------------------------------------------------- 1 | use crate::value::{get_component_value, get_declaration, get_function, Component, KeyValue}; 2 | 3 | pub fn get_supports_rule( 4 | supports: &swc_css_ast::SupportsCondition, 5 | block: &Option, 6 | ) -> (String, Vec) { 7 | let mut conditions = String::new(); 8 | for condition in &supports.conditions { 9 | conditions.push_str(&get_supports_condition(condition).ve); 10 | } 11 | 12 | let mut components: Vec = vec![]; 13 | if let Some(block) = &block { 14 | for simple_block_value in &block.value { 15 | for component_value in get_component_value(simple_block_value) { 16 | components.push(component_value); 17 | } 18 | } 19 | } 20 | 21 | (conditions, components) 22 | } 23 | 24 | fn get_supports_condition(condition: &swc_css_ast::SupportsConditionType) -> Component { 25 | let mut component = Component::default(); 26 | match condition { 27 | swc_css_ast::SupportsConditionType::Not(not) => { 28 | let supports_in_parens = &get_supports_in_parens(¬.condition); 29 | component.ve.push_str(&supports_in_parens.ve); 30 | for key_value in &supports_in_parens.key_value_pairs { 31 | component.ve.push_str(&format!( 32 | " not {}", 33 | &format!("({}:{})", key_value.key, key_value.value) 34 | )); 35 | } 36 | 37 | component 38 | } 39 | swc_css_ast::SupportsConditionType::And(and) => { 40 | let supports_in_parens = &get_supports_in_parens(&and.condition); 41 | component.ve.push_str(&supports_in_parens.ve); 42 | for key_value in &supports_in_parens.key_value_pairs { 43 | component.ve.push_str(&format!( 44 | " and {}", 45 | &format!("({}:{})", key_value.key, key_value.value) 46 | )); 47 | } 48 | 49 | component 50 | } 51 | swc_css_ast::SupportsConditionType::Or(or) => { 52 | let supports_in_parens = &get_supports_in_parens(&or.condition); 53 | component.ve.push_str(&supports_in_parens.ve); 54 | for key_value in &supports_in_parens.key_value_pairs { 55 | component.ve.push_str(&format!( 56 | " or {}", 57 | &format!("({}:{})", key_value.key, key_value.value) 58 | )); 59 | } 60 | 61 | component 62 | } 63 | swc_css_ast::SupportsConditionType::SupportsInParens(supports_in_parens) => { 64 | let supports_in_parens = get_supports_in_parens(supports_in_parens); 65 | component.ve.push_str(&supports_in_parens.ve); 66 | for key_value in &supports_in_parens.key_value_pairs { 67 | component 68 | .ve 69 | .push_str(&format!("({}:{})", key_value.key, key_value.value)); 70 | } 71 | 72 | component 73 | } 74 | } 75 | } 76 | 77 | fn get_supports_in_parens(supports_in_parens: &swc_css_ast::SupportsInParens) -> Component { 78 | let mut component = Component::default(); 79 | match supports_in_parens { 80 | swc_css_ast::SupportsInParens::SupportsCondition(supports_condition) => { 81 | let mut condition_as_str = String::new(); 82 | for condition in &supports_condition.conditions { 83 | condition_as_str.push_str(&get_supports_condition(condition).ve); 84 | } 85 | 86 | component.ve.push_str(&condition_as_str); 87 | component 88 | } 89 | swc_css_ast::SupportsInParens::Feature(feature) => { 90 | let supports_feature = get_supports_feature(feature); 91 | 92 | component.key_value_pairs.extend(supports_feature.0); 93 | component.ve.push_str(&supports_feature.1); 94 | component 95 | } 96 | swc_css_ast::SupportsInParens::GeneralEnclosed(general_enclosed) => { 97 | match general_enclosed { 98 | swc_css_ast::GeneralEnclosed::Function(function) => { 99 | let function = get_function(function); 100 | 101 | component.ve.push_str(&function); 102 | component 103 | } 104 | swc_css_ast::GeneralEnclosed::SimpleBlock(simple_block) => { 105 | let mut simple_block_values = String::new(); 106 | let mut key_value_pairs: Vec = vec![]; 107 | for simple_block_value in &simple_block.value { 108 | let component_value = get_component_value(simple_block_value); 109 | simple_block_values.push_str(&component_value[0].ve); 110 | key_value_pairs.extend(component_value[0].key_value_pairs.clone()); 111 | } 112 | 113 | component.key_value_pairs.extend(key_value_pairs); 114 | component.ve.push_str(&simple_block_values); 115 | component.key.push_str(&simple_block.name.to_string()); 116 | component 117 | } 118 | } 119 | } 120 | } 121 | } 122 | 123 | fn get_supports_feature(feature: &swc_css_ast::SupportsFeature) -> (Vec, String) { 124 | match feature { 125 | swc_css_ast::SupportsFeature::Declaration(declaration) => { 126 | (get_declaration(declaration).0, String::new()) 127 | } 128 | swc_css_ast::SupportsFeature::Function(function) => (vec![], get_function(function)), 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /packages/lib/src/utils.rs: -------------------------------------------------------------------------------- 1 | use regex::Regex; 2 | 3 | static CSSLANGS: &str = ".(css|sass|scss)$"; 4 | static SUPERFLUOU_LINE_BREAKS: &str = r"},\n\)"; 5 | 6 | pub fn parse_css(css: &str) -> Result { 7 | let start_pos = swc_common::BytePos(0); 8 | let end_pos = swc_common::BytePos(1); 9 | let errors: &mut Vec = &mut Vec::new(); 10 | 11 | swc_css_parser::parse_str::( 12 | css, 13 | start_pos, 14 | end_pos, 15 | swc_css_parser::parser::ParserConfig::default(), 16 | errors, 17 | ) 18 | } 19 | 20 | pub fn is_css_request(rquest: &str) -> bool { 21 | match Regex::new(CSSLANGS) { 22 | Ok(regex) => regex.is_match(rquest), 23 | Err(_) => false, 24 | } 25 | } 26 | 27 | pub fn wrap_export_const(key: String, rule: String) -> String { 28 | format!("export const {} = {}", key, rule) 29 | } 30 | 31 | pub fn wrap_style(rule: String) -> String { 32 | remove_superfluou_line_breaks(format!("style({{\n{}}});\n\n", rule)) 33 | } 34 | 35 | pub fn wrap_global_style(rule: String) -> String { 36 | // globalStyle(`input *`, { 37 | // boxSizing: 'border-box' 38 | // }); 39 | remove_superfluou_line_breaks(format!("globalStyle({});\n\n", rule)) 40 | } 41 | 42 | pub fn wrap_keyframes(rule: String) -> String { 43 | remove_superfluou_line_breaks(format!("globalKeyframes({});\n\n", rule)) 44 | } 45 | 46 | pub fn wrap_fontface(rule: String) -> String { 47 | remove_superfluou_line_breaks(format!("globalFontFace({});\n\n", rule)) 48 | } 49 | 50 | pub fn wrap_property(key: String, rule: String, spacer: Option) -> String { 51 | if key == "gridTemplateAreas" { 52 | format!("{}{}: `{}`,\n", generate_spaces(spacer), key, rule) 53 | } else { 54 | format!("{}{}: \"{}\",\n", generate_spaces(spacer), key, rule) 55 | } 56 | } 57 | 58 | fn wrap_properties( 59 | key: String, 60 | rule: String, 61 | separator: char, 62 | with_square_brackets: bool, 63 | spacer: Option, 64 | ) -> String { 65 | let spaces = generate_spaces(spacer); 66 | if rule.is_empty() { 67 | String::new() 68 | } else if key.contains("${") { 69 | if with_square_brackets { 70 | format!( 71 | "{}[`{}`]{} {{\n{}{}}},\n", 72 | spaces, key, separator, rule, spaces 73 | ) 74 | } else { 75 | format!( 76 | "{}`{}`{} {{\n{}{}}},\n", 77 | spaces, key, separator, rule, spaces 78 | ) 79 | } 80 | } else { 81 | format!( 82 | "{}\"{}\"{} {{\n{}{}}},\n", 83 | spaces, key, separator, rule, spaces 84 | ) 85 | } 86 | } 87 | 88 | pub fn wrap_properties_with_colon(key: String, rule: String, spacer: Option) -> String { 89 | wrap_properties(key, rule, ':', true, spacer) 90 | } 91 | 92 | pub fn wrap_properties_with_comma(key: String, rule: String, spacer: Option) -> String { 93 | wrap_properties(key, rule, ',', false, spacer) 94 | } 95 | 96 | pub fn generate_spaces(spacer: Option) -> String { 97 | let spaces: Vec = vec![" ".to_string(); spacer.unwrap_or(2).try_into().unwrap()]; 98 | spaces.concat() 99 | } 100 | 101 | pub fn remove_superfluou_line_breaks(value: String) -> String { 102 | let re = Regex::new(SUPERFLUOU_LINE_BREAKS).unwrap(); 103 | re.replace_all(&value, "})").to_string() 104 | } 105 | 106 | const PSEUDO_MAPCONST: [&str; 95] = [ 107 | ":-moz-any-link", 108 | ":-moz-full-screen", 109 | ":-moz-placeholder", 110 | ":-moz-read-only", 111 | ":-moz-read-write", 112 | ":-ms-fullscreen", 113 | ":-ms-input-placeholder", 114 | ":-webkit-any-link", 115 | ":-webkit-full-screen", 116 | "::-moz-placeholder", 117 | "::-moz-progress-bar", 118 | "::-moz-range-progress", 119 | "::-moz-range-thumb", 120 | "::-moz-range-track", 121 | "::-moz-selection", 122 | "::-ms-backdrop", 123 | "::-ms-browse", 124 | "::-ms-check", 125 | "::-ms-clear", 126 | "::-ms-fill", 127 | "::-ms-fill-lower", 128 | "::-ms-fill-upper", 129 | "::-ms-reveal", 130 | "::-ms-thumb", 131 | "::-ms-ticks-after", 132 | "::-ms-ticks-before", 133 | "::-ms-tooltip", 134 | "::-ms-track", 135 | "::-ms-value", 136 | "::-webkit-backdrop", 137 | "::-webkit-input-placeholder", 138 | "::-webkit-progress-bar", 139 | "::-webkit-progress-inner-value", 140 | "::-webkit-progress-value", 141 | "::-webkit-resizer", 142 | "::-webkit-scrollbar-button", 143 | "::-webkit-scrollbar-corner", 144 | "::-webkit-scrollbar-thumb", 145 | "::-webkit-scrollbar-track-piece", 146 | "::-webkit-scrollbar-track", 147 | "::-webkit-scrollbar", 148 | "::-webkit-slider-runnable-track", 149 | "::-webkit-slider-thumb", 150 | "::after", 151 | "::backdrop", 152 | "::before", 153 | "::cue", 154 | "::first-letter", 155 | "::first-line", 156 | "::grammar-error", 157 | "::placeholder", 158 | "::selection", 159 | "::spelling-error", 160 | ":active", 161 | ":after", 162 | ":any-link", 163 | ":before", 164 | ":blank", 165 | ":checked", 166 | ":default", 167 | ":defined", 168 | ":disabled", 169 | ":empty", 170 | ":enabled", 171 | ":first", 172 | ":first-child", 173 | ":first-letter", 174 | ":first-line", 175 | ":first-of-type", 176 | ":focus", 177 | ":focus-visible", 178 | ":focus-within", 179 | ":fullscreen", 180 | ":hover", 181 | ":in-range", 182 | ":indeterminate", 183 | ":invalid", 184 | ":last-child", 185 | ":last-of-type", 186 | ":left", 187 | ":link", 188 | ":only-child", 189 | ":only-of-type", 190 | ":optional", 191 | ":out-of-range", 192 | ":placeholder-shown", 193 | ":read-only", 194 | ":read-write", 195 | ":required", 196 | ":right", 197 | ":root", 198 | ":scope", 199 | ":target", 200 | ":valid", 201 | ":visited", 202 | ]; 203 | 204 | pub fn is_simple_pseudo_func(key: &str) -> bool { 205 | PSEUDO_MAPCONST.contains(&key) 206 | } 207 | 208 | #[cfg(test)] 209 | mod tests { 210 | use super::*; 211 | 212 | #[test] 213 | fn is_css_request_01() { 214 | assert!(is_css_request(".css")); 215 | } 216 | 217 | #[test] 218 | fn is_css_request_02() { 219 | assert!(is_css_request(".scss")); 220 | } 221 | 222 | #[test] 223 | fn is_css_request_03() { 224 | assert!(is_css_request(".sass")); 225 | } 226 | 227 | #[test] 228 | fn is_css_request_04() { 229 | assert!(!is_css_request(".soss")); 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /packages/lib/src/media.rs: -------------------------------------------------------------------------------- 1 | pub fn get_all_media_conditions(conditions: &[swc_css_ast::MediaConditionAllType]) -> String { 2 | let mut ve = String::new(); 3 | for condition in conditions { 4 | let condison_as_str = match condition { 5 | swc_css_ast::MediaConditionAllType::Not(not) => { 6 | format!(" not {}", &get_media_in_parens(¬.condition)) 7 | } 8 | swc_css_ast::MediaConditionAllType::And(and) => { 9 | format!(" and {}", &get_media_in_parens(&and.condition)) 10 | } 11 | swc_css_ast::MediaConditionAllType::Or(or) => { 12 | format!(" or {}", &get_media_in_parens(&or.condition)) 13 | } 14 | swc_css_ast::MediaConditionAllType::MediaInParens(media_in_parens) => { 15 | get_media_in_parens(media_in_parens) 16 | } 17 | }; 18 | println!("condison_as_str:{:?}", condison_as_str); 19 | ve.push_str(&condison_as_str); 20 | } 21 | println!("ve:{:?}", ve); 22 | ve 23 | } 24 | 25 | pub fn get_without_or_media_conditions( 26 | conditions: &[swc_css_ast::MediaConditionWithoutOrType], 27 | ) -> String { 28 | let mut ve = String::new(); 29 | for condition in conditions { 30 | let condition_as_str = match condition { 31 | swc_css_ast::MediaConditionWithoutOrType::Not(not) => { 32 | format!(" not {}", &get_media_in_parens(¬.condition)) 33 | } 34 | swc_css_ast::MediaConditionWithoutOrType::And(and) => { 35 | format!(" and {}", &get_media_in_parens(&and.condition)) 36 | } 37 | swc_css_ast::MediaConditionWithoutOrType::MediaInParens(media_in_parens) => { 38 | match media_in_parens { 39 | swc_css_ast::MediaInParens::MediaCondition(media_condition) => { 40 | get_all_media_conditions(&media_condition.conditions) 41 | } 42 | swc_css_ast::MediaInParens::Feature(feature) => get_media_feature(feature), 43 | } 44 | } 45 | }; 46 | ve.push_str(&condition_as_str); 47 | } 48 | 49 | ve 50 | } 51 | 52 | fn get_media_feature(feature: &swc_css_ast::MediaFeature) -> String { 53 | let mut ve = String::new(); 54 | match feature { 55 | swc_css_ast::MediaFeature::Plain(plain) => { 56 | ve.push_str(&String::from('(')); 57 | match &plain.name { 58 | swc_css_ast::MediaFeatureName::Ident(ident) => ve.push_str(ident.value.as_ref()), 59 | } 60 | ve.push_str(&String::from(": ")); 61 | ve.push_str(&get_media_feature_value(&plain.value)); 62 | ve.push_str(&String::from(')')); 63 | } 64 | swc_css_ast::MediaFeature::Boolean(boolean_value) => { 65 | ve.push_str(&String::from('(')); 66 | match &boolean_value.name { 67 | swc_css_ast::MediaFeatureName::Ident(ident) => ve.push_str(ident.value.as_ref()), 68 | } 69 | ve.push_str(&String::from(')')); 70 | } 71 | swc_css_ast::MediaFeature::Range(range) => { 72 | ve.push_str(&get_media_feature_value(&range.left)); 73 | ve.push_str(&format!(" {} ", range.comparison.as_str())); 74 | ve.push_str(&get_media_feature_value(&range.right)); 75 | } 76 | swc_css_ast::MediaFeature::RangeInterval(_) => todo!(), 77 | } 78 | 79 | ve 80 | } 81 | 82 | fn get_media_feature_value(media_feature_value: &swc_css_ast::MediaFeatureValue) -> String { 83 | match &media_feature_value { 84 | swc_css_ast::MediaFeatureValue::Number(number) => number.value.to_string(), 85 | swc_css_ast::MediaFeatureValue::Dimension(dimension) => match dimension { 86 | swc_css_ast::Dimension::Length(length) => { 87 | format!( 88 | "{}{}", 89 | &length.value.value.to_string(), 90 | length.unit.value.as_ref() 91 | ) 92 | } 93 | swc_css_ast::Dimension::Angle(angle) => { 94 | format!( 95 | "{}{}", 96 | &angle.value.value.to_string(), 97 | angle.unit.value.as_ref() 98 | ) 99 | } 100 | swc_css_ast::Dimension::Time(time) => { 101 | format!( 102 | "{}{}", 103 | &time.value.value.to_string(), 104 | time.unit.value.as_ref() 105 | ) 106 | } 107 | swc_css_ast::Dimension::Frequency(frequency) => { 108 | format!( 109 | "{}{}", 110 | &frequency.value.value.to_string(), 111 | frequency.unit.value.as_ref() 112 | ) 113 | } 114 | swc_css_ast::Dimension::Resolution(resolution) => { 115 | format!( 116 | "{}{}", 117 | &resolution.value.value.to_string(), 118 | resolution.unit.value.as_ref() 119 | ) 120 | } 121 | swc_css_ast::Dimension::Flex(flex) => { 122 | format!( 123 | "{}{}", 124 | &flex.value.value.to_string(), 125 | flex.unit.value.as_ref() 126 | ) 127 | } 128 | swc_css_ast::Dimension::UnknownDimension(_) => String::new(), 129 | }, 130 | swc_css_ast::MediaFeatureValue::Ident(ident) => ident.value.to_string(), 131 | swc_css_ast::MediaFeatureValue::Ratio(ratio) => { 132 | if let Some(value) = &ratio.right { 133 | format!( 134 | "{} / {}", 135 | &ratio.left.value.to_string(), 136 | &value.value.to_string() 137 | ) 138 | } else { 139 | ratio.left.value.to_string() 140 | } 141 | } 142 | } 143 | } 144 | 145 | fn get_media_in_parens(media_in_parens: &swc_css_ast::MediaInParens) -> String { 146 | match media_in_parens { 147 | swc_css_ast::MediaInParens::MediaCondition(media_condition) => { 148 | get_all_media_conditions(&media_condition.conditions) 149 | } 150 | swc_css_ast::MediaInParens::Feature(feature) => get_media_feature(feature), 151 | } 152 | } 153 | 154 | #[cfg(test)] 155 | mod tests { 156 | use super::*; 157 | 158 | use swc_common::{Span, SyntaxContext}; 159 | use swc_css_ast::{ 160 | Dimension, Ident, Length, MediaConditionAllType, MediaConditionWithoutOrType, MediaFeature, 161 | MediaFeatureRange, MediaFeatureRangeComparison, MediaFeatureValue, MediaInParens, Number, 162 | }; 163 | 164 | fn generate_span() -> swc_common::Span { 165 | return Span { 166 | lo: swc_common::BytePos(0), 167 | hi: swc_common::BytePos(0), 168 | ctxt: SyntaxContext::empty(), 169 | }; 170 | } 171 | 172 | #[test] 173 | fn get_without_or_media_conditions_01() { 174 | // https://github.com/swc-project/swc/blob/75b0ed55f6c352ab2cd918ac746ceef99fa0f124/crates/swc_css_prefixer/src/prefixer.rs#L388 175 | let media_in_parens = MediaConditionWithoutOrType::MediaInParens(MediaInParens::Feature( 176 | MediaFeature::Range(MediaFeatureRange { 177 | span: generate_span(), 178 | left: MediaFeatureValue::Ident(Ident { 179 | span: generate_span(), 180 | value: "width".into(), 181 | raw: Some("width".into()), 182 | }), 183 | comparison: MediaFeatureRangeComparison::Le, 184 | right: MediaFeatureValue::Dimension(Dimension::Length(Length { 185 | span: generate_span(), 186 | value: Number { 187 | span: generate_span(), 188 | value: 32.0, 189 | raw: Some("32.0".into()), 190 | }, 191 | unit: Ident { 192 | span: generate_span(), 193 | value: "em".into(), 194 | raw: Some("em".into()), 195 | }, 196 | })), 197 | }), 198 | )); 199 | 200 | let result = get_without_or_media_conditions(&[media_in_parens]); 201 | assert_eq!(result, "width <= 32em"); 202 | } 203 | 204 | #[test] 205 | fn get_all_media_conditions_01() { 206 | // https://github.com/swc-project/swc/blob/75b0ed55f6c352ab2cd918ac746ceef99fa0f124/crates/swc_css_prefixer/src/prefixer.rs#L388 207 | let media_in_parens = MediaConditionAllType::MediaInParens(MediaInParens::Feature( 208 | MediaFeature::Range(MediaFeatureRange { 209 | span: generate_span(), 210 | left: MediaFeatureValue::Ident(Ident { 211 | span: generate_span(), 212 | value: "width".into(), 213 | raw: Some("width".into()), 214 | }), 215 | comparison: MediaFeatureRangeComparison::Le, 216 | right: MediaFeatureValue::Dimension(Dimension::Length(Length { 217 | span: generate_span(), 218 | value: Number { 219 | span: generate_span(), 220 | value: 32.0, 221 | raw: Some("32.0".into()), 222 | }, 223 | unit: Ident { 224 | span: generate_span(), 225 | value: "em".into(), 226 | raw: Some("em".into()), 227 | }, 228 | })), 229 | }), 230 | )); 231 | 232 | let result = get_all_media_conditions(&[media_in_parens]); 233 | assert_eq!(result, "width <= 32em"); 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /packages/lib/src/value.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{BTreeMap, HashSet}; 2 | 3 | use crate::{ 4 | keyframes::get_keyframes, 5 | media::{get_all_media_conditions, get_without_or_media_conditions}, 6 | supports::get_supports_rule, 7 | utils::{ 8 | is_simple_pseudo_func, wrap_export_const, wrap_fontface, wrap_global_style, 9 | wrap_properties_with_colon, wrap_properties_with_comma, wrap_property, wrap_style, 10 | }, 11 | }; 12 | use convert_case::{Case, Casing}; 13 | use swc_css_ast::{ClassSelector, ComplexSelectorChildren}; 14 | 15 | type KeyValuePairs = Vec; 16 | type KeyValuePairsInPseudo = BTreeMap; 17 | type KeyValuePairsInSelectors = BTreeMap; 18 | 19 | #[derive(Debug)] 20 | pub struct Rule { 21 | pub key: String, 22 | pub key_value_pairs: KeyValuePairs, 23 | pub key_value_pairs_in_vars: KeyValuePairs, 24 | pub key_value_pairs_in_pseudo: KeyValuePairsInPseudo, 25 | pub key_value_pairs_in_selectors: KeyValuePairsInSelectors, 26 | pub is_global_style: bool, 27 | pub is_simple_pseudo: bool, 28 | } 29 | 30 | #[derive(Debug, Default, Clone)] 31 | pub struct KeyValue { 32 | pub key: String, 33 | pub value: String, 34 | } 35 | 36 | #[derive(Debug, Default)] 37 | pub struct Complex { 38 | pub pseudo: String, 39 | pub key: String, 40 | pub is_global_style: bool, 41 | pub is_simple_pseudo: bool, 42 | pub has_component_value: bool, 43 | } 44 | 45 | #[derive(Debug, Default, Clone)] 46 | pub struct Component { 47 | pub ve: String, 48 | pub key_value_pairs: KeyValuePairs, 49 | pub key_value_pairs_in_vars: KeyValuePairs, 50 | pub key_value_pairs_in_pseudo: KeyValuePairsInPseudo, 51 | pub key_value_pairs_in_selectors: KeyValuePairsInSelectors, 52 | pub key: String, 53 | pub is_global_style: bool, 54 | } 55 | 56 | #[derive(Debug, Default, Clone)] 57 | 58 | pub struct RuleMapValue { 59 | pub key_value_pairs: KeyValuePairs, 60 | pub key_value_pairs_in_vars: KeyValuePairs, 61 | pub key_value_pairs_in_media: KeyValuePairs, 62 | pub key_value_pairs_in_supports: KeyValuePairs, 63 | pub key_value_pairs_in_pseudo: KeyValuePairsInPseudo, 64 | pub key_value_pairs_in_selectors: KeyValuePairsInSelectors, 65 | pub selectors_in_media: BTreeMap, 66 | pub selectors_in_supports: BTreeMap, 67 | pub media_confition: String, 68 | pub supports_confition: String, 69 | } 70 | 71 | pub fn ast_to_vanilla_extract(parsed_css: swc_css_ast::Stylesheet) -> String { 72 | let mut ve: String = String::new(); 73 | let mut rule_map: BTreeMap = BTreeMap::new(); 74 | let mut global_rule_map: BTreeMap = BTreeMap::new(); 75 | let mut named_imports_hash: HashSet = HashSet::new(); 76 | 77 | for rule in &parsed_css.rules { 78 | match rule { 79 | swc_css_ast::Rule::QualifiedRule(qualfied_rule) => { 80 | let rules = get_qualified_rule(qualfied_rule); 81 | 82 | for rule in rules { 83 | if rule.is_global_style { 84 | let _global_rule_map = global_rule_map 85 | .entry(rule.key) 86 | .or_insert_with(RuleMapValue::default); 87 | _global_rule_map 88 | .key_value_pairs 89 | .extend(rule.key_value_pairs); 90 | _global_rule_map 91 | .key_value_pairs_in_vars 92 | .extend(rule.key_value_pairs_in_vars); 93 | 94 | for pseudo in rule.key_value_pairs_in_pseudo { 95 | let pseudo_map = _global_rule_map 96 | .key_value_pairs_in_pseudo 97 | .entry(pseudo.0) 98 | .or_insert_with(Vec::new); 99 | pseudo_map.extend(pseudo.1); 100 | } 101 | 102 | for selector in rule.key_value_pairs_in_selectors { 103 | let selector_map = _global_rule_map 104 | .key_value_pairs_in_selectors 105 | .entry(selector.0) 106 | .or_insert_with(Vec::new); 107 | selector_map.extend(selector.1); 108 | } 109 | } else { 110 | let _rule_map = rule_map 111 | .entry(rule.key) 112 | .or_insert_with(RuleMapValue::default); 113 | 114 | _rule_map.key_value_pairs.extend(rule.key_value_pairs); 115 | _rule_map 116 | .key_value_pairs_in_vars 117 | .extend(rule.key_value_pairs_in_vars); 118 | 119 | for pseudo in rule.key_value_pairs_in_pseudo { 120 | let pseudo_map = _rule_map 121 | .key_value_pairs_in_pseudo 122 | .entry(pseudo.0.to_string()) 123 | .or_insert_with(Vec::new); 124 | pseudo_map.extend(pseudo.1); 125 | } 126 | 127 | for selector in rule.key_value_pairs_in_selectors { 128 | let selector_map = _rule_map 129 | .key_value_pairs_in_selectors 130 | .entry(selector.0) 131 | .or_insert_with(Vec::new); 132 | selector_map.extend(selector.1); 133 | } 134 | } 135 | } 136 | } 137 | swc_css_ast::Rule::Invalid(_) => println!("Contains an invalid token."), 138 | swc_css_ast::Rule::AtRule(at_rule) => { 139 | if let Some(value) = &at_rule.prelude { 140 | match value { 141 | swc_css_ast::AtRulePrelude::ListOfComponentValues(_) => todo!(), 142 | swc_css_ast::AtRulePrelude::CharsetPrelude(_) => { 143 | println!("Not supportted. (AtRule::Charset)") 144 | } 145 | swc_css_ast::AtRulePrelude::PropertyPrelude(_) => todo!(), 146 | swc_css_ast::AtRulePrelude::CounterStylePrelude(_) => todo!(), 147 | swc_css_ast::AtRulePrelude::ColorProfilePrelude(_) => todo!(), 148 | swc_css_ast::AtRulePrelude::DocumentPrelude(_) => todo!(), 149 | swc_css_ast::AtRulePrelude::FontPaletteValuesPrelude(_) => todo!(), 150 | swc_css_ast::AtRulePrelude::NestPrelude(_) => todo!(), 151 | swc_css_ast::AtRulePrelude::KeyframesPrelude(keyframes_prelude) => { 152 | let keyframes_rule = get_keyframes(keyframes_prelude, &at_rule.block); 153 | 154 | if !keyframes_rule.is_empty() { 155 | named_imports_hash.insert("globalKeyframes".to_string()); 156 | } 157 | 158 | ve.push_str(&keyframes_rule); 159 | } 160 | swc_css_ast::AtRulePrelude::ImportPrelude(_) => todo!(), 161 | swc_css_ast::AtRulePrelude::NamespacePrelude(_) => todo!(), 162 | swc_css_ast::AtRulePrelude::MediaPrelude(media_prelude) => { 163 | let mut media_condition = String::new(); 164 | for (index, media_query) in media_prelude.queries.iter().enumerate() { 165 | if index > 0 { 166 | media_condition.push_str(&String::from(", ")); 167 | } 168 | let modifier = match &media_query.modifier { 169 | Some(value) => format!("{} ", &value.value.to_string()), 170 | None => String::new(), 171 | }; 172 | media_condition.push_str(&modifier); 173 | 174 | let media_type = match &media_query.media_type { 175 | Some(value) => value.value.to_string(), 176 | None => String::new(), 177 | }; 178 | media_condition.push_str(&media_type); 179 | 180 | let condition = match &media_query.condition { 181 | Some(value) => match value { 182 | swc_css_ast::MediaConditionType::All(all) => { 183 | get_all_media_conditions(&all.conditions) 184 | } 185 | swc_css_ast::MediaConditionType::WithoutOr(without_or) => { 186 | get_without_or_media_conditions(&without_or.conditions) 187 | } 188 | }, 189 | None => String::new(), 190 | }; 191 | 192 | if !condition.is_empty() && !media_type.is_empty() { 193 | media_condition.push_str(&String::from(" and ")); 194 | } 195 | media_condition.push_str(&condition); 196 | } 197 | println!("media_condition:{:?}", media_condition); 198 | println!("at_rule.block:{:?}", at_rule.block); 199 | if let Some(block) = &at_rule.block { 200 | for component_value in &block.value { 201 | let components = get_component_value(component_value); 202 | for component in components { 203 | let media_condition = media_condition.clone(); 204 | if component.is_global_style { 205 | let _global_rule_map = global_rule_map 206 | .entry(component.key) 207 | .or_insert_with(RuleMapValue::default); 208 | if _global_rule_map.media_confition.is_empty() { 209 | _global_rule_map 210 | .media_confition 211 | .push_str(&media_condition); 212 | } 213 | _global_rule_map 214 | .key_value_pairs_in_media 215 | .extend(component.key_value_pairs); 216 | 217 | for pseudo in component.key_value_pairs_in_pseudo { 218 | let pseudo_map = _global_rule_map 219 | .key_value_pairs_in_pseudo 220 | .entry(pseudo.0) 221 | .or_insert_with(Vec::new); 222 | pseudo_map.extend(pseudo.1); 223 | } 224 | 225 | let selectors_map = _global_rule_map 226 | .selectors_in_media 227 | .entry(media_condition) 228 | .or_insert_with(BTreeMap::new); 229 | 230 | for selector in component.key_value_pairs_in_selectors { 231 | let selector_map = selectors_map 232 | .entry(selector.0) 233 | .or_insert_with(Vec::new); 234 | selector_map.extend(selector.1); 235 | } 236 | } else { 237 | let _rule_map = rule_map 238 | .entry(component.key) 239 | .or_insert_with(RuleMapValue::default); 240 | if _rule_map.media_confition.is_empty() { 241 | _rule_map 242 | .media_confition 243 | .push_str(&media_condition); 244 | } 245 | _rule_map 246 | .key_value_pairs_in_media 247 | .extend(component.key_value_pairs); 248 | 249 | for pseudo in component.key_value_pairs_in_pseudo { 250 | let pseudo_map = _rule_map 251 | .key_value_pairs_in_pseudo 252 | .entry(pseudo.0) 253 | .or_insert_with(Vec::new); 254 | pseudo_map.extend(pseudo.1); 255 | } 256 | 257 | let selectors_map = _rule_map 258 | .selectors_in_media 259 | .entry(media_condition) 260 | .or_insert_with(BTreeMap::new); 261 | 262 | for selector in component.key_value_pairs_in_selectors { 263 | let selector_map = selectors_map 264 | .entry(selector.0) 265 | .or_insert_with(Vec::new); 266 | selector_map.extend(selector.1); 267 | } 268 | } 269 | } 270 | } 271 | } 272 | } 273 | swc_css_ast::AtRulePrelude::SupportsPrelude(supports_prelude) => { 274 | let supports_rule = get_supports_rule(supports_prelude, &at_rule.block); 275 | for component in supports_rule.1 { 276 | let supports_condition = supports_rule.0.clone(); 277 | 278 | if component.is_global_style { 279 | let _global_rule_map = global_rule_map 280 | .entry(component.key) 281 | .or_insert_with(RuleMapValue::default); 282 | if _global_rule_map.supports_confition.is_empty() { 283 | _global_rule_map 284 | .supports_confition 285 | .push_str(&supports_condition); 286 | } 287 | _global_rule_map 288 | .key_value_pairs_in_supports 289 | .extend(component.key_value_pairs); 290 | 291 | for pseudo in component.key_value_pairs_in_pseudo { 292 | let pseudo_map = _global_rule_map 293 | .key_value_pairs_in_pseudo 294 | .entry(pseudo.0) 295 | .or_insert_with(Vec::new); 296 | pseudo_map.extend(pseudo.1); 297 | } 298 | 299 | let selectors_map = _global_rule_map 300 | .selectors_in_supports 301 | .entry(supports_condition) 302 | .or_insert_with(BTreeMap::new); 303 | 304 | for selector in component.key_value_pairs_in_selectors { 305 | let selector_map = selectors_map 306 | .entry(selector.0) 307 | .or_insert_with(Vec::new); 308 | selector_map.extend(selector.1); 309 | } 310 | } else { 311 | let _rule_map = rule_map 312 | .entry(component.key) 313 | .or_insert_with(RuleMapValue::default); 314 | if _rule_map.supports_confition.is_empty() { 315 | _rule_map.supports_confition.push_str(&supports_condition); 316 | } 317 | _rule_map 318 | .key_value_pairs_in_supports 319 | .extend(component.key_value_pairs); 320 | 321 | for pseudo in component.key_value_pairs_in_pseudo { 322 | let pseudo_map = _rule_map 323 | .key_value_pairs_in_pseudo 324 | .entry(pseudo.0) 325 | .or_insert_with(Vec::new); 326 | pseudo_map.extend(pseudo.1); 327 | } 328 | 329 | let selectors_map = _rule_map 330 | .selectors_in_supports 331 | .entry(supports_condition) 332 | .or_insert_with(BTreeMap::new); 333 | 334 | for selector in component.key_value_pairs_in_selectors { 335 | let selector_map = selectors_map 336 | .entry(selector.0) 337 | .or_insert_with(Vec::new); 338 | selector_map.extend(selector.1); 339 | } 340 | } 341 | } 342 | } 343 | swc_css_ast::AtRulePrelude::PagePrelude(_) => todo!(), 344 | swc_css_ast::AtRulePrelude::LayerPrelude(_) => todo!(), 345 | } 346 | } else { 347 | match &at_rule.name { 348 | swc_css_ast::AtRuleName::DashedIdent(_) => todo!(), 349 | swc_css_ast::AtRuleName::Ident(ident) => { 350 | if let Some(name) = &ident.raw { 351 | if name == "font-face" { 352 | // globalFontFace('MyGlobalFont', { 353 | // src: 'local("Comic Sans MS")' 354 | // }); 355 | let mut block_values = String::new(); 356 | let mut font_face_key = String::new(); 357 | if let Some(block) = &at_rule.block { 358 | for block_value in &block.value { 359 | let components = get_component_value(block_value); 360 | for key_value in 361 | components[0].key_value_pairs.clone().into_iter() 362 | { 363 | if key_value.key == "fontFamily" { 364 | font_face_key.push_str(&key_value.value); 365 | } else { 366 | block_values.push_str(&wrap_property( 367 | key_value.key, 368 | key_value.value, 369 | None, 370 | )); 371 | } 372 | } 373 | } 374 | 375 | if !font_face_key.is_empty() { 376 | named_imports_hash.insert("globalFontFace".to_string()); 377 | } 378 | 379 | ve.push_str(&wrap_fontface(wrap_properties_with_comma( 380 | font_face_key, 381 | block_values, 382 | Some(0), 383 | ))); 384 | } 385 | } 386 | } 387 | } 388 | } 389 | } 390 | } 391 | } 392 | } 393 | 394 | ve.push_str(&finish_to_vanilla_extract(rule_map.clone(), false)); 395 | ve.push_str(&finish_to_vanilla_extract(global_rule_map.clone(), true)); 396 | 397 | if !rule_map.is_empty() { 398 | named_imports_hash.insert("style".to_string()); 399 | } 400 | if !global_rule_map.is_empty() { 401 | named_imports_hash.insert("globalStyle".to_string()); 402 | } 403 | 404 | let mut named_imports: Vec = named_imports_hash.into_iter().collect::>(); 405 | named_imports.sort(); 406 | 407 | ve.insert_str( 408 | 0, 409 | &format!( 410 | "import {{ {} }} from \"@vanilla-extract/css\"\n\n", 411 | named_imports.join(", ") 412 | ), 413 | ); 414 | 415 | ve 416 | } 417 | 418 | fn finish_to_vanilla_extract( 419 | rule_map: BTreeMap, 420 | is_global_rule: bool, 421 | ) -> String { 422 | let mut ve = String::new(); 423 | let mut ve_selectors = String::new(); 424 | 425 | for (key, value) in rule_map.into_iter() { 426 | let mut properties = String::new(); 427 | let has_selectors = !value.key_value_pairs_in_selectors.is_empty() 428 | || !value.selectors_in_media.is_empty() 429 | || !value.selectors_in_supports.is_empty(); 430 | 431 | for key_value in value.key_value_pairs.into_iter() { 432 | properties.push_str(&wrap_property(key_value.key, key_value.value, Some(2))); 433 | } 434 | 435 | if !value.key_value_pairs_in_vars.is_empty() { 436 | let mut var_rule = String::new(); 437 | for key_value in value.key_value_pairs_in_vars.into_iter() { 438 | var_rule.push_str(&wrap_property( 439 | format!("\"{}\"", key_value.key), 440 | key_value.value, 441 | Some(4), 442 | )); 443 | } 444 | properties.push_str(&wrap_properties_with_colon( 445 | "vars".to_string(), 446 | var_rule, 447 | Some(2), 448 | )); 449 | } 450 | 451 | if !value.key_value_pairs_in_pseudo.is_empty() { 452 | let mut pseudo_rule = String::new(); 453 | for (key, value) in value.key_value_pairs_in_pseudo.into_iter() { 454 | let mut properties = String::new(); 455 | for key_value in value.into_iter() { 456 | properties.push_str(&wrap_property(key_value.key, key_value.value, Some(4))); 457 | } 458 | pseudo_rule.push_str(&wrap_properties_with_colon(key, properties, Some(2))); 459 | } 460 | properties.push_str(&pseudo_rule); 461 | } 462 | 463 | if !value.media_confition.is_empty() || !value.selectors_in_media.is_empty() { 464 | let mut rule = String::new(); 465 | 466 | for key_value in value.key_value_pairs_in_media.into_iter() { 467 | rule.push_str(&wrap_property(key_value.key, key_value.value, Some(6))); 468 | } 469 | 470 | for (_key, selectors_value) in value.selectors_in_media.into_iter() { 471 | let mut selectors_rule = String::new(); 472 | for (key, value) in selectors_value.into_iter() { 473 | let mut properties = String::new(); 474 | for key_value in value.into_iter() { 475 | properties.push_str(&wrap_property( 476 | key_value.key, 477 | key_value.value, 478 | Some(10), 479 | )); 480 | } 481 | 482 | if key.contains("${") { 483 | selectors_rule.push_str(&wrap_properties_with_colon( 484 | key.to_string(), 485 | properties, 486 | Some(8), 487 | )); 488 | } else { 489 | selectors_rule.push_str(&wrap_properties_with_colon( 490 | format!("&{}", key), 491 | properties, 492 | Some(8), 493 | )); 494 | } 495 | } 496 | 497 | if !selectors_rule.is_empty() { 498 | rule.push_str(&wrap_properties_with_colon( 499 | "selectors".to_string(), 500 | selectors_rule, 501 | Some(6), 502 | )); 503 | } 504 | } 505 | 506 | properties.push_str(&wrap_properties_with_colon( 507 | String::from("@media"), 508 | wrap_properties_with_colon(value.media_confition, rule, Some(4)), 509 | Some(2), 510 | )); 511 | } 512 | if !value.supports_confition.is_empty() || !value.selectors_in_supports.is_empty() { 513 | let mut rule = String::new(); 514 | for key_value in value.key_value_pairs_in_supports.into_iter() { 515 | rule.push_str(&wrap_property(key_value.key, key_value.value, Some(6))); 516 | } 517 | 518 | for (_key, selectors_value) in value.selectors_in_supports.into_iter() { 519 | let mut selectors_rule = String::new(); 520 | for (key, value) in selectors_value.into_iter() { 521 | let mut properties = String::new(); 522 | for key_value in value.into_iter() { 523 | properties.push_str(&wrap_property( 524 | key_value.key, 525 | key_value.value, 526 | Some(10), 527 | )); 528 | } 529 | 530 | if key.contains("${") { 531 | selectors_rule.push_str(&wrap_properties_with_colon( 532 | key.to_string(), 533 | properties, 534 | Some(8), 535 | )); 536 | } else { 537 | selectors_rule.push_str(&wrap_properties_with_colon( 538 | format!("&{}", key), 539 | properties, 540 | Some(8), 541 | )); 542 | } 543 | } 544 | 545 | if !selectors_rule.is_empty() { 546 | rule.push_str(&wrap_properties_with_colon( 547 | "selectors".to_string(), 548 | selectors_rule, 549 | Some(6), 550 | )); 551 | } 552 | } 553 | 554 | properties.push_str(&wrap_properties_with_colon( 555 | String::from("@supports"), 556 | wrap_properties_with_colon(value.supports_confition, rule, Some(4)), 557 | Some(2), 558 | )); 559 | } 560 | 561 | if !value.key_value_pairs_in_selectors.is_empty() { 562 | let mut selectors = String::new(); 563 | 564 | for (key, value) in value.key_value_pairs_in_selectors.into_iter() { 565 | let mut properties = String::new(); 566 | for key_value in value.into_iter() { 567 | properties.push_str(&wrap_property(key_value.key, key_value.value, Some(6))); 568 | } 569 | if key.contains("${") { 570 | selectors.push_str(&wrap_properties_with_colon( 571 | key.to_string(), 572 | properties, 573 | Some(4), 574 | )); 575 | } else { 576 | selectors.push_str(&wrap_properties_with_colon( 577 | format!("&{}", key), 578 | properties, 579 | Some(4), 580 | )); 581 | } 582 | } 583 | 584 | properties.push_str(&wrap_properties_with_colon( 585 | "selectors".to_string(), 586 | selectors, 587 | Some(2), 588 | )) 589 | } 590 | 591 | // No output for unsupported rules. 592 | if !key.is_empty() { 593 | if is_global_rule { 594 | ve.push_str(&wrap_global_style(wrap_properties_with_comma( 595 | key, 596 | properties, 597 | Some(0), 598 | ))); 599 | } else if has_selectors { 600 | if ve_selectors.contains(&format!("${{{}}}", key)) { 601 | ve_selectors.insert_str(0, &wrap_export_const(key, wrap_style(properties))); 602 | } else { 603 | ve_selectors.push_str(&wrap_export_const(key, wrap_style(properties))); 604 | } 605 | } else { 606 | ve.push_str(&wrap_export_const(key, wrap_style(properties))); 607 | } 608 | } 609 | } 610 | 611 | ve.push_str(&ve_selectors); 612 | ve 613 | } 614 | 615 | pub fn get_qualified_rule(qualfied_rule: &swc_css_ast::QualifiedRule) -> Vec { 616 | let mut result: Vec = vec![]; 617 | 618 | match &qualfied_rule.prelude { 619 | swc_css_ast::QualifiedRulePrelude::ListOfComponentValues(_) => (), 620 | swc_css_ast::QualifiedRulePrelude::SelectorList(selector_list) => { 621 | for complex in get_complex_selectors(&selector_list.children) { 622 | let mut key_value_pairs: KeyValuePairs = KeyValuePairs::default(); 623 | let mut key_value_pairs_in_vars: KeyValuePairs = KeyValuePairs::default(); 624 | let mut key_value_pairs_in_pseudo: KeyValuePairsInPseudo = BTreeMap::default(); 625 | let mut key_value_pairs_in_selectors: KeyValuePairsInSelectors = 626 | KeyValuePairsInSelectors::default(); 627 | 628 | // input > .btn 629 | // { 630 | // position: absolute; 631 | // } 632 | if complex.has_component_value { 633 | for block_value in &qualfied_rule.block.value { 634 | let component_value = get_component_value(block_value); 635 | 636 | key_value_pairs.extend(component_value[0].key_value_pairs.clone()); 637 | key_value_pairs_in_vars 638 | .extend(component_value[0].key_value_pairs_in_vars.clone()); 639 | } 640 | 641 | if !complex.pseudo.is_empty() { 642 | if complex.is_simple_pseudo { 643 | key_value_pairs_in_pseudo 644 | .insert(complex.pseudo, key_value_pairs.clone()); 645 | } else { 646 | key_value_pairs_in_selectors 647 | .insert(complex.pseudo, key_value_pairs.clone()); 648 | } 649 | } 650 | } 651 | 652 | if !key_value_pairs_in_pseudo.is_empty() || !key_value_pairs_in_selectors.is_empty() 653 | { 654 | result.push(Rule { 655 | key: complex.key, 656 | key_value_pairs: KeyValuePairs::new(), 657 | key_value_pairs_in_vars, 658 | key_value_pairs_in_pseudo, 659 | key_value_pairs_in_selectors, 660 | is_global_style: complex.is_global_style, 661 | is_simple_pseudo: complex.is_simple_pseudo, 662 | }) 663 | } else { 664 | result.push(Rule { 665 | key: complex.key, 666 | key_value_pairs, 667 | key_value_pairs_in_vars, 668 | key_value_pairs_in_pseudo, 669 | key_value_pairs_in_selectors, 670 | is_global_style: complex.is_global_style, 671 | is_simple_pseudo: complex.is_simple_pseudo, 672 | }) 673 | } 674 | } 675 | } 676 | } 677 | 678 | result 679 | } 680 | 681 | fn get_complex_selectors(comples_selectors: &[swc_css_ast::ComplexSelector]) -> Vec { 682 | let mut complexes: Vec = vec![]; 683 | 684 | for complex_selector in comples_selectors { 685 | let mut pseudo = String::new(); 686 | let mut key: String = String::new(); 687 | let mut is_global_style: bool = false; 688 | let mut is_simple_pseudo: bool = false; 689 | let mut last_children_class: Option = None; 690 | 691 | if complex_selector.children.len() > 1 { 692 | if let Some(last_children) = complex_selector.children.last() { 693 | last_children_class = get_last_children_class(last_children); 694 | 695 | if let Some(value) = &last_children_class { 696 | key.push_str(&value.text.value.to_string().to_case(Case::Camel)); 697 | } 698 | } 699 | } 700 | 701 | for complex_selector_children in &complex_selector.children { 702 | match complex_selector_children { 703 | ComplexSelectorChildren::CompoundSelector(compound_selector) => { 704 | for nesting_selector in &compound_selector.nesting_selector { 705 | println!("nesting_selector: {:?}", nesting_selector) 706 | } 707 | 708 | for type_selector in &compound_selector.type_selector { 709 | match type_selector { 710 | swc_css_ast::TypeSelector::TagName(tag_name) => { 711 | if key.is_empty() { 712 | is_global_style = true; 713 | } 714 | 715 | if !key.is_empty() && !is_global_style { 716 | pseudo.push_str(&tag_name.name.value.value.to_string()); 717 | } else { 718 | key.push_str(&tag_name.name.value.value.to_string()); 719 | } 720 | } 721 | swc_css_ast::TypeSelector::Universal(_) => { 722 | if key.is_empty() { 723 | is_global_style = true; 724 | } 725 | 726 | if !key.is_empty() && !is_global_style { 727 | pseudo.push('*'); 728 | } else { 729 | key.push('*'); 730 | } 731 | } 732 | } 733 | } 734 | 735 | for subclass_selector in &compound_selector.subclass_selectors { 736 | match subclass_selector { 737 | swc_css_ast::SubclassSelector::Id(id) => { 738 | if key.is_empty() { 739 | is_global_style = true; 740 | } 741 | key.push_str(&format!("#{}", &id.text.value)); 742 | } 743 | swc_css_ast::SubclassSelector::Class(class) => { 744 | if key.is_empty() { 745 | // The first class name is used as the variable name. 746 | key.push_str( 747 | &class.text.value.to_string().to_case(Case::Camel), 748 | ); 749 | } else if !key.is_empty() && !is_global_style { 750 | let formatted_text_value = if let Some(value) = 751 | &last_children_class 752 | { 753 | if value.text == class.text { 754 | "&".to_string() 755 | } else { 756 | format!( 757 | "${{{}}}", 758 | &class.text.value.to_string().to_case(Case::Camel) 759 | ) 760 | } 761 | } else { 762 | format!( 763 | "${{{}}}", 764 | &class.text.value.to_string().to_case(Case::Camel) 765 | ) 766 | }; 767 | 768 | complexes.push(Complex { 769 | pseudo: String::new(), 770 | key: class.text.value.to_string().to_case(Case::Camel), 771 | is_global_style: false, 772 | is_simple_pseudo: false, 773 | has_component_value: false, 774 | }); 775 | 776 | pseudo.push_str(&formatted_text_value); 777 | } 778 | 779 | if is_global_style { 780 | let formatted_text_value = format!( 781 | "${{{}}}", 782 | &class.text.value.to_string().to_case(Case::Camel) 783 | ); 784 | 785 | if !key.is_empty() { 786 | complexes.push(Complex { 787 | pseudo: String::new(), 788 | key: class.text.value.to_string().to_case(Case::Camel), 789 | is_global_style: false, 790 | is_simple_pseudo: false, 791 | has_component_value: false, 792 | }) 793 | } 794 | key.push_str(&formatted_text_value); 795 | } 796 | } 797 | swc_css_ast::SubclassSelector::Attribute(attribute) => { 798 | if key.is_empty() { 799 | // [type=\"button\"]{cursor: pointer;} 800 | is_global_style = true; 801 | } 802 | 803 | let mut attribute_as_str = String::new(); 804 | attribute_as_str.push('['); 805 | attribute_as_str.push_str(&attribute.name.value.value); 806 | if let Some(ns_prefix) = &attribute.name.prefix { 807 | if let Some(prefix) = &ns_prefix.prefix { 808 | attribute_as_str.push_str(&prefix.value); 809 | } 810 | } 811 | if let Some(matcher) = &attribute.matcher { 812 | attribute_as_str.push_str(&matcher.value.to_string()) 813 | } 814 | if let Some(value) = &attribute.value { 815 | match value { 816 | swc_css_ast::AttributeSelectorValue::Str(str) => { 817 | attribute_as_str.push_str(&format!("'{}'", str.value)); 818 | } 819 | swc_css_ast::AttributeSelectorValue::Ident(ident) => { 820 | attribute_as_str 821 | .push_str(&format!("'{}'", ident.value)); 822 | } 823 | } 824 | } 825 | if let Some(modifier) = &attribute.modifier { 826 | attribute_as_str.push_str(&format!(" {}", modifier.value.value)) 827 | } 828 | attribute_as_str.push(']'); 829 | if is_global_style { 830 | key.push_str(&attribute_as_str) 831 | } else { 832 | pseudo.push_str(&attribute_as_str); 833 | is_simple_pseudo = is_simple_pseudo_func(&pseudo); 834 | } 835 | } 836 | swc_css_ast::SubclassSelector::PseudoClass(pseudo_class) => { 837 | let formatted_pseudo_class = 838 | format!(":{}", &pseudo_class.name.value.to_string()); 839 | if key.is_empty() & pseudo.is_empty() { 840 | is_global_style = true; 841 | } 842 | if is_global_style { 843 | key.push_str(&formatted_pseudo_class) 844 | } else { 845 | pseudo.push_str(&formatted_pseudo_class); 846 | 847 | is_simple_pseudo = is_simple_pseudo_func(&pseudo); 848 | } 849 | 850 | if let Some(children) = &pseudo_class.children { 851 | let psudo_class_children = 852 | get_pseudo_class_children(children, is_global_style); 853 | key.push_str(&psudo_class_children.key); 854 | pseudo.push_str(&psudo_class_children.pseudo); 855 | is_simple_pseudo = psudo_class_children.is_simple_pseudo; 856 | } 857 | } 858 | swc_css_ast::SubclassSelector::PseudoElement(pseudo_element) => { 859 | let formatted_pseudo_element = 860 | format!("::{}", &pseudo_element.name.value.to_string()); 861 | if key.is_empty() & pseudo.is_empty() { 862 | is_global_style = true; 863 | } 864 | if is_global_style { 865 | key.push_str(&formatted_pseudo_element) 866 | } else { 867 | pseudo.push_str(&formatted_pseudo_element); 868 | 869 | is_simple_pseudo = is_simple_pseudo_func(&pseudo); 870 | } 871 | } 872 | } 873 | } 874 | // input,div,sapn...,* 875 | } 876 | ComplexSelectorChildren::Combinator(combinator) => { 877 | if is_global_style { 878 | key.push_str(&get_combinator(combinator)); 879 | } else { 880 | pseudo.push_str(&get_combinator(combinator)); 881 | } 882 | } 883 | } 884 | } 885 | complexes.push(Complex { 886 | pseudo, 887 | key, 888 | is_global_style, 889 | is_simple_pseudo, 890 | has_component_value: true, 891 | }) 892 | } 893 | 894 | complexes 895 | } 896 | 897 | fn get_last_children_class(last_children: &ComplexSelectorChildren) -> Option { 898 | match last_children { 899 | ComplexSelectorChildren::CompoundSelector(compound_selector) => { 900 | match compound_selector.subclass_selectors.last() { 901 | Some(swc_css_ast::SubclassSelector::Class(class)) => Some(class.clone()), 902 | _ => None, 903 | } 904 | } 905 | _ => None, 906 | } 907 | } 908 | 909 | fn get_combinator(combinator: &swc_css_ast::Combinator) -> String { 910 | match combinator.value { 911 | swc_css_ast::CombinatorValue::Descendant => ' '.to_string(), 912 | swc_css_ast::CombinatorValue::NextSibling => " + ".to_string(), 913 | swc_css_ast::CombinatorValue::Child => " > ".to_string(), 914 | swc_css_ast::CombinatorValue::LaterSibling => " ~ ".to_string(), 915 | swc_css_ast::CombinatorValue::Column => " || ".to_string(), 916 | } 917 | } 918 | 919 | fn get_pseudo_class_children( 920 | pseudo_class_children: &[swc_css_ast::PseudoClassSelectorChildren], 921 | is_global_style: bool, 922 | ) -> Complex { 923 | let mut ve = String::new(); 924 | let mut pseudo = String::new(); 925 | let mut key: String = String::new(); 926 | let mut is_simple_pseudo: bool = false; 927 | 928 | for pseudo_class_children in pseudo_class_children { 929 | match pseudo_class_children { 930 | swc_css_ast::PseudoClassSelectorChildren::PreservedToken(_) => todo!(), 931 | swc_css_ast::PseudoClassSelectorChildren::AnPlusB(anplusb) => { 932 | let anplusb_as_str = match anplusb { 933 | swc_css_ast::AnPlusB::Ident(ident) => format!("{}", ident.value), 934 | swc_css_ast::AnPlusB::AnPlusBNotation(an_plus_b_notation) => { 935 | let mut an_plus_b_notation_as_str = String::new(); 936 | if let Some(a_raw) = &an_plus_b_notation.a_raw { 937 | an_plus_b_notation_as_str.push_str(a_raw); 938 | } 939 | if let Some(b_raw) = &an_plus_b_notation.b_raw { 940 | an_plus_b_notation_as_str.push_str(b_raw); 941 | } 942 | an_plus_b_notation_as_str 943 | } 944 | }; 945 | ve.push_str(&anplusb_as_str); 946 | } 947 | swc_css_ast::PseudoClassSelectorChildren::Ident(ident) => ve.push_str(&ident.value), 948 | swc_css_ast::PseudoClassSelectorChildren::Str(str) => ve.push_str(&str.value), 949 | swc_css_ast::PseudoClassSelectorChildren::Delimiter(_) => todo!(), 950 | swc_css_ast::PseudoClassSelectorChildren::SelectorList(selector_list) => { 951 | let complex_selectors = get_complex_selectors(&selector_list.children); 952 | for complex_selector in &complex_selectors { 953 | if complex_selector.is_global_style { 954 | ve.push_str(&complex_selector.key) 955 | } else { 956 | ve.push_str(&complex_selector.pseudo) 957 | } 958 | is_simple_pseudo = complex_selector.is_simple_pseudo; 959 | } 960 | } 961 | swc_css_ast::PseudoClassSelectorChildren::CompoundSelectorList(_) => todo!(), 962 | swc_css_ast::PseudoClassSelectorChildren::RelativeSelectorList(_) => todo!(), 963 | swc_css_ast::PseudoClassSelectorChildren::CompoundSelector(_) => todo!(), 964 | } 965 | } 966 | 967 | if !ve.is_empty() { 968 | if is_global_style { 969 | key = format!("({})", ve); 970 | } else { 971 | pseudo = format!("({})", ve); 972 | } 973 | } 974 | 975 | Complex { 976 | pseudo, 977 | key, 978 | is_global_style, 979 | is_simple_pseudo, 980 | has_component_value: true, 981 | } 982 | } 983 | 984 | pub fn get_component_value(component_value: &swc_css_ast::ComponentValue) -> Vec { 985 | let mut ve: String = String::new(); 986 | let mut key_value_pairs: KeyValuePairs = KeyValuePairs::new(); 987 | let mut key_value_pairs_in_vars: KeyValuePairs = KeyValuePairs::new(); 988 | 989 | match component_value { 990 | swc_css_ast::ComponentValue::PreservedToken(preserved_token) => { 991 | match &preserved_token.token { 992 | swc_css_ast::Token::Ident { value, raw: _ } => ve.push_str(value), 993 | swc_css_ast::Token::Function { value, raw: _ } => ve.push_str(value), 994 | swc_css_ast::Token::AtKeyword { value: _, raw: _ } => { 995 | todo!() 996 | } 997 | swc_css_ast::Token::Hash { 998 | is_id: _, 999 | value, 1000 | raw: _, 1001 | } => { 1002 | ve.push_str(&String::from("#")); 1003 | ve.push_str(value); 1004 | } 1005 | swc_css_ast::Token::String { value, raw: _ } => ve.push_str(value), 1006 | swc_css_ast::Token::BadString { value: _, raw: _ } => { 1007 | todo!() 1008 | } 1009 | swc_css_ast::Token::Url { 1010 | name: _, 1011 | raw_name: _, 1012 | before: _, 1013 | after: _, 1014 | value: _, 1015 | raw_value: _, 1016 | } => todo!(), 1017 | swc_css_ast::Token::BadUrl { 1018 | name: _, 1019 | raw_name: _, 1020 | value: _, 1021 | raw_value: _, 1022 | } => todo!(), 1023 | swc_css_ast::Token::Delim { value } => ve.push_str(&value.to_string()), 1024 | swc_css_ast::Token::Number { 1025 | value, 1026 | raw: _, 1027 | type_flag: _, 1028 | } => ve.push_str(&value.to_string()), 1029 | swc_css_ast::Token::Percentage { value, raw: _ } => { 1030 | ve.push_str(&format!("{}%", value)); 1031 | } 1032 | swc_css_ast::Token::Dimension { 1033 | value, 1034 | raw_value: _, 1035 | unit, 1036 | raw_unit: _, 1037 | type_flag: _, 1038 | } => { 1039 | ve.push_str(&format!("{}{}", value, unit)); 1040 | } 1041 | swc_css_ast::Token::WhiteSpace { value: _ } => ve.push(' '), 1042 | swc_css_ast::Token::CDO | swc_css_ast::Token::CDC => (), 1043 | swc_css_ast::Token::Colon => ve.push(':'), 1044 | swc_css_ast::Token::Semi => ve.push(';'), 1045 | swc_css_ast::Token::Comma => ve.push(','), 1046 | swc_css_ast::Token::LBracket => ve.push('['), 1047 | swc_css_ast::Token::RBracket => ve.push(']'), 1048 | swc_css_ast::Token::LParen => ve.push('('), 1049 | swc_css_ast::Token::RParen => ve.push(')'), 1050 | swc_css_ast::Token::LBrace => ve.push('{'), 1051 | swc_css_ast::Token::RBrace => ve.push('}'), 1052 | } 1053 | } 1054 | swc_css_ast::ComponentValue::Function(function) => { 1055 | ve.push_str(&get_function(function)); 1056 | } 1057 | swc_css_ast::ComponentValue::SimpleBlock(_) => todo!(), 1058 | swc_css_ast::ComponentValue::DeclarationOrAtRule(declaration_or_at_rule) => { 1059 | match declaration_or_at_rule { 1060 | swc_css_ast::DeclarationOrAtRule::Declaration(declaration) => { 1061 | let declaration_value = get_declaration(declaration); 1062 | key_value_pairs.extend(declaration_value.0); 1063 | key_value_pairs_in_vars.extend(declaration_value.1); 1064 | } 1065 | swc_css_ast::DeclarationOrAtRule::AtRule(_) => todo!(), 1066 | swc_css_ast::DeclarationOrAtRule::Invalid(_) => { 1067 | println!("Contains an invalid token.") 1068 | } 1069 | } 1070 | } 1071 | swc_css_ast::ComponentValue::Rule(rule) => match rule { 1072 | swc_css_ast::Rule::QualifiedRule(qualified_rule) => { 1073 | let mut components: Vec = vec![]; 1074 | 1075 | for rule in get_qualified_rule(qualified_rule) { 1076 | components.push(Component { 1077 | ve: String::new(), 1078 | key_value_pairs: rule.key_value_pairs, 1079 | key_value_pairs_in_vars: rule.key_value_pairs_in_vars, 1080 | key_value_pairs_in_pseudo: rule.key_value_pairs_in_pseudo, 1081 | key_value_pairs_in_selectors: rule.key_value_pairs_in_selectors, 1082 | key: rule.key, 1083 | is_global_style: rule.is_global_style, 1084 | }) 1085 | } 1086 | 1087 | return components; 1088 | } 1089 | swc_css_ast::Rule::Invalid(_) => { 1090 | println!("Contains an invalid token.") 1091 | } 1092 | swc_css_ast::Rule::AtRule(_) => println!("Not supportted. (Rule::AtRule)"), 1093 | }, 1094 | swc_css_ast::ComponentValue::StyleBlock(style_block) => match style_block { 1095 | swc_css_ast::StyleBlock::AtRule(_) => todo!(), 1096 | swc_css_ast::StyleBlock::Declaration(declaration) => { 1097 | let declaration_value = get_declaration(declaration); 1098 | key_value_pairs.extend(declaration_value.0); 1099 | key_value_pairs_in_vars.extend(declaration_value.1); 1100 | } 1101 | swc_css_ast::StyleBlock::QualifiedRule(qualified_rule) => { 1102 | for rule in get_qualified_rule(qualified_rule) { 1103 | key_value_pairs.extend(rule.key_value_pairs); 1104 | key_value_pairs_in_vars.extend(rule.key_value_pairs_in_vars); 1105 | } 1106 | } 1107 | swc_css_ast::StyleBlock::ListOfComponentValues(_) => todo!(), 1108 | }, 1109 | swc_css_ast::ComponentValue::KeyframeBlock(keyframe_block) => { 1110 | let mut keyframe_selector = String::new(); 1111 | 1112 | for prelude in &keyframe_block.prelude { 1113 | match prelude { 1114 | swc_css_ast::KeyframeSelector::Ident(ident) => { 1115 | keyframe_selector.push_str(ident.value.as_ref()); 1116 | } 1117 | swc_css_ast::KeyframeSelector::Percentage(percentage) => { 1118 | if !keyframe_selector.is_empty() { 1119 | keyframe_selector.push(','); 1120 | } 1121 | keyframe_selector.push_str(&format!("{}%", percentage.value.value)); 1122 | } 1123 | } 1124 | } 1125 | 1126 | ve.push_str(&keyframe_selector); 1127 | 1128 | for block_value in &keyframe_block.block.value { 1129 | let component_value = get_component_value(block_value); 1130 | key_value_pairs.extend(component_value[0].key_value_pairs.clone()); 1131 | } 1132 | } 1133 | swc_css_ast::ComponentValue::Ident(ident) => ve.push_str(&ident.value.to_string()), 1134 | swc_css_ast::ComponentValue::DashedIdent(dashed_ident) => { 1135 | ve.push_str(&dashed_ident.value.to_string()) 1136 | } 1137 | swc_css_ast::ComponentValue::Str(str) => { 1138 | // .foo { 1139 | // content: "-"; 1140 | // } 1141 | ve.push_str(str.value.as_ref()); 1142 | } 1143 | swc_css_ast::ComponentValue::Url(url) => { 1144 | let url_value = match &url.value { 1145 | Some(value) => match value { 1146 | swc_css_ast::UrlValue::Str(str) => str.value.as_ref(), 1147 | swc_css_ast::UrlValue::Raw(raw) => raw.value.as_ref(), 1148 | }, 1149 | None => "", 1150 | }; 1151 | 1152 | ve.push_str(&format!("url({})", url_value)); 1153 | } 1154 | swc_css_ast::ComponentValue::Integer(integer) => ve.push_str(&integer.value.to_string()), 1155 | swc_css_ast::ComponentValue::Number(number) => { 1156 | // .foo { 1157 | // color: rgba(34, 12, 64, 0.6); 1158 | // } 1159 | ve.push_str(&number.value.to_string()); 1160 | } 1161 | swc_css_ast::ComponentValue::Percentage(percentage) => { 1162 | // @keyframes identifier { 1163 | // 0% { top: 0; left: 0; } 1164 | // 30% { top: 50px; } 1165 | // 68%, 72% { left: 50px; } 1166 | // 100% { top: 100px; left: 100%; } 1167 | // } 1168 | ve.push_str(&format!("{}%", percentage.value.value)); 1169 | } 1170 | swc_css_ast::ComponentValue::Dimension(dimension) => { 1171 | ve.push_str(&get_dimension(dimension)); 1172 | } 1173 | swc_css_ast::ComponentValue::Ratio(_) => todo!(), 1174 | swc_css_ast::ComponentValue::UnicodeRange(_) => todo!(), 1175 | swc_css_ast::ComponentValue::Color(color) => match color { 1176 | swc_css_ast::Color::AbsoluteColorBase(color) => match color { 1177 | swc_css_ast::AbsoluteColorBase::HexColor(hex_color) => match &hex_color.raw { 1178 | Some(value) => { 1179 | ve.push_str(&String::from("#")); 1180 | ve.push_str(value); 1181 | } 1182 | None => ve.push_str(""), 1183 | }, 1184 | swc_css_ast::AbsoluteColorBase::NamedColorOrTransparent(_) => todo!(), 1185 | swc_css_ast::AbsoluteColorBase::Function(function) => { 1186 | ve.push_str(&get_function(function)); 1187 | } 1188 | }, 1189 | swc_css_ast::Color::Function(function) => ve.push_str(&get_function(function)), 1190 | swc_css_ast::Color::CurrentColorOrSystemColor(_) => todo!(), 1191 | }, 1192 | swc_css_ast::ComponentValue::AlphaValue(alpha_value) => match alpha_value { 1193 | swc_css_ast::AlphaValue::Number(number) => ve.push_str(&number.value.to_string()), 1194 | swc_css_ast::AlphaValue::Percentage(percentage) => { 1195 | ve.push_str(&format!("{}%", percentage.value.value)) 1196 | } 1197 | }, 1198 | swc_css_ast::ComponentValue::Hue(_) => todo!(), 1199 | swc_css_ast::ComponentValue::CmykComponent(_) => todo!(), 1200 | swc_css_ast::ComponentValue::Delimiter(delimiter) => { 1201 | ve.push_str(&delimiter.value.to_string()) 1202 | } 1203 | swc_css_ast::ComponentValue::CalcSum(calc_sum) => { 1204 | // .foo { 1205 | // width: calc(var(--variable-width) + 20px); 1206 | // } 1207 | ve.push_str(&get_calc_sum(calc_sum)); 1208 | } 1209 | swc_css_ast::ComponentValue::ComplexSelector(_) => todo!(), 1210 | swc_css_ast::ComponentValue::LayerName(_) => todo!(), 1211 | } 1212 | 1213 | [Component { 1214 | ve, 1215 | key_value_pairs, 1216 | key_value_pairs_in_vars, 1217 | key_value_pairs_in_pseudo: BTreeMap::default(), 1218 | key_value_pairs_in_selectors: BTreeMap::default(), 1219 | key: String::new(), 1220 | is_global_style: false, 1221 | }] 1222 | .to_vec() 1223 | } 1224 | 1225 | fn get_dimension(dimension: &swc_css_ast::Dimension) -> String { 1226 | match dimension { 1227 | swc_css_ast::Dimension::Length(length) => { 1228 | format!( 1229 | "{}{}", 1230 | &length.value.value.to_string(), 1231 | length.unit.value.as_ref() 1232 | ) 1233 | } 1234 | swc_css_ast::Dimension::Angle(angle) => { 1235 | format!( 1236 | "{}{}", 1237 | &angle.value.value.to_string(), 1238 | angle.unit.value.as_ref() 1239 | ) 1240 | } 1241 | swc_css_ast::Dimension::Time(time) => { 1242 | // .foo { 1243 | // animation: slidein 4s infinite; 1244 | // } 1245 | format!( 1246 | "{}{}", 1247 | &time.value.value.to_string(), 1248 | time.unit.value.as_ref() 1249 | ) 1250 | } 1251 | swc_css_ast::Dimension::Frequency(frequency) => { 1252 | // * { 1253 | // voice-pitch: 250Hz; 1254 | // } 1255 | format!( 1256 | "{}{}", 1257 | &frequency.value.value.to_string(), 1258 | frequency.unit.value.as_ref() 1259 | ) 1260 | } 1261 | swc_css_ast::Dimension::Resolution(resolution) => { 1262 | format!( 1263 | "{}{}", 1264 | &resolution.value.value.to_string(), 1265 | resolution.unit.value.as_ref() 1266 | ) 1267 | } 1268 | swc_css_ast::Dimension::Flex(flex) => { 1269 | format!( 1270 | "{}{}", 1271 | &flex.value.value.to_string(), 1272 | flex.unit.value.as_ref() 1273 | ) 1274 | } 1275 | swc_css_ast::Dimension::UnknownDimension(_) => String::new(), 1276 | } 1277 | } 1278 | 1279 | fn get_calc_sum(calc_sum: &swc_css_ast::CalcSum) -> String { 1280 | let mut ve: String = String::new(); 1281 | for calc_sum_expression in &calc_sum.expressions { 1282 | let calc_sum_expression_as_str = match calc_sum_expression { 1283 | swc_css_ast::CalcProductOrOperator::Product(product) => { 1284 | let mut product_expression_as_str = String::new(); 1285 | for product_expression in &product.expressions { 1286 | product_expression_as_str 1287 | .push_str(&get_calc_value_or_operator(product_expression)); 1288 | } 1289 | product_expression_as_str 1290 | } 1291 | swc_css_ast::CalcProductOrOperator::Operator(operator) => get_operator(operator), 1292 | }; 1293 | 1294 | ve.push_str(&calc_sum_expression_as_str) 1295 | } 1296 | 1297 | ve 1298 | } 1299 | 1300 | fn get_calc_value_or_operator(product_expression: &swc_css_ast::CalcValueOrOperator) -> String { 1301 | match product_expression { 1302 | swc_css_ast::CalcValueOrOperator::Value(value) => match value { 1303 | swc_css_ast::CalcValue::Number(number) => number.value.to_string(), 1304 | swc_css_ast::CalcValue::Dimension(dimension) => get_dimension(dimension), 1305 | swc_css_ast::CalcValue::Percentage(percentage) => { 1306 | format!("{}%", percentage.value.value) 1307 | } 1308 | swc_css_ast::CalcValue::Constant(constant) => constant.value.to_string(), 1309 | swc_css_ast::CalcValue::Sum(calc_sum) => get_calc_sum(calc_sum), 1310 | swc_css_ast::CalcValue::Function(function) => get_function(function), 1311 | }, 1312 | swc_css_ast::CalcValueOrOperator::Operator(operator) => get_operator(operator), 1313 | } 1314 | } 1315 | 1316 | fn get_operator(operator: &swc_css_ast::CalcOperator) -> String { 1317 | match operator.value { 1318 | swc_css_ast::CalcOperatorType::Add => '+'.to_string(), 1319 | swc_css_ast::CalcOperatorType::Sub => '-'.to_string(), 1320 | swc_css_ast::CalcOperatorType::Mul => '*'.to_string(), 1321 | swc_css_ast::CalcOperatorType::Div => '/'.to_string(), 1322 | } 1323 | } 1324 | 1325 | pub fn get_function(function: &swc_css_ast::Function) -> String { 1326 | // .foo { 1327 | // color: rgba(34, 12, 64, 0.6); 1328 | // } 1329 | let mut component_values = String::new(); 1330 | for component_value in &function.value { 1331 | let component_value_as_str = get_component_value(component_value); 1332 | component_values.push_str(&component_value_as_str[0].ve); 1333 | } 1334 | 1335 | format!( 1336 | "{}({})", 1337 | &function.name.value.to_string(), 1338 | &component_values 1339 | ) 1340 | } 1341 | 1342 | pub fn get_declaration(declaration: &swc_css_ast::Declaration) -> (KeyValuePairs, KeyValuePairs) { 1343 | let mut key_value_pairs: KeyValuePairs = KeyValuePairs::new(); 1344 | let mut key_value_pairs_in_vars: KeyValuePairs = KeyValuePairs::new(); 1345 | let mut declaration_name = String::new(); 1346 | let mut declaration_value = String::new(); 1347 | let mut dashed_ident = String::new(); 1348 | match &declaration.name { 1349 | swc_css_ast::DeclarationName::Ident(ident) => declaration_name.push_str(match &ident.raw { 1350 | Some(value) => value, 1351 | None => "", 1352 | }), 1353 | swc_css_ast::DeclarationName::DashedIdent(_dashed_ident) => { 1354 | dashed_ident = match &_dashed_ident.raw { 1355 | Some(value) => (&value).to_string(), 1356 | None => "".to_string(), 1357 | } 1358 | } 1359 | } 1360 | for declaration_component_value in &declaration.value { 1361 | let component_value = &get_component_value(declaration_component_value)[0].ve; 1362 | 1363 | if declaration_name == "grid-template-areas" { 1364 | if declaration_value.is_empty() { 1365 | declaration_value.push_str(&format!("{:?}", component_value)) 1366 | } else { 1367 | declaration_value.push_str(&format!(" {:?}", component_value)); 1368 | } 1369 | } else { 1370 | if declaration_value.is_empty() { 1371 | declaration_value.push_str(component_value) 1372 | } else { 1373 | declaration_value.push_str(&format!(" {}", component_value)); 1374 | } 1375 | } 1376 | } 1377 | 1378 | if declaration_name.starts_with("--") { 1379 | let formatted_declaration_name = format!("\"{}\"", &declaration_name); 1380 | declaration_name = formatted_declaration_name 1381 | } else if declaration_name.starts_with("-ms") { 1382 | declaration_name = declaration_name.to_case(Case::Camel) 1383 | } else if declaration_name.starts_with('-') { 1384 | declaration_name = declaration_name.to_case(Case::Pascal) 1385 | } else { 1386 | declaration_name = declaration_name.to_case(Case::Camel) 1387 | } 1388 | 1389 | if !dashed_ident.is_empty() { 1390 | key_value_pairs_in_vars.push(KeyValue { 1391 | key: dashed_ident, 1392 | value: declaration_value, 1393 | }) 1394 | } else { 1395 | key_value_pairs.push(KeyValue { 1396 | key: declaration_name, 1397 | value: declaration_value, 1398 | }); 1399 | } 1400 | 1401 | (key_value_pairs, key_value_pairs_in_vars) 1402 | } 1403 | 1404 | #[cfg(test)] 1405 | mod tests { 1406 | use swc_common::{BytePos, Span, SyntaxContext}; 1407 | use swc_css_ast::{ComponentValue, Dimension, Ident, Length, Number}; 1408 | 1409 | use super::*; 1410 | use crate::utils::parse_css; 1411 | 1412 | fn generate_span() -> Span { 1413 | return Span { 1414 | lo: BytePos(0), 1415 | hi: BytePos(0), 1416 | ctxt: SyntaxContext::empty(), 1417 | }; 1418 | } 1419 | 1420 | #[test] 1421 | fn get_component_value_01() { 1422 | let component_value = ComponentValue::Dimension(Dimension::Length(Length { 1423 | span: generate_span(), 1424 | value: Number { 1425 | span: generate_span(), 1426 | value: 10.0, 1427 | raw: Some("10.0".into()), 1428 | }, 1429 | unit: Ident { 1430 | span: generate_span(), 1431 | value: "px".into(), 1432 | raw: Some("px".into()), 1433 | }, 1434 | })); 1435 | 1436 | let result = get_component_value(&component_value); 1437 | assert_eq!(result[0].ve, "10px"); 1438 | } 1439 | 1440 | #[test] 1441 | fn ast_to_vanilla_extract_01() { 1442 | let parsed_css = parse_css(".foo,.b-a-r:hover {position: absolute;color: #fff;}").unwrap(); 1443 | 1444 | let result = ast_to_vanilla_extract(parsed_css); 1445 | 1446 | assert_eq!( 1447 | "import { style } from \"@vanilla-extract/css\"\n\nexport const bAR = style({\n \":hover\": {\n position: \"absolute\",\n color: \"#fff\",\n },\n});\n\nexport const foo = style({\n position: \"absolute\",\n color: \"#fff\",\n});\n\n", 1448 | result 1449 | ) 1450 | } 1451 | 1452 | #[test] 1453 | fn ast_to_vanilla_extract_02() { 1454 | let parsed_css = 1455 | parse_css("*,*::before,*::after,.b-a-r:hover {box-sizing: border-box;}").unwrap(); 1456 | 1457 | let result = ast_to_vanilla_extract(parsed_css); 1458 | 1459 | assert_eq!( 1460 | "import { globalStyle, style } from \"@vanilla-extract/css\"\n\nexport const bAR = style({\n \":hover\": {\n boxSizing: \"border-box\",\n },\n});\n\nglobalStyle(\"*\", {\n boxSizing: \"border-box\",\n});\n\nglobalStyle(\"*::after\", {\n boxSizing: \"border-box\",\n});\n\nglobalStyle(\"*::before\", {\n boxSizing: \"border-box\",\n});\n\n", 1461 | result 1462 | ) 1463 | } 1464 | 1465 | #[test] 1466 | fn ast_to_vanilla_extract_03() { 1467 | let parsed_css = 1468 | parse_css("ol ol{margin-bottom: 0;-ms-overflow-style: scrollbar;}").unwrap(); 1469 | 1470 | let result = ast_to_vanilla_extract(parsed_css); 1471 | 1472 | assert_eq!( 1473 | "import { globalStyle } from \"@vanilla-extract/css\"\n\nglobalStyle(\"ol ol\", {\n marginBottom: \"0\",\n msOverflowStyle: \"scrollbar\",\n});\n\n", 1474 | result 1475 | ) 1476 | } 1477 | 1478 | #[test] 1479 | fn ast_to_vanilla_extract_04() { 1480 | let parsed_css = parse_css( 1481 | "button:not(:disabled), [type^=\"button\" s]:not(:disabled){cursor: pointer;}", 1482 | ) 1483 | .unwrap(); 1484 | let result = ast_to_vanilla_extract(parsed_css); 1485 | 1486 | assert_eq!( 1487 | "import { globalStyle } from \"@vanilla-extract/css\"\n\nglobalStyle(\"[type^='button' s]:not(:disabled)\", {\n cursor: \"pointer\",\n});\n\nglobalStyle(\"button:not(:disabled)\", {\n cursor: \"pointer\",\n});\n\n", 1488 | result 1489 | ) 1490 | } 1491 | 1492 | #[test] 1493 | fn ast_to_vanilla_extract_05() { 1494 | let parsed_css = parse_css(" :root{--blue: #007bff;}").unwrap(); 1495 | let result = ast_to_vanilla_extract(parsed_css); 1496 | 1497 | assert_eq!( 1498 | "import { globalStyle } from \"@vanilla-extract/css\"\n\nglobalStyle(\":root\", {\n \"vars\": {\n \"--blue\": \"#007bff\",\n },\n});\n\n", 1499 | result 1500 | ) 1501 | } 1502 | 1503 | #[test] 1504 | fn ast_to_vanilla_extract_06() { 1505 | let parsed_css = parse_css("#foo {border: red 2px solid;}").unwrap(); 1506 | let result = ast_to_vanilla_extract(parsed_css); 1507 | 1508 | assert_eq!( 1509 | "import { globalStyle } from \"@vanilla-extract/css\"\n\nglobalStyle(\"#foo\", {\n border: \"red 2px solid\",\n});\n\n", 1510 | result 1511 | ) 1512 | } 1513 | 1514 | #[test] 1515 | fn ast_to_vanilla_extract_07() { 1516 | let parsed_css = 1517 | parse_css("@media(min-width: 1200px){.display-1 {font-size: 5rem; color:red;}}") 1518 | .unwrap(); 1519 | let result = ast_to_vanilla_extract(parsed_css); 1520 | 1521 | assert_eq!( 1522 | "import { style } from \"@vanilla-extract/css\"\n\nexport const display1 = style({\n \"@media\": {\n \"(min-width: 1200px)\": {\n fontSize: \"5rem\",\n color: \"red\",\n },\n },\n});\n\n", 1523 | result 1524 | ) 1525 | } 1526 | 1527 | #[test] 1528 | fn ast_to_vanilla_extract_08() { 1529 | let parsed_css = parse_css("@media(min-width: 1200px){input {font-size: 5rem;}}").unwrap(); 1530 | let result = ast_to_vanilla_extract(parsed_css); 1531 | 1532 | assert_eq!( 1533 | "import { globalStyle } from \"@vanilla-extract/css\"\n\nglobalStyle(\"input\", {\n \"@media\": {\n \"(min-width: 1200px)\": {\n fontSize: \"5rem\",\n },\n },\n});\n\n", 1534 | result 1535 | ) 1536 | } 1537 | 1538 | #[test] 1539 | fn ast_to_vanilla_extract_09() { 1540 | let parsed_css = parse_css("@media(min-width: 1200px){input {font-size: 5rem;} .display-2 {font-size: 5rem;color:red}}").unwrap(); 1541 | let result = ast_to_vanilla_extract(parsed_css); 1542 | 1543 | assert_eq!( 1544 | "import { globalStyle, style } from \"@vanilla-extract/css\"\n\nexport const display2 = style({\n \"@media\": {\n \"(min-width: 1200px)\": {\n fontSize: \"5rem\",\n color: \"red\",\n },\n },\n});\n\nglobalStyle(\"input\", {\n \"@media\": {\n \"(min-width: 1200px)\": {\n fontSize: \"5rem\",\n },\n },\n});\n\n", 1545 | result 1546 | ) 1547 | } 1548 | 1549 | #[test] 1550 | fn ast_to_vanilla_extract_10() { 1551 | let parsed_css = parse_css("@supports ((position: -webkit-sticky) or (position: sticky)) {.display-1 {font-size: 5rem;}}").unwrap(); 1552 | let result = ast_to_vanilla_extract(parsed_css); 1553 | 1554 | assert_eq!( 1555 | "import { style } from \"@vanilla-extract/css\"\n\nexport const display1 = style({\n \"@supports\": {\n \"(position:-webkit-sticky) or (position:sticky)\": {\n fontSize: \"5rem\",\n },\n },\n});\n\n", 1556 | result 1557 | ) 1558 | } 1559 | 1560 | #[test] 1561 | fn ast_to_vanilla_extract_11() { 1562 | let parsed_css = parse_css("@supports ((position: -webkit-sticky) or (position: sticky)) {input {font-size: 5rem;}}").unwrap(); 1563 | let result = ast_to_vanilla_extract(parsed_css); 1564 | 1565 | assert_eq!( 1566 | "import { globalStyle } from \"@vanilla-extract/css\"\n\nglobalStyle(\"input\", {\n \"@supports\": {\n \"(position:-webkit-sticky) or (position:sticky)\": {\n fontSize: \"5rem\",\n },\n },\n});\n\n", 1567 | result 1568 | ) 1569 | } 1570 | 1571 | #[test] 1572 | fn ast_to_vanilla_extract_12() { 1573 | let parsed_css = parse_css("@supports ((position: -webkit-sticky) or (position: sticky)) {input {font-size: 5rem;} .display-2 {font-size: 5rem;color:red}}").unwrap(); 1574 | let result = ast_to_vanilla_extract(parsed_css); 1575 | 1576 | assert_eq!( 1577 | "import { globalStyle, style } from \"@vanilla-extract/css\"\n\nexport const display2 = style({\n \"@supports\": {\n \"(position:-webkit-sticky) or (position:sticky)\": {\n fontSize: \"5rem\",\n color: \"red\",\n },\n },\n});\n\nglobalStyle(\"input\", {\n \"@supports\": {\n \"(position:-webkit-sticky) or (position:sticky)\": {\n fontSize: \"5rem\",\n },\n },\n});\n\n", 1578 | result 1579 | ) 1580 | } 1581 | 1582 | #[test] 1583 | fn ast_to_vanilla_extract_13() { 1584 | let parsed_css = parse_css(".toast:not(:last-child) {margin-bottom: 0.75rem;}").unwrap(); 1585 | let result = ast_to_vanilla_extract(parsed_css); 1586 | 1587 | assert_eq!( 1588 | "import { style } from \"@vanilla-extract/css\"\n\nexport const toast = style({\n \"selectors\": {\n \"&:not(:last-child)\": {\n marginBottom: \"0.75rem\",\n },\n },\n});\n\n", 1589 | result 1590 | ) 1591 | } 1592 | 1593 | #[test] 1594 | fn ast_to_vanilla_extract_14() { 1595 | let parsed_css = 1596 | parse_css(".tooltip[data-popper-placement^=top] {padding: 0.4rem 0;").unwrap(); 1597 | let result = ast_to_vanilla_extract(parsed_css); 1598 | 1599 | assert_eq!( 1600 | "import { style } from \"@vanilla-extract/css\"\n\nexport const tooltip = style({\n \"selectors\": {\n \"&[data-popper-placement^='top']\": {\n padding: \"0.4rem 0\",\n },\n },\n});\n\n", 1601 | result 1602 | ) 1603 | } 1604 | 1605 | #[test] 1606 | fn ast_to_vanilla_extract_15() { 1607 | let parsed_css = parse_css(".foo,.foo:hover {position: absolute;}").unwrap(); 1608 | let result = ast_to_vanilla_extract(parsed_css); 1609 | 1610 | assert_eq!( 1611 | "import { style } from \"@vanilla-extract/css\"\n\nexport const foo = style({\n position: \"absolute\",\n \":hover\": {\n position: \"absolute\",\n },\n});\n\n", 1612 | result 1613 | ) 1614 | } 1615 | 1616 | #[test] 1617 | fn ast_to_vanilla_extract_16() { 1618 | let parsed_css = 1619 | parse_css("@media (min-width: 1200px) {h4, .h4 {font-size: 1.5rem;}}").unwrap(); 1620 | let result = ast_to_vanilla_extract(parsed_css); 1621 | 1622 | assert_eq!( 1623 | "import { globalStyle, style } from \"@vanilla-extract/css\"\n\nexport const h4 = style({\n \"@media\": {\n \"(min-width: 1200px)\": {\n fontSize: \"1.5rem\",\n },\n },\n});\n\nglobalStyle(\"h4\", {\n \"@media\": {\n \"(min-width: 1200px)\": {\n fontSize: \"1.5rem\",\n },\n },\n});\n\n", 1624 | result 1625 | ) 1626 | } 1627 | 1628 | #[test] 1629 | fn ast_to_vanilla_extract_17() { 1630 | let parsed_css = 1631 | parse_css(".display-1 {background-color: blue;} @media (min-width: 1200px) {.display-1 {font-size: 5rem;color: red;}}").unwrap(); 1632 | let result = ast_to_vanilla_extract(parsed_css); 1633 | 1634 | assert_eq!( 1635 | "import { style } from \"@vanilla-extract/css\"\n\nexport const display1 = style({\n backgroundColor: \"blue\",\n \"@media\": {\n \"(min-width: 1200px)\": {\n fontSize: \"5rem\",\n color: \"red\",\n },\n },\n});\n\n", 1636 | result 1637 | ) 1638 | } 1639 | 1640 | #[test] 1641 | fn ast_to_vanilla_extract_18() { 1642 | let parsed_css = 1643 | parse_css(".display-1 {background-color: blue;} @supports((position: -webkit-sticky) or (position: sticky)) {.display-1 {font-size: 5rem;color: red;}}").unwrap(); 1644 | let result = ast_to_vanilla_extract(parsed_css); 1645 | 1646 | assert_eq!( 1647 | "import { style } from \"@vanilla-extract/css\"\n\nexport const display1 = style({\n backgroundColor: \"blue\",\n \"@supports\": {\n \"(position:-webkit-sticky) or (position:sticky)\": {\n fontSize: \"5rem\",\n color: \"red\",\n },\n },\n});\n\n", 1648 | result 1649 | ) 1650 | } 1651 | 1652 | #[test] 1653 | fn ast_to_vanilla_extract_19() { 1654 | let parsed_css = parse_css( 1655 | "@keyframes progress-bar-stripes {from {background-position: 1rem 0;}to {background-position: 0 0;}}", 1656 | ) 1657 | .unwrap(); 1658 | let result = ast_to_vanilla_extract(parsed_css); 1659 | 1660 | assert_eq!( 1661 | "import { globalKeyframes } from \"@vanilla-extract/css\"\n\nglobalKeyframes(\"progress-bar-stripes\", {\n \"from\": {\n backgroundPosition: \"1rem 0\",\n },\n \"to\": {\n backgroundPosition: \"0 0\",\n },\n});\n\n", 1662 | result 1663 | ) 1664 | } 1665 | 1666 | #[test] 1667 | fn ast_to_vanilla_extract_20() { 1668 | let parsed_css = parse_css(".accordion-button:not(.collapsed) {color: #0c63e4;}.accordion-button:not(.collapsed)::after {transform: rotate(-180deg);}") 1669 | .unwrap(); 1670 | let result = ast_to_vanilla_extract(parsed_css); 1671 | 1672 | assert_eq!( 1673 | "import { style } from \"@vanilla-extract/css\"\n\nexport const accordionButton = style({\n \"selectors\": {\n \"&:not\": {\n color: \"#0c63e4\",\n },\n \"&:not::after\": {\n transform: \"rotate(-180deg)\",\n },\n },\n});\n\n", 1674 | result 1675 | ) 1676 | } 1677 | 1678 | #[test] 1679 | fn ast_to_vanilla_extract_21() { 1680 | let parsed_css = parse_css("@supports((position: -webkit-sticky) or (position: sticky)) {.accordion-button:not(.collapsed) {color: #0c63e4;}.accordion-button:not(.collapsed)::after {transform: rotate(-180deg);}}") 1681 | .unwrap(); 1682 | let result = ast_to_vanilla_extract(parsed_css); 1683 | 1684 | assert_eq!( 1685 | "import { style } from \"@vanilla-extract/css\"\n\nexport const accordionButton = style({\n \"@supports\": {\n \"(position:-webkit-sticky) or (position:sticky)\": {\n \"selectors\": {\n \"&:not\": {\n color: \"#0c63e4\",\n },\n \"&:not::after\": {\n transform: \"rotate(-180deg)\",\n },\n },\n },\n },\n});\n\n", 1686 | result 1687 | ) 1688 | } 1689 | 1690 | #[test] 1691 | fn ast_to_vanilla_extract_22() { 1692 | let parsed_css = parse_css("@media (min-width: 1200px){.accordion-button:not(.collapsed) {color: #0c63e4;}.accordion-button:not(.collapsed)::after {transform: rotate(-180deg);}}") 1693 | .unwrap(); 1694 | let result = ast_to_vanilla_extract(parsed_css); 1695 | 1696 | assert_eq!( 1697 | "import { style } from \"@vanilla-extract/css\"\n\nexport const accordionButton = style({\n \"@media\": {\n \"(min-width: 1200px)\": {\n \"selectors\": {\n \"&:not\": {\n color: \"#0c63e4\",\n },\n \"&:not::after\": {\n transform: \"rotate(-180deg)\",\n },\n },\n },\n },\n});\n\n", 1698 | result 1699 | ) 1700 | } 1701 | 1702 | #[test] 1703 | fn ast_to_vanilla_extract_23() { 1704 | let parsed_css = parse_css("@font-face {font-family: \"Open Sans\";src: url(\"/fonts/OpenSans-Regular-webfont.woff2\") format(\"woff2\"),url(\"/fonts/OpenSans-Regular-webfont.woff\") format(\"woff\");}") 1705 | .unwrap(); 1706 | let result = ast_to_vanilla_extract(parsed_css); 1707 | 1708 | assert_eq!( 1709 | "import { globalFontFace } from \"@vanilla-extract/css\"\n\nglobalFontFace(\"Open Sans\", {\n src: \"url(/fonts/OpenSans-Regular-webfont.woff2) format(woff2) , url(/fonts/OpenSans-Regular-webfont.woff) format(woff)\",\n});\n\n", 1710 | result 1711 | ) 1712 | } 1713 | 1714 | #[test] 1715 | fn ast_to_vanilla_extract_24() { 1716 | let parsed_css = 1717 | parse_css(".input:invalid:focus { box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.25);}") 1718 | .unwrap(); 1719 | let result = ast_to_vanilla_extract(parsed_css); 1720 | 1721 | assert_eq!( 1722 | "import { style } from \"@vanilla-extract/css\"\n\nexport const input = style({\n \"selectors\": {\n \"&:invalid:focus\": {\n boxShadow: \"0 0 0 0.25rem rgba(220,53,69,0.25)\",\n },\n },\n});\n\n", 1723 | result 1724 | ) 1725 | } 1726 | 1727 | #[test] 1728 | fn ast_to_vanilla_extract_25() { 1729 | let parsed_css = 1730 | parse_css(".form:active::-moz-range-thumb {background-color: #b6d4fe;}").unwrap(); 1731 | let result = ast_to_vanilla_extract(parsed_css); 1732 | 1733 | assert_eq!( 1734 | "import { style } from \"@vanilla-extract/css\"\n\nexport const form = style({\n \"selectors\": {\n \"&:active::-moz-range-thumb\": {\n backgroundColor: \"#b6d4fe\",\n },\n },\n});\n\n", 1735 | result 1736 | ) 1737 | } 1738 | 1739 | #[test] 1740 | fn ast_to_vanilla_extract_26() { 1741 | let parsed_css = 1742 | parse_css(".input:checked[type=\"checkbox\"] {background-color: red;}").unwrap(); 1743 | let result = ast_to_vanilla_extract(parsed_css); 1744 | 1745 | assert_eq!( 1746 | "import { style } from \"@vanilla-extract/css\"\n\nexport const input = style({\n \"selectors\": {\n \"&:checked[type='checkbox']\": {\n backgroundColor: \"red\",\n },\n },\n});\n\n", 1747 | result 1748 | ) 1749 | } 1750 | 1751 | #[test] 1752 | fn ast_to_vanilla_extract_27() { 1753 | let parsed_css = 1754 | parse_css("input > .btn > .icon {position: absolute;} .btn {width: 100%;}").unwrap(); 1755 | 1756 | let result = ast_to_vanilla_extract(parsed_css); 1757 | 1758 | assert_eq!( 1759 | "import { style } from \"@vanilla-extract/css\"\n\nexport const btn = style({\n width: \"100%\",\n});\n\nexport const icon = style({\n \"selectors\": {\n [`input > ${btn} > &`]: {\n position: \"absolute\",\n },\n },\n});\n\n", 1760 | result 1761 | ) 1762 | } 1763 | 1764 | #[test] 1765 | fn ast_to_vanilla_extract_28() { 1766 | let parsed_css = 1767 | parse_css(".input > input > .btn > .icon {position: absolute;} .btn {width: 100%;}") 1768 | .unwrap(); 1769 | 1770 | let result = ast_to_vanilla_extract(parsed_css); 1771 | 1772 | assert_eq!( 1773 | "import { style } from \"@vanilla-extract/css\"\n\nexport const btn = style({\n width: \"100%\",\n});\n\nexport const input = style({\n});\n\nexport const icon = style({\n \"selectors\": {\n [`${input} > input > ${btn} > &`]: {\n position: \"absolute\",\n },\n },\n});\n\n", 1774 | result 1775 | ) 1776 | } 1777 | 1778 | #[test] 1779 | fn ast_to_vanilla_extract_29() { 1780 | // [Note] #31 Merge identical selectors. 1781 | let parsed_css = 1782 | parse_css(".foo .bar {background-color: blue;} .foo .bar {font-size: 5rem;}").unwrap(); 1783 | let result = ast_to_vanilla_extract(parsed_css); 1784 | 1785 | assert_eq!( 1786 | "import { style } from \"@vanilla-extract/css\"\n\nexport const foo = style({\n});\n\nexport const bar = style({\n \"selectors\": {\n [`${foo} &`]: {\n backgroundColor: \"blue\",\n fontSize: \"5rem\",\n },\n },\n});\n\n", 1787 | result 1788 | ) 1789 | } 1790 | 1791 | #[test] 1792 | fn ast_to_vanilla_extract_30() { 1793 | // [Note] #31 Merge identical pseudos. 1794 | let parsed_css = 1795 | parse_css(".foo:hover {background-color: blue;} .foo:hover {font-size: 5rem;}") 1796 | .unwrap(); 1797 | let result = ast_to_vanilla_extract(parsed_css); 1798 | 1799 | assert_eq!( 1800 | "import { style } from \"@vanilla-extract/css\"\n\nexport const foo = style({\n \":hover\": {\n backgroundColor: \"blue\",\n fontSize: \"5rem\",\n },\n});\n\n", 1801 | result 1802 | ) 1803 | } 1804 | 1805 | #[test] 1806 | fn ast_to_vanilla_extract_31() { 1807 | let parsed_css = parse_css("@supports((position: -webkit-sticky) or (position: sticky)) {.foo .bar {background-color: blue;}.foo .bar {font-size: 5rem;};}}") 1808 | .unwrap(); 1809 | let result = ast_to_vanilla_extract(parsed_css); 1810 | 1811 | assert_eq!( 1812 | "import { style } from \"@vanilla-extract/css\"\n\nexport const foo = style({\n});\n\nexport const bar = style({\n \"@supports\": {\n \"(position:-webkit-sticky) or (position:sticky)\": {\n \"selectors\": {\n [`${foo} &`]: {\n backgroundColor: \"blue\",\n fontSize: \"5rem\",\n },\n },\n },\n },\n});\n\n", 1813 | result 1814 | ) 1815 | } 1816 | 1817 | #[test] 1818 | fn ast_to_vanilla_extract_32() { 1819 | let parsed_css = parse_css("@supports((position: -webkit-sticky) or (position: sticky)) {.foo:hover {background-color: blue;}.foo:hover {font-size: 5rem;};}}") 1820 | .unwrap(); 1821 | let result = ast_to_vanilla_extract(parsed_css); 1822 | 1823 | assert_eq!( 1824 | "import { style } from \"@vanilla-extract/css\"\n\nexport const foo = style({\n \":hover\": {\n backgroundColor: \"blue\",\n fontSize: \"5rem\",\n },\n});\n\n", 1825 | result 1826 | ) 1827 | } 1828 | 1829 | #[test] 1830 | fn ast_to_vanilla_extract_33() { 1831 | let parsed_css = parse_css("@media (min-width: 1200px) {.foo .bar {background-color: blue;} .foo .bar {font-size: 5rem;};}}") 1832 | .unwrap(); 1833 | let result = ast_to_vanilla_extract(parsed_css); 1834 | 1835 | assert_eq!( 1836 | "import { style } from \"@vanilla-extract/css\"\n\nexport const foo = style({\n});\n\nexport const bar = style({\n \"@media\": {\n \"(min-width: 1200px)\": {\n \"selectors\": {\n [`${foo} &`]: {\n backgroundColor: \"blue\",\n fontSize: \"5rem\",\n },\n },\n },\n },\n});\n\n", 1837 | result 1838 | ) 1839 | } 1840 | 1841 | #[test] 1842 | fn ast_to_vanilla_extract_34() { 1843 | let parsed_css = parse_css("@media (min-width: 1200px) {.foo:hover {background-color: blue;}.foo:hover {font-size: 5rem;};}}") 1844 | .unwrap(); 1845 | let result = ast_to_vanilla_extract(parsed_css); 1846 | 1847 | assert_eq!( 1848 | "import { style } from \"@vanilla-extract/css\"\n\nexport const foo = style({\n \":hover\": {\n backgroundColor: \"blue\",\n fontSize: \"5rem\",\n },\n});\n\n", 1849 | result 1850 | ) 1851 | } 1852 | } 1853 | --------------------------------------------------------------------------------
107 | 116 |
4 | 5 |