diff --git a/.changeset/large-clubs-strive.md b/.changeset/large-clubs-strive.md new file mode 100644 index 00000000..007635ca --- /dev/null +++ b/.changeset/large-clubs-strive.md @@ -0,0 +1,8 @@ +--- +"@devup-ui/rsbuild-plugin": patch +"@devup-ui/webpack-plugin": patch +"@devup-ui/next-plugin": patch +"@devup-ui/vite-plugin": patch +--- + +Change temp dir diff --git a/.changeset/little-ways-sneeze.md b/.changeset/little-ways-sneeze.md new file mode 100644 index 00000000..2760c7e2 --- /dev/null +++ b/.changeset/little-ways-sneeze.md @@ -0,0 +1,5 @@ +--- +"@devup-ui/wasm": patch +--- + +Implement globalCss, Fix ?? operator issue diff --git a/.changeset/tidy-buses-float.md b/.changeset/tidy-buses-float.md new file mode 100644 index 00000000..303d84d6 --- /dev/null +++ b/.changeset/tidy-buses-float.md @@ -0,0 +1,5 @@ +--- +"@devup-ui/react": patch +--- + +Add globalCss diff --git a/.df/devup-ui.css b/.df/devup-ui.css new file mode 100644 index 00000000..62c487cc --- /dev/null +++ b/.df/devup-ui.css @@ -0,0 +1 @@ +/* C:/Users/owjs3/Desktop/projects/devup-ui/packages/components/src/components/Button/__tests__/index.browser.test.tsx 1752471792555 */ \ No newline at end of file diff --git a/.gitignore b/.gitignore index a8102526..3563db9f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,6 @@ node_modules target *.*.timestamp-* coverage -.df build_rs_cov.profraw tsconfig.tsbuildinfo codecov diff --git a/Cargo.lock b/Cargo.lock index 87ca9eaf..a48f69f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -53,9 +53,9 @@ checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" dependencies = [ "allocator-api2", ] @@ -255,6 +255,7 @@ dependencies = [ "once_cell", "phf", "regex", + "rstest", "serde", "serial_test", ] @@ -309,6 +310,7 @@ dependencies = [ "oxc_span", "oxc_syntax", "phf", + "rstest", "serial_test", "strum", "strum_macros", @@ -380,6 +382,17 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "futures-sink" version = "0.3.31" @@ -392,6 +405,12 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + [[package]] name = "futures-util" version = "0.3.31" @@ -401,6 +420,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-io", + "futures-macro", "futures-sink", "futures-task", "memchr", @@ -409,6 +429,12 @@ dependencies = [ "slab", ] +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + [[package]] name = "half" version = "2.6.0" @@ -623,9 +649,9 @@ dependencies = [ [[package]] name = "oxc_allocator" -version = "0.75.0" +version = "0.77.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ca46541ac8f29e31319b130e39a795b0bb72767c508d70e17843458b47c4f1" +checksum = "fdbeef2a832aa69e8ca20a8ef9e37da18d1a827cf34d682685a09fe2b6caaef1" dependencies = [ "allocator-api2", "bumpalo", @@ -636,9 +662,9 @@ dependencies = [ [[package]] name = "oxc_ast" -version = "0.75.0" +version = "0.77.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e20bc7d2bc9ff195fb30745be6f85bbe9a3355f25fb9e27f7dd1ba2f4125e90" +checksum = "b8135343afd05522ca069c33c663b71f4836f5aa8f951384327126febead4c30" dependencies = [ "bitflags", "oxc_allocator", @@ -652,9 +678,9 @@ dependencies = [ [[package]] name = "oxc_ast_macros" -version = "0.75.0" +version = "0.77.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743cfd932620544d8a5f6556120a589fea39fa964f262b98e14cad600fcb1221" +checksum = "8c33f44b3c4e5e473fcac4e0319f51b44a800b99d8a421d0a9e96e123b2cc1a6" dependencies = [ "phf", "proc-macro2", @@ -664,9 +690,9 @@ dependencies = [ [[package]] name = "oxc_ast_visit" -version = "0.75.0" +version = "0.77.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1b03354e570cc071bca9cd16cacaffb2c040ef5eb28a785c7b5f17713947101" +checksum = "9266313d0d04b48898f7243fb05a03c10aab1c854d5f2efb41b201bed1e8ef1a" dependencies = [ "oxc_allocator", "oxc_ast", @@ -676,9 +702,9 @@ dependencies = [ [[package]] name = "oxc_cfg" -version = "0.75.0" +version = "0.77.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d81491e60ed2308848475d5c1393df1d9870a42570d0c4c02e86dd25d1aa7b09" +checksum = "297c81043bd8adb4712e25ddeb6559703a8f6667ffa9b1968c089f53d41dbbaf" dependencies = [ "bitflags", "itertools 0.14.0", @@ -691,9 +717,9 @@ dependencies = [ [[package]] name = "oxc_codegen" -version = "0.75.0" +version = "0.77.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba472ac12faeb6e90fbb15a3f9fe0b94111b5fe5bef17ff0334290cd94b75744" +checksum = "28369f773f38efa0e0377a789a1aaed9eae4f9c90abeb2f91aa6de343eda29e8" dependencies = [ "bitflags", "cow-utils", @@ -712,28 +738,29 @@ dependencies = [ [[package]] name = "oxc_data_structures" -version = "0.75.0" +version = "0.77.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acca986d8bc723d8f65cf4111242a9d8fa77b6674a47fc7e2f972c1cd3c681e4" +checksum = "5514bed9595363bfa66bfcf6dcecedb0ed5c40f162c429a70893edb825dba9db" dependencies = [ "rustversion", ] [[package]] name = "oxc_diagnostics" -version = "0.75.0" +version = "0.77.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df31a0ef7abc29fa3e1e1fbac9dc14a9fbb062961d265d7a77f3a6e39a0a5de8" +checksum = "29e47cf799dd9d84be89dd3af9a110232fa6f9b305bf32f65df364c3a85dec76" dependencies = [ "cow-utils", "oxc-miette", + "percent-encoding", ] [[package]] name = "oxc_ecmascript" -version = "0.75.0" +version = "0.77.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d80328b98091673e95d2a8e1d8378271e42d7efcff9982477cfcaf54bd799564" +checksum = "8da2043abf37c986c29fa44470b7f41f94d147c626d2db8e9edc69b867a07b8b" dependencies = [ "num-bigint", "num-traits", @@ -744,9 +771,9 @@ dependencies = [ [[package]] name = "oxc_estree" -version = "0.75.0" +version = "0.77.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f30ee2c1758598f22bb6578c8cda33557a74ce7a53bdebac9b75a0aed909b5" +checksum = "11c7b19dfa467408ae3e1f8a52bdf0b79e421f0811bef9d57df44369ff5f14a8" [[package]] name = "oxc_index" @@ -756,9 +783,9 @@ checksum = "2fa07b0cfa997730afed43705766ef27792873fdf5215b1391949fec678d2392" [[package]] name = "oxc_parser" -version = "0.75.0" +version = "0.77.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298a69b099cf433e2cd2ca15e786afdd1dc944cc413bbfcd8b64c499d9fecb38" +checksum = "760adcacb436921e78a256ed467fa3cc4cd27147542ac0f94bcaf4a900a4002a" dependencies = [ "bitflags", "cow-utils", @@ -779,9 +806,9 @@ dependencies = [ [[package]] name = "oxc_regular_expression" -version = "0.75.0" +version = "0.77.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4288439a580f3c2b10c3ba20922908756aee61316fab82fcb209377e1d85099" +checksum = "6af8abf0a50098480239f588f6f997148c48e0f062b78b32aedf9a7a380442cb" dependencies = [ "bitflags", "oxc_allocator", @@ -795,9 +822,9 @@ dependencies = [ [[package]] name = "oxc_semantic" -version = "0.75.0" +version = "0.77.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b01414db4c8ca38f005c4b3f41910ceda600261c7ed7c45fda4f0738b64a8558" +checksum = "f67db9a6930a4dfcc849ee5cc95825882e50783f4e4e2550db4cd67f2f486121" dependencies = [ "itertools 0.14.0", "oxc_allocator", @@ -831,9 +858,9 @@ dependencies = [ [[package]] name = "oxc_span" -version = "0.75.0" +version = "0.77.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eb726c595d18fe5a7f66bf7f449883608a4c7bfd50097d49db3225d22cd45b6" +checksum = "a9be736965e94fed8a096679f493ef794718821ffc6a3e509cf6dc6c10f5d38a" dependencies = [ "compact_str", "oxc-miette", @@ -844,9 +871,9 @@ dependencies = [ [[package]] name = "oxc_syntax" -version = "0.75.0" +version = "0.77.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43d293457dacafd89fd73f9e933cc021cbb5ed1f615b21150cf727bc05b9cc6" +checksum = "4d69df31e7fc11197b27a3edfb316d0f9bf51778f824b1dafbca68c8fac59898" dependencies = [ "bitflags", "cow-utils", @@ -886,6 +913,12 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + [[package]] name = "petgraph" version = "0.8.2" @@ -981,6 +1014,15 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "proc-macro-crate" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.94" @@ -1057,12 +1099,57 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" + +[[package]] +name = "rstest" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fc39292f8613e913f7df8fa892b8944ceb47c247b78e1b1ae2f09e019be789d" +dependencies = [ + "futures-timer", + "futures-util", + "rstest_macros", + "rustc_version", +] + +[[package]] +name = "rstest_macros" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f168d99749d307be9de54d23fd226628d99768225ef08f6ffb52e0182a27746" +dependencies = [ + "cfg-if", + "glob", + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn", + "unicode-ident", +] + [[package]] name = "rustc-hash" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustversion" version = "1.0.20" @@ -1117,6 +1204,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749" +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + [[package]] name = "seq-macro" version = "0.3.6" @@ -1320,6 +1413,23 @@ dependencies = [ "serde_json", ] +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + [[package]] name = "unicode-id-start" version = "1.3.1" @@ -1546,3 +1656,12 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +dependencies = [ + "memchr", +] diff --git a/apps/landing/.gitignore b/apps/landing/.gitignore index 9a792a1f..cee5b2f0 100644 --- a/apps/landing/.gitignore +++ b/apps/landing/.gitignore @@ -6,7 +6,6 @@ public/search.json /.pnp .pnp.* .yarn/* -.df !.yarn/patches !.yarn/plugins !.yarn/releases diff --git a/apps/landing/src/app/layout.tsx b/apps/landing/src/app/layout.tsx index bf8fcb7d..0962c4b1 100644 --- a/apps/landing/src/app/layout.tsx +++ b/apps/landing/src/app/layout.tsx @@ -1,7 +1,6 @@ -import './markdown.css' import 'sanitize.css' -import { css, ThemeScript } from '@devup-ui/react' +import { css, globalCss, ThemeScript } from '@devup-ui/react' import type { Metadata } from 'next' import { Footer } from '../components/Footer' @@ -25,6 +24,37 @@ export const metadata: Metadata = { }, } +globalCss({ + imports: ['https://cdn.jsdelivr.net/gh/joungkyun/font-d2coding/d2coding.css'], + table: { + borderCollapse: 'collapse', + borderSpacing: 0, + border: '1px solid var(--text)', + color: 'var(--text, #2F2F2F)', + fontFamily: 'Pretendard', + fontSize: '16px', + fontStyle: 'normal', + fontWeight: 400, + lineHeight: '150%', + letterSpacing: '-0.48px', + }, + code: { + fontFamily: 'D2Coding', + fontSize: ['13px', '15px'], + fontStyle: 'normal', + fontWeight: 700, + lineHeight: '1.5', + letterSpacing: '-0.03em', + }, + 'th, td': { + border: '1px solid var(--text)', + padding: '6px 13px', + }, + pre: { + borderRadius: '10px', + }, +}) + export default function RootLayout({ children, }: Readonly<{ diff --git a/apps/landing/src/app/markdown.css b/apps/landing/src/app/markdown.css deleted file mode 100644 index 4cf3eb56..00000000 --- a/apps/landing/src/app/markdown.css +++ /dev/null @@ -1,40 +0,0 @@ -@import url(https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css); - -table { - border-collapse: collapse; - border-spacing: 0; - border: 1px solid var(--text); - color: var(--text, #2F2F2F); - - /* Desktop/bodyReg */ - font-family: Pretendard; - font-size: 16px; - font-style: normal; - font-weight: 400; - line-height: 150%; /* 24px */ - letter-spacing: -0.48px; -} - -code { - font-family: D2Coding; - font-size: 13px; - font-style: normal; - font-weight: 700; - line-height: 1.5; - letter-spacing: -0.03em; -} - -@media (min-width: 1280px) { - code { - font-size: 15px; - } -} - -th, td { - border: 1px solid var(--text); - padding: 6px 13px; -} - -pre { - border-radius: 10px; -} diff --git a/apps/landing/tsconfig.json b/apps/landing/tsconfig.json index ab49c986..426d9c2d 100644 --- a/apps/landing/tsconfig.json +++ b/apps/landing/tsconfig.json @@ -33,9 +33,9 @@ "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", - ".df/*.d.ts" + "df/*.d.ts" ], "exclude": [ "node_modules" ] -} +} \ No newline at end of file diff --git a/apps/next/.gitignore b/apps/next/.gitignore index ebe99d6c..5ef6a520 100644 --- a/apps/next/.gitignore +++ b/apps/next/.gitignore @@ -5,7 +5,6 @@ /.pnp .pnp.* .yarn/* -.df !.yarn/patches !.yarn/plugins !.yarn/releases diff --git a/apps/next/src/app/layout.tsx b/apps/next/src/app/layout.tsx index 209436c2..ec4bfaae 100644 --- a/apps/next/src/app/layout.tsx +++ b/apps/next/src/app/layout.tsx @@ -1,4 +1,4 @@ -import { Box } from '@devup-ui/react' +import { Box, globalCss } from '@devup-ui/react' import type { Metadata } from 'next' import { Geist, Geist_Mono } from 'next/font/google' @@ -17,6 +17,14 @@ export const metadata: Metadata = { description: 'Generated by create next app', } +// eslint-disable-next-line @typescript-eslint/no-unused-expressions +globalCss` + body { + background-color: #040 !important; + color: #248 !important; + } +` + export default function RootLayout({ children, }: Readonly<{ @@ -25,7 +33,7 @@ export default function RootLayout({ return ( - hello + hello {children} diff --git a/apps/next/tsconfig.json b/apps/next/tsconfig.json index ab49c986..426d9c2d 100644 --- a/apps/next/tsconfig.json +++ b/apps/next/tsconfig.json @@ -33,9 +33,9 @@ "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", - ".df/*.d.ts" + "df/*.d.ts" ], "exclude": [ "node_modules" ] -} +} \ No newline at end of file diff --git a/apps/vite-lib/tsconfig.json b/apps/vite-lib/tsconfig.json index 09085d3e..2d26fac1 100644 --- a/apps/vite-lib/tsconfig.json +++ b/apps/vite-lib/tsconfig.json @@ -26,9 +26,9 @@ "include": [ "**/*.ts", "**/*.tsx", - ".df/*.d.ts" + "df/*.d.ts" ], "exclude": [ "node_modules" ] -} +} \ No newline at end of file diff --git a/apps/vite-lib/vite.config.ts b/apps/vite-lib/vite.config.ts index 2357486e..2f372f09 100644 --- a/apps/vite-lib/vite.config.ts +++ b/apps/vite-lib/vite.config.ts @@ -20,7 +20,7 @@ export default defineConfig({ '**/*.test-d.(tsx|ts|js|jsx)', 'vite.config.ts', ], - include: ['**/src/**/*.(ts|tsx)', '.df/*.d.ts'], + include: ['**/src/**/*.(ts|tsx)', 'df/*.d.ts'], copyDtsFiles: true, compilerOptions: { isolatedModules: false, diff --git a/apps/vite/tsconfig.json b/apps/vite/tsconfig.json index 09085d3e..2d26fac1 100644 --- a/apps/vite/tsconfig.json +++ b/apps/vite/tsconfig.json @@ -26,9 +26,9 @@ "include": [ "**/*.ts", "**/*.tsx", - ".df/*.d.ts" + "df/*.d.ts" ], "exclude": [ "node_modules" ] -} +} \ No newline at end of file diff --git a/benchmark.js b/benchmark.js index 9a124a0b..7b1006c5 100644 --- a/benchmark.js +++ b/benchmark.js @@ -20,8 +20,8 @@ function clearBuildFile() { recursive: true, force: true, }) - if (existsSync('./benchmark/next-devup-ui/.df')) - rmSync('./benchmark/next-devup-ui/.df', { + if (existsSync('./benchmark/next-devup-ui/df')) + rmSync('./benchmark/next-devup-ui/df', { recursive: true, force: true, }) diff --git a/benchmark/next-chakra-ui/.gitignore b/benchmark/next-chakra-ui/.gitignore index ebe99d6c..5ef6a520 100644 --- a/benchmark/next-chakra-ui/.gitignore +++ b/benchmark/next-chakra-ui/.gitignore @@ -5,7 +5,6 @@ /.pnp .pnp.* .yarn/* -.df !.yarn/patches !.yarn/plugins !.yarn/releases diff --git a/benchmark/next-chakra-ui/tsconfig.json b/benchmark/next-chakra-ui/tsconfig.json index ab49c986..426d9c2d 100644 --- a/benchmark/next-chakra-ui/tsconfig.json +++ b/benchmark/next-chakra-ui/tsconfig.json @@ -33,9 +33,9 @@ "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", - ".df/*.d.ts" + "df/*.d.ts" ], "exclude": [ "node_modules" ] -} +} \ No newline at end of file diff --git a/benchmark/next-devup-ui/.gitignore b/benchmark/next-devup-ui/.gitignore index ebe99d6c..5ef6a520 100644 --- a/benchmark/next-devup-ui/.gitignore +++ b/benchmark/next-devup-ui/.gitignore @@ -5,7 +5,6 @@ /.pnp .pnp.* .yarn/* -.df !.yarn/patches !.yarn/plugins !.yarn/releases diff --git a/benchmark/next-devup-ui/tsconfig.json b/benchmark/next-devup-ui/tsconfig.json index ab49c986..426d9c2d 100644 --- a/benchmark/next-devup-ui/tsconfig.json +++ b/benchmark/next-devup-ui/tsconfig.json @@ -33,9 +33,9 @@ "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", - ".df/*.d.ts" + "df/*.d.ts" ], "exclude": [ "node_modules" ] -} +} \ No newline at end of file diff --git a/benchmark/next-kuma-ui/.gitignore b/benchmark/next-kuma-ui/.gitignore index ebe99d6c..5ef6a520 100644 --- a/benchmark/next-kuma-ui/.gitignore +++ b/benchmark/next-kuma-ui/.gitignore @@ -5,7 +5,6 @@ /.pnp .pnp.* .yarn/* -.df !.yarn/patches !.yarn/plugins !.yarn/releases diff --git a/benchmark/next-kuma-ui/tsconfig.json b/benchmark/next-kuma-ui/tsconfig.json index ab49c986..426d9c2d 100644 --- a/benchmark/next-kuma-ui/tsconfig.json +++ b/benchmark/next-kuma-ui/tsconfig.json @@ -33,9 +33,9 @@ "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", - ".df/*.d.ts" + "df/*.d.ts" ], "exclude": [ "node_modules" ] -} +} \ No newline at end of file diff --git a/bindings/devup-ui-wasm/src/lib.rs b/bindings/devup-ui-wasm/src/lib.rs index 16e60098..7a4014fe 100644 --- a/bindings/devup-ui-wasm/src/lib.rs +++ b/bindings/devup-ui-wasm/src/lib.rs @@ -1,5 +1,5 @@ -use css::{get_class_map, set_class_map}; -use extractor::extract_style::ExtractStyleValue; +use css::class_map::{get_class_map, set_class_map}; +use extractor::extract_style::extract_style_value::ExtractStyleValue; use extractor::{ExtractOption, StyleProperty, extract}; use once_cell::sync::Lazy; use sheet::StyleSheet; @@ -22,6 +22,10 @@ extern "C" { fn log(s: &JsValue); #[wasm_bindgen(js_namespace = console, js_name = log)] fn log_str(s: &str); + #[wasm_bindgen(js_namespace = console, js_name = time)] + fn time(s: &str); + #[wasm_bindgen(js_namespace = console, js_name = timeEnd)] + fn time_end(s: &str); } #[wasm_bindgen] @@ -43,16 +47,17 @@ impl Output { let mut sheet = GLOBAL_STYLE_SHEET.lock().unwrap(); let mut collected = false; for style in self.styles.iter() { - let (cls, variable) = match style.extract() { - StyleProperty::ClassName(cls) => (cls, None), - StyleProperty::Variable { - class_name, - variable_name, - .. - } => (class_name, Some(variable_name)), - }; match style { ExtractStyleValue::Static(st) => { + let (cls, _) = match style.extract() { + Some(StyleProperty::ClassName(cls)) => (cls, None), + Some(StyleProperty::Variable { + class_name, + variable_name, + .. + }) => (class_name, Some(variable_name)), + None => continue, + }; if sheet.add_property( &cls, st.property(), @@ -65,6 +70,15 @@ impl Output { } } ExtractStyleValue::Dynamic(dy) => { + let (cls, variable) = match style.extract() { + Some(StyleProperty::ClassName(cls)) => (cls, None), + Some(StyleProperty::Variable { + class_name, + variable_name, + .. + }) => (class_name, Some(variable_name)), + None => continue, + }; if sheet.add_property( &cls, dy.property(), @@ -77,13 +91,17 @@ impl Output { } } ExtractStyleValue::Css(cs) => { - if sheet.add_css(&cls, &cs.css) { + if sheet.add_css(&cs.file, &cs.css) { collected = true; } } ExtractStyleValue::Typography(_) => {} + ExtractStyleValue::Import(st) => { + sheet.add_import(&st.file, &st.url); + } } } + if !collected { return None; } @@ -94,12 +112,12 @@ impl Output { #[wasm_bindgen(js_name = "setDebug")] pub fn set_debug(debug: bool) { - css::set_debug(debug); + css::debug::set_debug(debug); } #[wasm_bindgen(js_name = "isDebug")] pub fn is_debug() { - css::is_debug(); + css::debug::is_debug(); } #[wasm_bindgen(js_name = "importSheet")] @@ -136,6 +154,9 @@ pub fn code_extract( package: &str, css_file: &str, ) -> Result { + let mut sheet = GLOBAL_STYLE_SHEET.lock().unwrap(); + sheet.rm_global_css(filename); + match extract( filename, code, diff --git a/libs/css/Cargo.toml b/libs/css/Cargo.toml index fb414571..09d1f590 100644 --- a/libs/css/Cargo.toml +++ b/libs/css/Cargo.toml @@ -9,3 +9,6 @@ phf = { version = "0.12", features = ["macros"] } serial_test = "3.2.0" serde = { version = "1.0.219", features = ["derive"] } regex = "1.11.1" + +[dev-dependencies] +rstest = "0.25.0" diff --git a/libs/css/src/class_map.rs b/libs/css/src/class_map.rs new file mode 100644 index 00000000..f2c7b992 --- /dev/null +++ b/libs/css/src/class_map.rs @@ -0,0 +1,21 @@ +use std::{collections::HashMap, sync::Mutex}; + +use once_cell::sync::Lazy; + +pub(crate) static GLOBAL_CLASS_MAP: Lazy>> = + Lazy::new(|| Mutex::new(HashMap::new())); + +/// for test +pub fn reset_class_map() { + let mut map = GLOBAL_CLASS_MAP.lock().unwrap(); + map.clear(); +} + +pub fn set_class_map(map: HashMap) { + let mut global_map = GLOBAL_CLASS_MAP.lock().unwrap(); + *global_map = map; +} + +pub fn get_class_map() -> HashMap { + GLOBAL_CLASS_MAP.lock().unwrap().clone() +} diff --git a/libs/css/src/constant.rs b/libs/css/src/constant.rs new file mode 100644 index 00000000..4781765f --- /dev/null +++ b/libs/css/src/constant.rs @@ -0,0 +1,82 @@ +use std::collections::HashMap; + +use once_cell::sync::Lazy; +use phf::{phf_map, phf_set}; +use regex::Regex; + +pub(super) static SELECTOR_ORDER_MAP: Lazy> = Lazy::new(|| { + let mut map = HashMap::new(); + for (idx, selector) in [ + "hover", + "focus-visible", + "focus", + "active", + "selected", + "disabled", + ] + .into_iter() + .enumerate() + { + map.insert(format!(":{selector}"), idx as u8); + } + map +}); + +pub(super) static GLOBAL_STYLE_PROPERTY: phf::Map<&str, &[&str]> = phf_map! { + "bg" => &["background"], + "bgAttachment" => &["background-attachment"], + "bgClip" => &["background-clip"], + "bgColor" => &["background-color"], + "bgImage" => &["background-image"], + "bgOrigin" => &["background-origin"], + "bgPosition" => &["background-position"], + "bgPositionX" => &["background-position-x"], + "bgPositionY" => &["background-position-y"], + "bgRepeat" => &["background-repeat"], + "bgSize" => &["background-size"], + "animationDir" => &["animation-direction"], + "flexDir" => &["flex-direction"], + "pos" => &["position"], + "m" => &["margin"], + "mt" => &["margin-top"], + "mr" => &["margin-right"], + "mb" => &["margin-bottom"], + "ml" => &["margin-left"], + "p" => &["padding"], + "pt" => &["padding-top"], + "pr" => &["padding-right"], + "pb" => &["padding-bottom"], + "pl" => &["padding-left"], + "w" => &["width"], + "h" => &["height"], + "minW" => &["min-width"], + "minH" => &["min-height"], + "maxW" => &["max-width"], + "maxH" => &["max-height"], + "mx" => &["margin-left", "margin-right"], + "my" => &["margin-top", "margin-bottom"], + "px" => &["padding-left", "padding-right"], + "py" => &["padding-top", "padding-bottom"], + "boxSize" => &["width", "height"], + "borderBottomRadius" => &["border-bottom-left-radius", "border-bottom-right-radius"], + "borderTopRadius" => &["border-top-left-radius", "border-top-right-radius"], + "borderLeftRadius" => &["border-top-left-radius", "border-bottom-left-radius"], + "borderRightRadius" => &["border-top-right-radius", "border-bottom-right-radius"], +}; + +pub(super) static DOUBLE_SEPARATOR: phf::Set<&str> = phf_set! { + "placeholder", + "before", + "after", + "highlight", + "view-transition", + "view-transition-group", + "view-transition-image-pair", + "view-transition-new", + "view-transition-old", +}; + +pub(super) static F_SPACE_RE: Lazy = Lazy::new(|| Regex::new(r"\s*,\s*").unwrap()); +pub(super) static COLOR_HASH: Lazy = Lazy::new(|| Regex::new(r"#([0-9a-zA-Z]+)").unwrap()); +pub(super) static ZERO_RE: Lazy = + Lazy::new(|| Regex::new(r"(^|\s|\(|,)-?0(px|em|rem|vh|vw|%|dvh|dvw)").unwrap()); diff --git a/libs/css/src/debug.rs b/libs/css/src/debug.rs new file mode 100644 index 00000000..78380abb --- /dev/null +++ b/libs/css/src/debug.rs @@ -0,0 +1,27 @@ +use std::sync::Mutex; + +static DEBUG: Mutex = Mutex::new(false); + +pub fn set_debug(value: bool) { + let mut debug = DEBUG.lock().unwrap(); + *debug = value; +} + +pub fn is_debug() -> bool { + *DEBUG.lock().unwrap() +} + +#[cfg(test)] +mod tests { + use super::*; + use serial_test::serial; + + #[test] + #[serial] + fn test_set_debug() { + set_debug(true); + assert!(is_debug()); + set_debug(false); + assert!(!is_debug()); + } +} diff --git a/libs/css/src/lib.rs b/libs/css/src/lib.rs index fb76049d..28d692aa 100644 --- a/libs/css/src/lib.rs +++ b/libs/css/src/lib.rs @@ -1,334 +1,33 @@ -use once_cell::sync::Lazy; -use phf::{phf_map, phf_set}; -use regex::Regex; -use serde::{Deserialize, Serialize}; -use std::cmp::Ordering; -use std::collections::HashMap; -use std::fmt::{Display, Formatter}; -use std::hash::{DefaultHasher, Hash, Hasher}; -use std::sync::Mutex; - -static SELECTOR_ORDER_MAP: Lazy> = Lazy::new(|| { - let mut map = HashMap::new(); - for (idx, selector) in [ - "hover", - "focus-visible", - "focus", - "active", - "selected", - "disabled", - ] - .into_iter() - .enumerate() - { - map.insert(format!("&:{selector}"), idx as u8); - } - map -}); - -static DEBUG: Mutex = Mutex::new(false); - -pub fn set_debug(value: bool) { - let mut debug = DEBUG.lock().unwrap(); - *debug = value; -} - -pub fn is_debug() -> bool { - *DEBUG.lock().unwrap() -} - -#[derive(Debug, PartialEq, Clone, Hash, Eq, Serialize, Deserialize)] -pub enum StyleSelector { - Media(String), - Selector(String), -} - -impl PartialOrd for StyleSelector { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -fn get_selector_order(selector: &str) -> u8 { - // & count - let t = if selector.chars().filter(|c| c == &'&').count() == 1 { - selector - .split('&') - .next_back() - .map(|a| format!("&{a}")) - .unwrap_or(selector.to_string()) - } else { - selector.to_string() - }; - - *SELECTOR_ORDER_MAP - .get(&t) - .unwrap_or(if t.starts_with("&") { &0 } else { &99 }) -} +pub mod class_map; +mod constant; +pub mod debug; +pub mod optimize_value; +pub mod property_type; +mod selector_separator; +pub mod style_selector; +pub mod utils; -impl Ord for StyleSelector { - fn cmp(&self, other: &Self) -> Ordering { - match (self, other) { - (StyleSelector::Media(a), StyleSelector::Media(b)) => a.cmp(b), - (StyleSelector::Selector(a), StyleSelector::Selector(b)) => { - get_selector_order(a).cmp(&get_selector_order(b)) - } - (StyleSelector::Media(_), StyleSelector::Selector(_)) => Ordering::Greater, - (StyleSelector::Selector(_), StyleSelector::Media(_)) => Ordering::Less, - } - } -} - -impl From<&str> for StyleSelector { - fn from(value: &str) -> Self { - if value.contains("&") { - StyleSelector::Selector(value.to_string()) - } else if let Some(s) = value.strip_prefix("group") { - let post = to_kebab_case(s); - StyleSelector::Selector(format!( - "{}{}{} &", - "*[role=group]", - get_selector_separator(&post), - post - )) - } else if let Some(s) = value.strip_prefix("theme") { - // first character should lower case - StyleSelector::Selector(format!( - ":root[data-theme={}{}] &", - s.chars().next().unwrap().to_ascii_lowercase(), - &s[1..] - )) - } else if value == "print" { - StyleSelector::Media("print".to_string()) - } else if value.ends_with(" ") { - StyleSelector::Selector(format!("{} &", value.trim())) - } else { - let post = to_kebab_case(value); - - StyleSelector::Selector(format!("&{}{}", get_selector_separator(&post), post)) - } - } -} - -impl From<[&str; 2]> for StyleSelector { - fn from(value: [&str; 2]) -> Self { - let post = to_kebab_case(value[1]); - StyleSelector::Selector(format!( - "{}{}{}", - StyleSelector::from(value[0]), - get_selector_separator(&post), - post - )) - } -} +use std::hash::{DefaultHasher, Hash, Hasher}; -impl Display for StyleSelector { - fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { - write!( - f, - "{}", - match self { - StyleSelector::Selector(value) => value.to_string(), - StyleSelector::Media(value) => format!("@{value}"), - } - ) - } -} +use crate::class_map::GLOBAL_CLASS_MAP; +use crate::constant::{COLOR_HASH, F_SPACE_RE, GLOBAL_STYLE_PROPERTY, ZERO_RE}; +use crate::debug::is_debug; +use crate::optimize_value::optimize_value; +use crate::style_selector::StyleSelector; +use crate::utils::{to_camel_case, to_kebab_case}; pub fn merge_selector(class_name: &str, selector: Option<&StyleSelector>) -> String { if let Some(selector) = selector { match selector { StyleSelector::Selector(value) => value.replace("&", &format!(".{class_name}")), StyleSelector::Media(_) => format!(".{class_name}"), + StyleSelector::Global(v, _) => v.to_string(), } } else { format!(".{class_name}") } } -pub enum SelectorSeparator { - Single, - Double, - None, -} -impl Display for SelectorSeparator { - fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { - write!( - f, - "{}", - match self { - SelectorSeparator::Single => ":", - SelectorSeparator::Double => "::", - SelectorSeparator::None => "", - } - ) - } -} - -static DOUBLE_SEPARATOR: phf::Set<&str> = phf_set! { - "placeholder", - "before", - "after", - "highlight", - "view-transition", - "view-transition-group", - "view-transition-image-pair", - "view-transition-new", - "view-transition-old", -}; - -pub fn get_selector_separator(key: &str) -> SelectorSeparator { - if key.starts_with(":") || key.is_empty() || key.starts_with("[") { - SelectorSeparator::None - } else if DOUBLE_SEPARATOR.contains(key) { - SelectorSeparator::Double - } else { - SelectorSeparator::Single - } -} - -#[derive(Clone, Debug, PartialEq)] -pub enum PropertyType { - Single(String), - Multi(Vec), -} - -impl From<&str> for PropertyType { - fn from(value: &str) -> Self { - value.to_string().into() - } -} - -impl From for PropertyType { - fn from(value: String) -> Self { - PropertyType::Single(value) - } -} - -impl From<[&str; 2]> for PropertyType { - fn from(value: [&str; 2]) -> Self { - PropertyType::Multi(value.iter().map(|v| v.to_string()).collect()) - } -} - -static GLOBAL_STYLE_PROPERTY: phf::Map<&str, &[&str]> = phf_map! { - "bg" => &["background"], - "bgAttachment" => &["background-attachment"], - "bgClip" => &["background-clip"], - "bgColor" => &["background-color"], - "bgImage" => &["background-image"], - "bgOrigin" => &["background-origin"], - "bgPosition" => &["background-position"], - "bgPositionX" => &["background-position-x"], - "bgPositionY" => &["background-position-y"], - "bgRepeat" => &["background-repeat"], - "bgSize" => &["background-size"], - "animationDir" => &["animation-direction"], - "flexDir" => &["flex-direction"], - "pos" => &["position"], - "m" => &["margin"], - "mt" => &["margin-top"], - "mr" => &["margin-right"], - "mb" => &["margin-bottom"], - "ml" => &["margin-left"], - "p" => &["padding"], - "pt" => &["padding-top"], - "pr" => &["padding-right"], - "pb" => &["padding-bottom"], - "pl" => &["padding-left"], - "w" => &["width"], - "h" => &["height"], - "minW" => &["min-width"], - "minH" => &["min-height"], - "maxW" => &["max-width"], - "maxH" => &["max-height"], - "mx" => &["margin-left", "margin-right"], - "my" => &["margin-top", "margin-bottom"], - "px" => &["padding-left", "padding-right"], - "py" => &["padding-top", "padding-bottom"], - "boxSize" => &["width", "height"], - "borderBottomRadius" => &["border-bottom-left-radius", "border-bottom-right-radius"], - "borderTopRadius" => &["border-top-left-radius", "border-top-right-radius"], - "borderLeftRadius" => &["border-top-left-radius", "border-bottom-left-radius"], - "borderRightRadius" => &["border-top-right-radius", "border-bottom-right-radius"], -}; - -static GLOBAL_CLASS_MAP: Lazy>> = - Lazy::new(|| Mutex::new(HashMap::new())); - -/// for test -pub fn reset_class_map() { - let mut map = GLOBAL_CLASS_MAP.lock().unwrap(); - map.clear(); -} - -pub fn set_class_map(map: HashMap) { - let mut global_map = GLOBAL_CLASS_MAP.lock().unwrap(); - *global_map = map; -} - -pub fn get_class_map() -> HashMap { - GLOBAL_CLASS_MAP.lock().unwrap().clone() -} - -pub fn to_kebab_case(value: &str) -> String { - value - .chars() - .enumerate() - .map(|(i, c)| { - if c.is_uppercase() { - if i == 0 { - c.to_ascii_lowercase().to_string() - } else { - format!("-{}", c.to_ascii_lowercase()) - } - } else { - c.to_string() - } - }) - .collect() -} - -pub fn to_camel_case(value: &str) -> String { - value - .split('-') - .enumerate() - .map(|(i, s)| { - if i == 0 { - s.to_string() - } else { - format!("{}{}", s[0..1].to_uppercase(), &s[1..]) - } - }) - .collect() -} - -pub fn convert_property(property: &str) -> PropertyType { - GLOBAL_STYLE_PROPERTY - .get(property) - .cloned() - .map(|v| match v.len() { - 1 => PropertyType::Single(v[0].to_string()), - _ => PropertyType::Multi(v.iter().map(|v| v.to_string()).collect()), - }) - .unwrap_or_else(|| { - if (property.starts_with("Webkit") - && property.len() > 6 - && property.chars().nth(6).unwrap().is_uppercase()) - || (property.starts_with("Moz") - && property.len() > 3 - && property.chars().nth(3).unwrap().is_uppercase()) - || (property.starts_with("ms") - && property.len() > 2 - && property.chars().nth(2).unwrap().is_uppercase()) - { - PropertyType::Single(format!("-{}", to_kebab_case(property))) - } else { - to_kebab_case(property).into() - } - }) -} - pub fn short_to_long(property: &str) -> String { GLOBAL_STYLE_PROPERTY .get(property) @@ -339,61 +38,6 @@ pub fn short_to_long(property: &str) -> String { .unwrap_or_else(|| property.to_string()) } -static F_SPACE_RE: Lazy = Lazy::new(|| Regex::new(r"\s*,\s*").unwrap()); -static COLOR_HASH: Lazy = Lazy::new(|| Regex::new(r"#([0-9a-zA-Z]+)").unwrap()); -static ZERO_RE: Lazy = - Lazy::new(|| Regex::new(r"(^|\s|\(|,)-?0(px|em|rem|vh|vw|%|dvh|dvw)").unwrap()); - -fn optimize_color(value: &str) -> String { - let mut ret = value.to_string().to_uppercase(); - - if ret.len() == 6 { - let ch = ret.chars().collect::>(); - if ch[0] == ch[1] && ch[2] == ch[3] && ch[4] == ch[5] { - ret = format!("{}{}{}", ch[0], ch[2], ch[4]); - } - } else if ret.len() == 8 { - let ch = ret.chars().collect::>(); - if ch[0] == ch[1] && ch[2] == ch[3] && ch[4] == ch[5] && ch[6] == ch[7] { - ret = format!("{}{}{}{}", ch[0], ch[2], ch[4], ch[6]); - } - } - - format!("#{ret}") -} - -pub fn optimize_value(value: &str) -> String { - let mut ret = value.trim().to_string(); - if ret.contains(",") { - ret = F_SPACE_RE.replace_all(&ret, ",").trim().to_string(); - } - if ret.contains("#") { - ret = COLOR_HASH - .replace_all(&ret, |c: ®ex::Captures| optimize_color(&c[1])) - .to_string(); - } - if ret.contains("0") { - ret = ZERO_RE.replace_all(&ret, "${1}0").to_string(); - } - // remove ; from dynamic value - for str_symbol in ["", "`", "\"", "'"] { - if ret.ends_with(&format!(";{str_symbol}")) { - ret = format!( - "{}{}", - ret[..ret.len() - str_symbol.len() - 1].trim_end_matches(';'), - str_symbol - ); - } else if ret.ends_with(&format!(";{str_symbol})")) { - ret = format!( - "{}{})", - ret[..ret.len() - str_symbol.len() - 2].trim_end_matches(';'), - str_symbol - ); - } - } - ret -} - pub fn sheet_to_classname( property: &str, level: u8, @@ -401,7 +45,7 @@ pub fn sheet_to_classname( selector: Option<&str>, style_order: Option, ) -> String { - if *DEBUG.lock().unwrap() { + if is_debug() { let selector = selector.unwrap_or("").trim(); format!( "{}-{}-{}-{}-{}", @@ -435,23 +79,8 @@ pub fn sheet_to_classname( } } -pub fn css_to_classname(css: &str) -> String { - if *DEBUG.lock().unwrap() { - let mut hasher = DefaultHasher::new(); - css.hash(&mut hasher); - format!("css-{}", hasher.finish()) - } else { - let mut map = GLOBAL_CLASS_MAP.lock().unwrap(); - map.get(css).map(|v| format!("d{v}")).unwrap_or_else(|| { - let len = map.len(); - map.insert(css.to_string(), len as i32); - format!("d{}", map.len() - 1) - }) - } -} - pub fn sheet_to_variable_name(property: &str, level: u8, selector: Option<&str>) -> String { - if *DEBUG.lock().unwrap() { + if is_debug() { let selector = selector.unwrap_or("").trim(); format!( "--{}-{}-{}", @@ -468,30 +97,26 @@ pub fn sheet_to_variable_name(property: &str, level: u8, selector: Option<&str>) } else { let key = format!("{}-{}-{}", property, level, selector.unwrap_or("").trim()); let mut map = GLOBAL_CLASS_MAP.lock().unwrap(); - map.get(&key) - .map(|v| format!("--d{v}")) - .unwrap_or_else(|| { - let len = map.len(); - map.insert(key, len as i32); - format!("--d{}", map.len() - 1) - }) + map.get(&key).map(|v| format!("--d{v}")).unwrap_or_else(|| { + let len = map.len(); + map.insert(key, len as i32); + format!("--d{}", map.len() - 1) + }) } } #[cfg(test)] mod tests { + use std::collections::HashMap; + + use crate::{ + class_map::{get_class_map, reset_class_map, set_class_map}, + debug::set_debug, + }; + use super::*; use serial_test::serial; - #[test] - #[serial] - fn test_set_debug() { - set_debug(true); - assert!(is_debug()); - set_debug(false); - assert!(!is_debug()); - } - #[test] #[serial] fn test_sheet_to_variable_name() { @@ -531,49 +156,6 @@ mod tests { ); } - #[test] - #[serial] - fn test_optimize_value() { - assert_eq!(optimize_value("0px"), "0"); - assert_eq!(optimize_value("0em"), "0"); - assert_eq!(optimize_value("0rem"), "0"); - assert_eq!(optimize_value("0vh"), "0"); - assert_eq!(optimize_value("0vw"), "0"); - assert_eq!(optimize_value("0%"), "0"); - assert_eq!(optimize_value("0dvh"), "0"); - assert_eq!(optimize_value("0dvw"), "0"); - assert_eq!(optimize_value("0px 0px"), "0 0"); - assert_eq!(optimize_value("0em 0em"), "0 0"); - assert_eq!(optimize_value("0rem 0rem"), "0 0"); - assert_eq!(optimize_value("0vh 0vh"), "0 0"); - assert_eq!(optimize_value("0vw 0vw"), "0 0"); - assert_eq!(optimize_value("-0vw -0vw"), "0 0"); - assert_eq!(optimize_value("scale(0px)"), "scale(0)"); - assert_eq!(optimize_value("scale(-0px)"), "scale(0)"); - assert_eq!(optimize_value("scale(-0px);"), "scale(0)"); - assert_eq!(optimize_value("red;"), "red"); - assert_eq!(optimize_value("translate(0px)"), "translate(0)"); - assert_eq!(optimize_value("translate(-0px,0px)"), "translate(0,0)"); - assert_eq!(optimize_value("translate(-0px, 0px)"), "translate(0,0)"); - assert_eq!(optimize_value("translate(0px, 0px)"), "translate(0,0)"); - assert_eq!(optimize_value("translate(0px, 0px)"), "translate(0,0)"); - assert_eq!(optimize_value("translate(10px, 0px)"), "translate(10px,0)"); - assert_eq!(optimize_value("\"red\""), "\"red\""); - assert_eq!(optimize_value("'red'"), "'red'"); - assert_eq!(optimize_value("`red`"), "`red`"); - assert_eq!(optimize_value("\"red;\""), "\"red\""); - assert_eq!(optimize_value("'red;'"), "'red'"); - assert_eq!(optimize_value("`red;`"), "`red`"); - assert_eq!(optimize_value("(\"red;\")"), "(\"red\")"); - assert_eq!(optimize_value("(`red;`)"), "(`red`)"); - assert_eq!(optimize_value("('red;')"), "('red')"); - assert_eq!(optimize_value("('red') + 'blue;'"), "('red') + 'blue'"); - assert_eq!( - optimize_value("translateX(0px) translateY(0px)"), - "translateX(0) translateY(0)" - ); - } - #[test] #[serial] fn test_sheet_to_classname() { @@ -793,303 +375,6 @@ mod tests { ); } - #[test] - #[serial] - fn test_css_to_classname() { - set_debug(false); - reset_class_map(); - assert_eq!(css_to_classname("background: red"), "d0"); - assert_eq!(css_to_classname("background: blue"), "d1"); - } - #[test] - #[serial] - fn test_debug_css_to_classname() { - set_debug(true); - assert_eq!( - css_to_classname("background: red"), - "css-10773204219957113694" - ); - assert_eq!( - css_to_classname("background: blue"), - "css-1226995032436176700" - ); - set_debug(true); - reset_class_map(); - assert_eq!( - css_to_classname("background: red"), - "css-10773204219957113694" - ); - } - - #[test] - fn test_convert_property() { - assert_eq!( - convert_property("bg"), - PropertyType::Single("background".to_string()) - ); - assert_eq!( - convert_property("bgColor"), - PropertyType::Single("background-color".to_string()) - ); - assert_eq!( - convert_property("color"), - PropertyType::Single("color".to_string()) - ); - assert_eq!( - convert_property("m"), - PropertyType::Single("margin".to_string()) - ); - assert_eq!( - convert_property("mt"), - PropertyType::Single("margin-top".to_string()) - ); - assert_eq!( - convert_property("mr"), - PropertyType::Single("margin-right".to_string()) - ); - assert_eq!( - convert_property("mb"), - PropertyType::Single("margin-bottom".to_string()) - ); - assert_eq!( - convert_property("ml"), - PropertyType::Single("margin-left".to_string()) - ); - assert_eq!( - convert_property("p"), - PropertyType::Single("padding".to_string()) - ); - assert_eq!( - convert_property("pt"), - PropertyType::Single("padding-top".to_string()) - ); - assert_eq!( - convert_property("pr"), - PropertyType::Single("padding-right".to_string()) - ); - assert_eq!( - convert_property("pb"), - PropertyType::Single("padding-bottom".to_string()) - ); - assert_eq!( - convert_property("pl"), - PropertyType::Single("padding-left".to_string()) - ); - assert_eq!( - convert_property("w"), - PropertyType::Single("width".to_string()) - ); - assert_eq!( - convert_property("h"), - PropertyType::Single("height".to_string()) - ); - assert_eq!( - convert_property("minW"), - PropertyType::Single("min-width".to_string()) - ); - assert_eq!( - convert_property("minH"), - PropertyType::Single("min-height".to_string()) - ); - assert_eq!( - convert_property("maxW"), - PropertyType::Single("max-width".to_string()) - ); - assert_eq!( - convert_property("maxH"), - PropertyType::Single("max-height".to_string()) - ); - assert_eq!( - convert_property("mx"), - PropertyType::Multi(vec!["margin-left".to_string(), "margin-right".to_string()]) - ); - assert_eq!( - convert_property("my"), - PropertyType::Multi(vec!["margin-top".to_string(), "margin-bottom".to_string()]) - ); - assert_eq!( - convert_property("px"), - PropertyType::Multi(vec![ - "padding-left".to_string(), - "padding-right".to_string() - ]) - ); - assert_eq!( - convert_property("py"), - PropertyType::Multi(vec![ - "padding-top".to_string(), - "padding-bottom".to_string() - ]) - ); - } - - #[test] - fn test_convert_vendor_property() { - assert_eq!( - convert_property("MozUserSelect"), - PropertyType::Single("-moz-user-select".to_string()) - ); - assert_eq!( - convert_property("msAccelerator"), - PropertyType::Single("-ms-accelerator".to_string()) - ); - assert_eq!( - convert_property("WebkitAlignContent"), - PropertyType::Single("-webkit-align-content".to_string()) - ); - } - - #[test] - fn test_property_type_from() { - assert_eq!( - PropertyType::from("background"), - PropertyType::Single("background".to_string()) - ); - assert_eq!( - PropertyType::from("background-color"), - PropertyType::Single("background-color".to_string()) - ); - assert_eq!( - PropertyType::from("color"), - PropertyType::Single("color".to_string()) - ); - assert_eq!( - PropertyType::from("margin"), - PropertyType::Single("margin".to_string()) - ); - assert_eq!( - PropertyType::from("margin-top"), - PropertyType::Single("margin-top".to_string()) - ); - assert_eq!( - PropertyType::from("margin-right"), - PropertyType::Single("margin-right".to_string()) - ); - assert_eq!( - PropertyType::from("margin-bottom"), - PropertyType::Single("margin-bottom".to_string()) - ); - assert_eq!( - PropertyType::from("margin-left"), - PropertyType::Single("margin-left".to_string()) - ); - assert_eq!( - PropertyType::from("padding"), - PropertyType::Single("padding".to_string()) - ); - assert_eq!( - PropertyType::from("padding-top"), - PropertyType::Single("padding-top".to_string()) - ); - assert_eq!( - PropertyType::from("padding-right"), - PropertyType::Single("padding-right".to_string()) - ); - assert_eq!( - PropertyType::from("padding-bottom"), - PropertyType::Single("padding-bottom".to_string()) - ); - assert_eq!( - PropertyType::from("padding-left"), - PropertyType::Single("padding-left".to_string()) - ); - assert_eq!( - PropertyType::from("width"), - PropertyType::Single("width".to_string()) - ); - assert_eq!( - PropertyType::from("height"), - PropertyType::Single("height".to_string()) - ); - assert_eq!( - PropertyType::from("min-width"), - PropertyType::Single("min-width".to_string()) - ); - assert_eq!( - PropertyType::from("min-height"), - PropertyType::Single("min-height".to_string()) - ); - assert_eq!( - PropertyType::from("max-width"), - PropertyType::Single("max-width".to_string()) - ); - assert_eq!( - PropertyType::from("max-height"), - PropertyType::Single("max-height".to_string()) - ); - assert_eq!( - PropertyType::from(["margin-left", "margin-right"]), - PropertyType::Multi(vec!["margin-left".to_string(), "margin-right".to_string()]) - ); - assert_eq!( - PropertyType::from(["margin-top", "margin-bottom"]), - PropertyType::Multi(vec!["margin-top".to_string(), "margin-bottom".to_string()]) - ); - } - - #[test] - fn test_kebab_case() { - assert_eq!(to_kebab_case("backgroundColor"), "background-color"); - assert_eq!(to_kebab_case("color"), "color"); - assert_eq!(to_kebab_case("margin"), "margin"); - assert_eq!(to_kebab_case("marginTop"), "margin-top"); - assert_eq!(to_kebab_case("marginRight"), "margin-right"); - assert_eq!(to_kebab_case("marginBottom"), "margin-bottom"); - assert_eq!(to_kebab_case("marginLeft"), "margin-left"); - assert_eq!(to_kebab_case("padding"), "padding"); - assert_eq!(to_kebab_case("paddingTop"), "padding-top"); - assert_eq!(to_kebab_case("paddingRight"), "padding-right"); - assert_eq!(to_kebab_case("paddingBottom"), "padding-bottom"); - assert_eq!(to_kebab_case("paddingLeft"), "padding-left"); - assert_eq!(to_kebab_case("width"), "width"); - assert_eq!(to_kebab_case("height"), "height"); - assert_eq!(to_kebab_case("minWidth"), "min-width"); - assert_eq!(to_kebab_case("minHeight"), "min-height"); - assert_eq!(to_kebab_case("maxWidth"), "max-width"); - assert_eq!(to_kebab_case("maxHeight"), "max-height"); - assert_eq!(to_kebab_case("MaxHeight"), "max-height"); - assert_eq!(to_kebab_case("Hover"), "hover"); - } - - #[test] - fn test_style_selector() { - assert_eq!( - StyleSelector::from("hover"), - StyleSelector::Selector("&:hover".to_string()) - ); - assert_eq!( - StyleSelector::from("focusVisible"), - StyleSelector::Selector("&:focus-visible".to_string()) - ); - assert_eq!( - StyleSelector::from("groupHover"), - StyleSelector::Selector("*[role=group]:hover &".to_string()) - ); - assert_eq!( - StyleSelector::from("groupFocusVisible"), - StyleSelector::Selector("*[role=group]:focus-visible &".to_string()) - ); - assert_eq!( - StyleSelector::from("group1"), - StyleSelector::Selector("*[role=group]:1 &".to_string()) - ); - - assert_eq!( - StyleSelector::from(["themeDark", "placeholder"]), - StyleSelector::Selector(":root[data-theme=dark] &::placeholder".to_string()) - ); - - assert_eq!( - StyleSelector::from("themeLight"), - StyleSelector::Selector(":root[data-theme=light] &".to_string()) - ); - - assert_eq!( - StyleSelector::from("*[aria=disabled='true'] &:hover"), - StyleSelector::Selector("*[aria=disabled='true'] &:hover".to_string()) - ); - } - #[test] fn test_merge_selector() { assert_eq!(merge_selector("cls", Some(&"hover".into())), ".cls:hover"); @@ -1125,41 +410,6 @@ mod tests { ":root[data-theme=dark] .cls:hover" ); } - #[test] - fn test_get_selector_separator() { - assert!(matches!( - get_selector_separator("placeholder"), - SelectorSeparator::Double - )); - assert!(matches!( - get_selector_separator("before"), - SelectorSeparator::Double - )); - assert!(matches!( - get_selector_separator("after"), - SelectorSeparator::Double - )); - - assert!(matches!( - get_selector_separator("hover"), - SelectorSeparator::Single - )); - - assert!(matches!( - get_selector_separator(":hover"), - SelectorSeparator::None - )); - - assert!(matches!( - get_selector_separator("::placeholder"), - SelectorSeparator::None - )); - - assert!(matches!( - get_selector_separator("[aria-disabled='true']"), - SelectorSeparator::None - )); - } #[test] #[serial] diff --git a/libs/css/src/optimize_value.rs b/libs/css/src/optimize_value.rs new file mode 100644 index 00000000..57358b78 --- /dev/null +++ b/libs/css/src/optimize_value.rs @@ -0,0 +1,109 @@ +use crate::{COLOR_HASH, F_SPACE_RE, ZERO_RE}; + +pub fn optimize_value(value: &str) -> String { + let mut ret = value.trim().to_string(); + if ret.contains(",") { + ret = F_SPACE_RE.replace_all(&ret, ",").trim().to_string(); + } + if ret.contains("#") { + ret = COLOR_HASH + .replace_all(&ret, |c: ®ex::Captures| optimize_color(&c[1])) + .to_string(); + } + if ret.contains("0") { + ret = ZERO_RE.replace_all(&ret, "${1}0").to_string(); + } + // remove ; from dynamic value + for str_symbol in ["", "`", "\"", "'"] { + if ret.ends_with(&format!(";{str_symbol}")) { + ret = format!( + "{}{}", + ret[..ret.len() - str_symbol.len() - 1].trim_end_matches(';'), + str_symbol + ); + } else if ret.ends_with(&format!(";{str_symbol})")) { + ret = format!( + "{}{})", + ret[..ret.len() - str_symbol.len() - 2].trim_end_matches(';'), + str_symbol + ); + } + } + ret +} + +fn optimize_color(value: &str) -> String { + let mut ret = value.to_string().to_uppercase(); + + if ret.len() == 6 { + let ch = ret.chars().collect::>(); + if ch[0] == ch[1] && ch[2] == ch[3] && ch[4] == ch[5] { + ret = format!("{}{}{}", ch[0], ch[2], ch[4]); + } + } else if ret.len() == 8 { + let ch = ret.chars().collect::>(); + if ch[0] == ch[1] && ch[2] == ch[3] && ch[4] == ch[5] && ch[6] == ch[7] { + ret = format!("{}{}{}{}", ch[0], ch[2], ch[4], ch[6]); + } + } + + format!("#{ret}") +} + +#[cfg(test)] +mod tests { + use super::*; + use rstest::rstest; + + #[rstest] + #[case("0px", "0")] + #[case("0em", "0")] + #[case("0rem", "0")] + #[case("0vh", "0")] + #[case("0vw", "0")] + #[case("0%", "0")] + #[case("0dvh", "0")] + #[case("0dvw", "0")] + #[case("0px 0px", "0 0")] + #[case("0em 0em", "0 0")] + #[case("0rem 0rem", "0 0")] + #[case("0vh 0vh", "0 0")] + #[case("0vw 0vw", "0 0")] + #[case("-0vw -0vw", "0 0")] + #[case("scale(0px)", "scale(0)")] + #[case("scale(-0px)", "scale(0)")] + #[case("scale(-0px);", "scale(0)")] + #[case("red;", "red")] + #[case("translate(0px)", "translate(0)")] + #[case("translate(-0px,0px)", "translate(0,0)")] + #[case("translate(-0px, 0px)", "translate(0,0)")] + #[case("translate(0px, 0px)", "translate(0,0)")] + #[case("translate(10px, 0px)", "translate(10px,0)")] + #[case("\"red\"", "\"red\"")] + #[case("'red'", "'red'")] + #[case("`red`", "`red`")] + #[case("\"red;\"", "\"red\"")] + #[case("'red;'", "'red'")] + #[case("`red;`", "`red`")] + #[case("(\"red;\")", "(\"red\")")] + #[case("(`red;`)", "(`red`)")] + #[case("('red;')", "('red')")] + #[case("('red') + 'blue;'", "('red') + 'blue'")] + #[case("translateX(0px) translateY(0px)", "translateX(0) translateY(0)")] + fn test_optimize_value(#[case] input: &str, #[case] expected: &str) { + assert_eq!(optimize_value(input), expected); + } + + #[rstest] + #[case("#ff0000", "#F00")] + #[case("#123456", "#123456")] + #[case("#ff0000ff", "#F00F")] + #[case("#f00", "#F00")] + #[case("#f00f", "#F00F")] + #[case("red", "red")] + #[case("blue", "blue")] + #[case("transparent", "transparent")] + fn test_optimize_color(#[case] input: &str, #[case] expected: &str) { + assert_eq!(optimize_value(input), expected); + } +} diff --git a/libs/css/src/property_type.rs b/libs/css/src/property_type.rs new file mode 100644 index 00000000..5d1827dc --- /dev/null +++ b/libs/css/src/property_type.rs @@ -0,0 +1,254 @@ +use crate::{constant::GLOBAL_STYLE_PROPERTY, utils::to_kebab_case}; + +#[derive(Clone, Debug, PartialEq)] +pub enum PropertyType { + Single(String), + Multi(Vec), +} + +impl From<&str> for PropertyType { + fn from(value: &str) -> Self { + GLOBAL_STYLE_PROPERTY + .get(value) + .cloned() + .map(|v| match v.len() { + 1 => PropertyType::Single(v[0].to_string()), + _ => PropertyType::Multi(v.iter().map(|v| v.to_string()).collect()), + }) + .unwrap_or_else(|| { + if (value.starts_with("Webkit") + && value.len() > 6 + && value.chars().nth(6).unwrap().is_uppercase()) + || (value.starts_with("Moz") + && value.len() > 3 + && value.chars().nth(3).unwrap().is_uppercase()) + || (value.starts_with("ms") + && value.len() > 2 + && value.chars().nth(2).unwrap().is_uppercase()) + { + PropertyType::Single(format!("-{}", to_kebab_case(value))) + } else { + PropertyType::Single(to_kebab_case(value)) + } + }) + } +} + +impl From<[&str; 2]> for PropertyType { + fn from(value: [&str; 2]) -> Self { + PropertyType::Multi(value.iter().map(|v| v.to_string()).collect()) + } +} + +#[cfg(test)] + +mod tests { + + use super::*; + + #[test] + fn test_convert_property() { + assert_eq!( + PropertyType::from("bg"), + PropertyType::Single("background".to_string()) + ); + assert_eq!( + PropertyType::from("bgColor"), + PropertyType::Single("background-color".to_string()) + ); + assert_eq!( + PropertyType::from("color"), + PropertyType::Single("color".to_string()) + ); + assert_eq!( + PropertyType::from("m"), + PropertyType::Single("margin".to_string()) + ); + assert_eq!( + PropertyType::from("mt"), + PropertyType::Single("margin-top".to_string()) + ); + assert_eq!( + PropertyType::from("mr"), + PropertyType::Single("margin-right".to_string()) + ); + assert_eq!( + PropertyType::from("mb"), + PropertyType::Single("margin-bottom".to_string()) + ); + assert_eq!( + PropertyType::from("ml"), + PropertyType::Single("margin-left".to_string()) + ); + assert_eq!( + PropertyType::from("p"), + PropertyType::Single("padding".to_string()) + ); + assert_eq!( + PropertyType::from("pt"), + PropertyType::Single("padding-top".to_string()) + ); + assert_eq!( + PropertyType::from("pr"), + PropertyType::Single("padding-right".to_string()) + ); + assert_eq!( + PropertyType::from("pb"), + PropertyType::Single("padding-bottom".to_string()) + ); + assert_eq!( + PropertyType::from("pl"), + PropertyType::Single("padding-left".to_string()) + ); + assert_eq!( + PropertyType::from("w"), + PropertyType::Single("width".to_string()) + ); + assert_eq!( + PropertyType::from("h"), + PropertyType::Single("height".to_string()) + ); + assert_eq!( + PropertyType::from("minW"), + PropertyType::Single("min-width".to_string()) + ); + assert_eq!( + PropertyType::from("minH"), + PropertyType::Single("min-height".to_string()) + ); + assert_eq!( + PropertyType::from("maxW"), + PropertyType::Single("max-width".to_string()) + ); + assert_eq!( + PropertyType::from("maxH"), + PropertyType::Single("max-height".to_string()) + ); + assert_eq!( + PropertyType::from("mx"), + PropertyType::Multi(vec!["margin-left".to_string(), "margin-right".to_string()]) + ); + assert_eq!( + PropertyType::from("my"), + PropertyType::Multi(vec!["margin-top".to_string(), "margin-bottom".to_string()]) + ); + assert_eq!( + PropertyType::from("px"), + PropertyType::Multi(vec![ + "padding-left".to_string(), + "padding-right".to_string() + ]) + ); + assert_eq!( + PropertyType::from("py"), + PropertyType::Multi(vec![ + "padding-top".to_string(), + "padding-bottom".to_string() + ]) + ); + } + + #[test] + fn test_convert_vendor_property() { + assert_eq!( + PropertyType::from("MozUserSelect"), + PropertyType::Single("-moz-user-select".to_string()) + ); + assert_eq!( + PropertyType::from("msAccelerator"), + PropertyType::Single("-ms-accelerator".to_string()) + ); + assert_eq!( + PropertyType::from("WebkitAlignContent"), + PropertyType::Single("-webkit-align-content".to_string()) + ); + } + + #[test] + fn test_property_type_from() { + assert_eq!( + PropertyType::from("background"), + PropertyType::Single("background".to_string()) + ); + assert_eq!( + PropertyType::from("background-color"), + PropertyType::Single("background-color".to_string()) + ); + assert_eq!( + PropertyType::from("color"), + PropertyType::Single("color".to_string()) + ); + assert_eq!( + PropertyType::from("margin"), + PropertyType::Single("margin".to_string()) + ); + assert_eq!( + PropertyType::from("margin-top"), + PropertyType::Single("margin-top".to_string()) + ); + assert_eq!( + PropertyType::from("margin-right"), + PropertyType::Single("margin-right".to_string()) + ); + assert_eq!( + PropertyType::from("margin-bottom"), + PropertyType::Single("margin-bottom".to_string()) + ); + assert_eq!( + PropertyType::from("margin-left"), + PropertyType::Single("margin-left".to_string()) + ); + assert_eq!( + PropertyType::from("padding"), + PropertyType::Single("padding".to_string()) + ); + assert_eq!( + PropertyType::from("padding-top"), + PropertyType::Single("padding-top".to_string()) + ); + assert_eq!( + PropertyType::from("padding-right"), + PropertyType::Single("padding-right".to_string()) + ); + assert_eq!( + PropertyType::from("padding-bottom"), + PropertyType::Single("padding-bottom".to_string()) + ); + assert_eq!( + PropertyType::from("padding-left"), + PropertyType::Single("padding-left".to_string()) + ); + assert_eq!( + PropertyType::from("width"), + PropertyType::Single("width".to_string()) + ); + assert_eq!( + PropertyType::from("height"), + PropertyType::Single("height".to_string()) + ); + assert_eq!( + PropertyType::from("min-width"), + PropertyType::Single("min-width".to_string()) + ); + assert_eq!( + PropertyType::from("min-height"), + PropertyType::Single("min-height".to_string()) + ); + assert_eq!( + PropertyType::from("max-width"), + PropertyType::Single("max-width".to_string()) + ); + assert_eq!( + PropertyType::from("max-height"), + PropertyType::Single("max-height".to_string()) + ); + assert_eq!( + PropertyType::from(["margin-left", "margin-right"]), + PropertyType::Multi(vec!["margin-left".to_string(), "margin-right".to_string()]) + ); + assert_eq!( + PropertyType::from(["margin-top", "margin-bottom"]), + PropertyType::Multi(vec!["margin-top".to_string(), "margin-bottom".to_string()]) + ); + } +} diff --git a/libs/css/src/selector_separator.rs b/libs/css/src/selector_separator.rs new file mode 100644 index 00000000..d64856ba --- /dev/null +++ b/libs/css/src/selector_separator.rs @@ -0,0 +1,63 @@ +use std::fmt::{Display, Formatter}; + +use crate::constant::DOUBLE_SEPARATOR; + +pub enum SelectorSeparator { + Single, + Double, + None, +} +impl Display for SelectorSeparator { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + write!( + f, + "{}", + match self { + SelectorSeparator::Single => ":", + SelectorSeparator::Double => "::", + SelectorSeparator::None => "", + } + ) + } +} +impl From<&str> for SelectorSeparator { + fn from(value: &str) -> Self { + if value.starts_with(":") || value.is_empty() || value.starts_with("[") { + SelectorSeparator::None + } else if DOUBLE_SEPARATOR.contains(value) { + SelectorSeparator::Double + } else { + SelectorSeparator::Single + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_from() { + assert!(matches!("placeholder".into(), SelectorSeparator::Double)); + assert!(matches!("before".into(), SelectorSeparator::Double)); + assert!(matches!("after".into(), SelectorSeparator::Double)); + + assert!(matches!("hover".into(), SelectorSeparator::Single)); + + assert!(matches!(":hover".into(), SelectorSeparator::None)); + + assert!(matches!("::placeholder".into(), SelectorSeparator::None)); + + assert!(matches!( + "[aria-disabled='true']".into(), + SelectorSeparator::None + )); + } + + #[test] + fn test_display() { + assert_eq!(SelectorSeparator::Double.to_string(), "::"); + assert_eq!(SelectorSeparator::Single.to_string(), ":"); + assert_eq!(SelectorSeparator::None.to_string(), ""); + } +} diff --git a/libs/css/src/style_selector.rs b/libs/css/src/style_selector.rs new file mode 100644 index 00000000..77190cc9 --- /dev/null +++ b/libs/css/src/style_selector.rs @@ -0,0 +1,296 @@ +use std::{ + cmp::Ordering, + fmt::{Display, Formatter}, +}; + +use serde::{Deserialize, Serialize}; + +use crate::{constant::SELECTOR_ORDER_MAP, selector_separator::SelectorSeparator, to_kebab_case}; + +#[derive(Debug, PartialEq, Clone, Hash, Eq, Serialize, Deserialize)] +pub enum StyleSelector { + Media(String), + Selector(String), + // selector, file + Global(String, String), +} + +impl PartialOrd for StyleSelector { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl Ord for StyleSelector { + fn cmp(&self, other: &Self) -> Ordering { + match (self, other) { + (StyleSelector::Media(a), StyleSelector::Media(b)) => a.cmp(b), + (StyleSelector::Selector(a), StyleSelector::Selector(b)) => { + get_selector_order(a).cmp(&get_selector_order(b)) + } + (StyleSelector::Media(_), StyleSelector::Selector(_)) => Ordering::Greater, + (StyleSelector::Selector(_), StyleSelector::Media(_)) => Ordering::Less, + (StyleSelector::Global(a, _), StyleSelector::Global(b, _)) => { + if a == b { + return Ordering::Equal; + } + match (a.contains(":"), b.contains(":")) { + (true, true) => { + let a_order = format!(":{}", a.split(":").nth(1).unwrap()); + let b_order = format!(":{}", b.split(":").nth(1).unwrap()); + let mut a_order_value = 0; + let mut b_order_value = 0; + for (order, order_value) in SELECTOR_ORDER_MAP.iter() { + if a_order.contains(order) { + a_order_value = *order_value; + } + if b_order.contains(order) { + b_order_value = *order_value; + } + } + if a_order_value == b_order_value { + a.cmp(b) + } else { + a_order_value.cmp(&b_order_value) + } + } + (true, false) => Ordering::Greater, + (false, true) => Ordering::Less, + (false, false) => a.cmp(b), + } + } + (StyleSelector::Global(_, _), _) => Ordering::Less, + (_, StyleSelector::Global(_, _)) => Ordering::Greater, + } + } +} + +impl From<&str> for StyleSelector { + fn from(value: &str) -> Self { + if value.contains("&") { + StyleSelector::Selector(value.to_string()) + } else if let Some(s) = value.strip_prefix("group") { + let post = to_kebab_case(s); + StyleSelector::Selector(format!( + "{}{}{} &", + "*[role=group]", + SelectorSeparator::from(post.as_str()), + post + )) + } else if let Some(s) = value.strip_prefix("theme") { + // first character should lower case + StyleSelector::Selector(format!( + ":root[data-theme={}{}] &", + s.chars().next().unwrap().to_ascii_lowercase(), + &s[1..] + )) + } else if value == "print" { + StyleSelector::Media("print".to_string()) + } else { + let post = to_kebab_case(value); + + StyleSelector::Selector(format!( + "&{}{}", + SelectorSeparator::from(post.as_str()), + post + )) + } + } +} + +impl From<[&str; 2]> for StyleSelector { + fn from(value: [&str; 2]) -> Self { + let post = to_kebab_case(value[1]); + StyleSelector::Selector(format!( + "{}{}{}", + StyleSelector::from(value[0]), + SelectorSeparator::from(post.as_str()), + post + )) + } +} +impl From<(&StyleSelector, &str)> for StyleSelector { + fn from(value: (&StyleSelector, &str)) -> Self { + if let StyleSelector::Global(_, file) = value.0 { + let post = to_kebab_case(value.1); + StyleSelector::Global( + format!( + "{}{}{}", + value.0, + SelectorSeparator::from(post.as_str()), + post + ), + file.clone(), + ) + } else { + StyleSelector::from([&value.0.to_string(), value.1]) + } + } +} + +impl Display for StyleSelector { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + write!( + f, + "{}", + match self { + StyleSelector::Selector(value) => value.to_string(), + StyleSelector::Media(value) => format!("@{value}"), + StyleSelector::Global(value, _) => format!("{value}"), + } + ) + } +} + +fn get_selector_order(selector: &str) -> u8 { + // & count + let t = if selector.chars().filter(|c| c == &'&').count() == 1 { + selector + .split('&') + .next_back() + .map(|a| a.to_string()) + .unwrap_or(selector.to_string()) + } else { + selector.to_string() + }; + + *SELECTOR_ORDER_MAP + .get(&t) + .unwrap_or(if t.starts_with("&") { &0 } else { &99 }) +} + +#[cfg(test)] +mod tests { + use super::*; + + use rstest::rstest; + + #[rstest] + #[case("hover", StyleSelector::Selector("&:hover".to_string()))] + #[case("focusVisible", StyleSelector::Selector("&:focus-visible".to_string()))] + #[case("groupHover", StyleSelector::Selector("*[role=group]:hover &".to_string()))] + #[case("groupFocusVisible", StyleSelector::Selector("*[role=group]:focus-visible &".to_string()))] + #[case("group1", StyleSelector::Selector("*[role=group]:1 &".to_string()))] + #[case(["themeDark", "placeholder"], StyleSelector::Selector(":root[data-theme=dark] &::placeholder".to_string()))] + #[case("themeLight", StyleSelector::Selector(":root[data-theme=light] &".to_string()))] + #[case("*[aria=disabled='true'] &:hover", StyleSelector::Selector("*[aria=disabled='true'] &:hover".to_string()))] + fn test_style_selector( + #[case] input: impl Into, + #[case] expected: StyleSelector, + ) { + assert_eq!(input.into(), expected); + } + + #[rstest] + #[case(StyleSelector::Selector("&:hover".to_string()), "&:hover")] + #[case(StyleSelector::Media("screen and (max-width: 600px)".to_string()), "@screen and (max-width: 600px)")] + #[case(StyleSelector::Global(":root[data-theme=dark]".to_string(), "file.rs".to_string()), ":root[data-theme=dark]")] + fn test_style_selector_display(#[case] selector: StyleSelector, #[case] expected: &str) { + let output = format!("{selector}"); + assert_eq!(output, expected); + } + + #[rstest] + #[case( + StyleSelector::Media("screen".to_string()), + StyleSelector::Selector("&:hover".to_string()), + std::cmp::Ordering::Greater + )] + #[case( + StyleSelector::Selector("&:hover".to_string()), + StyleSelector::Selector("&:focus-visible".to_string()), + std::cmp::Ordering::Less + )] + #[case( + StyleSelector::Media("a".to_string()), + StyleSelector::Media("b".to_string()), + std::cmp::Ordering::Less + )] + #[case( + StyleSelector::Global(":root[data-theme=dark]".to_string(), "file1.rs".to_string()), + StyleSelector::Global(":root[data-theme=light]".to_string(), "file2.rs".to_string()), + std::cmp::Ordering::Less + )] + #[case( + StyleSelector::from(":root[data-theme=dark] &:hover"), + StyleSelector::from(":root[data-theme=dark] &:focus-visible"), + std::cmp::Ordering::Less + )] + #[case( + StyleSelector::Selector("&:hover".to_string()), + StyleSelector::Media("screen".to_string()), + std::cmp::Ordering::Less + )] + #[case( + StyleSelector::from("&:hover"), + StyleSelector::from("&:hover"), + std::cmp::Ordering::Equal + )] + #[case( + StyleSelector::Global(":root[data-theme=dark]".to_string(), "file1.rs".to_string()), + StyleSelector::Global(":root[data-theme=dark]".to_string(), "file2.rs".to_string()), + std::cmp::Ordering::Equal + )] + #[case( + StyleSelector::Global("div".to_string(), "file1.rs".to_string()), + StyleSelector::Global("div:hover".to_string(), "file2.rs".to_string()), + std::cmp::Ordering::Less + )] + #[case( + StyleSelector::Global("div:hover".to_string(), "file2.rs".to_string()), + StyleSelector::Global("div".to_string(), "file1.rs".to_string()), + std::cmp::Ordering::Greater + )] + #[case( + StyleSelector::Global("div:hover".to_string(), "file2.rs".to_string()), + StyleSelector::Global("span:hover".to_string(), "file1.rs".to_string()), + "div".cmp(&"span") + )] + #[case( + StyleSelector::Global("div:hover".to_string(), "file2.rs".to_string()), + StyleSelector::Global("span:focus".to_string(), "file1.rs".to_string()), + std::cmp::Ordering::Less + )] + #[case( + StyleSelector::Global("div:".to_string(), "file2.rs".to_string()), + StyleSelector::Global("span:".to_string(), "file1.rs".to_string()), + "div".cmp(&"span") + )] + // global selector always less than selector + #[case( + StyleSelector::Global("div:".to_string(), "file2.rs".to_string()), + StyleSelector::Selector("&:hover".to_string()), + std::cmp::Ordering::Less + )] + #[case( + StyleSelector::Selector("&:hover".to_string()), + StyleSelector::Global("div:".to_string(), "file2.rs".to_string()), + std::cmp::Ordering::Greater + )] + fn test_style_selector_ord( + #[case] a: StyleSelector, + #[case] b: StyleSelector, + #[case] expected: std::cmp::Ordering, + ) { + assert_eq!(a.cmp(&b), expected); + assert_eq!(a.partial_cmp(&b), Some(expected)); + } + + #[rstest] + #[case("&:hover", 0)] + #[case("&:focus-visible", 1)] + #[case("&:focus", 2)] + #[case("&:active", 3)] + #[case("&:selected", 4)] + #[case("&:disabled", 5)] + #[case("&:not-exist", 99)] + #[case(":root[data-theme=dark] &:hover", 0)] + #[case(":root[data-theme=dark] &:focus-visible", 1)] + #[case(":root[data-theme=dark] &:focus", 2)] + #[case(":root[data-theme=dark] &:active", 3)] + #[case(":root[data-theme=dark] &:selected", 4)] + #[case(":root[data-theme=dark] &:disabled", 5)] + #[case(":root[data-theme=dark] &:not-exist", 99)] + fn test_get_selector_order(#[case] selector: &str, #[case] expected: u8) { + assert_eq!(get_selector_order(selector), expected); + } +} diff --git a/libs/css/src/utils.rs b/libs/css/src/utils.rs new file mode 100644 index 00000000..686ea48b --- /dev/null +++ b/libs/css/src/utils.rs @@ -0,0 +1,57 @@ +pub fn to_kebab_case(value: &str) -> String { + value + .chars() + .enumerate() + .map(|(i, c)| { + if c.is_uppercase() { + if i == 0 { + c.to_ascii_lowercase().to_string() + } else { + format!("-{}", c.to_ascii_lowercase()) + } + } else { + c.to_string() + } + }) + .collect() +} + +pub fn to_camel_case(value: &str) -> String { + value + .split('-') + .enumerate() + .map(|(i, s)| { + if i == 0 { + s.to_string() + } else { + format!("{}{}", s[0..1].to_uppercase(), &s[1..]) + } + }) + .collect() +} + +#[cfg(test)] +mod tests { + use super::*; + use rstest::rstest; + + #[rstest] + #[case("background-color", "backgroundColor")] + #[case("min-width", "minWidth")] + #[case("max-height", "maxHeight")] + #[case("border-radius", "borderRadius")] + #[case("color", "color")] + fn test_to_camel_case(#[case] input: &str, #[case] expected: &str) { + assert_eq!(to_camel_case(input), expected); + } + + #[rstest] + #[case("backgroundColor", "background-color")] + #[case("minWidth", "min-width")] + #[case("maxHeight", "max-height")] + #[case("borderRadius", "border-radius")] + #[case("color", "color")] + fn test_to_kebab_case(#[case] input: &str, #[case] expected: &str) { + assert_eq!(to_kebab_case(input), expected); + } +} diff --git a/libs/extractor/Cargo.toml b/libs/extractor/Cargo.toml index de0b9934..a0b6be7b 100644 --- a/libs/extractor/Cargo.toml +++ b/libs/extractor/Cargo.toml @@ -4,13 +4,13 @@ version = "0.1.0" edition = "2024" [dependencies] -oxc_parser = "0.75.0" -oxc_syntax = "0.75.0" -oxc_span = "0.75.0" -oxc_allocator = "0.75.0" -oxc_ast = "0.75.0" -oxc_ast_visit = "0.75.0" -oxc_codegen = "0.75.0" +oxc_parser = "0.77.0" +oxc_syntax = "0.77.0" +oxc_span = "0.77.0" +oxc_allocator = "0.77.0" +oxc_ast = "0.77.0" +oxc_ast_visit = "0.77.0" +oxc_codegen = "0.77.0" css = { path = "../css" } phf = "0.12" strum = "0.27.1" @@ -19,3 +19,4 @@ strum_macros = "0.27.1" [dev-dependencies] insta = "1.43.1" serial_test = "3.2.0" +rstest = "0.25.0" diff --git a/libs/extractor/src/component.rs b/libs/extractor/src/component.rs index 758e6e87..4ee4ef1b 100644 --- a/libs/extractor/src/component.rs +++ b/libs/extractor/src/component.rs @@ -1,9 +1,10 @@ -use crate::ExtractStyleValue; -use crate::extract_style::ExtractStaticStyle; -use crate::extract_style::ExtractStyleValue::Static; use strum::IntoEnumIterator; use strum_macros::{Display, EnumIter}; +use crate::extract_style::{ + extract_static_style::ExtractStaticStyle, extract_style_value::ExtractStyleValue, +}; + /// devup-ui export variable kind #[derive(Debug, PartialEq, Clone, EnumIter, Display)] pub enum ExportVariableKind { @@ -44,14 +45,16 @@ impl ExportVariableKind { | ExportVariableKind::Image | ExportVariableKind::Box => vec![], ExportVariableKind::Flex => { - vec![Static(ExtractStaticStyle::new_basic( + vec![ExtractStyleValue::Static(ExtractStaticStyle::new_basic( "display", "flex", 0, None, ))] } ExportVariableKind::VStack => { vec![ - Static(ExtractStaticStyle::new_basic("display", "flex", 0, None)), - Static(ExtractStaticStyle::new_basic( + ExtractStyleValue::Static(ExtractStaticStyle::new_basic( + "display", "flex", 0, None, + )), + ExtractStyleValue::Static(ExtractStaticStyle::new_basic( "flexDirection", "column", 0, @@ -61,14 +64,16 @@ impl ExportVariableKind { } ExportVariableKind::Center => { vec![ - Static(ExtractStaticStyle::new_basic("display", "flex", 0, None)), - Static(ExtractStaticStyle::new_basic( + ExtractStyleValue::Static(ExtractStaticStyle::new_basic( + "display", "flex", 0, None, + )), + ExtractStyleValue::Static(ExtractStaticStyle::new_basic( "justifyContent", "center", 0, None, )), - Static(ExtractStaticStyle::new_basic( + ExtractStyleValue::Static(ExtractStaticStyle::new_basic( "alignItems", "center", 0, @@ -77,7 +82,7 @@ impl ExportVariableKind { ] } ExportVariableKind::Grid => { - vec![Static(ExtractStaticStyle::new_basic( + vec![ExtractStyleValue::Static(ExtractStaticStyle::new_basic( "display", "grid", 0, None, ))] } @@ -166,15 +171,17 @@ mod tests { assert_eq!(ExportVariableKind::Input.extract(), vec![]); assert_eq!( ExportVariableKind::Flex.extract(), - vec![Static(ExtractStaticStyle::new_basic( + vec![ExtractStyleValue::Static(ExtractStaticStyle::new_basic( "display", "flex", 0, None, ))] ); assert_eq!( ExportVariableKind::VStack.extract(), vec![ - Static(ExtractStaticStyle::new_basic("display", "flex", 0, None,)), - Static(ExtractStaticStyle::new_basic( + ExtractStyleValue::Static(ExtractStaticStyle::new_basic( + "display", "flex", 0, None, + )), + ExtractStyleValue::Static(ExtractStaticStyle::new_basic( "flexDirection", "column", 0, @@ -185,14 +192,16 @@ mod tests { assert_eq!( ExportVariableKind::Center.extract(), vec![ - Static(ExtractStaticStyle::new_basic("display", "flex", 0, None,)), - Static(ExtractStaticStyle::new_basic( + ExtractStyleValue::Static(ExtractStaticStyle::new_basic( + "display", "flex", 0, None, + )), + ExtractStyleValue::Static(ExtractStaticStyle::new_basic( "justifyContent", "center", 0, None, )), - Static(ExtractStaticStyle::new_basic( + ExtractStyleValue::Static(ExtractStaticStyle::new_basic( "alignItems", "center", 0, @@ -202,7 +211,7 @@ mod tests { ); assert_eq!( ExportVariableKind::Grid.extract(), - vec![Static(ExtractStaticStyle::new_basic( + vec![ExtractStyleValue::Static(ExtractStaticStyle::new_basic( "display", "grid", 0, None, ))] ); diff --git a/libs/extractor/src/css_type.rs b/libs/extractor/src/css_type.rs new file mode 100644 index 00000000..edfa00da --- /dev/null +++ b/libs/extractor/src/css_type.rs @@ -0,0 +1,34 @@ +#[derive(Debug, PartialEq)] +pub enum CssType { + Css, + GlobalCss, +} + +impl TryFrom for CssType { + type Error = String; + + fn try_from(value: String) -> Result { + if value == "css" { + Ok(CssType::Css) + } else if value == "globalCss" { + Ok(CssType::GlobalCss) + } else { + Err(value) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use rstest::rstest; + + #[rstest] + #[case("css".to_string(), Ok(CssType::Css))] + #[case("globalCss".to_string(), Ok(CssType::GlobalCss))] + #[case("unknown".to_string(), Err("unknown".to_string()))] + #[case("".to_string(), Err("".to_string()))] + fn test_css_type_try_from(#[case] input: String, #[case] expected: Result) { + assert_eq!(CssType::try_from(input), expected); + } +} diff --git a/libs/extractor/src/css_utils.rs b/libs/extractor/src/css_utils.rs new file mode 100644 index 00000000..35d5e7a2 --- /dev/null +++ b/libs/extractor/src/css_utils.rs @@ -0,0 +1,146 @@ +use css::{style_selector::StyleSelector, utils::to_camel_case}; + +use crate::{ + ExtractStyleProp, + extract_style::{ + extract_static_style::ExtractStaticStyle, extract_style_value::ExtractStyleValue, + }, +}; + +pub fn css_to_style<'a>( + css: &str, + level: u8, + selector: &Option<&StyleSelector>, +) -> Vec> { + css.split(";") + .map(|s| { + let s = s.trim(); + if s.is_empty() { + None + } else { + let mut iter = s.split(":").map(|s| s.trim()); + let property = to_camel_case(iter.next().unwrap()); + let value = iter.next().unwrap(); + Some(ExtractStyleProp::Static(ExtractStyleValue::Static( + ExtractStaticStyle::new(&property, value, level, selector.cloned()), + ))) + } + }) + .flatten() + .collect() +} + +pub fn optimize_css_block(css: &str) -> String { + css.split("{") + .map(|s| s.trim().to_string()) + .collect::>() + .join("{") + .split("}") + .map(|s| s.trim().to_string()) + .collect::>() + .join("}") + .split(";") + .map(|s| { + if !s.contains(":") { + s.to_string().trim().to_string() + } else { + let mut iter = s.split(":"); + let property = iter.next().unwrap().trim(); + let value = iter.next().unwrap().trim(); + format!("{}:{}", property, value) + } + }) + .collect::>() + .join(";") + .trim() + .replace(";}", "}") + .to_string() +} + +#[cfg(test)] +mod tests { + use super::*; + + use rstest::rstest; + + #[rstest] + #[case( + " img { background-color : red; } ", + "img{background-color:red}" + )] + #[case( + " img { background-color : red; color : blue; } ", + "img{background-color:red;color:blue}" + )] + #[case("div{margin : 0 ; padding : 0 ; }", "div{margin:0;padding:0}")] + #[case( + "a { text-decoration : none ; color : black ; }", + "a{text-decoration:none;color:black}" + )] + #[case("body{background: #fff;}", "body{background:#fff}")] + #[case( + "h1{ font-size : 2rem ; font-weight : bold ; }", + "h1{font-size:2rem;font-weight:bold}" + )] + #[case("span { }", "span{}")] + #[case("p{color:blue;}", "p{color:blue}")] + #[case( + "ul { list-style : none ; margin : 0 ; padding : 0 ; }", + "ul{list-style:none;margin:0;padding:0}" + )] + #[case("section{ }", "section{}")] + fn test_optimize_css_block(#[case] input: &str, #[case] expected: &str) { + assert_eq!(optimize_css_block(input), expected); + } + + #[rstest] + #[case( + "color: red; background: blue;", + vec![ + ("color", "red"), + ("background", "blue"), + ] + )] + #[case( + "margin:0;padding:0;", + vec![ + ("margin", "0"), + ("padding", "0"), + ] + )] + #[case( + "font-size: 16px;", + vec![ + ("fontSize", "16px"), + ] + )] + #[case( + "border: 1px solid #000; color: #fff;", + vec![ + ("border", "1px solid #000"), + ("color", "#FFF"), + ] + )] + #[case( + "", + vec![] + )] + fn test_css_to_style(#[case] input: &str, #[case] expected: Vec<(&str, &str)>) { + let styles = css_to_style(input, 0, &None); + let mut result: Vec<(&str, &str)> = styles + .iter() + .filter_map(|prop| { + if let crate::ExtractStyleProp::Static(crate::ExtractStyleValue::Static(st)) = prop + { + Some((st.property(), st.value())) + } else { + None + } + }) + .collect(); + result.sort(); + let mut expected_sorted = expected.clone(); + expected_sorted.sort(); + assert_eq!(result, expected_sorted); + } +} diff --git a/libs/extractor/src/extract_style/constant.rs b/libs/extractor/src/extract_style/constant.rs new file mode 100644 index 00000000..51a0a0c8 --- /dev/null +++ b/libs/extractor/src/extract_style/constant.rs @@ -0,0 +1,22 @@ +use phf::phf_set; + +pub(super) static MAINTAIN_VALUE_PROPERTIES: phf::Set<&str> = phf_set! { + "opacity", + "flex", + "zIndex", + "lineClamp", + "fontWeight", + "lineHeight", + "scale", + "aspectRatio", + "flexGrow", + "flexShrink", + "order", + "gridColumn", + "gridColumnStart", + "gridColumnEnd", + "gridRow", + "gridRowStart", + "gridRowEnd", + "animationIterationCount" +}; diff --git a/libs/extractor/src/extract_style/extract_css.rs b/libs/extractor/src/extract_style/extract_css.rs new file mode 100644 index 00000000..5913869c --- /dev/null +++ b/libs/extractor/src/extract_style/extract_css.rs @@ -0,0 +1,6 @@ +#[derive(Debug, PartialEq, Clone, Eq, Hash, Ord, PartialOrd)] +pub struct ExtractCss { + /// css must be global css + pub css: String, + pub file: String, +} diff --git a/libs/extractor/src/extract_style/extract_dynamic_style.rs b/libs/extractor/src/extract_style/extract_dynamic_style.rs new file mode 100644 index 00000000..44d66ed1 --- /dev/null +++ b/libs/extractor/src/extract_style/extract_dynamic_style.rs @@ -0,0 +1,94 @@ +use css::{ + optimize_value::optimize_value, sheet_to_classname, sheet_to_variable_name, short_to_long, + style_selector::StyleSelector, +}; + +use crate::{StyleProperty, extract_style::ExtractStyleProperty}; + +#[derive(Debug, PartialEq, Clone, Eq, Hash, Ord, PartialOrd)] +pub struct ExtractDynamicStyle { + /// property + property: String, + /// responsive + level: u8, + identifier: String, + + /// selector + selector: Option, + + pub(super) style_order: Option, +} + +impl ExtractDynamicStyle { + /// create a new ExtractDynamicStyle + pub fn new( + property: &str, + level: u8, + identifier: &str, + selector: Option, + ) -> Self { + Self { + property: short_to_long(property), + level, + identifier: optimize_value(identifier), + selector, + style_order: None, + } + } + + pub fn property(&self) -> &str { + self.property.as_str() + } + + pub fn level(&self) -> u8 { + self.level + } + + pub fn selector(&self) -> Option<&StyleSelector> { + self.selector.as_ref() + } + + pub fn identifier(&self) -> &str { + self.identifier.as_str() + } + + pub fn style_order(&self) -> Option { + self.style_order + } +} + +impl ExtractStyleProperty for ExtractDynamicStyle { + fn extract(&self) -> StyleProperty { + let selector = self.selector.clone().map(|s| s.to_string()); + StyleProperty::Variable { + class_name: sheet_to_classname( + self.property.as_str(), + self.level, + None, + selector.as_deref(), + self.style_order, + ), + variable_name: sheet_to_variable_name( + self.property.as_str(), + self.level, + selector.as_deref(), + ), + identifier: self.identifier.clone(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_extract_dynamic_style() { + let style = ExtractDynamicStyle::new("color", 0, "primary", None); + assert_eq!(style.property(), "color"); + assert_eq!(style.level(), 0); + assert_eq!(style.selector(), None); + assert_eq!(style.identifier(), "primary"); + assert_eq!(style.style_order(), None); + } +} diff --git a/libs/extractor/src/extract_style/extract_import.rs b/libs/extractor/src/extract_style/extract_import.rs new file mode 100644 index 00000000..5c52c82f --- /dev/null +++ b/libs/extractor/src/extract_style/extract_import.rs @@ -0,0 +1,6 @@ +#[derive(Debug, PartialEq, Clone, Eq, Hash, Ord, PartialOrd)] +pub struct ExtractImport { + /// import must be global css + pub url: String, + pub file: String, +} diff --git a/libs/extractor/src/extract_style/extract_static_style.rs b/libs/extractor/src/extract_style/extract_static_style.rs new file mode 100644 index 00000000..99c24da5 --- /dev/null +++ b/libs/extractor/src/extract_style/extract_static_style.rs @@ -0,0 +1,114 @@ +use css::{ + optimize_value::optimize_value, sheet_to_classname, short_to_long, + style_selector::StyleSelector, +}; + +use crate::{ + StyleProperty, + extract_style::{ExtractStyleProperty, constant::MAINTAIN_VALUE_PROPERTIES}, + utils::convert_value, +}; + +#[derive(Debug, PartialEq, Clone, Eq, Hash, Ord, PartialOrd)] +pub struct ExtractStaticStyle { + /// property + pub property: String, + /// fixed value + pub value: String, + /// responsive level + pub level: u8, + /// selector + pub selector: Option, + /// None is inf, 0 is first, 1 is second, etc + pub style_order: Option, +} + +impl ExtractStaticStyle { + /// create a new ExtractStaticStyle + pub fn new(property: &str, value: &str, level: u8, selector: Option) -> Self { + Self { + value: optimize_value(&if MAINTAIN_VALUE_PROPERTIES.contains(property) { + value.to_string() + } else { + convert_value(value) + }), + property: short_to_long(property), + level, + selector, + style_order: None, + } + } + + pub fn new_basic( + property: &str, + value: &str, + level: u8, + selector: Option, + ) -> Self { + Self { + value: optimize_value(&if MAINTAIN_VALUE_PROPERTIES.contains(property) { + value.to_string() + } else { + convert_value(value) + }), + property: property.to_string(), + level, + selector, + style_order: Some(0), + } + } + + pub fn property(&self) -> &str { + self.property.as_str() + } + + pub fn value(&self) -> &str { + self.value.as_str() + } + + pub fn level(&self) -> u8 { + self.level + } + + pub fn selector(&self) -> Option<&StyleSelector> { + self.selector.as_ref() + } + + pub fn style_order(&self) -> Option { + self.style_order + } +} + +impl ExtractStyleProperty for ExtractStaticStyle { + fn extract(&self) -> StyleProperty { + let s = self.selector.clone().map(|s| s.to_string()); + StyleProperty::ClassName(sheet_to_classname( + self.property.as_str(), + self.level, + Some(&optimize_value( + &if MAINTAIN_VALUE_PROPERTIES.contains(self.property.as_str()) { + self.value.to_string() + } else { + convert_value(self.value.as_str()) + }, + )), + s.as_deref(), + self.style_order, + )) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_extract_static_style() { + let style = ExtractStaticStyle::new("color", "red", 0, None); + assert_eq!(style.property(), "color"); + assert_eq!(style.value(), "red"); + assert_eq!(style.level(), 0); + assert_eq!(style.selector(), None); + assert_eq!(style.style_order(), None); + } +} diff --git a/libs/extractor/src/extract_style/extract_style_value.rs b/libs/extractor/src/extract_style/extract_style_value.rs new file mode 100644 index 00000000..c944f02e --- /dev/null +++ b/libs/extractor/src/extract_style/extract_style_value.rs @@ -0,0 +1,63 @@ +use crate::{ + StyleProperty, + extract_style::{ + ExtractStyleProperty, extract_css::ExtractCss, extract_dynamic_style::ExtractDynamicStyle, + extract_import::ExtractImport, extract_static_style::ExtractStaticStyle, + }, +}; + +#[derive(Debug, PartialEq, Clone, Eq, Hash, Ord, PartialOrd)] +pub enum ExtractStyleValue { + Static(ExtractStaticStyle), + Typography(String), + Dynamic(ExtractDynamicStyle), + Css(ExtractCss), + Import(ExtractImport), +} + +impl ExtractStyleValue { + pub fn extract(&self) -> Option { + match self { + ExtractStyleValue::Static(style) => Some(style.extract()), + ExtractStyleValue::Dynamic(style) => Some(style.extract()), + ExtractStyleValue::Typography(typo) => { + Some(StyleProperty::ClassName(format!("typo-{typo}"))) + } + ExtractStyleValue::Css(_) | ExtractStyleValue::Import(_) => None, + } + } + pub fn set_style_order(&mut self, order: u8) { + match self { + ExtractStyleValue::Static(style) => { + if style.style_order.is_none() { + style.style_order = Some(order); + } + } + ExtractStyleValue::Dynamic(style) => { + style.style_order = Some(order); + } + _ => {} + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_style_order() { + let mut style = + ExtractStyleValue::Static(ExtractStaticStyle::new("margin", "10px", 0, None)); + style.set_style_order(1); + if let ExtractStyleValue::Static(style) = style { + assert_eq!(style.style_order(), Some(1)); + } + let mut style = + ExtractStyleValue::Dynamic(ExtractDynamicStyle::new("margin", 0, "10px", None)); + style.set_style_order(1); + if let ExtractStyleValue::Dynamic(style) = style { + assert_eq!(style.style_order(), Some(1)); + } + } +} diff --git a/libs/extractor/src/extract_style/mod.rs b/libs/extractor/src/extract_style/mod.rs index 269768b3..d5571caf 100644 --- a/libs/extractor/src/extract_style/mod.rs +++ b/libs/extractor/src/extract_style/mod.rs @@ -1,272 +1,13 @@ -use crate::StyleProperty; -use crate::utils::convert_value; -use css::{ - StyleSelector, css_to_classname, optimize_value, sheet_to_classname, sheet_to_variable_name, - short_to_long, -}; -use phf::phf_set; - -#[derive(Debug, PartialEq, Clone, Eq, Hash, Ord, PartialOrd)] -pub struct ExtractStaticStyle { - /// property - property: String, - /// fixed value - value: String, - /// responsive level - level: u8, - /// selector - selector: Option, - /// None is inf, 0 is first, 1 is second, etc - style_order: Option, -} - -static MAINTAIN_VALUE_PROPERTIES: phf::Set<&str> = phf_set! { - "opacity", - "flex", - "zIndex", - "lineClamp", - "fontWeight", - "lineHeight", - "scale", - "aspectRatio", - "flexGrow", - "flexShrink", - "order", - "gridColumn", - "gridColumnStart", - "gridColumnEnd", - "gridRow", - "gridRowStart", - "gridRowEnd", - "animationIterationCount" -}; - -impl ExtractStaticStyle { - /// create a new ExtractStaticStyle - pub fn new(property: &str, value: &str, level: u8, selector: Option) -> Self { - Self { - value: optimize_value(&if MAINTAIN_VALUE_PROPERTIES.contains(property) { - value.to_string() - } else { - convert_value(value) - }), - property: short_to_long(property), - level, - selector, - style_order: None, - } - } - - pub fn new_basic( - property: &str, - value: &str, - level: u8, - selector: Option, - ) -> Self { - Self { - value: optimize_value(&if MAINTAIN_VALUE_PROPERTIES.contains(property) { - value.to_string() - } else { - convert_value(value) - }), - property: property.to_string(), - level, - selector, - style_order: Some(0), - } - } - - pub fn property(&self) -> &str { - self.property.as_str() - } - - pub fn value(&self) -> &str { - self.value.as_str() - } - - pub fn level(&self) -> u8 { - self.level - } - - pub fn selector(&self) -> Option<&StyleSelector> { - self.selector.as_ref() - } +pub(super) mod constant; +pub(super) mod extract_css; +pub(super) mod extract_dynamic_style; +pub(super) mod extract_import; +pub(super) mod extract_static_style; +pub mod extract_style_value; - pub fn style_order(&self) -> Option { - self.style_order - } -} +use crate::StyleProperty; pub trait ExtractStyleProperty { /// extract style properties fn extract(&self) -> StyleProperty; } - -impl ExtractStyleProperty for ExtractStaticStyle { - fn extract(&self) -> StyleProperty { - let s = self.selector.clone().map(|s| s.to_string()); - StyleProperty::ClassName(sheet_to_classname( - self.property.as_str(), - self.level, - Some(&optimize_value( - &if MAINTAIN_VALUE_PROPERTIES.contains(self.property.as_str()) { - self.value.to_string() - } else { - convert_value(self.value.as_str()) - }, - )), - s.as_deref(), - self.style_order, - )) - } -} -#[derive(Debug, PartialEq, Clone, Eq, Hash, Ord, PartialOrd)] -pub struct ExtractCss { - /// css code - pub css: String, -} - -impl ExtractStyleProperty for ExtractCss { - /// hashing css code to class name - fn extract(&self) -> StyleProperty { - StyleProperty::ClassName(css_to_classname(self.css.as_str())) - } -} - -#[derive(Debug, PartialEq, Clone, Eq, Hash, Ord, PartialOrd)] -pub struct ExtractDynamicStyle { - /// property - property: String, - /// responsive - level: u8, - identifier: String, - - /// selector - selector: Option, - - style_order: Option, -} - -impl ExtractDynamicStyle { - /// create a new ExtractDynamicStyle - pub fn new( - property: &str, - level: u8, - identifier: &str, - selector: Option, - ) -> Self { - Self { - property: short_to_long(property), - level, - identifier: optimize_value(identifier), - selector, - style_order: None, - } - } - - pub fn property(&self) -> &str { - self.property.as_str() - } - - pub fn level(&self) -> u8 { - self.level - } - - pub fn selector(&self) -> Option<&StyleSelector> { - self.selector.as_ref() - } - - pub fn identifier(&self) -> &str { - self.identifier.as_str() - } - - pub fn style_order(&self) -> Option { - self.style_order - } -} - -impl ExtractStyleProperty for ExtractDynamicStyle { - fn extract(&self) -> StyleProperty { - let selector = self.selector.clone().map(|s| s.to_string()); - StyleProperty::Variable { - class_name: sheet_to_classname( - self.property.as_str(), - self.level, - None, - selector.as_deref(), - self.style_order, - ), - variable_name: sheet_to_variable_name( - self.property.as_str(), - self.level, - selector.as_deref(), - ), - identifier: self.identifier.clone(), - } - } -} -#[derive(Debug, PartialEq, Clone, Eq, Hash, Ord, PartialOrd)] -pub enum ExtractStyleValue { - Static(ExtractStaticStyle), - Typography(String), - Dynamic(ExtractDynamicStyle), - Css(ExtractCss), -} - -impl ExtractStyleValue { - pub fn extract(&self) -> StyleProperty { - match self { - ExtractStyleValue::Static(style) => style.extract(), - ExtractStyleValue::Dynamic(style) => style.extract(), - ExtractStyleValue::Css(css) => css.extract(), - ExtractStyleValue::Typography(typo) => { - StyleProperty::ClassName(format!("typo-{typo}")) - } - } - } - pub fn set_style_order(&mut self, order: u8) { - match self { - ExtractStyleValue::Static(style) => { - if style.style_order.is_none() { - style.style_order = Some(order); - } - } - ExtractStyleValue::Dynamic(style) => { - style.style_order = Some(order); - } - _ => {} - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_extract_static_style() { - let style = ExtractStaticStyle::new("color", "red", 0, None); - assert_eq!(style.property(), "color"); - assert_eq!(style.value(), "red"); - assert_eq!(style.level(), 0); - assert_eq!(style.selector(), None); - } - - #[test] - fn test_extract_dynamic_style() { - let style = ExtractDynamicStyle::new("color", 0, "primary", None); - assert_eq!(style.property(), "color"); - assert_eq!(style.level(), 0); - assert_eq!(style.selector(), None); - assert_eq!(style.identifier(), "primary"); - } - - #[test] - fn test_extract_basic_static_style() { - let style = ExtractStaticStyle::new_basic("color", "red", 0, None); - assert_eq!(style.property(), "color"); - assert_eq!(style.value(), "red"); - assert_eq!(style.level(), 0); - assert_eq!(style.selector(), None); - } -} diff --git a/libs/extractor/src/gen_class_name.rs b/libs/extractor/src/gen_class_name.rs index 72356f69..3fa25566 100644 --- a/libs/extractor/src/gen_class_name.rs +++ b/libs/extractor/src/gen_class_name.rs @@ -1,11 +1,9 @@ use crate::prop_modify_utils::convert_class_name; +use crate::utils::is_same_expression; use crate::{ExtractStyleProp, StyleProperty}; use oxc_allocator::CloneIn; use oxc_ast::AstBuilder; -use oxc_ast::ast::{ - Expression, PropertyKey, PropertyKind, TemplateElement, - TemplateElementValue, -}; +use oxc_ast::ast::{Expression, PropertyKey, PropertyKind, TemplateElementValue}; use oxc_span::SPAN; pub fn gen_class_names<'a>( @@ -38,8 +36,9 @@ fn gen_class_name<'a>( Some(ast_builder.expression_string_literal( SPAN, ast_builder.atom(match &target { - StyleProperty::ClassName(cls) => cls, - StyleProperty::Variable { class_name, .. } => class_name, + Some(StyleProperty::ClassName(cls)) => cls, + Some(StyleProperty::Variable { class_name, .. }) => class_name, + None => return None, }), None, )) @@ -115,24 +114,6 @@ fn gen_class_name<'a>( } } } -fn is_same_expression<'a>(a: &Expression<'a>, b: &Expression<'a>) -> bool { - match (a, b) { - (Expression::StringLiteral(a), Expression::StringLiteral(b)) => a.value == b.value, - (Expression::TemplateLiteral(a), Expression::TemplateLiteral(b)) => { - a.quasis.len() == b.quasis.len() - && a.expressions.len() == b.expressions.len() - && a.quasis - .iter() - .zip(b.quasis.iter()) - .all(|(a, b)| a.value.raw == b.value.raw && a.tail == b.tail) - && a.expressions - .iter() - .zip(b.expressions.iter()) - .all(|(a, b)| is_same_expression(a, b)) - } - _ => false, - } -} pub fn merge_expression_for_class_name<'a>( ast_builder: &AstBuilder<'a>, @@ -141,13 +122,10 @@ pub fn merge_expression_for_class_name<'a>( let mut class_names = vec![]; let mut unknown_expr = vec![]; for expr in expressions { - match expr { - Expression::StringLiteral(str) => { - class_names.push(str.value.to_string().trim().to_string()) - } - _ => { - unknown_expr.push(expr); - } + if let Expression::StringLiteral(str) = &expr { + class_names.push(str.value.to_string().trim().to_string()) + } else { + unknown_expr.push(expr); } } if unknown_expr.is_empty() && class_names.is_empty() { @@ -160,42 +138,26 @@ pub fn merge_expression_for_class_name<'a>( } else { let mut qu = oxc_allocator::Vec::new_in(ast_builder.allocator); for idx in 0..unknown_expr.len() + 1 { - if idx == 0 { - qu.push(TemplateElement { - span: SPAN, - value: TemplateElementValue { - raw: ast_builder.atom(if class_name.is_empty() { + let tail = idx == unknown_expr.len(); + qu.push(ast_builder.template_element( + SPAN, + TemplateElementValue { + raw: ast_builder.atom(if idx == 0 { + if class_name.is_empty() { "" } else { class_name.push(' '); class_name.as_str() - }), - cooked: None, - }, - tail: false, - lone_surrogates: false, - }); - } else if idx == unknown_expr.len() { - qu.push(TemplateElement { - span: SPAN, - value: TemplateElementValue { - raw: ast_builder.atom(""), - cooked: None, - }, - tail: true, - lone_surrogates: false, - }); - } else { - qu.push(TemplateElement { - span: SPAN, - value: TemplateElementValue { - raw: ast_builder.atom(" "), - cooked: None, - }, - tail: false, - lone_surrogates: false, - }); - } + } + } else if tail { + "" + } else { + " " + }), + cooked: None, + }, + tail, + )); } Some(ast_builder.expression_template_literal( diff --git a/libs/extractor/src/gen_style.rs b/libs/extractor/src/gen_style.rs index 074608d9..2dc68c42 100644 --- a/libs/extractor/src/gen_style.rs +++ b/libs/extractor/src/gen_style.rs @@ -30,28 +30,32 @@ fn gen_style<'a>( ) -> Vec> { let mut properties = vec![]; match style { - ExtractStyleProp::Static(st) => match st.extract() { - StyleProperty::ClassName(_) => {} - StyleProperty::Variable { - variable_name, - identifier, - .. - } => { - properties.push(ast_builder.object_property_kind_object_property( - SPAN, - PropertyKind::Init, - PropertyKey::StringLiteral(ast_builder.alloc_string_literal( - SPAN, - ast_builder.atom(&variable_name), - None, - )), - ast_builder.expression_identifier(SPAN, ast_builder.atom(&identifier)), - false, - false, - false, - )); + ExtractStyleProp::Static(st) => { + if let Some(ex) = st.extract() { + match ex { + StyleProperty::ClassName(_) => {} + StyleProperty::Variable { + variable_name, + identifier, + .. + } => { + properties.push(ast_builder.object_property_kind_object_property( + SPAN, + PropertyKind::Init, + PropertyKey::StringLiteral(ast_builder.alloc_string_literal( + SPAN, + ast_builder.atom(&variable_name), + None, + )), + ast_builder.expression_identifier(SPAN, ast_builder.atom(&identifier)), + false, + false, + false, + )); + } + } } - }, + } ExtractStyleProp::StaticArray(res) => { properties.append( &mut res @@ -122,40 +126,37 @@ fn gen_style<'a>( ObjectPropertyKind::ObjectProperty(p), ObjectPropertyKind::ObjectProperty(q), ) = (p, q) + && p.key.name() == q.key.name() { - if p.key.name() == q.key.name() { - found = true; - properties.push(ast_builder.object_property_kind_object_property( - SPAN, - PropertyKind::Init, - p.key.clone_in(ast_builder.allocator), - ast_builder.expression_conditional( - SPAN, - condition.clone_in(ast_builder.allocator), - p.value.clone_in(ast_builder.allocator), - q.value.clone_in(ast_builder.allocator), - ), - false, - false, - false, - )); - break; - } - } - } - if !found { - if let ObjectPropertyKind::ObjectProperty(p) = p { + found = true; properties.push(ast_builder.object_property_kind_object_property( SPAN, PropertyKind::Init, p.key.clone_in(ast_builder.allocator), - p.value.clone_in(ast_builder.allocator), + ast_builder.expression_conditional( + SPAN, + condition.clone_in(ast_builder.allocator), + p.value.clone_in(ast_builder.allocator), + q.value.clone_in(ast_builder.allocator), + ), false, false, false, )); + break; } } + if !found && let ObjectPropertyKind::ObjectProperty(p) = p { + properties.push(ast_builder.object_property_kind_object_property( + SPAN, + PropertyKind::Init, + p.key.clone_in(ast_builder.allocator), + p.value.clone_in(ast_builder.allocator), + false, + false, + false, + )); + } } for q in collect_a.iter() { @@ -187,11 +188,11 @@ fn gen_style<'a>( }, ExtractStyleProp::Expression { styles, .. } => { for style in styles { - if let StyleProperty::Variable { + if let Some(StyleProperty::Variable { variable_name, identifier, .. - } = style.extract() + }) = style.extract() { properties.push(ast_builder.object_property_kind_object_property( SPAN, @@ -213,11 +214,11 @@ fn gen_style<'a>( let mut tmp_map = BTreeMap::>::new(); for (key, value) in map.iter() { for style in value.extract() { - if let StyleProperty::Variable { + if let Some(StyleProperty::Variable { variable_name, identifier, .. - } = style.extract() + }) = style.extract() { tmp_map .entry(variable_name) diff --git a/libs/extractor/src/lib.rs b/libs/extractor/src/lib.rs index 99971d7b..740fc514 100644 --- a/libs/extractor/src/lib.rs +++ b/libs/extractor/src/lib.rs @@ -1,4 +1,6 @@ mod component; +mod css_type; +mod css_utils; pub mod extract_style; mod gen_class_name; mod gen_style; @@ -6,7 +8,7 @@ mod prop_modify_utils; mod style_extractor; mod utils; mod visit; -use crate::extract_style::ExtractStyleValue; +use crate::extract_style::extract_style_value::ExtractStyleValue; use crate::visit::DevupVisitor; use oxc_allocator::Allocator; use oxc_ast::ast::Expression; @@ -116,6 +118,7 @@ pub fn extract( } let mut visitor = DevupVisitor::new( &allocator, + filename, &option.package, &option .css_file @@ -141,7 +144,7 @@ mod tests { use std::collections::BTreeSet; use super::*; - use css::reset_class_map; + use css::class_map::reset_class_map; use insta::assert_debug_snapshot; use serial_test::serial; @@ -462,6 +465,28 @@ mod tests { )); } + #[test] + #[serial] + fn extract_style_props_with_var_css() { + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r"import {css} from '@devup-ui/core' + const newCss=css; +
+ ", + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + } + #[test] #[serial] fn extract_style_props_with_default_import() { @@ -1121,6 +1146,134 @@ import clsx from 'clsx' )); } + #[test] + #[serial] + fn extract_same_value_conditional_style_props() { + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { Box } from "@devup-ui/core"; +; +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { Box } from "@devup-ui/core"; +; +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { Box } from "@devup-ui/core"; +; +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { Box } from "@devup-ui/core"; +; +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { Box } from "@devup-ui/core"; +; +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { Box } from "@devup-ui/core"; +; +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn extract_same_dynamic_value_conditional_style_props() { + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { Box } from "@devup-ui/core"; +; +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { Box } from "@devup-ui/core"; +; +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + } + #[test] #[serial] fn extract_responsive_conditional_style_props() { @@ -1299,6 +1452,70 @@ import clsx from 'clsx' "test.tsx", r#"import { Box } from "@devup-ui/core"; ; +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn extract_dynamic_logical_case() { + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { Box } from "@devup-ui/core"; +; +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { Box } from "@devup-ui/core"; +; +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { Box } from "@devup-ui/core"; +; +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { Box } from "@devup-ui/core"; +; "#, ExtractOption { package: "@devup-ui/core".to_string(), @@ -1397,6 +1614,45 @@ import clsx from 'clsx' )); } + #[test] + #[serial] + fn extract_selector_with_literal() { + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import {Box} from '@devup-ui/core' + + "#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import {Box} from '@devup-ui/core' + + "#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + } #[test] #[serial] fn extract_nested_selector() { @@ -1670,6 +1926,27 @@ import clsx from 'clsx' ) .unwrap() )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r"import {Box} from '@devup-ui/core' + + ", + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); } #[test] @@ -1808,18 +2085,19 @@ import clsx from 'clsx' ) .unwrap() )); - } - #[test] - #[serial] - fn extract_static_css_with_theme() { reset_class_map(); assert_debug_snapshot!(ToBTreeSet::from( extract( "test.tsx", - r#"import {Box} from '@devup-ui/core' - - "#, + r#"import { css } from "@devup-ui/core"; +; +"#, ExtractOption { package: "@devup-ui/core".to_string(), css_file: None @@ -1832,9 +2110,54 @@ import clsx from 'clsx' assert_debug_snapshot!(ToBTreeSet::from( extract( "test.tsx", - r#"import {Box} from '@devup-ui/core' - - "#, + r#"import { css } from "@devup-ui/core"; +; +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn extract_static_css_with_theme() { + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import {Box} from '@devup-ui/core' + + "#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import {Box} from '@devup-ui/core' + + "#, ExtractOption { package: "@devup-ui/core".to_string(), css_file: None @@ -3790,6 +4113,419 @@ export { r#"import { jsx as e } from "react/jsx-runtime"; import { Box as o } from "@devup-ui/core"; e(o, { styleVars: { c: "yellow" } }) +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn extract_global_css() { + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { globalCss } from "@devup-ui/core"; +globalCss({ + "div": { + bg: "red" + } +}) +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { globalCss } from "@devup-ui/core"; +globalCss({ + div: { + bg: "blue" + } +}) +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { globalCss } from "@devup-ui/core"; +globalCss({ + ["div"]: { + bg: "yellow" + } +}) +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { globalCss } from "@devup-ui/core"; +globalCss({ + [`div`]: { + bg: "green" + } +}) +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + } + #[test] + #[serial] + fn extract_global_css_with_selector() { + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { globalCss } from "@devup-ui/core"; +globalCss({ + "div": { + bg: "red", + color: "blue", + _hover: { + bg: "blue", + color: "red" + } + } +}) +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { globalCss } from "@devup-ui/core"; +globalCss({ + "div": { + bg: "red", + color: "blue", + _hover: { + bg: "blue", + color: "red" + } + }, + "span": { + bg: "red", + color: "blue", + _hover: { + bg: "blue", + color: "red" + } + } +}) +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { globalCss } from "@devup-ui/core"; +globalCss({ + "div": { + bg: "red", + color: "blue", + _hover: { + bg: "blue", + color: "red" + } + }, + "span": { + bg: "red", + color: "blue", + _hover: { + bg: "blue", + color: "red" + } + } +}) +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { globalCss } from "@devup-ui/core"; +globalCss({ + ["div"]: { + bg: "red", + color: "blue", + _hover: { + bg: "blue", + color: "red" + } + }, + ["span"]: { + bg: "red", + color: "blue", + _hover: { + bg: "blue", + color: "red" + } + }, + "body[data-theme='dark']": { + bg: "red", + color: "blue", + _hover: { + bg: "blue", + color: "red" + } + } +}) +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn extract_global_css_with_template_literal() { + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { globalCss } from "@devup-ui/core"; + globalCss({ + "div": ` + background-color: red + ` + }) + "#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { globalCss } from "@devup-ui/core"; + globalCss` + div { + background-color: red; + color: blue; + } + ` + "#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn extract_global_css_with_imports() { + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { globalCss } from "@devup-ui/core"; +globalCss({ + imports: ["@devup-ui/core/css/global.css"] +}) +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { globalCss } from "@devup-ui/core"; +globalCss({ + imports: ["@devup-ui/core/css/global.css", "@devup-ui/core/css/global2.css"] +}) +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { globalCss } from "@devup-ui/core"; +globalCss({ + imports: [`@devup-ui/core/css/global3.css`, `@devup-ui/core/css/global4.css`] +}) +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn extract_global_css_with_empty() { + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { globalCss } from "@devup-ui/core"; +globalCss({ + "div": {} +}) +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { globalCss } from "@devup-ui/core"; +globalCss({ + div: {}, + span: {} +}) +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { globalCss } from "@devup-ui/core"; +globalCss({ + div: `` +}) +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { globalCss } from "@devup-ui/core"; +globalCss({ + div: ``, + span: `` +}) +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { globalCss } from "@devup-ui/core"; +globalCss({}) +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { globalCss } from "@devup-ui/core"; +globalCss() "#, ExtractOption { package: "@devup-ui/core".to_string(), diff --git a/libs/extractor/src/prop_modify_utils.rs b/libs/extractor/src/prop_modify_utils.rs index 3660b6ad..a8a8fbb0 100644 --- a/libs/extractor/src/prop_modify_utils.rs +++ b/libs/extractor/src/prop_modify_utils.rs @@ -133,8 +133,6 @@ pub fn modify_props<'a>( } } } - println!("class_name_prop: {:?}", class_name_prop); - println!("style_prop: {:?}", style_prop); if let Some(ex) = get_class_name_expression( ast_builder, &class_name_prop, @@ -419,14 +417,24 @@ pub fn convert_style_vars<'a>( PropertyKey::TemplateLiteral(ast_builder.alloc_template_literal( SPAN, oxc_allocator::Vec::from_array_in( - [ast_builder.template_element( - SPAN, - TemplateElementValue { - raw: ast_builder.atom("--"), - cooked: None, - }, - false, - )], + [ + ast_builder.template_element( + SPAN, + TemplateElementValue { + raw: ast_builder.atom("--"), + cooked: None, + }, + false, + ), + ast_builder.template_element( + SPAN, + TemplateElementValue { + raw: ast_builder.atom(""), + cooked: None, + }, + true, + ), + ], ast_builder.allocator, ), oxc_allocator::Vec::from_array_in( diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_dynamic_logical_case-2.snap b/libs/extractor/src/snapshots/extractor__tests__extract_dynamic_logical_case-2.snap new file mode 100644 index 00000000..8ec1fa1c --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_dynamic_logical_case-2.snap @@ -0,0 +1,18 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { Box } from \"@devup-ui/core\";\n;\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Dynamic( + ExtractDynamicStyle { + property: "margin", + level: 0, + identifier: "a", + selector: None, + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_dynamic_logical_case-3.snap b/libs/extractor/src/snapshots/extractor__tests__extract_dynamic_logical_case-3.snap new file mode 100644 index 00000000..50ae5625 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_dynamic_logical_case-3.snap @@ -0,0 +1,27 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { Box } from \"@devup-ui/core\";\n;\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Dynamic( + ExtractDynamicStyle { + property: "margin", + level: 0, + identifier: "a", + selector: None, + style_order: None, + }, + ), + Dynamic( + ExtractDynamicStyle { + property: "margin", + level: 0, + identifier: "b", + selector: None, + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_dynamic_logical_case-4.snap b/libs/extractor/src/snapshots/extractor__tests__extract_dynamic_logical_case-4.snap new file mode 100644 index 00000000..b38ea20e --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_dynamic_logical_case-4.snap @@ -0,0 +1,18 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { Box } from \"@devup-ui/core\";\n;\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Dynamic( + ExtractDynamicStyle { + property: "margin", + level: 0, + identifier: "a", + selector: None, + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_dynamic_logical_case.snap b/libs/extractor/src/snapshots/extractor__tests__extract_dynamic_logical_case.snap new file mode 100644 index 00000000..edb8eee5 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_dynamic_logical_case.snap @@ -0,0 +1,18 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { Box } from \"@devup-ui/core\";\n;\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Dynamic( + ExtractDynamicStyle { + property: "margin", + level: 0, + identifier: "a", + selector: None, + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css-2.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css-2.snap new file mode 100644 index 00000000..a0ff5e10 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css-2.snap @@ -0,0 +1,25 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n div: {\n bg: \"blue\"\n }\n})\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "background", + value: "blue", + level: 0, + selector: Some( + Global( + "div", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\n\"\";\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css-3.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css-3.snap new file mode 100644 index 00000000..a1f31f08 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css-3.snap @@ -0,0 +1,25 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n [\"div\"]: {\n bg: \"yellow\"\n }\n})\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "background", + value: "yellow", + level: 0, + selector: Some( + Global( + "div", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\n\"\";\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css-4.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css-4.snap new file mode 100644 index 00000000..7414bf61 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css-4.snap @@ -0,0 +1,25 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n [`div`]: {\n bg: \"green\"\n }\n})\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "background", + value: "green", + level: 0, + selector: Some( + Global( + "div", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\n\"\";\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css.snap new file mode 100644 index 00000000..29617ddd --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css.snap @@ -0,0 +1,25 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n \"div\": {\n bg: \"red\"\n }\n})\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "background", + value: "red", + level: 0, + selector: Some( + Global( + "div", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\n\"\";\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_empty-2.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_empty-2.snap new file mode 100644 index 00000000..a91d8622 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_empty-2.snap @@ -0,0 +1,8 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n div: {},\n span: {}\n})\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: {}, + code: "(\"\");\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_empty-3.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_empty-3.snap new file mode 100644 index 00000000..86f8d5f7 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_empty-3.snap @@ -0,0 +1,8 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n div: ``\n})\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: {}, + code: "(\"\");\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_empty-4.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_empty-4.snap new file mode 100644 index 00000000..8d0efe5a --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_empty-4.snap @@ -0,0 +1,8 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n div: ``,\n span: ``\n})\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: {}, + code: "(\"\");\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_empty-5.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_empty-5.snap new file mode 100644 index 00000000..c75b80a7 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_empty-5.snap @@ -0,0 +1,8 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({})\n\"#, ExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: {}, + code: "(\"\");\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_empty-6.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_empty-6.snap new file mode 100644 index 00000000..4d70c073 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_empty-6.snap @@ -0,0 +1,8 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss()\n\"#, ExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: {}, + code: ";\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_empty.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_empty.snap new file mode 100644 index 00000000..8379b4c5 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_empty.snap @@ -0,0 +1,8 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n \"div\": {}\n})\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: {}, + code: "(\"\");\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_imports-2.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_imports-2.snap new file mode 100644 index 00000000..e0ee7a19 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_imports-2.snap @@ -0,0 +1,21 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n imports: [\"@devup-ui/core/css/global.css\", \"@devup-ui/core/css/global2.css\"]\n})\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Import( + ExtractImport { + url: "@devup-ui/core/css/global.css", + file: "test.tsx", + }, + ), + Import( + ExtractImport { + url: "@devup-ui/core/css/global2.css", + file: "test.tsx", + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\n\"\";\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_imports-3.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_imports-3.snap new file mode 100644 index 00000000..297babeb --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_imports-3.snap @@ -0,0 +1,21 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n imports: [`@devup-ui/core/css/global3.css`, `@devup-ui/core/css/global4.css`]\n})\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Import( + ExtractImport { + url: "@devup-ui/core/css/global3.css", + file: "test.tsx", + }, + ), + Import( + ExtractImport { + url: "@devup-ui/core/css/global4.css", + file: "test.tsx", + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\n\"\";\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_imports.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_imports.snap new file mode 100644 index 00000000..65d9d011 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_imports.snap @@ -0,0 +1,15 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n imports: [\"@devup-ui/core/css/global.css\"]\n})\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Import( + ExtractImport { + url: "@devup-ui/core/css/global.css", + file: "test.tsx", + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\n\"\";\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_selector-2.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_selector-2.snap new file mode 100644 index 00000000..1730a1e3 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_selector-2.snap @@ -0,0 +1,137 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n \"div\": {\n bg: \"red\",\n color: \"blue\",\n _hover: {\n bg: \"blue\",\n color: \"red\"\n }\n },\n \"span\": {\n bg: \"red\",\n color: \"blue\",\n _hover: {\n bg: \"blue\",\n color: \"red\"\n }\n }\n})\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "background", + value: "blue", + level: 0, + selector: Some( + Global( + "div:hover", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "background", + value: "blue", + level: 0, + selector: Some( + Global( + "span:hover", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "background", + value: "red", + level: 0, + selector: Some( + Global( + "div", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "background", + value: "red", + level: 0, + selector: Some( + Global( + "span", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "blue", + level: 0, + selector: Some( + Global( + "div", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "blue", + level: 0, + selector: Some( + Global( + "span", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: Some( + Global( + "div:hover", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: Some( + Global( + "span:hover", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\n\"\";\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_selector-3.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_selector-3.snap new file mode 100644 index 00000000..1730a1e3 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_selector-3.snap @@ -0,0 +1,137 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n \"div\": {\n bg: \"red\",\n color: \"blue\",\n _hover: {\n bg: \"blue\",\n color: \"red\"\n }\n },\n \"span\": {\n bg: \"red\",\n color: \"blue\",\n _hover: {\n bg: \"blue\",\n color: \"red\"\n }\n }\n})\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "background", + value: "blue", + level: 0, + selector: Some( + Global( + "div:hover", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "background", + value: "blue", + level: 0, + selector: Some( + Global( + "span:hover", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "background", + value: "red", + level: 0, + selector: Some( + Global( + "div", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "background", + value: "red", + level: 0, + selector: Some( + Global( + "span", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "blue", + level: 0, + selector: Some( + Global( + "div", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "blue", + level: 0, + selector: Some( + Global( + "span", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: Some( + Global( + "div:hover", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: Some( + Global( + "span:hover", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\n\"\";\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_selector-4.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_selector-4.snap new file mode 100644 index 00000000..6faa2d72 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_selector-4.snap @@ -0,0 +1,201 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n [\"div\"]: {\n bg: \"red\",\n color: \"blue\",\n _hover: {\n bg: \"blue\",\n color: \"red\"\n }\n },\n [\"span\"]: {\n bg: \"red\",\n color: \"blue\",\n _hover: {\n bg: \"blue\",\n color: \"red\"\n }\n },\n \"body[data-theme='dark']\": {\n bg: \"red\",\n color: \"blue\",\n _hover: {\n bg: \"blue\",\n color: \"red\"\n }\n }\n})\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "background", + value: "blue", + level: 0, + selector: Some( + Global( + "body[data-theme='dark']:hover", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "background", + value: "blue", + level: 0, + selector: Some( + Global( + "div:hover", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "background", + value: "blue", + level: 0, + selector: Some( + Global( + "span:hover", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "background", + value: "red", + level: 0, + selector: Some( + Global( + "body[data-theme='dark']", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "background", + value: "red", + level: 0, + selector: Some( + Global( + "div", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "background", + value: "red", + level: 0, + selector: Some( + Global( + "span", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "blue", + level: 0, + selector: Some( + Global( + "body[data-theme='dark']", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "blue", + level: 0, + selector: Some( + Global( + "div", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "blue", + level: 0, + selector: Some( + Global( + "span", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: Some( + Global( + "body[data-theme='dark']:hover", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: Some( + Global( + "div:hover", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: Some( + Global( + "span:hover", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\n\"\";\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_selector.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_selector.snap new file mode 100644 index 00000000..f6e2a2cf --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_selector.snap @@ -0,0 +1,73 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n \"div\": {\n bg: \"red\",\n color: \"blue\",\n _hover: {\n bg: \"blue\",\n color: \"red\"\n }\n }\n})\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "background", + value: "blue", + level: 0, + selector: Some( + Global( + "div:hover", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "background", + value: "red", + level: 0, + selector: Some( + Global( + "div", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "blue", + level: 0, + selector: Some( + Global( + "div", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: Some( + Global( + "div:hover", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\n\"\";\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_template_literal-2.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_template_literal-2.snap new file mode 100644 index 00000000..a05c5c3d --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_template_literal-2.snap @@ -0,0 +1,15 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\n globalCss`\n div {\n background-color: red;\n color: blue;\n }\n `\n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Css( + ExtractCss { + css: "div{background-color:red;color:blue}", + file: "test.tsx", + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\n;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_template_literal.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_template_literal.snap new file mode 100644 index 00000000..b8b13e50 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_template_literal.snap @@ -0,0 +1,25 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\n globalCss({\n \"div\": `\n background-color: red\n `\n })\n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "backgroundColor", + value: "red", + level: 0, + selector: Some( + Global( + "div", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\n\"\";\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_logical_case-3.snap b/libs/extractor/src/snapshots/extractor__tests__extract_logical_case-3.snap index a85c0b5f..ee281de9 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_logical_case-3.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_logical_case-3.snap @@ -13,6 +13,15 @@ ToBTreeSet { style_order: None, }, ), + Dynamic( + ExtractDynamicStyle { + property: "margin", + level: 0, + identifier: "a", + selector: None, + style_order: None, + }, + ), }, - code: "import \"@devup-ui/core/devup-ui.css\";\n
;\n", + code: "import \"@devup-ui/core/devup-ui.css\";\n
;\n", } diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_same_dynamic_value_conditional_style_props-2.snap b/libs/extractor/src/snapshots/extractor__tests__extract_same_dynamic_value_conditional_style_props-2.snap new file mode 100644 index 00000000..4dc4563a --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_same_dynamic_value_conditional_style_props-2.snap @@ -0,0 +1,18 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { Box } from \"@devup-ui/core\";\n;\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Dynamic( + ExtractDynamicStyle { + property: "margin", + level: 0, + identifier: "`${a}`", + selector: None, + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_same_dynamic_value_conditional_style_props.snap b/libs/extractor/src/snapshots/extractor__tests__extract_same_dynamic_value_conditional_style_props.snap new file mode 100644 index 00000000..56ac0a8d --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_same_dynamic_value_conditional_style_props.snap @@ -0,0 +1,18 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { Box } from \"@devup-ui/core\";\n;\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Dynamic( + ExtractDynamicStyle { + property: "margin", + level: 0, + identifier: "a", + selector: None, + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_same_value_conditional_style_props-2.snap b/libs/extractor/src/snapshots/extractor__tests__extract_same_value_conditional_style_props-2.snap new file mode 100644 index 00000000..e8812702 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_same_value_conditional_style_props-2.snap @@ -0,0 +1,18 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { Box } from \"@devup-ui/core\";\n;\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "margin", + value: "16px", + level: 0, + selector: None, + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_same_value_conditional_style_props-3.snap b/libs/extractor/src/snapshots/extractor__tests__extract_same_value_conditional_style_props-3.snap new file mode 100644 index 00000000..044a4506 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_same_value_conditional_style_props-3.snap @@ -0,0 +1,18 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { Box } from \"@devup-ui/core\";\n;\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "margin", + value: "4px", + level: 0, + selector: None, + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_same_value_conditional_style_props-4.snap b/libs/extractor/src/snapshots/extractor__tests__extract_same_value_conditional_style_props-4.snap new file mode 100644 index 00000000..a28ab61c --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_same_value_conditional_style_props-4.snap @@ -0,0 +1,18 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { Box } from \"@devup-ui/core\";\n;\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "margin", + value: "1px", + level: 0, + selector: None, + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_same_value_conditional_style_props-5.snap b/libs/extractor/src/snapshots/extractor__tests__extract_same_value_conditional_style_props-5.snap new file mode 100644 index 00000000..227d5e3a --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_same_value_conditional_style_props-5.snap @@ -0,0 +1,18 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { Box } from \"@devup-ui/core\";\n;\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "margin", + value: "1px", + level: 0, + selector: None, + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_same_value_conditional_style_props-6.snap b/libs/extractor/src/snapshots/extractor__tests__extract_same_value_conditional_style_props-6.snap new file mode 100644 index 00000000..ad6c9f20 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_same_value_conditional_style_props-6.snap @@ -0,0 +1,18 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { Box } from \"@devup-ui/core\";\n;\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "margin", + value: "1px", + level: 0, + selector: None, + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_same_value_conditional_style_props.snap b/libs/extractor/src/snapshots/extractor__tests__extract_same_value_conditional_style_props.snap new file mode 100644 index 00000000..e78858bd --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_same_value_conditional_style_props.snap @@ -0,0 +1,18 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { Box } from \"@devup-ui/core\";\n;\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "margin", + value: "4px", + level: 0, + selector: None, + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_selector_with_literal-2.snap b/libs/extractor/src/snapshots/extractor__tests__extract_selector_with_literal-2.snap new file mode 100644 index 00000000..bd486921 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_selector_with_literal-2.snap @@ -0,0 +1,22 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {Box} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "backgroundColor", + value: "red", + level: 0, + selector: Some( + Selector( + "&:hover", + ), + ), + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_selector_with_literal.snap b/libs/extractor/src/snapshots/extractor__tests__extract_selector_with_literal.snap new file mode 100644 index 00000000..2c511645 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_selector_with_literal.snap @@ -0,0 +1,22 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {Box} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "backgroundColor", + value: "red", + level: 0, + selector: Some( + Selector( + "&:hover", + ), + ), + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_selector_with_responsive-3.snap b/libs/extractor/src/snapshots/extractor__tests__extract_selector_with_responsive-3.snap new file mode 100644 index 00000000..fc664d14 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_selector_with_responsive-3.snap @@ -0,0 +1,61 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr\"import {Box} from '@devup-ui/core'\n \n \",\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "marginLeft", + value: "10px", + level: 0, + selector: Some( + Selector( + "&:hover", + ), + ), + style_order: None, + }, + ), + Static( + ExtractStaticStyle { + property: "marginLeft", + value: "20px", + level: 1, + selector: Some( + Selector( + "&:hover", + ), + ), + style_order: None, + }, + ), + Static( + ExtractStaticStyle { + property: "marginRight", + value: "10px", + level: 0, + selector: Some( + Selector( + "&:hover", + ), + ), + style_order: None, + }, + ), + Static( + ExtractStaticStyle { + property: "marginRight", + value: "20px", + level: 1, + selector: Some( + Selector( + "&:hover", + ), + ), + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_static_css_class_name_props-10.snap b/libs/extractor/src/snapshots/extractor__tests__extract_static_css_class_name_props-10.snap new file mode 100644 index 00000000..8cd3a8ff --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_static_css_class_name_props-10.snap @@ -0,0 +1,87 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { css } from \"@devup-ui/core\";\n;\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "backgroundColor", + value: "green", + level: 1, + selector: Some( + Selector( + "&:hover", + ), + ), + style_order: None, + }, + ), + Static( + ExtractStaticStyle { + property: "backgroundColor", + value: "red", + level: 0, + selector: Some( + Selector( + "&:hover", + ), + ), + style_order: None, + }, + ), + Static( + ExtractStaticStyle { + property: "backgroundColor", + value: "red", + level: 2, + selector: Some( + Selector( + "&:hover", + ), + ), + style_order: None, + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "blue", + level: 0, + selector: Some( + Selector( + "&:hover", + ), + ), + style_order: None, + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "blue", + level: 2, + selector: Some( + Selector( + "&:hover", + ), + ), + style_order: None, + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "yellow", + level: 1, + selector: Some( + Selector( + "&:hover", + ), + ), + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\n;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_static_css_class_name_props-2.snap b/libs/extractor/src/snapshots/extractor__tests__extract_static_css_class_name_props-2.snap index 1c42ee64..afbec06e 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_static_css_class_name_props-2.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_static_css_class_name_props-2.snap @@ -4,9 +4,13 @@ expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { css as c } fro --- ToBTreeSet { styles: { - Css( - ExtractCss { - css: "background-color: red;", + Static( + ExtractStaticStyle { + property: "backgroundColor", + value: "red", + level: 0, + selector: None, + style_order: None, }, ), }, diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_static_css_class_name_props-9.snap b/libs/extractor/src/snapshots/extractor__tests__extract_static_css_class_name_props-9.snap new file mode 100644 index 00000000..5e38ad93 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_static_css_class_name_props-9.snap @@ -0,0 +1,35 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { css } from \"@devup-ui/core\";\n;\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "backgroundColor", + value: "red", + level: 0, + selector: Some( + Selector( + "&:hover", + ), + ), + style_order: None, + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "blue", + level: 0, + selector: Some( + Selector( + "&:hover", + ), + ), + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\n;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_static_css_class_name_props.snap b/libs/extractor/src/snapshots/extractor__tests__extract_static_css_class_name_props.snap index 3ca5832d..6c2345ea 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_static_css_class_name_props.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_static_css_class_name_props.snap @@ -4,9 +4,13 @@ expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { css } from \"@ --- ToBTreeSet { styles: { - Css( - ExtractCss { - css: "background-color: red;", + Static( + ExtractStaticStyle { + property: "backgroundColor", + value: "red", + level: 0, + selector: None, + style_order: None, }, ), }, diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_style_props_with_var_css.snap b/libs/extractor/src/snapshots/extractor__tests__extract_style_props_with_var_css.snap new file mode 100644 index 00000000..c5e5b223 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_style_props_with_var_css.snap @@ -0,0 +1,18 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr\"import {css} from '@devup-ui/core'\n const newCss=css;\n
\n \",\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: None, + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\nconst newCss = css;\n
;\n", +} diff --git a/libs/extractor/src/style_extractor.rs b/libs/extractor/src/style_extractor.rs index da4d5c0a..6bd7db5e 100644 --- a/libs/extractor/src/style_extractor.rs +++ b/libs/extractor/src/style_extractor.rs @@ -1,4 +1,11 @@ -use crate::utils::{expression_to_code, get_number_by_literal_expression, is_special_property}; +use crate::css_utils::css_to_style; +use crate::extract_style::extract_dynamic_style::ExtractDynamicStyle; +use crate::extract_style::extract_import::ExtractImport; +use crate::extract_style::extract_static_style::ExtractStaticStyle; +use crate::extract_style::extract_style_value::ExtractStyleValue; +use crate::utils::{ + expression_to_code, get_number_by_literal_expression, is_same_expression, is_special_property, +}; use crate::{ExtractStyleProp, utils}; use oxc_allocator::CloneIn; use oxc_ast::ast::{ @@ -7,9 +14,7 @@ use oxc_ast::ast::{ }; use std::collections::BTreeMap; -use crate::extract_style::ExtractStyleValue::{Dynamic, Static, Typography}; -use crate::extract_style::{ExtractDynamicStyle, ExtractStaticStyle}; -use css::StyleSelector; +use css::style_selector::StyleSelector; use oxc_ast::AstBuilder; use oxc_span::SPAN; use oxc_syntax::operator::{BinaryOperator, LogicalOperator}; @@ -35,6 +40,12 @@ pub enum ExtractResult<'a> { }, } +#[derive(Debug)] +pub struct GlobalExtractResult<'a> { + pub styles: Vec>, + pub style_order: Option, +} + pub fn extract_style_from_jsx_attr<'a>( ast_builder: &AstBuilder<'a>, name: &str, @@ -215,46 +226,6 @@ pub fn extract_style_from_expression<'a>( style_order: None, style_vars: None, }; - - // return match expression { - // Expression::StringLiteral(ident) => ExtractResult::ChangeTag( - // Expression::StringLiteral(ident.clone_in(ast_builder.allocator)), - // ), - // Expression::TemplateLiteral(tmp) => { - // if tmp.quasis.len() == 1 { - // ExtractResult::ChangeTag(Expression::TemplateLiteral( - // tmp.clone_in(ast_builder.allocator), - // )) - // } else { - // ExtractResult::Remove - // } - // } - // Expression::ConditionalExpression(ref mut conditional) => { - // let mut consequent = None; - // let mut alternate = None; - // if let ExtractResult::ExtractStyle(mut styles) = extract_style_from_expression( - // ast_builder, - // None, - // &mut conditional.consequent, - // level, - // None, - // ) { - // consequent = Some(Box::new(styles.remove(0))); - // } - // if let ExtractResult::ExtractStyle(mut styles) = extract_style_from_expression( - // ast_builder, - // None, - // &mut conditional.alternate, - // level, - // selector, - // ) { - // alternate = Some(Box::new(styles.remove(0))); - // } - // ExtractResult::ChangeTag( - // ) - // } - // _ => ExtractResult::Remove, - // }; } if name == "selectors" && let Expression::ObjectExpression(obj) = expression @@ -300,7 +271,7 @@ pub fn extract_style_from_expression<'a>( expression, level, Some(&if let Some(selector) = selector { - [&selector.to_string(), new_selector].into() + (selector, new_selector).into() } else { new_selector.into() }), @@ -309,29 +280,37 @@ pub fn extract_style_from_expression<'a>( typo = name == "typography"; } if let Some(value) = utils::get_string_by_literal_expression(expression) { - name.map(|name| ExtractResult::Extract { - style_order: None, - style_vars: None, - tag: None, - styles: Some(vec![ExtractStyleProp::Static(if typo { - Typography(value.to_string()) - } else { - Static(ExtractStaticStyle::new( - name, - &value, - level, - selector.cloned(), - )) - })]), - }) - .unwrap_or(ExtractResult::Maintain) + if let Some(name) = name { + ExtractResult::Extract { + style_order: None, + style_vars: None, + tag: None, + styles: Some(vec![ExtractStyleProp::Static(if typo { + ExtractStyleValue::Typography(value.to_string()) + } else { + ExtractStyleValue::Static(ExtractStaticStyle::new( + name, + &value, + level, + selector.cloned(), + )) + })]), + } + } else { + ExtractResult::Extract { + style_order: None, + style_vars: None, + tag: None, + styles: Some(css_to_style(&value, level, &selector)), + } + } } else { match expression { Expression::UnaryExpression(_) | Expression::BinaryExpression(_) | Expression::StaticMemberExpression(_) | Expression::CallExpression(_) => ExtractResult::Extract { - styles: Some(vec![ExtractStyleProp::Static(Dynamic( + styles: Some(vec![ExtractStyleProp::Static(ExtractStyleValue::Dynamic( ExtractDynamicStyle::new( name.unwrap(), level, @@ -358,9 +337,9 @@ pub fn extract_style_from_expression<'a>( if tmp.quasis.len() == 1 { ExtractResult::Extract { styles: Some(vec![ExtractStyleProp::Static(if typo { - Typography(tmp.quasis[0].value.raw.to_string()) + ExtractStyleValue::Typography(tmp.quasis[0].value.raw.to_string()) } else { - Static(ExtractStaticStyle::new( + ExtractStyleValue::Static(ExtractStaticStyle::new( name, &tmp.quasis[0].value.raw, level, @@ -406,14 +385,14 @@ pub fn extract_style_from_expression<'a>( } } else { ExtractResult::Extract { - styles: Some(vec![ExtractStyleProp::Static(Dynamic( - ExtractDynamicStyle::new( + styles: Some(vec![ExtractStyleProp::Static( + ExtractStyleValue::Dynamic(ExtractDynamicStyle::new( name, level, &expression_to_code(expression), selector.cloned(), - ), - ))]), + )), + )]), tag: None, style_order: None, style_vars: None, @@ -468,14 +447,14 @@ pub fn extract_style_from_expression<'a>( } } else { ExtractResult::Extract { - styles: Some(vec![ExtractStyleProp::Static(Dynamic( - ExtractDynamicStyle::new( + styles: Some(vec![ExtractStyleProp::Static( + ExtractStyleValue::Dynamic(ExtractDynamicStyle::new( name, level, &identifier.name, selector.cloned(), - ), - ))]), + )), + )]), tag: None, style_order: None, style_vars: None, @@ -538,7 +517,19 @@ pub fn extract_style_from_expression<'a>( ast_builder.expression_identifier(SPAN, "undefined"), ), ), - consequent: None, + consequent: match extract_style_from_expression( + ast_builder, + name, + &mut logical.left, + level, + selector, + ) { + ExtractResult::Extract { + styles: Some(styles), + .. + } => Some(Box::new(ExtractStyleProp::StaticArray(styles))), + _ => None, + }, alternate: res, }]), tag: None, @@ -583,47 +574,59 @@ pub fn extract_style_from_expression<'a>( } } } - Expression::ConditionalExpression(conditional) => ExtractResult::Extract { - styles: Some(vec![ExtractStyleProp::Conditional { - condition: conditional.test.clone_in(ast_builder.allocator), - consequent: if let ExtractResult::Extract { - styles: Some(styles), - .. - } = extract_style_from_expression( + Expression::ConditionalExpression(conditional) => { + if is_same_expression(&conditional.consequent, &conditional.alternate) { + extract_style_from_expression( ast_builder, name, &mut conditional.consequent, level, selector, - ) { - Some(Box::new(ExtractStyleProp::StaticArray(styles))) - } else { - None - }, - alternate: if let ExtractResult::Extract { - styles: Some(styles), - .. - } = extract_style_from_expression( - ast_builder, - name, - &mut conditional.alternate, - level, - selector, - ) { - Some(Box::new(ExtractStyleProp::StaticArray(styles))) - } else { - None - }, - }]), - tag: None, - style_order: None, - style_vars: None, - }, + ) + } else { + ExtractResult::Extract { + styles: Some(vec![ExtractStyleProp::Conditional { + condition: conditional.test.clone_in(ast_builder.allocator), + consequent: if let ExtractResult::Extract { + styles: Some(styles), + .. + } = extract_style_from_expression( + ast_builder, + name, + &mut conditional.consequent, + level, + selector, + ) { + Some(Box::new(ExtractStyleProp::StaticArray(styles))) + } else { + None + }, + alternate: if let ExtractResult::Extract { + styles: Some(styles), + .. + } = extract_style_from_expression( + ast_builder, + name, + &mut conditional.alternate, + level, + selector, + ) { + Some(Box::new(ExtractStyleProp::StaticArray(styles))) + } else { + None + }, + }]), + tag: None, + style_order: None, + style_vars: None, + } + } + } Expression::ObjectExpression(obj) => { let mut props = vec![]; for p in obj.properties.iter_mut() { - if let ObjectPropertyKind::ObjectProperty(o) = p { - if let ExtractResult::Extract { + if let ObjectPropertyKind::ObjectProperty(o) = p + && let ExtractResult::Extract { styles: Some(mut styles), .. } = extract_style_from_expression( @@ -632,10 +635,10 @@ pub fn extract_style_from_expression<'a>( &mut o.value, level, selector, - ) { - props.append(&mut styles); - } - }; + ) + { + props.append(&mut styles); + } } ExtractResult::Extract { styles: Some(props), @@ -686,8 +689,8 @@ fn extract_style_from_member_expression<'a>( etc = Some(sp.argument.clone_in(ast_builder.allocator)); continue; } - if idx as f64 == num { - if let ExtractResult::Extract { + if idx as f64 == num + && let ExtractResult::Extract { styles: Some(styles), .. } = extract_style_from_expression( @@ -696,31 +699,33 @@ fn extract_style_from_member_expression<'a>( p.to_expression_mut(), level, selector, - ) { - return ExtractResult::Extract { - styles: Some(styles), - tag: None, - style_order: None, - style_vars: None, - }; - } + ) + { + return ExtractResult::Extract { + styles: Some(styles), + tag: None, + style_order: None, + style_vars: None, + }; } } return ExtractResult::Extract { styles: etc.map(|etc| { - vec![ExtractStyleProp::Static(Dynamic(ExtractDynamicStyle::new( - name.unwrap(), - level, - &expression_to_code(&Expression::ComputedMemberExpression( - ast_builder.alloc_computed_member_expression( - SPAN, - etc, - mem_expression.clone_in(ast_builder.allocator), - false, - ), - )), - selector.cloned(), - )))] + vec![ExtractStyleProp::Static(ExtractStyleValue::Dynamic( + ExtractDynamicStyle::new( + name.unwrap(), + level, + &expression_to_code(&Expression::ComputedMemberExpression( + ast_builder.alloc_computed_member_expression( + SPAN, + etc, + mem_expression.clone_in(ast_builder.allocator), + false, + ), + )), + selector.cloned(), + ), + ))] }), tag: None, style_order: None, @@ -733,19 +738,21 @@ fn extract_style_from_member_expression<'a>( if let ArrayExpressionElement::SpreadElement(sp) = p { map.insert( idx.to_string(), - Box::new(ExtractStyleProp::Static(Dynamic(ExtractDynamicStyle::new( - name.unwrap(), - level, - &expression_to_code(&Expression::ComputedMemberExpression( - ast_builder.alloc_computed_member_expression( - SPAN, - sp.argument.clone_in(ast_builder.allocator), - mem_expression.clone_in(ast_builder.allocator), - false, - ), - )), - selector.cloned(), - )))), + Box::new(ExtractStyleProp::Static(ExtractStyleValue::Dynamic( + ExtractDynamicStyle::new( + name.unwrap(), + level, + &expression_to_code(&Expression::ComputedMemberExpression( + ast_builder.alloc_computed_member_expression( + SPAN, + sp.argument.clone_in(ast_builder.allocator), + mem_expression.clone_in(ast_builder.allocator), + false, + ), + )), + selector.cloned(), + ), + ))), ); } else if let ExtractResult::Extract { styles: Some(styles), @@ -784,26 +791,25 @@ fn extract_style_from_member_expression<'a>( let mut etc = None; for p in obj.properties.iter_mut() { if let ObjectPropertyKind::ObjectProperty(o) = p { - if let PropertyKey::StaticIdentifier(ref pk) = o.key { - if pk.name == k { - if let ExtractResult::Extract { - styles: Some(styles), - .. - } = extract_style_from_expression( - ast_builder, - name, - &mut o.value, - level, - selector, - ) { - return ExtractResult::Extract { - styles: Some(styles), - tag: None, - style_order: None, - style_vars: None, - }; - } - } + if let PropertyKey::StaticIdentifier(ref pk) = o.key + && pk.name == k + && let ExtractResult::Extract { + styles: Some(styles), + .. + } = extract_style_from_expression( + ast_builder, + name, + &mut o.value, + level, + selector, + ) + { + return ExtractResult::Extract { + styles: Some(styles), + tag: None, + style_order: None, + style_vars: None, + }; } } else if let ObjectPropertyKind::SpreadProperty(sp) = p { etc = Some(sp.argument.clone_in(ast_builder.allocator)); @@ -819,8 +825,8 @@ fn extract_style_from_member_expression<'a>( style_vars: None, }; } - Some(etc) => { - ret.push(ExtractStyleProp::Static(Dynamic(ExtractDynamicStyle::new( + Some(etc) => ret.push(ExtractStyleProp::Static(ExtractStyleValue::Dynamic( + ExtractDynamicStyle::new( name.unwrap(), level, &expression_to_code(&Expression::ComputedMemberExpression( @@ -832,33 +838,31 @@ fn extract_style_from_member_expression<'a>( ), )), selector.cloned(), - )))) - } + ), + ))), } } for p in obj.properties.iter_mut() { - if let ObjectPropertyKind::ObjectProperty(o) = p { - if let PropertyKey::StaticIdentifier(_) + if let ObjectPropertyKind::ObjectProperty(o) = p + && let PropertyKey::StaticIdentifier(_) | PropertyKey::NumericLiteral(_) | PropertyKey::StringLiteral(_) = o.key - { - if let ExtractResult::Extract { - styles: Some(styles), - .. - } = extract_style_from_expression( - ast_builder, - name, - &mut o.value, - level, - selector, - ) { - map.insert( - o.key.name().unwrap().to_string(), - Box::new(ExtractStyleProp::StaticArray(styles)), - ); - } - } + && let ExtractResult::Extract { + styles: Some(styles), + .. + } = extract_style_from_expression( + ast_builder, + name, + &mut o.value, + level, + selector, + ) + { + map.insert( + o.key.name().unwrap().to_string(), + Box::new(ExtractStyleProp::StaticArray(styles)), + ); } } ret.push(ExtractStyleProp::MemberExpression { @@ -868,19 +872,21 @@ fn extract_style_from_member_expression<'a>( } Expression::Identifier(_) => { if let Some(name) = name { - ret.push(ExtractStyleProp::Static(Dynamic(ExtractDynamicStyle::new( - name, - level, - &expression_to_code(&Expression::ComputedMemberExpression( - ast_builder.alloc_computed_member_expression( - SPAN, - mem.object.clone_in(ast_builder.allocator), - mem_expression.clone_in(ast_builder.allocator), - false, - ), - )), - selector.cloned(), - )))) + ret.push(ExtractStyleProp::Static(ExtractStyleValue::Dynamic( + ExtractDynamicStyle::new( + name, + level, + &expression_to_code(&Expression::ComputedMemberExpression( + ast_builder.alloc_computed_member_expression( + SPAN, + mem.object.clone_in(ast_builder.allocator), + mem_expression.clone_in(ast_builder.allocator), + false, + ), + )), + selector.cloned(), + ), + ))) } } _ => {} @@ -893,3 +899,74 @@ fn extract_style_from_member_expression<'a>( style_vars: None, } } + +pub fn extract_global_style_from_expression<'a>( + ast_builder: &AstBuilder<'a>, + expression: &mut Expression<'a>, + file: &str, +) -> GlobalExtractResult<'a> { + let mut styles = vec![]; + + if let Expression::ObjectExpression(obj) = expression { + for p in obj.properties.iter_mut() { + if let ObjectPropertyKind::ObjectProperty(o) = p { + let name = if let PropertyKey::StaticIdentifier(ident) = &o.key { + ident.name.to_string() + } else if let PropertyKey::StringLiteral(s) = &o.key { + s.value.to_string() + } else if let PropertyKey::TemplateLiteral(t) = &o.key { + t.quasis + .iter() + .map(|q| q.value.raw.as_str()) + .collect::>() + .join("") + } else { + continue; + }; + + if name == "imports" { + if let Expression::ArrayExpression(arr) = &o.value { + for p in arr.elements.iter() { + styles.push(ExtractStyleProp::Static(ExtractStyleValue::Import( + ExtractImport { + url: if let ArrayExpressionElement::StringLiteral(s) = p { + s.value.trim().to_string() + } else if let ArrayExpressionElement::TemplateLiteral(t) = p { + t.quasis + .iter() + .map(|q| q.value.raw.as_str()) + .collect::>() + .join("") + .trim() + .to_string() + } else { + continue; + }, + file: file.to_string(), + }, + ))); + } + } + continue; + } + + if let ExtractResult::Extract { + styles: Some(_styles), + .. + } = extract_style_from_expression( + ast_builder, + None, + &mut o.value, + 0, + Some(&StyleSelector::Global(name.clone(), file.to_string())), + ) { + styles.extend(_styles); + } + } + } + } + GlobalExtractResult { + styles, + style_order: None, + } +} diff --git a/libs/extractor/src/utils.rs b/libs/extractor/src/utils.rs index 29d7433f..09231c67 100644 --- a/libs/extractor/src/utils.rs +++ b/libs/extractor/src/utils.rs @@ -7,13 +7,13 @@ use oxc_syntax::operator::UnaryOperator; use phf::phf_set; /// Convert a value to a pixel value -pub fn convert_value(value: &str) -> String { +pub(super) fn convert_value(value: &str) -> String { value .parse::() .map_or_else(|_| value.to_string(), |num| format!("{}px", num * 4.0)) } -pub fn expression_to_code(expression: &Expression) -> String { +pub(super) fn expression_to_code(expression: &Expression) -> String { let allocator = Allocator::default(); let mut parsed = Parser::new(&allocator, "", SourceType::d_ts()).parse(); parsed.program.body.insert( @@ -27,6 +27,26 @@ pub fn expression_to_code(expression: &Expression) -> String { code[0..code.len() - 2].to_string() } +pub(super) fn is_same_expression<'a>(a: &Expression<'a>, b: &Expression<'a>) -> bool { + match (a, b) { + (Expression::StringLiteral(a), Expression::StringLiteral(b)) => a.value == b.value, + (Expression::TemplateLiteral(a), Expression::TemplateLiteral(b)) => { + a.quasis.len() == b.quasis.len() + && a.expressions.len() == b.expressions.len() + && a.quasis + .iter() + .zip(b.quasis.iter()) + .all(|(a, b)| a.value.raw == b.value.raw && a.tail == b.tail) + && a.expressions + .iter() + .zip(b.expressions.iter()) + .all(|(a, b)| is_same_expression(a, b)) + } + (Expression::Identifier(a), Expression::Identifier(b)) => a.name == b.name, + _ => false, + } +} + static SPECIAL_PROPERTIES: phf::Set<&str> = phf_set! { "style", "className", @@ -158,14 +178,14 @@ static SPECIAL_PROPERTIES: phf::Set<&str> = phf_set! { "selectedOptions" }; -pub fn is_special_property(name: &str) -> bool { +pub(super) fn is_special_property(name: &str) -> bool { name.starts_with("on") || name.starts_with("data-") || name.starts_with("aria-") || SPECIAL_PROPERTIES.contains(name) } -pub fn jsx_expression_to_number(expr: &JSXAttributeValue) -> Option { +pub(super) fn jsx_expression_to_number(expr: &JSXAttributeValue) -> Option { match expr { JSXAttributeValue::StringLiteral(sl) => get_number_by_literal_expression( &Expression::StringLiteral(sl.clone_in(&Allocator::default())), @@ -177,7 +197,7 @@ pub fn jsx_expression_to_number(expr: &JSXAttributeValue) -> Option { } } -pub fn get_number_by_literal_expression(expr: &Expression) -> Option { +pub(super) fn get_number_by_literal_expression(expr: &Expression) -> Option { match expr { Expression::ParenthesizedExpression(parenthesized) => { get_number_by_literal_expression(&parenthesized.expression) @@ -202,7 +222,7 @@ pub fn get_number_by_literal_expression(expr: &Expression) -> Option { } } -pub fn get_string_by_literal_expression(expr: &Expression) -> Option { +pub(super) fn get_string_by_literal_expression(expr: &Expression) -> Option { get_number_by_literal_expression(expr) .map(|num| num.to_string()) .or_else(|| match expr { diff --git a/libs/extractor/src/visit.rs b/libs/extractor/src/visit.rs index 7c8584bc..3ffdc5dc 100644 --- a/libs/extractor/src/visit.rs +++ b/libs/extractor/src/visit.rs @@ -1,11 +1,14 @@ use crate::component::ExportVariableKind; -use crate::extract_style::ExtractCss; +use crate::css_type::CssType; +use crate::css_utils::{css_to_style, optimize_css_block}; +use crate::extract_style::extract_css::ExtractCss; use crate::gen_class_name::gen_class_names; use crate::prop_modify_utils::{modify_prop_object, modify_props}; use crate::style_extractor::{ - ExtractResult, extract_style_from_expression, extract_style_from_jsx_attr, + ExtractResult, GlobalExtractResult, extract_global_style_from_expression, + extract_style_from_expression, extract_style_from_jsx_attr, }; -use crate::{ExtractStyleProp, ExtractStyleValue, StyleProperty}; +use crate::{ExtractStyleProp, ExtractStyleValue}; use css::short_to_long; use oxc_allocator::{Allocator, CloneIn}; use oxc_ast::ast::ImportDeclarationSpecifier::{self, ImportSpecifier}; @@ -13,13 +16,13 @@ use oxc_ast::ast::JSXAttributeItem::Attribute; use oxc_ast::ast::JSXAttributeName::Identifier; use oxc_ast::ast::{ Argument, BindingPatternKind, CallExpression, Expression, ImportDeclaration, - ImportOrExportKind, JSXAttributeValue, JSXElement, Program, PropertyKey, - Statement, VariableDeclarator, WithClause, + ImportOrExportKind, JSXAttributeValue, JSXElement, Program, PropertyKey, Statement, + VariableDeclarator, WithClause, }; use oxc_ast_visit::VisitMut; use oxc_ast_visit::walk_mut::{ walk_call_expression, walk_expression, walk_import_declaration, walk_jsx_element, walk_program, - walk_variable_declarator, + walk_variable_declarator, walk_variable_declarators, }; use strum::IntoEnumIterator; @@ -27,23 +30,26 @@ use crate::utils::jsx_expression_to_number; use oxc_ast::AstBuilder; use oxc_span::SPAN; use std::collections::{HashMap, HashSet}; +use std::rc::Rc; pub struct DevupVisitor<'a> { pub ast: AstBuilder<'a>, + filename: String, imports: HashMap, import_object: Option, jsx_imports: HashMap, - css_imports: HashMap, + css_imports: HashMap>, jsx_object: Option, package: String, - css_file: String, + pub css_file: String, pub styles: HashSet, } impl<'a> DevupVisitor<'a> { - pub fn new(allocator: &'a Allocator, package: &str, css_file: &str) -> Self { + pub fn new(allocator: &'a Allocator, filename: &str, package: &str, css_file: &str) -> Self { Self { ast: AstBuilder::new(allocator), + filename: filename.to_string(), imports: HashMap::new(), jsx_imports: HashMap::new(), package: package.to_string(), @@ -57,6 +63,25 @@ impl<'a> DevupVisitor<'a> { } impl<'a> VisitMut<'a> for DevupVisitor<'a> { + fn visit_variable_declarators( + &mut self, + it: &mut oxc_allocator::Vec<'a, VariableDeclarator<'a>>, + ) { + for v in it.iter() { + if let VariableDeclarator { + id, + init: Some(Expression::Identifier(ident)), + .. + } = v + && let Some(css_import_key) = self.css_imports.get(ident.name.as_str()) + && let Some(name) = id.get_binding_identifier().map(|id| id.name.to_string()) + { + self.css_imports.insert(name, css_import_key.clone()); + } + } + walk_variable_declarators(self, it); + } + fn visit_program(&mut self, it: &mut Program<'a>) { walk_program(self, it); if !self.styles.is_empty() { @@ -100,51 +125,86 @@ impl<'a> VisitMut<'a> for DevupVisitor<'a> { }; if let Some(css_import_key) = css_import_key - && self.css_imports.contains_key(&css_import_key) + && let Some(css_type) = self.css_imports.get(&css_import_key) { - if call.arguments.is_empty() { - *it = self - .ast - .expression_string_literal(SPAN, self.ast.atom(""), None); - } else if call.arguments.len() == 1 { - if let ExtractResult::Extract { - styles: Some(mut styles), - style_order, - .. - } = extract_style_from_expression( - &self.ast, - None, - call.arguments[0].to_expression_mut(), - 0, - None, - ) { - // css can not reachable - // ExtractResult::ExtractStyleWithChangeTag(styles, _) - let class_name = gen_class_names(&self.ast, &mut styles, style_order); + if call.arguments.len() != 1 { + *it = match css_type.as_ref() { + CssType::Css => { + self.ast + .expression_string_literal(SPAN, self.ast.atom(""), None) + } + CssType::GlobalCss => { + self.ast.expression_identifier(SPAN, self.ast.atom("")) + } + }; + } else { + *it = match css_type.as_ref() { + CssType::Css => { + if let ExtractResult::Extract { + styles: Some(mut styles), + style_order, + .. + } = extract_style_from_expression( + &self.ast, + None, + call.arguments[0].to_expression_mut(), + 0, + None, + ) { + // css can not reachable + // ExtractResult::ExtractStyleWithChangeTag(styles, _) + let class_name = + gen_class_names(&self.ast, &mut styles, style_order); - self.styles.extend( - styles - .into_iter() - // already set style order - .flat_map(|ex| ex.extract()), - ); - if let Some(cls) = class_name { - *it = cls; - } else { - *it = self - .ast - .expression_string_literal(SPAN, self.ast.atom(""), None); + self.styles.extend( + styles + .into_iter() + // already set style order + .flat_map(|ex| ex.extract()), + ); + if let Some(cls) = class_name { + cls + } else { + self.ast.expression_string_literal( + SPAN, + self.ast.atom(""), + None, + ) + } + } else { + self.ast + .expression_string_literal(SPAN, self.ast.atom(""), None) + } + } + CssType::GlobalCss => { + let GlobalExtractResult { + styles, + style_order, + } = extract_global_style_from_expression( + &self.ast, + call.arguments[0].to_expression_mut(), + &self.filename, + ); + self.styles.extend( + styles + .into_iter() + // already set style order + .flat_map(|mut ex| { + if let ExtractStyleProp::Static(css) = &mut ex { + css.set_style_order(style_order.unwrap_or(0)); + } + ex.extract() + }), + ); + self.ast + .expression_string_literal(SPAN, self.ast.atom(""), None) } - } else { - *it = self - .ast - .expression_string_literal(SPAN, self.ast.atom(""), None); } } } } else if let Expression::TaggedTemplateExpression(tag) = it { if let Expression::Identifier(ident) = &tag.tag - && self.css_imports.contains_key(ident.name.as_str()) + && let Some(css_type) = self.css_imports.get(ident.name.as_str()) { let css_str = tag .quasi @@ -153,16 +213,31 @@ impl<'a> VisitMut<'a> for DevupVisitor<'a> { .map(|quasi| quasi.value.raw.as_str()) .collect::>() .join(""); - let css = ExtractStyleValue::Css(ExtractCss { - css: css_str.trim().to_string(), - }); + match css_type.as_ref() { + CssType::Css => { + let mut styles = css_to_style(&css_str, 0, &None); + let class_name = gen_class_names(&self.ast, &mut styles, None); + + if let Some(cls) = class_name { + *it = cls; + } + self.styles.extend( + styles + .into_iter() + // already set style order + .flat_map(|ex| ex.extract()), + ); + } + CssType::GlobalCss => { + let css = ExtractStyleValue::Css(ExtractCss { + css: optimize_css_block(&css_str), + file: self.filename.clone(), + }); - if let StyleProperty::ClassName(cls) = css.extract() { - *it = self - .ast - .expression_string_literal(SPAN, self.ast.atom(&cls), None); + *it = self.ast.expression_identifier(SPAN, self.ast.atom("")); + self.styles.insert(css); + } } - self.styles.insert(css); } } } @@ -324,9 +399,9 @@ impl<'a> VisitMut<'a> for DevupVisitor<'a> { { self.imports.insert(import.local.to_string(), kind); specifiers.remove(i); - } else if import.imported.to_string() == "css" { + } else if let Ok(kind) = CssType::try_from(import.imported.to_string()) { self.css_imports - .insert(import.local.to_string(), it.source.value.to_string()); + .insert(import.local.to_string(), Rc::new(kind)); specifiers.remove(i); } } @@ -341,7 +416,12 @@ impl<'a> VisitMut<'a> for DevupVisitor<'a> { } self.css_imports.insert( format!("{}.{}", import_default_specifier.local, "css"), - it.source.value.to_string(), + Rc::new(CssType::Css), + ); + + self.css_imports.insert( + format!("{}.{}", import_default_specifier.local, "globalCss"), + Rc::new(CssType::GlobalCss), ); } ImportDeclarationSpecifier::ImportNamespaceSpecifier( @@ -355,7 +435,11 @@ impl<'a> VisitMut<'a> for DevupVisitor<'a> { } self.css_imports.insert( format!("{}.{}", import_namespace_specifier.local, "css"), - it.source.value.to_string(), + Rc::new(CssType::Css), + ); + self.css_imports.insert( + format!("{}.{}", import_namespace_specifier.local, "globalCss"), + Rc::new(CssType::GlobalCss), ); } } diff --git a/libs/sheet/src/lib.rs b/libs/sheet/src/lib.rs index bfbfd54f..33e99c3b 100644 --- a/libs/sheet/src/lib.rs +++ b/libs/sheet/src/lib.rs @@ -1,13 +1,14 @@ pub mod theme; use crate::theme::Theme; -use css::{PropertyType, StyleSelector, convert_property, merge_selector}; +use css::property_type::PropertyType; +use css::{merge_selector, style_selector::StyleSelector}; use once_cell::sync::Lazy; use regex::Regex; use serde::de::Error; use serde::{Deserialize, Deserializer, Serialize}; use std::cmp::Ordering::Equal; -use std::collections::{BTreeMap, HashSet}; +use std::collections::{BTreeMap, BTreeSet, HashSet}; trait ExtractStyle { fn extract(&self) -> String; @@ -48,7 +49,7 @@ impl Ord for StyleSheetProperty { impl ExtractStyle for StyleSheetProperty { fn extract(&self) -> String { - match convert_property(self.property.as_str()) { + match self.property.as_str().into() { PropertyType::Single(prop) => { format!( "{}{{{}:{}}}", @@ -84,15 +85,14 @@ fn convert_theme_variable_value(value: &str) -> String { } } -#[derive(Debug, Hash, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Hash, Eq, PartialEq, Deserialize, Serialize, Ord, PartialOrd)] pub struct StyleSheetCss { - pub class_name: String, pub css: String, } impl ExtractStyle for StyleSheetCss { fn extract(&self) -> String { - format!(".{}{{{}}}", self.class_name, self.css) + self.css.clone() } } @@ -123,7 +123,12 @@ where pub struct StyleSheet { #[serde(deserialize_with = "deserialize_btree_map_u8")] pub properties: PropertyMap, - pub css: HashSet, + pub css: BTreeMap>, + + #[serde(default)] + pub global_css_files: BTreeSet, + #[serde(default)] + pub imports: BTreeMap, #[serde(skip)] pub theme: Theme, } @@ -138,6 +143,11 @@ impl StyleSheet { selector: Option<&StyleSelector>, style_order: Option, ) -> bool { + // register global css file for cache + if let Some(StyleSelector::Global(_, file)) = selector { + self.global_css_files.insert(file.clone()); + } + self.properties .entry(style_order.unwrap_or(255)) .or_default() @@ -151,11 +161,38 @@ impl StyleSheet { }) } - pub fn add_css(&mut self, class_name: &str, css: &str) -> bool { - self.css.insert(StyleSheetCss { - class_name: class_name.to_string(), - css: css.to_string(), - }) + pub fn add_import(&mut self, file: &str, import: &str) { + self.global_css_files.insert(file.to_string()); + self.imports.insert(file.to_string(), import.to_string()); + } + + pub fn add_css(&mut self, file: &str, css: &str) -> bool { + self.global_css_files.insert(file.to_string()); + self.css + .entry(file.to_string()) + .or_default() + .insert(StyleSheetCss { + css: css.to_string(), + }) + } + + pub fn rm_global_css(&mut self, file: &str) { + if !self.global_css_files.contains(file) { + return; + } + self.global_css_files.remove(file); + self.css.remove(file); + for (_, map) in self.properties.iter_mut() { + for (_, props) in map.iter_mut() { + props.retain(|prop| { + if let Some(StyleSelector::Global(_, f)) = prop.selector.as_ref() { + f != file + } else { + true + } + }); + } + } } pub fn set_theme(&mut self, theme: Theme) { @@ -163,12 +200,28 @@ impl StyleSheet { } pub fn create_css(&self) -> String { - let mut css = self.theme.to_css(); + let mut css = self + .imports + .iter() + .map(|(_, import)| format!("@import \"{}\";", import)) + .collect::>() + .join(""); + css.push_str(&self.theme.to_css()); + + for (_, _css) in self.css.iter() { + for _css in _css.iter() { + css.push_str(&_css.extract()); + } + } // order for (_, map) in self.properties.iter() { for (level, props) in map.iter() { - let (mut medias, mut sorted_props): (Vec<_>, Vec<_>) = props + let (mut global_props, rest): (Vec<_>, Vec<_>) = props + .iter() + .partition(|prop| matches!(prop.selector, Some(StyleSelector::Global(_, _)))); + global_props.sort(); + let (mut medias, mut sorted_props): (Vec<&StyleSheetProperty>, Vec<_>) = rest .iter() .partition(|prop| matches!(prop.selector, Some(StyleSelector::Media(_)))); sorted_props.sort(); @@ -196,6 +249,23 @@ impl StyleSheet { .unwrap_or_else(|| self.theme.breakpoints.last().cloned().unwrap_or(0)), ) }; + + if !global_props.is_empty() { + let inner_css = global_props + .into_iter() + .map(ExtractStyle::extract) + .collect::>() + .join(""); + css.push_str( + if let Some(break_point) = break_point { + format!("\n@media (min-width:{break_point}px){{{inner_css}}}") + } else { + inner_css + } + .as_str(), + ); + } + if !sorted_props.is_empty() { let inner_css = sorted_props .into_iter() @@ -230,9 +300,6 @@ impl StyleSheet { } } } - for prop in self.css.iter() { - css.push_str(&prop.extract()); - } css } } @@ -347,7 +414,7 @@ mod tests { assert_debug_snapshot!(sheet.create_css()); let mut sheet = StyleSheet::default(); - sheet.add_css("test", "display:flex;"); + sheet.add_css("test.tsx", "div {display:flex;}"); assert_debug_snapshot!(sheet.create_css()); let mut sheet = StyleSheet::default(); @@ -466,6 +533,18 @@ mod tests { assert_debug_snapshot!(sheet.create_css()); } + #[test] + fn test_reset_global_css() { + let mut sheet = StyleSheet::default(); + sheet.add_css("test.tsx", "div {display:flex;}"); + sheet.add_css("test2.tsx", "div {display:flex;}"); + assert_debug_snapshot!(sheet.create_css()); + + sheet.rm_global_css("test.tsx"); + + assert_debug_snapshot!(sheet.create_css()); + } + #[test] fn test_style_order_create_css() { let mut sheet = StyleSheet::default(); @@ -568,7 +647,7 @@ mod tests { ] } }, - "css": [], + "css": {}, "theme": { "breakPoints": [ 640, @@ -621,4 +700,95 @@ mod tests { assert!(sheet.is_err()); } } + + #[test] + fn test_create_css_with_global_selector() { + let mut sheet = StyleSheet::default(); + sheet.add_property( + "test", + "background-color", + 0, + "red", + Some(&StyleSelector::Global( + "div".to_string(), + "test.tsx".to_string(), + )), + None, + ); + assert_debug_snapshot!(sheet.create_css()); + let mut sheet = StyleSheet::default(); + sheet.add_property( + "test", + "background-color", + 1, + "red", + Some(&StyleSelector::Global( + "div".to_string(), + "test.tsx".to_string(), + )), + None, + ); + assert_debug_snapshot!(sheet.create_css()); + + let mut sheet = StyleSheet::default(); + + sheet.add_property( + "test", + "background-color", + 2, + "blue", + Some(&StyleSelector::Global( + "div".to_string(), + "test.tsx".to_string(), + )), + None, + ); + sheet.add_property( + "test", + "background-color", + 1, + "red", + Some(&StyleSelector::Global( + "div".to_string(), + "test.tsx".to_string(), + )), + None, + ); + assert_debug_snapshot!(sheet.create_css()); + + let mut sheet = StyleSheet::default(); + sheet.add_property( + "test", + "background-color", + 1, + "blue", + Some(&StyleSelector::Global( + "div".to_string(), + "test.tsx".to_string(), + )), + Some(0), + ); + sheet.add_property( + "test", + "background-color", + 0, + "red", + Some(&StyleSelector::Global( + "div".to_string(), + "test.tsx".to_string(), + )), + Some(255), + ); + assert_debug_snapshot!(sheet.create_css()); + } + + #[test] + fn test_create_css_with_imports() { + let mut sheet = StyleSheet::default(); + sheet.add_import("test.tsx", "@devup-ui/core/css/global.css"); + sheet.add_import("test2.tsx", "@devup-ui/core/css/global2.css"); + sheet.add_import("test3.tsx", "@devup-ui/core/css/global3.css"); + sheet.add_import("test4.tsx", "@devup-ui/core/css/global4.css"); + assert_debug_snapshot!(sheet.create_css()); + } } diff --git a/libs/sheet/src/snapshots/sheet__tests__create_css-2.snap b/libs/sheet/src/snapshots/sheet__tests__create_css-2.snap index 0b67b7c8..ca2936a5 100644 --- a/libs/sheet/src/snapshots/sheet__tests__create_css-2.snap +++ b/libs/sheet/src/snapshots/sheet__tests__create_css-2.snap @@ -2,4 +2,4 @@ source: libs/sheet/src/lib.rs expression: sheet.create_css() --- -".test{display:flex;}" +"div {display:flex;}" diff --git a/libs/sheet/src/snapshots/sheet__tests__create_css_with_global_selector-2.snap b/libs/sheet/src/snapshots/sheet__tests__create_css_with_global_selector-2.snap new file mode 100644 index 00000000..19812568 --- /dev/null +++ b/libs/sheet/src/snapshots/sheet__tests__create_css_with_global_selector-2.snap @@ -0,0 +1,5 @@ +--- +source: libs/sheet/src/lib.rs +expression: sheet.create_css() +--- +"\n@media (min-width:480px){div{background-color:red}}" diff --git a/libs/sheet/src/snapshots/sheet__tests__create_css_with_global_selector-3.snap b/libs/sheet/src/snapshots/sheet__tests__create_css_with_global_selector-3.snap new file mode 100644 index 00000000..4c4c5142 --- /dev/null +++ b/libs/sheet/src/snapshots/sheet__tests__create_css_with_global_selector-3.snap @@ -0,0 +1,5 @@ +--- +source: libs/sheet/src/lib.rs +expression: sheet.create_css() +--- +"\n@media (min-width:480px){div{background-color:red}}\n@media (min-width:768px){div{background-color:blue}}" diff --git a/libs/sheet/src/snapshots/sheet__tests__create_css_with_global_selector-4.snap b/libs/sheet/src/snapshots/sheet__tests__create_css_with_global_selector-4.snap new file mode 100644 index 00000000..486fb96f --- /dev/null +++ b/libs/sheet/src/snapshots/sheet__tests__create_css_with_global_selector-4.snap @@ -0,0 +1,5 @@ +--- +source: libs/sheet/src/lib.rs +expression: sheet.create_css() +--- +"\n@media (min-width:480px){div{background-color:blue}}div{background-color:red}" diff --git a/libs/sheet/src/snapshots/sheet__tests__create_css_with_global_selector.snap b/libs/sheet/src/snapshots/sheet__tests__create_css_with_global_selector.snap new file mode 100644 index 00000000..fea50d3f --- /dev/null +++ b/libs/sheet/src/snapshots/sheet__tests__create_css_with_global_selector.snap @@ -0,0 +1,5 @@ +--- +source: libs/sheet/src/lib.rs +expression: sheet.create_css() +--- +"div{background-color:red}" diff --git a/libs/sheet/src/snapshots/sheet__tests__create_css_with_imports.snap b/libs/sheet/src/snapshots/sheet__tests__create_css_with_imports.snap new file mode 100644 index 00000000..04beaf78 --- /dev/null +++ b/libs/sheet/src/snapshots/sheet__tests__create_css_with_imports.snap @@ -0,0 +1,5 @@ +--- +source: libs/sheet/src/lib.rs +expression: sheet.create_css() +--- +"@import \"@devup-ui/core/css/global.css\";@import \"@devup-ui/core/css/global2.css\";@import \"@devup-ui/core/css/global3.css\";@import \"@devup-ui/core/css/global4.css\";" diff --git a/libs/sheet/src/snapshots/sheet__tests__deserialize.snap b/libs/sheet/src/snapshots/sheet__tests__deserialize.snap index 509cf5a8..91f75db5 100644 --- a/libs/sheet/src/snapshots/sheet__tests__deserialize.snap +++ b/libs/sheet/src/snapshots/sheet__tests__deserialize.snap @@ -16,6 +16,8 @@ StyleSheet { }, }, css: {}, + global_css_files: {}, + imports: {}, theme: Theme { colors: {}, breakpoints: [ diff --git a/libs/sheet/src/snapshots/sheet__tests__reset_global_css-2.snap b/libs/sheet/src/snapshots/sheet__tests__reset_global_css-2.snap new file mode 100644 index 00000000..ca2936a5 --- /dev/null +++ b/libs/sheet/src/snapshots/sheet__tests__reset_global_css-2.snap @@ -0,0 +1,5 @@ +--- +source: libs/sheet/src/lib.rs +expression: sheet.create_css() +--- +"div {display:flex;}" diff --git a/libs/sheet/src/snapshots/sheet__tests__reset_global_css.snap b/libs/sheet/src/snapshots/sheet__tests__reset_global_css.snap new file mode 100644 index 00000000..709df552 --- /dev/null +++ b/libs/sheet/src/snapshots/sheet__tests__reset_global_css.snap @@ -0,0 +1,5 @@ +--- +source: libs/sheet/src/lib.rs +expression: sheet.create_css() +--- +"div {display:flex;}div {display:flex;}" diff --git a/packages/next-plugin/package.json b/packages/next-plugin/package.json index b2c087ad..b783ed37 100644 --- a/packages/next-plugin/package.json +++ b/packages/next-plugin/package.json @@ -42,13 +42,15 @@ "types": "./dist/index.d.ts", "dependencies": { "@devup-ui/webpack-plugin": "workspace:*", - "next": "^15.3" + "next": "^15.3", + "browserslist": "latest" }, "devDependencies": { "vite": "^7.0.3", "vite-plugin-dts": "^4.5.4", "vitest": "^3.2.4", - "typescript": "^5.8.3" + "typescript": "^5.8.3", + "@types/webpack": "^5.28.5" }, "peerDependencies": { "@devup-ui/webpack-plugin": "*", diff --git a/packages/next-plugin/src/__tests__/plugin.test.ts b/packages/next-plugin/src/__tests__/plugin.test.ts index 7ace0140..73bf982b 100644 --- a/packages/next-plugin/src/__tests__/plugin.test.ts +++ b/packages/next-plugin/src/__tests__/plugin.test.ts @@ -20,6 +20,17 @@ describe('plugin', () => { }) }) + it('should apply webpack plugin with dev', async () => { + const ret = DevupUI({}) + + ret.webpack!({ plugins: [] }, { buildId: 'tmpBuildId', dev: true } as any) + + expect(DevupUIWebpackPlugin).toHaveBeenCalledWith({ + cssFile: resolve('df', 'devup-ui_tmpBuildId.css'), + watch: true, + }) + }) + it('should apply webpack plugin with config', async () => { const ret = DevupUI( {}, @@ -78,9 +89,9 @@ describe('plugin', () => { loader: '@devup-ui/webpack-plugin/loader', options: { package: '@devup-ui/react', - cssFile: resolve('.df', 'devup-ui.css'), - sheetFile: join('.df', 'sheet.json'), - classMapFile: join('.df', 'classMap.json'), + cssFile: resolve('df', 'devup-ui.css'), + sheetFile: join('df', 'sheet.json'), + classMapFile: join('df', 'classMap.json'), watch: false, }, }, @@ -89,7 +100,7 @@ describe('plugin', () => { }, }) }) - it('should apply turbo config with create .df', async () => { + it('should apply turbo config with create df', async () => { vi.stubEnv('TURBOPACK', '1') vi.mocked(existsSync).mockReturnValue(false) vi.mocked(mkdirSync).mockReturnValue('') @@ -112,9 +123,9 @@ describe('plugin', () => { loader: '@devup-ui/webpack-plugin/loader', options: { package: '@devup-ui/react', - cssFile: resolve('.df', 'devup-ui.css'), - sheetFile: join('.df', 'sheet.json'), - classMapFile: join('.df', 'classMap.json'), + cssFile: resolve('df', 'devup-ui.css'), + sheetFile: join('df', 'sheet.json'), + classMapFile: join('df', 'classMap.json'), watch: false, }, }, @@ -122,9 +133,9 @@ describe('plugin', () => { }, }, }) - expect(mkdirSync).toHaveBeenCalledWith('.df') + expect(mkdirSync).toHaveBeenCalledWith('df') expect(writeFileSync).toHaveBeenCalledWith( - resolve('.df', 'devup-ui.css'), + resolve('df', 'devup-ui.css'), '/* devup-ui */', ) }) diff --git a/packages/next-plugin/src/plugin.ts b/packages/next-plugin/src/plugin.ts index ae545872..052cda09 100644 --- a/packages/next-plugin/src/plugin.ts +++ b/packages/next-plugin/src/plugin.ts @@ -29,7 +29,7 @@ export function DevupUI( config.turbopack.rules ??= {} const { package: libPackage = '@devup-ui/react', - interfacePath = '.df', + interfacePath = 'df', cssFile = resolve(interfacePath, 'devup-ui.css'), } = options @@ -66,7 +66,7 @@ export function DevupUI( const { webpack } = config config.webpack = (config, _options) => { options.cssFile ??= resolve( - options.interfacePath ?? '.next/cache', + _options.dev ? (options.interfacePath ?? 'df') : '.next/cache', `devup-ui_${_options.buildId}.css`, ) config.plugins.push( diff --git a/packages/react/package.json b/packages/react/package.json index 1067deed..6ec5208d 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -46,7 +46,6 @@ "rollup-plugin-preserve-directives": "^0.4.0", "vite": "^7.0.3", "vite-plugin-dts": "^4.5.4", - "vitest": "^3.2.4", "typescript": "^5.8.3", "@types/react": "^19" }, diff --git a/packages/react/src/__tests__/index.test.ts b/packages/react/src/__tests__/index.test.ts index d4bf977f..4518d54f 100644 --- a/packages/react/src/__tests__/index.test.ts +++ b/packages/react/src/__tests__/index.test.ts @@ -13,6 +13,7 @@ describe('export', () => { Grid: expect.any(Function), css: expect.any(Function), + globalCss: expect.any(Function), ThemeScript: expect.any(Function), diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index b3de7f72..40ba543a 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -14,5 +14,6 @@ export type { DevupTheme, DevupThemeColors } from './types/theme' export type { DevupThemeTypography } from './types/typography' export { css } from './utils/css' export { getTheme } from './utils/get-theme' +export { globalCss } from './utils/global-css' export { initTheme } from './utils/init-theme' export { setTheme } from './utils/set-theme' diff --git a/packages/react/src/types/props/__tests__/index.test-d.ts b/packages/react/src/types/props/__tests__/index.test-d.ts new file mode 100644 index 00000000..ba8d11a6 --- /dev/null +++ b/packages/react/src/types/props/__tests__/index.test-d.ts @@ -0,0 +1,59 @@ +import type { DevupCommonProps, DevupProps } from '..' + +describe('index', () => { + it('DevupCommonProps', () => { + assertType({ + bg: 'red', + bgColor: 'red', + }) + }) + + it('DevupCommonProps _selector', () => { + assertType>({ + _hover: { + bg: 'red', + _active: { + bg: 'blue', + }, + }, + }) + + assertType>({ + _hover: ` + background-color: red; + `, + }) + + expectTypeOf>().toExtend['_hover']>() + }) + + it('DevupCommonProps selectors', () => { + assertType>({ + selectors: { + '&:hover': { + bg: 'red', + }, + }, + }) + assertType>({ + selectors: { + '&:hover': ` + background-color: red; + `, + }, + }) + + assertType>({ + selectors: { + '&:hover': [ + ` + background-color: red; + `, + { + bg: 'blue', + }, + ], + }, + }) + }) +}) diff --git a/packages/react/src/types/props/index.ts b/packages/react/src/types/props/index.ts index 760f2f82..d3b03cf6 100644 --- a/packages/react/src/types/props/index.ts +++ b/packages/react/src/types/props/index.ts @@ -62,12 +62,12 @@ export interface DevupCommonProps DevupUiTransitionProps, DevupUiUiProps, DevupUiViewTransitionProps, - DevupSelectorProps, - DevupThemeSelectorProps, DevupUiSvgProps {} export interface DevupProps - extends DevupCommonProps { + extends DevupCommonProps, + DevupSelectorProps, + DevupThemeSelectorProps { as?: T styleVars?: Record } diff --git a/packages/react/src/types/props/selector/index.ts b/packages/react/src/types/props/selector/index.ts index 9138f837..a82e2ded 100644 --- a/packages/react/src/types/props/selector/index.ts +++ b/packages/react/src/types/props/selector/index.ts @@ -1,3 +1,4 @@ +import type { ResponsiveValue } from '../../responsive-value' import type { DevupTheme } from '../../theme' import type { DevupCommonProps } from '../index' @@ -12,62 +13,68 @@ export type DevupThemeSelectorProps = keyof DevupTheme extends undefined DevupSelectorProps } +type SelectorProps = ResponsiveValue< + | (DevupCommonProps & DevupSelectorProps & DevupThemeSelectorProps) + | string + | false +> + export interface DevupSelectorProps { - _active?: DevupCommonProps | false - _checked?: DevupCommonProps | false - _default?: DevupCommonProps | false - _disabled?: DevupCommonProps | false - _empty?: DevupCommonProps | false - _enabled?: DevupCommonProps | false - _first?: DevupCommonProps | false - _firstChild?: DevupCommonProps | false - _firstOfType?: DevupCommonProps | false - _focus?: DevupCommonProps | false - _focusVisible?: DevupCommonProps | false - _focusWithin?: DevupCommonProps | false - _hover?: DevupCommonProps | false - _invalid?: DevupCommonProps | false - _lastChild?: DevupCommonProps | false - _lastOfType?: DevupCommonProps | false - _link?: DevupCommonProps | false - _onlyChild?: DevupCommonProps | false - _optional?: DevupCommonProps | false - _readOnly?: DevupCommonProps | false - _print?: DevupCommonProps | false + _active?: SelectorProps + _checked?: SelectorProps + _default?: SelectorProps + _disabled?: SelectorProps + _empty?: SelectorProps + _enabled?: SelectorProps + _first?: SelectorProps + _firstChild?: SelectorProps + _firstOfType?: SelectorProps + _focus?: SelectorProps + _focusVisible?: SelectorProps + _focusWithin?: SelectorProps + _hover?: SelectorProps + _invalid?: SelectorProps + _lastChild?: SelectorProps + _lastOfType?: SelectorProps + _link?: SelectorProps + _onlyChild?: SelectorProps + _optional?: SelectorProps + _readOnly?: SelectorProps + _print?: SelectorProps - _groupActive?: DevupCommonProps | false - _groupChecked?: DevupCommonProps | false - _groupDefault?: DevupCommonProps | false - _groupDisabled?: DevupCommonProps | false - _groupEmpty?: DevupCommonProps | false - _groupEnabled?: DevupCommonProps | false - _groupFirst?: DevupCommonProps | false - _groupFirstChild?: DevupCommonProps | false - _groupFirstOfType?: DevupCommonProps | false - _groupFocus?: DevupCommonProps | false - _groupFocusVisible?: DevupCommonProps | false - _groupFocusWithin?: DevupCommonProps | false - _groupHover?: DevupCommonProps | false - _groupInvalid?: DevupCommonProps | false - _groupLastChild?: DevupCommonProps | false - _groupLastOfType?: DevupCommonProps | false - _groupLink?: DevupCommonProps | false - _groupOnlyChild?: DevupCommonProps | false - _groupOptional?: DevupCommonProps | false - _groupReadOnly?: DevupCommonProps | false + _groupActive?: SelectorProps + _groupChecked?: SelectorProps + _groupDefault?: SelectorProps + _groupDisabled?: SelectorProps + _groupEmpty?: SelectorProps + _groupEnabled?: SelectorProps + _groupFirst?: SelectorProps + _groupFirstChild?: SelectorProps + _groupFirstOfType?: SelectorProps + _groupFocus?: SelectorProps + _groupFocusVisible?: SelectorProps + _groupFocusWithin?: SelectorProps + _groupHover?: SelectorProps + _groupInvalid?: SelectorProps + _groupLastChild?: SelectorProps + _groupLastOfType?: SelectorProps + _groupLink?: SelectorProps + _groupOnlyChild?: SelectorProps + _groupOptional?: SelectorProps + _groupReadOnly?: SelectorProps // double separator - _placeholder?: DevupCommonProps | false - _before?: DevupCommonProps | false - _after?: DevupCommonProps | false - _highlight?: DevupCommonProps | false - _viewTransition?: DevupCommonProps | false - _viewTransitionGroup?: DevupCommonProps | false - _viewTransitionImagePair?: DevupCommonProps | false - _viewTransitionNew?: DevupCommonProps | false - _viewTransitionOld?: DevupCommonProps | false + _placeholder?: SelectorProps + _before?: SelectorProps + _after?: SelectorProps + _highlight?: SelectorProps + _viewTransition?: SelectorProps + _viewTransitionGroup?: SelectorProps + _viewTransitionImagePair?: SelectorProps + _viewTransitionNew?: SelectorProps + _viewTransitionOld?: SelectorProps - selectors?: Record + selectors?: Record styleOrder?: number } diff --git a/packages/react/src/types/props/text.ts b/packages/react/src/types/props/text.ts index d884a0a3..3af5db80 100644 --- a/packages/react/src/types/props/text.ts +++ b/packages/react/src/types/props/text.ts @@ -45,5 +45,3 @@ export interface DevupUiTextProps { typography?: Conditional } - -// for skip type checking without .df diff --git a/packages/react/src/utils/__tests__/global-css.test.ts b/packages/react/src/utils/__tests__/global-css.test.ts new file mode 100644 index 00000000..97363bde --- /dev/null +++ b/packages/react/src/utils/__tests__/global-css.test.ts @@ -0,0 +1,13 @@ +import { globalCss } from '../global-css' + +describe('globalCss', () => { + it('should return className', () => { + expect(() => globalCss`virtual-css`).toThrowError( + 'Cannot run on the runtime', + ) + expect(() => globalCss('class name' as any)).toThrowError( + 'Cannot run on the runtime', + ) + expect(() => globalCss()).toThrowError('Cannot run on the runtime') + }) +}) diff --git a/packages/react/src/utils/css.ts b/packages/react/src/utils/css.ts index 17a8731f..22414662 100644 --- a/packages/react/src/utils/css.ts +++ b/packages/react/src/utils/css.ts @@ -1,11 +1,9 @@ -import { DevupCommonProps } from '../types/props' -import type { - DevupSelectorProps, - DevupThemeSelectorProps, -} from '../types/props/selector' +import type { DevupCommonProps } from '../types/props' +import type { DevupSelectorProps } from '../types/props/selector' +import type { DevupThemeSelectorProps } from '../types/props/selector' export function css( - props: DevupCommonProps & DevupSelectorProps & DevupThemeSelectorProps, + props: DevupCommonProps | DevupSelectorProps | DevupThemeSelectorProps, ): string export function css(strings: TemplateStringsArray): string export function css(): string @@ -14,7 +12,9 @@ export function css( // eslint-disable-next-line @typescript-eslint/no-unused-vars strings?: | TemplateStringsArray - | (DevupCommonProps & DevupSelectorProps & DevupThemeSelectorProps), + | DevupCommonProps + | DevupSelectorProps + | DevupThemeSelectorProps, ): string { throw new Error('Cannot run on the runtime') } diff --git a/packages/react/src/utils/get-theme.ts b/packages/react/src/utils/get-theme.ts index 134e8445..56808740 100644 --- a/packages/react/src/utils/get-theme.ts +++ b/packages/react/src/utils/get-theme.ts @@ -1,6 +1,6 @@ 'use client' -import { DevupTheme } from '../types/theme' +import type { DevupTheme } from '../types/theme' export function getTheme(): | (keyof DevupTheme extends undefined ? string : keyof DevupTheme) diff --git a/packages/react/src/utils/global-css.ts b/packages/react/src/utils/global-css.ts new file mode 100644 index 00000000..8d5e1df0 --- /dev/null +++ b/packages/react/src/utils/global-css.ts @@ -0,0 +1,34 @@ +import type { DevupCommonProps } from '../types/props' +import type { DevupThemeSelectorProps } from '../types/props/selector' +import type { DevupSelectorProps } from '../types/props/selector' + +type GlobalCssProps = { + [key in + | keyof HTMLElementTagNameMap + | keyof SVGElementTagNameMap + | (string & {})]?: + | DevupCommonProps + | DevupSelectorProps + | DevupThemeSelectorProps +} + +export function globalCss( + strings?: + | TemplateStringsArray + | (Omit & { + imports?: string[] + }), +): never + +export function globalCss(): never + +export function globalCss( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + strings?: + | TemplateStringsArray + | (Omit & { + imports?: string[] + }), +): never { + throw new Error('Cannot run on the runtime') +} diff --git a/packages/react/src/utils/init-theme.ts b/packages/react/src/utils/init-theme.ts index fa06ec13..14ca9e39 100644 --- a/packages/react/src/utils/init-theme.ts +++ b/packages/react/src/utils/init-theme.ts @@ -2,7 +2,7 @@ import type { Conditional } from 'src/types/utils' -import { DevupTheme } from '../types/theme' +import type { DevupTheme } from '../types/theme' /** * Initialize the theme, if you can't use the `ThemeScript` component diff --git a/packages/react/src/utils/set-theme.ts b/packages/react/src/utils/set-theme.ts index 94269790..d417a280 100644 --- a/packages/react/src/utils/set-theme.ts +++ b/packages/react/src/utils/set-theme.ts @@ -1,6 +1,6 @@ 'use client' -import { DevupTheme } from '../types/theme' +import type { DevupTheme } from '../types/theme' export function setTheme( theme: keyof DevupTheme extends undefined ? string : keyof DevupTheme, diff --git a/packages/rsbuild-plugin/src/__tests__/plugin.test.ts b/packages/rsbuild-plugin/src/__tests__/plugin.test.ts index 07aa51ca..e2c2b0d5 100644 --- a/packages/rsbuild-plugin/src/__tests__/plugin.test.ts +++ b/packages/rsbuild-plugin/src/__tests__/plugin.test.ts @@ -82,7 +82,7 @@ describe('DevupUIRsbuildPlugin', () => { expect(transform).toHaveBeenCalled() expect(transform).toHaveBeenCalledWith( { - test: resolve('.df', 'devup-ui.css'), + test: resolve('df', 'devup-ui.css'), }, expect.any(Function), ) @@ -165,7 +165,7 @@ const App = () => `, map: undefined, }) expect(writeFile).toHaveBeenCalledWith( - resolve('.df', 'devup-ui.css'), + resolve('df', 'devup-ui.css'), expect.stringMatching(/\/\* src\/App\.tsx \d+ \*\//), { encoding: 'utf-8', diff --git a/packages/rsbuild-plugin/src/plugin.ts b/packages/rsbuild-plugin/src/plugin.ts index 31e739e5..bb6edfc6 100644 --- a/packages/rsbuild-plugin/src/plugin.ts +++ b/packages/rsbuild-plugin/src/plugin.ts @@ -1,5 +1,5 @@ import { mkdir, writeFile } from 'node:fs/promises' -import { resolve } from 'node:path' +import { join, resolve } from 'node:path' import { codeExtract } from '@devup-ui/wasm' import type { RsbuildPlugin } from '@rsbuild/core' @@ -20,7 +20,7 @@ export const DevupUIRsbuildPlugin = ({ include = [], package: libPackage = '@devup-ui/react', extractCss = true, - interfacePath = '.df', + interfacePath = 'df', cssFile = resolve(interfacePath, 'devup-ui.css'), }: Partial = {}): RsbuildPlugin => ({ name: 'devup-ui-rsbuild-plugin', @@ -28,6 +28,9 @@ export const DevupUIRsbuildPlugin = ({ async setup(api) { if (!extractCss) return await mkdir(interfacePath, { recursive: true }) + await writeFile(join(interfacePath, '.gitignore'), '*', { + encoding: 'utf-8', + }) await writeFile(cssFile, '') api.transform( diff --git a/packages/vite-plugin/src/__tests__/plugin.test.ts b/packages/vite-plugin/src/__tests__/plugin.test.ts index 0762489d..32fe9bf8 100644 --- a/packages/vite-plugin/src/__tests__/plugin.test.ts +++ b/packages/vite-plugin/src/__tests__/plugin.test.ts @@ -27,7 +27,7 @@ describe('devupUIPlugin', () => { console.error = vi.fn() it('should write data files', () => { const devupPath = 'devup.json' - const interfacePath = '.df' + const interfacePath = 'df' const cssFile = join(_dirname, 'devup-ui.css') const libPackage = '@devup-ui/react' vi.mocked(existsSync).mockReturnValueOnce(false).mockReturnValueOnce(true) @@ -142,7 +142,7 @@ describe('devupUIPlugin', () => { }) it('should transform code', () => { const devupPath = 'devup.json' - const interfacePath = '.df' + const interfacePath = 'df' const cssFile = join(_dirname, 'devup-ui.css') const libPackage = '@devup-ui/react' vi.mocked(existsSync).mockReturnValueOnce(false).mockReturnValueOnce(true) @@ -190,7 +190,7 @@ describe('devupUIPlugin', () => { }) it('should not extract code', () => { const devupPath = 'devup.json' - const interfacePath = '.df' + const interfacePath = 'df' const cssFile = join(_dirname, 'devup-ui.css') const libPackage = '@devup-ui/react' vi.mocked(existsSync).mockReturnValueOnce(false).mockReturnValueOnce(true) @@ -230,7 +230,7 @@ describe('devupUIPlugin', () => { }) it('should catch error', () => { const devupPath = 'devup.json' - const interfacePath = '.df' + const interfacePath = 'df' const cssFile = join(_dirname, 'devup-ui.css') const libPackage = '@devup-ui/react' vi.mocked(existsSync).mockReturnValueOnce(false).mockReturnValueOnce(true) @@ -270,7 +270,7 @@ describe('devupUIPlugin', () => { it('should return true on apply', () => { const devupPath = 'devup.json' - const interfacePath = '.df' + const interfacePath = 'df' const cssFile = join(_dirname, 'devup-ui.css') const libPackage = '@devup-ui/react' vi.mocked(existsSync).mockReturnValueOnce(false).mockReturnValueOnce(true) @@ -308,7 +308,7 @@ describe('devupUIPlugin', () => { it('should include', () => { const devupPath = 'devup.json' - const interfacePath = '.df' + const interfacePath = 'df' const cssFile = join(_dirname, 'devup-ui.css') const libPackage = '@devup-ui/react' const plugin = DevupUI({ @@ -348,7 +348,7 @@ describe('devupUIPlugin', () => { describe('basic', () => { const devupPath = 'devup.json' - const interfacePath = '.df' + const interfacePath = 'df' const cssFile = join(_dirname, 'devup-ui.css') const libPackage = '@devup-ui/react' vi.mocked(existsSync).mockReturnValueOnce(false).mockReturnValueOnce(true) diff --git a/packages/vite-plugin/src/plugin.ts b/packages/vite-plugin/src/plugin.ts index 55b60a1a..30aa64f2 100644 --- a/packages/vite-plugin/src/plugin.ts +++ b/packages/vite-plugin/src/plugin.ts @@ -46,6 +46,9 @@ function writeDataFiles( writeFileSync(options.cssFile, '', { encoding: 'utf-8', }) + writeFileSync(join(options.interfacePath, '.gitignore'), '*', { + encoding: 'utf-8', + }) } let globalCss = '' @@ -53,7 +56,7 @@ let globalCss = '' export function DevupUI({ package: libPackage = '@devup-ui/react', devupPath = 'devup.json', - interfacePath = '.df', + interfacePath = 'df', cssFile = resolve(interfacePath, 'devup-ui.css'), extractCss = true, debug = false, diff --git a/packages/webpack-plugin/src/__tests__/css-loader.test.ts b/packages/webpack-plugin/src/__tests__/css-loader.test.ts index 09288a5e..1cbc2fb2 100644 --- a/packages/webpack-plugin/src/__tests__/css-loader.test.ts +++ b/packages/webpack-plugin/src/__tests__/css-loader.test.ts @@ -35,7 +35,7 @@ describe('devupUICssLoader', () => { addContextDependency, getOptions: () => ({ watch: true }), } as any)(Buffer.from('data'), '') - expect(callback).toBeCalledWith(null, 'get css') + expect(callback).toBeCalledWith(null, 'get css', '', undefined) expect(getCss).toBeCalledTimes(1) vi.mocked(getCss).mockReset() devupUICssLoader.bind({ diff --git a/packages/webpack-plugin/src/__tests__/loader.test.ts b/packages/webpack-plugin/src/__tests__/loader.test.ts index b1646ff4..1b5e981f 100644 --- a/packages/webpack-plugin/src/__tests__/loader.test.ts +++ b/packages/webpack-plugin/src/__tests__/loader.test.ts @@ -46,7 +46,7 @@ describe('devupUILoader', () => { 'index.tsx', 'code', 'package', - 'cssFile', + './cssFile', ) await vi.waitFor(() => { expect(t.async()).toHaveBeenCalledWith(null, 'code', {}) @@ -82,7 +82,7 @@ describe('devupUILoader', () => { 'index.tsx', 'code', 'package', - 'cssFile', + './cssFile', ) expect(t.async()).toHaveBeenCalledWith(null, 'code', null) expect(writeFile).not.toHaveBeenCalledWith('cssFile', 'css', { @@ -134,7 +134,7 @@ describe('devupUILoader', () => { 'index.tsx', 'code', 'package', - 'cssFile', + './cssFile', ) }) }) diff --git a/packages/webpack-plugin/src/__tests__/plugin.test.ts b/packages/webpack-plugin/src/__tests__/plugin.test.ts index 3bf1aefb..44db6e1e 100644 --- a/packages/webpack-plugin/src/__tests__/plugin.test.ts +++ b/packages/webpack-plugin/src/__tests__/plugin.test.ts @@ -35,9 +35,9 @@ describe('devupUIPlugin', () => { expect(new DevupUIWebpackPlugin({}).options).toEqual({ include: [], package: '@devup-ui/react', - cssFile: resolve('.df', 'devup-ui.css'), + cssFile: resolve('df', 'devup-ui.css'), devupPath: 'devup.json', - interfacePath: '.df', + interfacePath: 'df', watch: false, debug: false, }) @@ -84,7 +84,7 @@ describe('devupUIPlugin', () => { 'DevupTheme', ) expect(writeFileSync).toHaveBeenCalledWith( - join('.df', 'theme.d.ts'), + join('df', 'theme.d.ts'), 'interfaceCode', { encoding: 'utf-8', @@ -147,7 +147,7 @@ describe('devupUIPlugin', () => { hasErrors: () => false, }) expect(writeFileSync).toHaveBeenCalledWith( - resolve('.df', 'devup-ui.css'), + resolve('df', 'devup-ui.css'), 'css', { encoding: 'utf-8', @@ -230,7 +230,7 @@ describe('devupUIPlugin', () => { plugin.apply(compiler) expect(writeFileSync).toHaveBeenCalledWith( - resolve('.df', 'devup-ui.css'), + resolve('df', 'devup-ui.css'), '', { encoding: 'utf-8', @@ -356,7 +356,7 @@ describe('devupUIPlugin', () => { plugin.apply(compiler) expect(writeFileSync).toHaveBeenCalledWith( - resolve('.df', 'devup-ui.css'), + resolve('df', 'devup-ui.css'), '', { encoding: 'utf-8', diff --git a/packages/webpack-plugin/src/css-loader.ts b/packages/webpack-plugin/src/css-loader.ts index 1420f5a4..3503ea30 100644 --- a/packages/webpack-plugin/src/css-loader.ts +++ b/packages/webpack-plugin/src/css-loader.ts @@ -6,17 +6,17 @@ let prevTime = '' const devupUICssLoader: RawLoaderDefinitionFunction<{ watch: boolean -}> = function (source) { +}> = function (source, map, meta) { const { watch } = this.getOptions() if (!watch) return this.callback(null, getCss()) const stringSource = (this._compiler as any)?.__DEVUP_CACHE || source.toString() if (prevTime === stringSource) { - this.callback(null, prevData) + this.callback(null, prevData, map, meta) return } prevTime = stringSource - this.callback(null, (prevData = getCss())) + this.callback(null, (prevData = getCss()), map, meta) } export default devupUICssLoader diff --git a/packages/webpack-plugin/src/loader.ts b/packages/webpack-plugin/src/loader.ts index 0ce6d993..752d57a4 100644 --- a/packages/webpack-plugin/src/loader.ts +++ b/packages/webpack-plugin/src/loader.ts @@ -25,11 +25,16 @@ const devupUILoader: RawLoaderDefinitionFunction = const id = this.resourcePath try { + let rel = relative(dirname(this.resourcePath), cssFile).replaceAll( + '\\', + '/', + ) + if (!rel.startsWith('./')) rel = `./${rel}` const { code, css, map } = codeExtract( id, source.toString(), libPackage, - relative(dirname(this.resourcePath), cssFile).replaceAll('\\', '/'), + rel, ) const sourceMap = map ? JSON.parse(map) : null if (css && watch) { diff --git a/packages/webpack-plugin/src/plugin.ts b/packages/webpack-plugin/src/plugin.ts index 12f7a534..843e218c 100644 --- a/packages/webpack-plugin/src/plugin.ts +++ b/packages/webpack-plugin/src/plugin.ts @@ -35,7 +35,7 @@ export class DevupUIWebpackPlugin { constructor({ package: libPackage = '@devup-ui/react', devupPath = 'devup.json', - interfacePath = '.df', + interfacePath = 'df', cssFile = resolve(interfacePath, 'devup-ui.css'), watch = false, debug = false, @@ -72,6 +72,10 @@ export class DevupUIWebpackPlugin { }, ) } + writeFileSync(join(this.options.interfacePath, '.gitignore'), '*', { + encoding: 'utf-8', + }) + if (this.options.watch) { writeFileSync(this.options.cssFile, `/* ${Date.now()} */`, { encoding: 'utf-8', @@ -153,7 +157,7 @@ export class DevupUIWebpackPlugin { test: /\.(tsx|ts|js|mjs|jsx)$/, exclude: new RegExp( this.options.include.length - ? `node_modules(?!(.*${this.options.include.join('|').replaceAll('/', '[\\/\\\\]')})([\\/\\\\]|$))` + ? `node_modules(?!.*(${this.options.include.join('|').replaceAll('/', '[\\/\\\\]')})([\\/\\\\]|$))` : 'node_modules', ), enforce: 'pre', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 659c9218..131a072a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -397,10 +397,16 @@ importers: '@devup-ui/webpack-plugin': specifier: workspace:* version: link:../webpack-plugin + browserslist: + specifier: latest + version: 4.25.1 next: specifier: ^15.3 version: 15.3.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) devDependencies: + '@types/webpack': + specifier: ^5.28.5 + version: 5.28.5 typescript: specifier: ^5.8.3 version: 5.8.3 @@ -438,9 +444,6 @@ importers: vite-plugin-dts: specifier: ^4.5.4 version: 4.5.4(@types/node@24.0.12)(rollup@4.44.0)(typescript@5.8.3)(vite@6.3.5(@types/node@24.0.12)(jiti@2.4.2)(terser@5.43.1)) - vitest: - specifier: ^3.2.4 - version: 3.2.4(@types/debug@4.1.12)(@types/node@24.0.12)(happy-dom@18.0.1)(jiti@2.4.2)(terser@5.43.1) packages/rsbuild-plugin: dependencies: @@ -2977,8 +2980,8 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - browserslist@4.25.0: - resolution: {integrity: sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==} + browserslist@4.25.1: + resolution: {integrity: sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -3012,6 +3015,9 @@ packages: caniuse-lite@1.0.30001724: resolution: {integrity: sha512-WqJo7p0TbHDOythNTqYujmaJTvtYRZrjpP8TCvH6Vb9CYJerJNKamKzIWOM4BkQatWj9H2lYulpdAQNBe7QhNA==} + caniuse-lite@1.0.30001727: + resolution: {integrity: sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==} + ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -3229,6 +3235,9 @@ packages: electron-to-chromium@1.5.171: resolution: {integrity: sha512-scWpzXEJEMrGJa4Y6m/tVotb0WuvNmasv3wWVzUAeCgKU0ToFOhUW6Z+xWnRQANMYGxN4ngJXIThgBJOqzVPCQ==} + electron-to-chromium@1.5.183: + resolution: {integrity: sha512-vCrDBYjQCAEefWGjlK3EpoSKfKbT10pR4XXPdn65q7snuNOZnthoVpBfZPykmDapOKfoD+MMIPG8ZjKyyc9oHA==} + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -5565,7 +5574,7 @@ snapshots: dependencies: '@babel/compat-data': 7.27.5 '@babel/helper-validator-option': 7.27.1 - browserslist: 4.25.0 + browserslist: 4.25.1 lru-cache: 5.1.1 semver: 6.3.1 @@ -8636,12 +8645,12 @@ snapshots: node-releases: 2.0.19 update-browserslist-db: 1.1.3(browserslist@4.21.5) - browserslist@4.25.0: + browserslist@4.25.1: dependencies: - caniuse-lite: 1.0.30001724 - electron-to-chromium: 1.5.171 + caniuse-lite: 1.0.30001727 + electron-to-chromium: 1.5.183 node-releases: 2.0.19 - update-browserslist-db: 1.1.3(browserslist@4.25.0) + update-browserslist-db: 1.1.3(browserslist@4.25.1) buffer-from@1.1.2: {} @@ -8672,6 +8681,8 @@ snapshots: caniuse-lite@1.0.30001724: {} + caniuse-lite@1.0.30001727: {} + ccount@2.0.1: {} chai@5.2.0: @@ -8762,7 +8773,7 @@ snapshots: core-js-compat@3.43.0: dependencies: - browserslist: 4.25.0 + browserslist: 4.25.1 core-js@3.44.0: {} @@ -8867,6 +8878,8 @@ snapshots: electron-to-chromium@1.5.171: {} + electron-to-chromium@1.5.183: {} + emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} @@ -11539,9 +11552,9 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 - update-browserslist-db@1.1.3(browserslist@4.25.0): + update-browserslist-db@1.1.3(browserslist@4.25.1): dependencies: - browserslist: 4.25.0 + browserslist: 4.25.1 escalade: 3.2.0 picocolors: 1.1.1 @@ -11711,7 +11724,7 @@ snapshots: '@webassemblyjs/wasm-edit': 1.14.1 '@webassemblyjs/wasm-parser': 1.14.1 acorn: 8.15.0 - browserslist: 4.25.0 + browserslist: 4.25.1 chrome-trace-event: 1.0.4 enhanced-resolve: 5.18.1 es-module-lexer: 1.7.0