This commit is contained in:
Alex 2024-04-27 02:05:00 -04:00
parent 5cdb5e705a
commit 8010c774cd
23 changed files with 271 additions and 4206 deletions

2
.gitignore vendored
View File

@ -1,4 +1,6 @@
*.draft
*.draft.* *.draft.*
src/converts
# Logs # Logs
logs logs

View File

@ -1,6 +1,6 @@
{ {
"name": "@aet/babel-tailwind", "name": "@aet/tailwind",
"version": "0.0.1-beta.8", "version": "0.0.1-beta.5",
"main": "dist/index.js", "main": "dist/index.js",
"license": "MIT", "license": "MIT",
"private": true, "private": true,
@ -12,9 +12,9 @@
"dist" "dist"
], ],
"devDependencies": { "devDependencies": {
"@aet/eslint-rules": "^0.0.22", "@aet/eslint-rules": "^0.0.25",
"@types/babel__core": "^7.20.5", "@types/babel__core": "^7.20.5",
"@types/bun": "^1.0.12", "@types/bun": "^1.1.0",
"@types/dedent": "^0.7.2", "@types/dedent": "^0.7.2",
"@types/lodash": "^4.17.0", "@types/lodash": "^4.17.0",
"@types/node": "^20.12.7", "@types/node": "^20.12.7",
@ -32,8 +32,8 @@
"tailwindcss": "^3.4.3", "tailwindcss": "^3.4.3",
"tsup": "^8.0.2", "tsup": "^8.0.2",
"typescript": "^5.4.5", "typescript": "^5.4.5",
"vite": "^5.2.8", "vite": "^5.2.10",
"vitest": "^1.4.0" "vitest": "^1.5.0"
}, },
"peerDependencies": { "peerDependencies": {
"tailwindcss": "^3.4.3" "tailwindcss": "^3.4.3"
@ -52,4 +52,4 @@
"singleQuote": false, "singleQuote": false,
"trailingComma": "es5" "trailingComma": "es5"
} }
} }

195
pnpm-lock.yaml generated
View File

@ -20,14 +20,14 @@ dependencies:
devDependencies: devDependencies:
'@aet/eslint-rules': '@aet/eslint-rules':
specifier: ^0.0.22 specifier: ^0.0.25
version: 0.0.22(eslint@8.57.0)(typescript@5.4.5) version: 0.0.25(eslint@8.57.0)(typescript@5.4.5)
'@types/babel__core': '@types/babel__core':
specifier: ^7.20.5 specifier: ^7.20.5
version: 7.20.5 version: 7.20.5
'@types/bun': '@types/bun':
specifier: ^1.0.12 specifier: ^1.1.0
version: 1.0.12 version: 1.1.0
'@types/dedent': '@types/dedent':
specifier: ^0.7.2 specifier: ^0.7.2
version: 0.7.2 version: 0.7.2
@ -80,11 +80,11 @@ devDependencies:
specifier: ^5.4.5 specifier: ^5.4.5
version: 5.4.5 version: 5.4.5
vite: vite:
specifier: ^5.2.8 specifier: ^5.2.10
version: 5.2.8(@types/node@20.12.7) version: 5.2.10(@types/node@20.12.7)
vitest: vitest:
specifier: ^1.4.0 specifier: ^1.5.0
version: 1.4.0(@types/node@20.12.7) version: 1.5.0(@types/node@20.12.7)
packages: packages:
@ -93,18 +93,18 @@ packages:
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: true dev: true
/@aet/eslint-rules@0.0.22(eslint@8.57.0)(typescript@5.4.5): /@aet/eslint-rules@0.0.25(eslint@8.57.0)(typescript@5.4.5):
resolution: {integrity: sha512-PLAnaYlb6GdG6gKjQP5B8XckOa7driZL1rbw51wGTYTl/0Etqu5R/u4TkQSYePZab7xCIrJuPSC3PsBEhI5pvQ==} resolution: {integrity: sha512-6F6iSb5Oc8DAqUVIPzXMQuAgzPceowZMh/lGfV++sk9t36nVefy+8UDWoDixVyHjoAbGPWjV4xx1EHi2xawk9g==}
peerDependencies: peerDependencies:
eslint: ^8.57.0 eslint: ^8.57.0
typescript: ^5.4.4 typescript: ^5.4.4
dependencies: dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
'@types/eslint': 8.56.7 '@types/eslint': 8.56.10
'@typescript-eslint/eslint-plugin': 7.5.0(@typescript-eslint/parser@7.5.0)(eslint@8.57.0)(typescript@5.4.5) '@typescript-eslint/eslint-plugin': 7.7.0(@typescript-eslint/parser@7.7.0)(eslint@8.57.0)(typescript@5.4.5)
'@typescript-eslint/parser': 7.5.0(eslint@8.57.0)(typescript@5.4.5) '@typescript-eslint/parser': 7.7.0(eslint@8.57.0)(typescript@5.4.5)
'@typescript-eslint/type-utils': 7.5.0(eslint@8.57.0)(typescript@5.4.5) '@typescript-eslint/type-utils': 7.7.0(eslint@8.57.0)(typescript@5.4.5)
'@typescript-eslint/utils': 7.5.0(eslint@8.57.0)(typescript@5.4.5) '@typescript-eslint/utils': 7.7.0(eslint@8.57.0)(typescript@5.4.5)
aria-query: 5.3.0 aria-query: 5.3.0
axe-core: 4.9.0 axe-core: 4.9.0
axobject-query: 4.0.0 axobject-query: 4.0.0
@ -117,10 +117,12 @@ packages:
eslint-config-prettier: 9.1.0(eslint@8.57.0) eslint-config-prettier: 9.1.0(eslint@8.57.0)
eslint-define-config: 1.24.1 eslint-define-config: 1.24.1
eslint-import-resolver-node: 0.3.9 eslint-import-resolver-node: 0.3.9
eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.5.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0) eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.7.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0)
eslint-plugin-es-x: 7.6.0(eslint@8.57.0) eslint-plugin-es-x: 7.6.0(eslint@8.57.0)
eslint-plugin-jsdoc: 48.2.3(eslint@8.57.0) eslint-plugin-jsdoc: 48.2.3(eslint@8.57.0)
eslint-plugin-unicorn: 52.0.0(eslint@8.57.0) eslint-plugin-unicorn: 52.0.0(eslint@8.57.0)
esprima: 4.0.1
esquery: 1.5.0
estraverse: 5.3.0 estraverse: 5.3.0
fast-glob: 3.3.2 fast-glob: 3.3.2
get-tsconfig: 4.7.3 get-tsconfig: 4.7.3
@ -1057,18 +1059,18 @@ packages:
'@babel/types': 7.24.0 '@babel/types': 7.24.0
dev: true dev: true
/@types/bun@1.0.12: /@types/bun@1.1.0:
resolution: {integrity: sha512-qPb5FcygbpSS1NDBjWyQCWeI9kKXwSYSR1Enu7yb+gMXgFwGMhlyOvgV/7FGrdvAjlSXWRY6IDepos7k8WzAtQ==} resolution: {integrity: sha512-QGK0yU4jh0OK1A7DyhPkQuKjHQCC5jSJa3dpWIEhHv/rPfb6zLfdArc4/uUUZBMTcjilsafRXnPWO+1owb572Q==}
dependencies: dependencies:
bun-types: 1.0.36 bun-types: 1.1.0
dev: true dev: true
/@types/dedent@0.7.2: /@types/dedent@0.7.2:
resolution: {integrity: sha512-kRiitIeUg1mPV9yH4VUJ/1uk2XjyANfeL8/7rH1tsjvHeO9PJLBHJIYsFWmAvmGj5u8rj+1TZx7PZzW2qLw3Lw==} resolution: {integrity: sha512-kRiitIeUg1mPV9yH4VUJ/1uk2XjyANfeL8/7rH1tsjvHeO9PJLBHJIYsFWmAvmGj5u8rj+1TZx7PZzW2qLw3Lw==}
dev: true dev: true
/@types/eslint@8.56.7: /@types/eslint@8.56.10:
resolution: {integrity: sha512-SjDvI/x3zsZnOkYZ3lCt9lOZWZLB2jIlNKz+LBgCtDurK0JZcwucxYHn1w2BJkD34dgX9Tjnak0txtq4WTggEA==} resolution: {integrity: sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==}
dependencies: dependencies:
'@types/estree': 1.0.5 '@types/estree': 1.0.5
'@types/json-schema': 7.0.15 '@types/json-schema': 7.0.15
@ -1118,8 +1120,8 @@ packages:
'@types/node': 20.12.7 '@types/node': 20.12.7
dev: true dev: true
/@typescript-eslint/eslint-plugin@7.5.0(@typescript-eslint/parser@7.5.0)(eslint@8.57.0)(typescript@5.4.5): /@typescript-eslint/eslint-plugin@7.7.0(@typescript-eslint/parser@7.7.0)(eslint@8.57.0)(typescript@5.4.5):
resolution: {integrity: sha512-HpqNTH8Du34nLxbKgVMGljZMG0rJd2O9ecvr2QLYp+7512ty1j42KnsFwspPXg1Vh8an9YImf6CokUBltisZFQ==} resolution: {integrity: sha512-GJWR0YnfrKnsRoluVO3PRb9r5aMZriiMMM/RHj5nnTrBy1/wIgk76XCtCKcnXGjpZQJQRFtGV9/0JJ6n30uwpQ==}
engines: {node: ^18.18.0 || >=20.0.0} engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies: peerDependencies:
'@typescript-eslint/parser': ^7.0.0 '@typescript-eslint/parser': ^7.0.0
@ -1130,11 +1132,11 @@ packages:
optional: true optional: true
dependencies: dependencies:
'@eslint-community/regexpp': 4.10.0 '@eslint-community/regexpp': 4.10.0
'@typescript-eslint/parser': 7.5.0(eslint@8.57.0)(typescript@5.4.5) '@typescript-eslint/parser': 7.7.0(eslint@8.57.0)(typescript@5.4.5)
'@typescript-eslint/scope-manager': 7.5.0 '@typescript-eslint/scope-manager': 7.7.0
'@typescript-eslint/type-utils': 7.5.0(eslint@8.57.0)(typescript@5.4.5) '@typescript-eslint/type-utils': 7.7.0(eslint@8.57.0)(typescript@5.4.5)
'@typescript-eslint/utils': 7.5.0(eslint@8.57.0)(typescript@5.4.5) '@typescript-eslint/utils': 7.7.0(eslint@8.57.0)(typescript@5.4.5)
'@typescript-eslint/visitor-keys': 7.5.0 '@typescript-eslint/visitor-keys': 7.7.0
debug: 4.3.4 debug: 4.3.4
eslint: 8.57.0 eslint: 8.57.0
graphemer: 1.4.0 graphemer: 1.4.0
@ -1147,8 +1149,8 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/parser@7.5.0(eslint@8.57.0)(typescript@5.4.5): /@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.4.5):
resolution: {integrity: sha512-cj+XGhNujfD2/wzR1tabNsidnYRaFfEkcULdcIyVBYcXjBvBKOes+mpMBP7hMpOyk+gBcfXsrg4NBGAStQyxjQ==} resolution: {integrity: sha512-fNcDm3wSwVM8QYL4HKVBggdIPAy9Q41vcvC/GtDobw3c4ndVT3K6cqudUmjHPw8EAp4ufax0o58/xvWaP2FmTg==}
engines: {node: ^18.18.0 || >=20.0.0} engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies: peerDependencies:
eslint: ^8.56.0 eslint: ^8.56.0
@ -1157,10 +1159,10 @@ packages:
typescript: typescript:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/scope-manager': 7.5.0 '@typescript-eslint/scope-manager': 7.7.0
'@typescript-eslint/types': 7.5.0 '@typescript-eslint/types': 7.7.0
'@typescript-eslint/typescript-estree': 7.5.0(typescript@5.4.5) '@typescript-eslint/typescript-estree': 7.7.0(typescript@5.4.5)
'@typescript-eslint/visitor-keys': 7.5.0 '@typescript-eslint/visitor-keys': 7.7.0
debug: 4.3.4 debug: 4.3.4
eslint: 8.57.0 eslint: 8.57.0
typescript: 5.4.5 typescript: 5.4.5
@ -1168,16 +1170,16 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/scope-manager@7.5.0: /@typescript-eslint/scope-manager@7.7.0:
resolution: {integrity: sha512-Z1r7uJY0MDeUlql9XJ6kRVgk/sP11sr3HKXn268HZyqL7i4cEfrdFuSSY/0tUqT37l5zT0tJOsuDP16kio85iA==} resolution: {integrity: sha512-/8INDn0YLInbe9Wt7dK4cXLDYp0fNHP5xKLHvZl3mOT5X17rK/YShXaiNmorl+/U4VKCVIjJnx4Ri5b0y+HClw==}
engines: {node: ^18.18.0 || >=20.0.0} engines: {node: ^18.18.0 || >=20.0.0}
dependencies: dependencies:
'@typescript-eslint/types': 7.5.0 '@typescript-eslint/types': 7.7.0
'@typescript-eslint/visitor-keys': 7.5.0 '@typescript-eslint/visitor-keys': 7.7.0
dev: true dev: true
/@typescript-eslint/type-utils@7.5.0(eslint@8.57.0)(typescript@5.4.5): /@typescript-eslint/type-utils@7.7.0(eslint@8.57.0)(typescript@5.4.5):
resolution: {integrity: sha512-A021Rj33+G8mx2Dqh0nMO9GyjjIBK3MqgVgZ2qlKf6CJy51wY/lkkFqq3TqqnH34XyAHUkq27IjlUkWlQRpLHw==} resolution: {integrity: sha512-bOp3ejoRYrhAlnT/bozNQi3nio9tIgv3U5C0mVDdZC7cpcQEDZXvq8inrHYghLVwuNABRqrMW5tzAv88Vy77Sg==}
engines: {node: ^18.18.0 || >=20.0.0} engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies: peerDependencies:
eslint: ^8.56.0 eslint: ^8.56.0
@ -1186,8 +1188,8 @@ packages:
typescript: typescript:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/typescript-estree': 7.5.0(typescript@5.4.5) '@typescript-eslint/typescript-estree': 7.7.0(typescript@5.4.5)
'@typescript-eslint/utils': 7.5.0(eslint@8.57.0)(typescript@5.4.5) '@typescript-eslint/utils': 7.7.0(eslint@8.57.0)(typescript@5.4.5)
debug: 4.3.4 debug: 4.3.4
eslint: 8.57.0 eslint: 8.57.0
ts-api-utils: 1.3.0(typescript@5.4.5) ts-api-utils: 1.3.0(typescript@5.4.5)
@ -1196,13 +1198,13 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/types@7.5.0: /@typescript-eslint/types@7.7.0:
resolution: {integrity: sha512-tv5B4IHeAdhR7uS4+bf8Ov3k793VEVHd45viRRkehIUZxm0WF82VPiLgHzA/Xl4TGPg1ZD49vfxBKFPecD5/mg==} resolution: {integrity: sha512-G01YPZ1Bd2hn+KPpIbrAhEWOn5lQBrjxkzHkWvP6NucMXFtfXoevK82hzQdpfuQYuhkvFDeQYbzXCjR1z9Z03w==}
engines: {node: ^18.18.0 || >=20.0.0} engines: {node: ^18.18.0 || >=20.0.0}
dev: true dev: true
/@typescript-eslint/typescript-estree@7.5.0(typescript@5.4.5): /@typescript-eslint/typescript-estree@7.7.0(typescript@5.4.5):
resolution: {integrity: sha512-YklQQfe0Rv2PZEueLTUffiQGKQneiIEKKnfIqPIOxgM9lKSZFCjT5Ad4VqRKj/U4+kQE3fa8YQpskViL7WjdPQ==} resolution: {integrity: sha512-8p71HQPE6CbxIBy2kWHqM1KGrC07pk6RJn40n0DSc6bMOBBREZxSDJ+BmRzc8B5OdaMh1ty3mkuWRg4sCFiDQQ==}
engines: {node: ^18.18.0 || >=20.0.0} engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies: peerDependencies:
typescript: '*' typescript: '*'
@ -1210,12 +1212,12 @@ packages:
typescript: typescript:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/types': 7.5.0 '@typescript-eslint/types': 7.7.0
'@typescript-eslint/visitor-keys': 7.5.0 '@typescript-eslint/visitor-keys': 7.7.0
debug: 4.3.4 debug: 4.3.4
globby: 11.1.0 globby: 11.1.0
is-glob: 4.0.3 is-glob: 4.0.3
minimatch: 9.0.3 minimatch: 9.0.4
semver: 7.6.0 semver: 7.6.0
ts-api-utils: 1.3.0(typescript@5.4.5) ts-api-utils: 1.3.0(typescript@5.4.5)
typescript: 5.4.5 typescript: 5.4.5
@ -1223,8 +1225,8 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/utils@7.5.0(eslint@8.57.0)(typescript@5.4.5): /@typescript-eslint/utils@7.7.0(eslint@8.57.0)(typescript@5.4.5):
resolution: {integrity: sha512-3vZl9u0R+/FLQcpy2EHyRGNqAS/ofJ3Ji8aebilfJe+fobK8+LbIFmrHciLVDxjDoONmufDcnVSF38KwMEOjzw==} resolution: {integrity: sha512-LKGAXMPQs8U/zMRFXDZOzmMKgFv3COlxUQ+2NMPhbqgVm6R1w+nU1i4836Pmxu9jZAuIeyySNrN/6Rc657ggig==}
engines: {node: ^18.18.0 || >=20.0.0} engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies: peerDependencies:
eslint: ^8.56.0 eslint: ^8.56.0
@ -1232,9 +1234,9 @@ packages:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
'@types/json-schema': 7.0.15 '@types/json-schema': 7.0.15
'@types/semver': 7.5.8 '@types/semver': 7.5.8
'@typescript-eslint/scope-manager': 7.5.0 '@typescript-eslint/scope-manager': 7.7.0
'@typescript-eslint/types': 7.5.0 '@typescript-eslint/types': 7.7.0
'@typescript-eslint/typescript-estree': 7.5.0(typescript@5.4.5) '@typescript-eslint/typescript-estree': 7.7.0(typescript@5.4.5)
eslint: 8.57.0 eslint: 8.57.0
semver: 7.6.0 semver: 7.6.0
transitivePeerDependencies: transitivePeerDependencies:
@ -1242,11 +1244,11 @@ packages:
- typescript - typescript
dev: true dev: true
/@typescript-eslint/visitor-keys@7.5.0: /@typescript-eslint/visitor-keys@7.7.0:
resolution: {integrity: sha512-mcuHM/QircmA6O7fy6nn2w/3ditQkj+SgtOc8DW3uQ10Yfj42amm2i+6F2K4YAOPNNTmE6iM1ynM6lrSwdendA==} resolution: {integrity: sha512-h0WHOj8MhdhY8YWkzIF30R379y0NqyOHExI9N9KCzvmu05EgG4FumeYa3ccfKUSphyWkWQE1ybVrgz/Pbam6YA==}
engines: {node: ^18.18.0 || >=20.0.0} engines: {node: ^18.18.0 || >=20.0.0}
dependencies: dependencies:
'@typescript-eslint/types': 7.5.0 '@typescript-eslint/types': 7.7.0
eslint-visitor-keys: 3.4.3 eslint-visitor-keys: 3.4.3
dev: true dev: true
@ -1254,38 +1256,38 @@ packages:
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
dev: true dev: true
/@vitest/expect@1.4.0: /@vitest/expect@1.5.0:
resolution: {integrity: sha512-Jths0sWCJZ8BxjKe+p+eKsoqev1/T8lYcrjavEaz8auEJ4jAVY0GwW3JKmdVU4mmNPLPHixh4GNXP7GFtAiDHA==} resolution: {integrity: sha512-0pzuCI6KYi2SIC3LQezmxujU9RK/vwC1U9R0rLuGlNGcOuDWxqWKu6nUdFsX9tH1WU0SXtAxToOsEjeUn1s3hA==}
dependencies: dependencies:
'@vitest/spy': 1.4.0 '@vitest/spy': 1.5.0
'@vitest/utils': 1.4.0 '@vitest/utils': 1.5.0
chai: 4.4.1 chai: 4.4.1
dev: true dev: true
/@vitest/runner@1.4.0: /@vitest/runner@1.5.0:
resolution: {integrity: sha512-EDYVSmesqlQ4RD2VvWo3hQgTJ7ZrFQ2VSJdfiJiArkCerDAGeyF1i6dHkmySqk573jLp6d/cfqCN+7wUB5tLgg==} resolution: {integrity: sha512-7HWwdxXP5yDoe7DTpbif9l6ZmDwCzcSIK38kTSIt6CFEpMjX4EpCgT6wUmS0xTXqMI6E/ONmfgRKmaujpabjZQ==}
dependencies: dependencies:
'@vitest/utils': 1.4.0 '@vitest/utils': 1.5.0
p-limit: 5.0.0 p-limit: 5.0.0
pathe: 1.1.2 pathe: 1.1.2
dev: true dev: true
/@vitest/snapshot@1.4.0: /@vitest/snapshot@1.5.0:
resolution: {integrity: sha512-saAFnt5pPIA5qDGxOHxJ/XxhMFKkUSBJmVt5VgDsAqPTX6JP326r5C/c9UuCMPoXNzuudTPsYDZCoJ5ilpqG2A==} resolution: {integrity: sha512-qpv3fSEuNrhAO3FpH6YYRdaECnnRjg9VxbhdtPwPRnzSfHVXnNzzrpX4cJxqiwgRMo7uRMWDFBlsBq4Cr+rO3A==}
dependencies: dependencies:
magic-string: 0.30.8 magic-string: 0.30.8
pathe: 1.1.2 pathe: 1.1.2
pretty-format: 29.7.0 pretty-format: 29.7.0
dev: true dev: true
/@vitest/spy@1.4.0: /@vitest/spy@1.5.0:
resolution: {integrity: sha512-Ywau/Qs1DzM/8Uc+yA77CwSegizMlcgTJuYGAi0jujOteJOUf1ujunHThYo243KG9nAyWT3L9ifPYZ5+As/+6Q==} resolution: {integrity: sha512-vu6vi6ew5N5MMHJjD5PoakMRKYdmIrNJmyfkhRpQt5d9Ewhw9nZ5Aqynbi3N61bvk9UvZ5UysMT6ayIrZ8GA9w==}
dependencies: dependencies:
tinyspy: 2.2.1 tinyspy: 2.2.1
dev: true dev: true
/@vitest/utils@1.4.0: /@vitest/utils@1.5.0:
resolution: {integrity: sha512-mx3Yd1/6e2Vt/PUC98DcqTirtfxUyAZ32uK82r8rZzbtBeBo+nqgnjx/LvqQdWsrvNtm14VmurNgcf4nqY5gJg==} resolution: {integrity: sha512-BDU0GNL8MWkRkSRdNFvCUCAVOeHaUlVJ9Tx0TYBZyXaaOTmGtUFObzchCivIBrIwKzvZA7A9sCejVhXM2aY98A==}
dependencies: dependencies:
diff-sequences: 29.6.3 diff-sequences: 29.6.3
estree-walker: 3.0.3 estree-walker: 3.0.3
@ -1449,8 +1451,8 @@ packages:
engines: {node: '>=6'} engines: {node: '>=6'}
dev: true dev: true
/bun-types@1.0.36: /bun-types@1.1.0:
resolution: {integrity: sha512-gaIb1SyhB0JZfIEg73/kSFhqolUqJXC68peguhXGwqr27HuvI8nkD0LTIHp/1DY4cNadfXHYgYrZIWX7oEoXlg==} resolution: {integrity: sha512-GhMDD7TosdJzQPGUOcQD5PZshvXVxDfwGAZs2dq+eSaPsRn3iUCzvpFlsg7Q51bXVzLAUs+FWHlnmpgZ5UggIg==}
dependencies: dependencies:
'@types/node': 20.11.30 '@types/node': 20.11.30
'@types/ws': 8.5.10 '@types/ws': 8.5.10
@ -1865,7 +1867,7 @@ packages:
- supports-color - supports-color
dev: true dev: true
/eslint-module-utils@2.8.1(@typescript-eslint/parser@7.5.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0): /eslint-module-utils@2.8.1(@typescript-eslint/parser@7.7.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0):
resolution: {integrity: sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==} resolution: {integrity: sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==}
engines: {node: '>=4'} engines: {node: '>=4'}
peerDependencies: peerDependencies:
@ -1886,7 +1888,7 @@ packages:
eslint-import-resolver-webpack: eslint-import-resolver-webpack:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/parser': 7.5.0(eslint@8.57.0)(typescript@5.4.5) '@typescript-eslint/parser': 7.7.0(eslint@8.57.0)(typescript@5.4.5)
debug: 3.2.7 debug: 3.2.7
eslint: 8.57.0 eslint: 8.57.0
eslint-import-resolver-node: 0.3.9 eslint-import-resolver-node: 0.3.9
@ -2022,6 +2024,12 @@ packages:
eslint-visitor-keys: 3.4.3 eslint-visitor-keys: 3.4.3
dev: true dev: true
/esprima@4.0.1:
resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
engines: {node: '>=4'}
hasBin: true
dev: true
/esquery@1.5.0: /esquery@1.5.0:
resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==}
engines: {node: '>=0.10'} engines: {node: '>=0.10'}
@ -2636,13 +2644,6 @@ packages:
brace-expansion: 1.1.11 brace-expansion: 1.1.11
dev: true dev: true
/minimatch@9.0.3:
resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==}
engines: {node: '>=16 || 14 >=14.17'}
dependencies:
brace-expansion: 2.0.1
dev: true
/minimatch@9.0.4: /minimatch@9.0.4:
resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==} resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==}
engines: {node: '>=16 || 14 >=14.17'} engines: {node: '>=16 || 14 >=14.17'}
@ -3555,8 +3556,8 @@ packages:
spdx-expression-parse: 3.0.1 spdx-expression-parse: 3.0.1
dev: true dev: true
/vite-node@1.4.0(@types/node@20.12.7): /vite-node@1.5.0(@types/node@20.12.7):
resolution: {integrity: sha512-VZDAseqjrHgNd4Kh8icYHWzTKSCZMhia7GyHfhtzLW33fZlG9SwsB6CEhgyVOWkJfJ2pFLrp/Gj1FSfAiqH9Lw==} resolution: {integrity: sha512-tV8h6gMj6vPzVCa7l+VGq9lwoJjW8Y79vst8QZZGiuRAfijU+EEWuc0kFpmndQrWhMMhet1jdSF+40KSZUqIIw==}
engines: {node: ^18.0.0 || >=20.0.0} engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true hasBin: true
dependencies: dependencies:
@ -3564,7 +3565,7 @@ packages:
debug: 4.3.4 debug: 4.3.4
pathe: 1.1.2 pathe: 1.1.2
picocolors: 1.0.0 picocolors: 1.0.0
vite: 5.2.8(@types/node@20.12.7) vite: 5.2.10(@types/node@20.12.7)
transitivePeerDependencies: transitivePeerDependencies:
- '@types/node' - '@types/node'
- less - less
@ -3576,8 +3577,8 @@ packages:
- terser - terser
dev: true dev: true
/vite@5.2.8(@types/node@20.12.7): /vite@5.2.10(@types/node@20.12.7):
resolution: {integrity: sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==} resolution: {integrity: sha512-PAzgUZbP7msvQvqdSD+ErD5qGnSFiGOoWmV5yAKUEI0kdhjbH6nMWVyZQC/hSc4aXwc0oJ9aEdIiF9Oje0JFCw==}
engines: {node: ^18.0.0 || >=20.0.0} engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
@ -3612,15 +3613,15 @@ packages:
fsevents: 2.3.3 fsevents: 2.3.3
dev: true dev: true
/vitest@1.4.0(@types/node@20.12.7): /vitest@1.5.0(@types/node@20.12.7):
resolution: {integrity: sha512-gujzn0g7fmwf83/WzrDTnncZt2UiXP41mHuFYFrdwaLRVQ6JYQEiME2IfEjU3vcFL3VKa75XhI3lFgn+hfVsQw==} resolution: {integrity: sha512-d8UKgR0m2kjdxDWX6911uwxout6GHS0XaGH1cksSIVVG8kRlE7G7aBw7myKQCvDI5dT4j7ZMa+l706BIORMDLw==}
engines: {node: ^18.0.0 || >=20.0.0} engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
'@edge-runtime/vm': '*' '@edge-runtime/vm': '*'
'@types/node': ^18.0.0 || >=20.0.0 '@types/node': ^18.0.0 || >=20.0.0
'@vitest/browser': 1.4.0 '@vitest/browser': 1.5.0
'@vitest/ui': 1.4.0 '@vitest/ui': 1.5.0
happy-dom: '*' happy-dom: '*'
jsdom: '*' jsdom: '*'
peerDependenciesMeta: peerDependenciesMeta:
@ -3638,11 +3639,11 @@ packages:
optional: true optional: true
dependencies: dependencies:
'@types/node': 20.12.7 '@types/node': 20.12.7
'@vitest/expect': 1.4.0 '@vitest/expect': 1.5.0
'@vitest/runner': 1.4.0 '@vitest/runner': 1.5.0
'@vitest/snapshot': 1.4.0 '@vitest/snapshot': 1.5.0
'@vitest/spy': 1.4.0 '@vitest/spy': 1.5.0
'@vitest/utils': 1.4.0 '@vitest/utils': 1.5.0
acorn-walk: 8.3.2 acorn-walk: 8.3.2
chai: 4.4.1 chai: 4.4.1
debug: 4.3.4 debug: 4.3.4
@ -3655,8 +3656,8 @@ packages:
strip-literal: 2.1.0 strip-literal: 2.1.0
tinybench: 2.6.0 tinybench: 2.6.0
tinypool: 0.8.3 tinypool: 0.8.3
vite: 5.2.8(@types/node@20.12.7) vite: 5.2.10(@types/node@20.12.7)
vite-node: 1.4.0(@types/node@20.12.7) vite-node: 1.5.0(@types/node@20.12.7)
why-is-node-running: 2.2.2 why-is-node-running: 2.2.2
transitivePeerDependencies: transitivePeerDependencies:
- less - less

View File

@ -1,8 +1,8 @@
import { basename, dirname, extname, join } from "node:path"; import { basename, dirname, extname, join } from "node:path";
import type babel from "@babel/core"; import type babel from "@babel/core";
import { type NodePath, type types as t } from "@babel/core"; import { type NodePath, type types as t } from "@babel/core";
import type { SourceLocation, StyleMap, StyleMapEntry } from "./shared"; import type { SourceLocation, StyleMapEntry } from "./shared";
import { type TailwindPluginOptions, getClassName } from "./index"; import { type ResolveTailwindOptions, getClassName } from "./index";
const definePlugin = const definePlugin =
<T>(fn: (runtime: typeof babel) => babel.Visitor<babel.PluginPass & T>) => <T>(fn: (runtime: typeof babel) => babel.Visitor<babel.PluginPass & T>) =>
@ -32,15 +32,18 @@ interface BabelPluginState {
tailwindMap: Map<string, StyleMapEntry>; tailwindMap: Map<string, StyleMapEntry>;
} }
export type ClassNameCollector = (path: string, entries: StyleMapEntry[]) => void;
export function babelTailwind( export function babelTailwind(
styleMap: StyleMap,
{ {
styleMap,
clsx, clsx,
getClassName: getClass = getClassName, getClassName: getClass = getClassName,
taggedTemplateName, taggedTemplateName,
jsxAttributeAction = "delete", jsxAttributeAction = "delete",
jsxAttributeName = "css", jsxAttributeName = "css",
}: TailwindPluginOptions }: ResolveTailwindOptions,
onCollect: ClassNameCollector | undefined
) { ) {
function getClsxImport(t: typeof babel.types, cx: t.Identifier) { function getClsxImport(t: typeof babel.types, cx: t.Identifier) {
switch (clsx) { switch (clsx) {
@ -103,7 +106,10 @@ export function babelTailwind(
t.importDeclaration([], t.stringLiteral(`tailwind:./${cssName}`)) t.importDeclaration([], t.stringLiteral(`tailwind:./${cssName}`))
); );
styleMap.set(join(dirname(filename), cssName), Array.from(tailwindMap.values())); const path = join(dirname(filename), cssName);
const value = Array.from(tailwindMap.values());
styleMap.set(path, value);
onCollect?.(path, value);
}, },
}, },
@ -185,18 +191,24 @@ export function babelTailwind(
if (classNameAttribute) { if (classNameAttribute) {
const attrValue = classNameAttribute.value!; const attrValue = classNameAttribute.value!;
const valuePathNode = valuePath.node;
const wrap = (originalValue: babel.types.Expression) =>
t.callExpression(getCx(), [originalValue, extractJSXContainer(valuePathNode)]);
// If both are string literals, we can merge them directly here // If both are string literals, we can merge them directly here
if (t.isStringLiteral(attrValue) && t.isStringLiteral(valuePath.node)) { if (t.isStringLiteral(attrValue) && t.isStringLiteral(valuePathNode)) {
attrValue.value += attrValue.value +=
(attrValue.value.at(-1) === " " ? "" : " ") + valuePath.node.value; (attrValue.value.at(-1) === " " ? "" : " ") + valuePathNode.value;
} else { } else {
classNameAttribute.value = t.jsxExpressionContainer( const internalAttrValue = extractJSXContainer(attrValue);
t.callExpression(getCx(), [ if (
extractJSXContainer(attrValue), t.isArrowFunctionExpression(internalAttrValue) &&
extractJSXContainer(valuePath.node), !t.isBlockStatement(internalAttrValue.body)
]) ) {
); internalAttrValue.body = wrap(internalAttrValue.body);
} else {
classNameAttribute.value = t.jsxExpressionContainer(wrap(internalAttrValue));
}
} }
} else { } else {
parent.attributes.push( parent.attributes.push(

View File

View File

@ -1,129 +0,0 @@
import { type AtRule, type ChildNode, type Node, Rule } from "postcss";
import { isAtRuleNode, isChildNode } from "./utils";
export interface ResolvedTailwindNode {
rule: Rule;
tailwindClasses: string[];
}
interface UnresolvedTailwindNode {
key: string;
rootRuleSelector?: string | null;
originalRule: Rule;
classesPrefix: string;
tailwindClasses: string[];
}
export type TailwindNode = ResolvedTailwindNode | UnresolvedTailwindNode;
function isResolvedTailwindNode(node: TailwindNode): node is ResolvedTailwindNode {
return "rule" in node;
}
export class TailwindNodesManager {
protected nodesMap: Map<string, ResolvedTailwindNode>;
constructor(initialNodes?: TailwindNode[]) {
this.nodesMap = new Map();
if (initialNodes?.length) {
this.mergeNodes(initialNodes);
}
}
mergeNode(node: TailwindNode) {
const nodeIsResolved = isResolvedTailwindNode(node);
const nodeKey = nodeIsResolved
? TailwindNodesManager.convertRuleToKey(node.rule)
: node.key;
const foundNode = this.nodesMap.get(nodeKey);
if (foundNode) {
foundNode.tailwindClasses = foundNode.tailwindClasses.concat(
nodeIsResolved
? node.tailwindClasses
: node.tailwindClasses.map(className => `${node.classesPrefix}${className}`)
);
return;
}
if (nodeIsResolved) {
this.nodesMap.set(nodeKey, node);
} else {
let rule;
if (node.rootRuleSelector) {
rule = new Rule({
selector: node.rootRuleSelector,
});
const rootChild = this.upToRootChild(node.originalRule);
if (rootChild) {
node.originalRule.root().insertBefore(rootChild, rule);
}
} else {
rule = node.originalRule;
}
this.nodesMap.set(nodeKey, {
rule,
tailwindClasses: node.tailwindClasses.map(
className => `${node.classesPrefix}${className}`
),
});
}
}
mergeNodes(nodes: TailwindNode[]) {
for (const node of nodes) {
this.mergeNode(node);
}
}
hasNode(key: string) {
return this.nodesMap.has(key);
}
getNode(key: string) {
return this.nodesMap.get(key) || null;
}
getNodes() {
return Array.from(this.nodesMap.values());
}
protected upToRootChild(node: Node) {
let childNode: ChildNode | null = null;
while (node.parent && node.parent.type !== "root" && isChildNode(node.parent)) {
childNode = node = node.parent;
continue;
}
return childNode;
}
static convertRuleToKey(rule: Rule, overriddenSelector: string | null = null) {
let currentParent = rule.parent;
let parentKey = "";
while (isChildNode(currentParent)) {
parentKey +=
(isAtRuleNode(currentParent)
? this.makeSingleAtRuleKey(currentParent)
: this.makeSingleRuleKey(currentParent)) + "__";
currentParent = currentParent.parent;
}
return parentKey + (overriddenSelector == null ? rule.selector : overriddenSelector);
}
protected static makeSingleAtRuleKey(atRule: AtRule) {
return `a(${atRule.name}|${atRule.params})`;
}
protected static makeSingleRuleKey(atRule: Rule) {
return `r(${atRule.selector})`;
}
}

View File

@ -1,230 +0,0 @@
# Convert CSS to TailwindCSS 3.x
[![npm package][npm-img]][npm-url]
[![Build Status][build-img]][build-url]
[![Downloads][downloads-img]][downloads-url]
[![Issues][issues-img]][issues-url]
[![Semantic Release][semantic-release-img]][semantic-release-url]
> Convert your CSS to TailwindCSS 3.x respecting TailwindCSS configuration
**[🔗 Demo](https://transform.tools/css-to-tailwind)**
**[🔗 VS Code Extension already available 🎉](https://github.com/Jackardios/vscode-css-to-tailwindcss)**
![VSCode demo](.github/demo.gif)
## Features:
- supports almost all the features (except custom plugins) currently available in TailwindCSS
- the ability to set your own TailwindCSS configuration
- colors are matched regardless of the format used
- rem is converted to px (it is possible to configure the rem size)
- non-convertible CSS declarations are simply skipped
- [ambiguities](https://tailwindcss.com/docs/adding-custom-styles#resolving-ambiguities) when using css variables are resolved automatically
## Install
```bash
npm install css-to-tailwindcss
```
## Usage
```ts
import { TailwindConverter } from 'css-to-tailwindcss';
const converter = new TailwindConverter({
remInPx: 16, // set null if you don't want to convert rem to pixels
postCSSPlugins: [require('postcss-nested')], // add any postcss plugins to this array
tailwindConfig: {
// your tailwind config here
content: [],
theme: {
extend: {
colors: {
'custom-color': {
100: '#123456',
200: 'hsla(210, 100%, 51.0%, 0.016)',
300: '#654321',
gold: 'hsl(41, 28.3%, 79.8%)',
marine: 'rgb(4, 55, 242, 0.75)',
},
},
screens: {
'custom-screen': { min: '768px', max: '1024px' },
},
},
supports: {
grid: 'display: grid',
flex: 'display: flex',
},
},
},
});
const inputCSS = `
:root {
--some-color: #090909;
}
.foo {
padding: 0.875em 256px;
margin-left: 16px;
text-align: center;
font-size: 12px;
transition: color, background-color, border-color, text-decoration-color, fill,
stroke 200ms cubic-bezier(0, 0, 0.2, 1);
animation-delay: 200ms;
&:hover {
filter: blur(4px) brightness(0.5) sepia(100%) contrast(1) hue-rotate(30deg)
invert(0) opacity(0.05) saturate(1.5);
color: hsl(41, 28.3%, 79.8%);
font-size: 1.25rem;
}
&[aria-disabled="true"] {
width: 25%;
color: var(--some-color);
font-size: 1em;
}
@media screen and (min-width: 768px) {
top: auto;
bottom: auto;
left: 25%;
right: 25%;
}
@media (min-width: 768px) and (max-width: 1024px) {
min-width: 100%;
margin-right: -24px;
}
@supports (display: grid) {
display: grid;
grid-column: span 1 / span 1;
}
}
.foo.bar {
padding: 0.875rem 256px 15%;
transform: translateX(12px) translateY(-0.5em) skew(1deg, 3deg)
scale(-0.75, 1.05) rotate(-0.25turn);
&::after {
content: "*";
animation: spin 1s linear infinite;
}
}
`;
converter.convertCSS(inputCSS).then(({ convertedRoot, nodes }) => {
console.log(convertedRoot.toString());
console.log(nodes);
});
```
Console output
`convertedRoot.toString()`:
```css
:root {
--some-color: #090909;
}
.foo {
@apply text-center text-xs transition-colors duration-200 ease-out ml-4 px-64 py-[0.875em] hover:blur-sm hover:brightness-50 hover:sepia hover:contrast-100 hover:hue-rotate-30 hover:invert-0 hover:opacity-5 hover:saturate-150 hover:text-custom-color-gold hover:text-xl disabled:w-3/12 disabled:text-[color:var(--some-color)] disabled:text-[1em] md:inset-x-1/4 md:inset-y-auto custom-screen:min-w-full custom-screen:-mr-6 supports-grid:grid supports-grid:col-span-1;
animation-delay: 200ms;
}
.foo.bar {
@apply translate-x-3 translate-y-[-0.5em] skew-x-1 skew-y-3 rotate-[-0.25turn] pt-3.5 pb-[15%] px-64 -scale-x-75 scale-y-105 after:content-["*"] after:animate-spin;
}
```
`nodes`:
```js
[
{
rule: {
selector: '.foo',
// ...
},
tailwindClasses: [
'text-center',
'text-xs',
'transition-colors',
'duration-200',
'ease-out',
'ml-4',
'px-64',
'py-[0.875em]',
'hover:blur-sm',
'hover:brightness-50',
'hover:sepia',
'hover:contrast-100',
'hover:hue-rotate-30',
'hover:invert-0',
'hover:opacity-5',
'hover:saturate-150',
'hover:text-custom-color-gold',
'hover:text-xl',
'disabled:w-3/12',
'disabled:text-[color:var(--some-color)]',
'disabled:text-[1em]',
'md:inset-x-1/4',
'md:inset-y-auto',
'custom-screen:min-w-full',
'custom-screen:-mr-6',
'supports-grid:grid',
'supports-grid:col-span-1',
],
},
{
rule: {
selector: '.foo.bar',
// ...
},
tailwindClasses: [
'translate-x-3',
'translate-y-[-0.5em]',
'skew-x-1',
'skew-y-3',
'rotate-[-0.25turn]',
'pt-3.5',
'pb-[15%]',
'px-64',
'-scale-x-75',
'scale-y-105',
'after:content-["*"]',
'after:animate-spin',
],
},
];
```
## API
### TailwindConverter(options?)
| Option | Type | Default | Description |
| ---------------------------- | ------------------ | ------- | ---------------------------------------------------------------------------------------- |
| remInPx | `number` \| `null` | `null` | `rem` in `px` unit. Set null if you don't want to convert rem to pixels |
| arbitraryPropertiesIsEnabled | `boolean` | `false` | defines whether non-convertible properties should be converted as "arbitrary properties" |
| tailwindConfig | `Config` | {} | Set your tailwind config here |
| postCSSPlugins | `AcceptedPlugin[]` | [] | Array of acceptable postcss plugins |
[build-img]: https://github.com/jackardios/css-to-tailwindcss/actions/workflows/release.yml/badge.svg
[build-url]: https://github.com/jackardios/css-to-tailwindcss/actions/workflows/release.yml
[downloads-img]: https://img.shields.io/npm/dt/css-to-tailwindcss
[downloads-url]: https://www.npmtrends.com/css-to-tailwindcss
[npm-img]: https://img.shields.io/npm/v/css-to-tailwindcss
[npm-url]: https://www.npmjs.com/package/css-to-tailwindcss
[issues-img]: https://img.shields.io/github/issues/jackardios/css-to-tailwindcss
[issues-url]: https://github.com/jackardios/css-to-tailwindcss/issues
[semantic-release-img]: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg
[semantic-release-url]: https://github.com/semantic-release/semantic-release

View File

@ -1,405 +0,0 @@
import {
type AttributeSelector,
type Selector,
isTraversal,
parse,
stringify,
} from "css-what";
import type { Config } from "tailwindcss";
import type { Expand } from "tailwindcss/types/config";
import type { CorePluginList } from "tailwindcss/types/generated/corePluginList";
import baseResolveConfig from "tailwindcss/resolveConfig";
import postcss, { AtRule } from "postcss";
import type {
AcceptedPlugin,
Container,
Declaration,
Document,
Root,
Rule,
} from "postcss";
import postcssSafeParser from "postcss-safe-parser";
import type { ConverterMapping } from "./types";
import { type TailwindNode, TailwindNodesManager } from "./NodesManager";
import {
detectIndent,
isAtRuleNode,
isChildNode,
removeUnnecessarySpaces,
} from "./utils";
import {
converterMappingByTailwindTheme,
normalizeAtRuleParams,
} from "./utils/converterMappingByTailwindTheme";
import {
DECLARATION_CONVERTERS_MAPPING,
convertDeclarationValue,
prepareArbitraryValue,
} from "./mappings/declaration-converters-mapping";
import MEDIA_PARAMS_MAPPING from "./mappings/media-params-mapping.json";
import PSEUDOS_MAPPING from "./mappings/pseudos-mapping.json";
import { reduceTailwindClasses } from "./utils/reduceTailwindClasses";
export interface TailwindConverterConfig {
remInPx?: number | null;
tailwindConfig?: Config;
postCSSPlugins: AcceptedPlugin[];
arbitraryPropertiesIsEnabled: boolean;
}
export const DEFAULT_CONVERTER_CONFIG: Omit<TailwindConverterConfig, "tailwindConfig"> = {
postCSSPlugins: [],
arbitraryPropertiesIsEnabled: false,
};
export interface ResolvedTailwindConfig extends Config {
prefix: string;
separator: string;
corePlugins: Expand<Partial<Record<CorePluginList, boolean>>>;
}
export type ResolvedConfig = ReturnType<typeof resolveConfig>;
export type CorePlugins = ResolvedTailwindConfig["corePlugins"];
export function resolveConfig(config: Config) {
const resolved = baseResolveConfig(config);
const corePlugins: CorePlugins = {};
for (const current of resolved["corePlugins"] as CorePluginList[]) {
corePlugins[current] = true;
}
return {
...resolved,
prefix: resolved.prefix || "",
separator: resolved.separator || ":",
corePlugins,
};
}
export class TailwindConverter {
protected config: TailwindConverterConfig;
private tailwindConfig: ResolvedConfig;
private mapping: ConverterMapping;
constructor({
tailwindConfig,
...converterConfig
}: Partial<TailwindConverterConfig> = {}) {
const resolvedTailwindConfig = resolveConfig(
tailwindConfig || ({ content: [] } as Config)
);
this.tailwindConfig = resolvedTailwindConfig;
this.mapping = converterMappingByTailwindTheme(
resolvedTailwindConfig.theme,
converterConfig.remInPx
);
this.config = {
...DEFAULT_CONVERTER_CONFIG,
...converterConfig,
};
}
async convertCSS(css: string) {
const nodesManager = new TailwindNodesManager();
const parsed = await postcss(this.config.postCSSPlugins).process(css, {
parser: postcssSafeParser,
});
parsed.root.walkRules(rule => {
const converted = this.convertRule(rule);
if (converted) {
nodesManager.mergeNode(converted);
}
});
const nodes = nodesManager.getNodes();
for (const node of nodes) {
if (node.tailwindClasses.length) {
node.rule.prepend(
new AtRule({
name: "apply",
params: node.tailwindClasses.join(" "),
})
);
}
}
this.cleanRaws(parsed.root);
return {
nodes,
convertedRoot: parsed.root,
};
}
protected cleanRaws(root: Root | Document) {
root.raws.indent = detectIndent(root);
root.walkRules(node => {
if (node.nodes?.length === 0) {
node.remove();
} else {
node.cleanRaws(true);
}
});
root.walkAtRules(node => {
if (node.nodes?.length === 0) {
node.remove();
} else {
node.cleanRaws(true);
}
});
}
protected convertRule(rule: Rule): TailwindNode | null {
let tailwindClasses: string[] = [];
rule.walkDecls(declaration => {
const converted = this.convertDeclarationToClasses(declaration);
if (converted?.length) {
declaration.remove();
tailwindClasses = tailwindClasses.concat(converted);
}
});
if (tailwindClasses.length) {
tailwindClasses = reduceTailwindClasses(tailwindClasses);
if (this.tailwindConfig.prefix) {
tailwindClasses = tailwindClasses.map(className =>
className[0] === "[" // is "arbitrary property" class
? className
: `${this.tailwindConfig.prefix}${className}`
);
}
return this.makeTailwindNode(rule, tailwindClasses);
}
return null;
}
protected convertDeclarationToClasses(declaration: Declaration) {
const converter = DECLARATION_CONVERTERS_MAPPING[declaration.prop];
const classes =
!converter.flag || this.tailwindConfig.corePlugins[converter.flag]
? converter?.map(declaration, this.mapping, this.config, this.tailwindConfig)
: [];
if (classes.length === 0 && this.config.arbitraryPropertiesIsEnabled) {
return [`[${declaration.prop}:${prepareArbitraryValue(declaration.value)}]`];
}
return classes;
}
protected makeTailwindNode(rule: Rule, tailwindClasses: string[]): TailwindNode {
const { baseSelector, classPrefix } = this.parseSelector(rule.selector);
const classPrefixByParentNodes = this.convertContainerToClassPrefix(rule.parent);
if (classPrefixByParentNodes) {
return {
key: baseSelector,
rootRuleSelector: baseSelector,
originalRule: rule,
classesPrefix: classPrefixByParentNodes + classPrefix,
tailwindClasses,
};
}
if (classPrefix) {
const key = TailwindNodesManager.convertRuleToKey(rule, baseSelector);
const isRootRule = key === baseSelector;
return {
key,
rootRuleSelector: isRootRule ? baseSelector : null,
originalRule: rule,
classesPrefix: classPrefix,
tailwindClasses,
};
}
return { rule, tailwindClasses };
}
protected parseSelector(rawSelector: string) {
const parsedSelectors = parse(rawSelector);
if (parsedSelectors.length !== 1) {
return { baseSelector: rawSelector, classPrefix: "" };
}
const parsedSelector = parsedSelectors[0];
let baseSelectors: Array<Selector> = [];
let classPrefixes: Array<string> = [];
parsedSelector?.forEach((selectorItem, index) => {
if (isTraversal(selectorItem)) {
baseSelectors = parsedSelector.slice(0, index + 1);
classPrefixes = [];
return;
}
const classPrefix = this.convertSelectorToClassPrefix(selectorItem);
if (classPrefix) {
classPrefixes.push(classPrefix);
} else {
baseSelectors.push(selectorItem);
}
});
return {
baseSelector: stringify([baseSelectors]),
classPrefix: classPrefixes.join(""),
};
}
protected convertSelectorToClassPrefix(selector: Selector) {
if (selector.type === "pseudo" || selector.type === "pseudo-element") {
const mapped = (PSEUDOS_MAPPING as any)[selector.name];
return mapped ? `${mapped}${this.tailwindConfig.separator}` : null;
}
if (selector.type === "attribute") {
if (selector.name.startsWith("aria-")) {
const mappingKey = this.attributeSelectorToMappingKey(selector, 6);
const mapped = this.mapping.aria?.[mappingKey];
if (!mapped) {
return null;
}
return `${mapped}${this.tailwindConfig.separator}`;
}
if (selector.name.startsWith("data-")) {
const mappingKey = this.attributeSelectorToMappingKey(selector, 6);
const mapped = this.mapping.data?.[mappingKey];
if (!mapped) {
return null;
}
return `${mapped}${this.tailwindConfig.separator}`;
}
}
return null;
}
protected attributeSelectorToMappingKey(selector: AttributeSelector, from = 1) {
const stringifiedSelector = stringify([[selector]]);
return stringifiedSelector.substring(from, stringifiedSelector.length - 1);
}
protected convertContainerToClassPrefix(container: Container | undefined) {
let currentContainer: Document | Container | undefined = container;
const mediaParams: string[] = [];
const supportsParams: string[] = [];
while (isChildNode(currentContainer)) {
if (!isAtRuleNode(currentContainer)) {
// do not convert if parent is not at-rule
return "";
}
if (currentContainer.name === "media") {
mediaParams.push(currentContainer.params);
} else if (currentContainer.name === "supports") {
supportsParams.push(currentContainer.params);
} else {
return "";
}
currentContainer = currentContainer.parent;
}
let mediaPrefixes = "";
let supportsPrefixes = "";
if (mediaParams.length) {
mediaPrefixes = this.convertMediaParamsToClassPrefix(mediaParams.reverse());
if (!mediaPrefixes) {
return "";
}
}
if (supportsParams.length) {
supportsPrefixes = this.convertSupportsParamsToClassPrefix(
supportsParams.reverse()
);
if (!supportsPrefixes) {
return "";
}
}
return mediaPrefixes + supportsPrefixes;
}
protected convertMediaParamsToClassPrefix(mediaParams: string[]) {
const modifiers: string[] = [];
const screens: string[] = [];
for (let i = 0; i < mediaParams.length; i++) {
const splitted = mediaParams[i].split(" and ");
for (let j = 0; j < splitted.length; j++) {
const param = normalizeAtRuleParams(splitted[j].trim());
if (param === "screen") {
continue;
}
if (param.includes("width") || param.includes("height")) {
screens.push(param);
continue;
}
const mapped = (MEDIA_PARAMS_MAPPING as any)[param.replace(/\s+/g, "")];
if (mapped) {
modifiers.push(mapped);
continue;
}
// do not convert if not convertable media
return "";
}
}
if (screens.length > 0) {
const mappedScreen = this.mapping.screens[screens.join(" and ")];
if (!mappedScreen) {
return "";
}
modifiers.push(mappedScreen);
}
const classPrefix = modifiers.join(this.tailwindConfig.separator);
return classPrefix ? classPrefix + this.tailwindConfig.separator : "";
}
protected convertSupportsParamsToClassPrefix(supportParams: string[]) {
const buildParams = supportParams.join(" and ");
const classPrefix = convertDeclarationValue({
value: {
value:
supportParams.length > 1
? removeUnnecessarySpaces(buildParams)
: normalizeAtRuleParams(buildParams),
valuesMap: this.mapping.supports || {},
classPrefix: "supports",
},
});
return classPrefix ? classPrefix.join(",") + this.tailwindConfig.separator : "";
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +0,0 @@
{
"print": "print",
"orientation:portrait": "portrait",
"orientation:landscape": "landscape",
"prefers-contrast:more": "contrast-more",
"prefers-contrast:less": "contrast-less",
"prefers-color-scheme:dark": "dark",
"prefers-reduced-motion:no-preference": "motion-safe",
"prefers-reduced-motion:reduce": "motion-reduce"
}

View File

@ -1,40 +0,0 @@
{
"hover": "hover",
"focus": "focus",
"focus-within": "focus-within",
"focus-visible": "focus-visible",
"active": "active",
"visited": "visited",
"target": "target",
"first-child": "first",
"last-child": "last",
"only-child": "only",
"nth-child(odd)": "odd",
"nth-child(2n+1)": "odd",
"nth-child(even)": "even",
"nth-child(2n)": "even",
"first-of-type": "first-of-type",
"last-of-type": "last-of-type",
"only-of-type": "only-of-type",
"empty": "empty",
"disabled": "disabled",
"enabled": "enabled",
"checked": "checked",
"indeterminate": "indeterminate",
"default": "default",
"required": "required",
"valid": "valid",
"invalid": "invalid",
"in-range": "in-range",
"out-of-range": "out-of-range",
"autofill": "autofill",
"read-only": "read-only",
"before": "before",
"after": "after",
"first-letter": "first-letter",
"first-line": "first-line",
"marker": "marker",
"selection": "selection",
"file-selector-button": "file",
"backdrop": "backdrop"
}

View File

@ -1,445 +0,0 @@
{
"alignContent": {
"center": "content-center",
"flex-start": "content-start",
"flex-end": "content-end",
"space-between": "content-between",
"space-around": "content-around",
"space-evenly": "content-evenly",
"baseline": "content-baseline"
},
"alignItems": {
"flex-start": "items-start",
"flex-end": "items-end",
"center": "items-center",
"baseline": "items-baseline",
"stretch": "items-stretch"
},
"alignSelf": {
"auto": "self-auto",
"flex-start": "self-start",
"flex-end": "self-end",
"center": "self-center",
"stretch": "self-stretch",
"baseline": "self-baseline"
},
"appearance": {
"none": "appearance-none"
},
"backgroundAttachment": {
"fixed": "bg-fixed",
"local": "bg-local",
"scroll": "bg-scroll"
},
"backgroundBlendMode": {
"normal": "bg-blend-normal",
"multiply": "bg-blend-multiply",
"screen": "bg-blend-screen",
"overlay": "bg-blend-overlay",
"darken": "bg-blend-darken",
"lighten": "bg-blend-lighten",
"color-dodge": "bg-blend-color-dodge",
"color-burn": "bg-blend-color-burn",
"hard-light": "bg-blend-hard-light",
"soft-light": "bg-blend-soft-light",
"difference": "bg-blend-difference",
"exclusion": "bg-blend-exclusion",
"hue": "bg-blend-hue",
"saturation": "bg-blend-saturation",
"color": "bg-blend-color",
"luminosity": "bg-blend-luminosity"
},
"backgroundClip": {
"border-box": "bg-clip-border",
"padding-box": "bg-clip-padding",
"content-box": "bg-clip-content",
"text": "bg-clip-text"
},
"backgroundOrigin": {
"border-box": "bg-origin-border",
"padding-box": "bg-origin-padding",
"content-box": "bg-origin-content"
},
"backgroundRepeat": {
"repeat": "bg-repeat",
"no-repeat": "bg-no-repeat",
"repeat-x": "bg-repeat-x",
"repeat-y": "bg-repeat-y",
"round": "bg-repeat-round",
"space": "bg-repeat-space"
},
"borderStyle": {
"solid": "border-solid",
"dashed": "border-dashed",
"dotted": "border-dotted",
"double": "border-double",
"hidden": "border-hidden",
"none": "border-none"
},
"borderCollapse": {
"collapse": "border-collapse",
"separate": "border-separate"
},
"boxDecorationBreak": {
"slice": "box-decoration-slice",
"clone": "box-decoration-clone"
},
"boxSizing": {
"border-box": "box-border",
"content-box": "box-content"
},
"breakAfter": {
"auto": "break-after-auto",
"avoid": "break-after-avoid",
"all": "break-after-all",
"avoid-page": "break-after-avoid-page",
"page": "break-after-page",
"left": "break-after-left",
"right": "break-after-right",
"column": "break-after-column"
},
"breakBefore": {
"auto": "break-before-auto",
"avoid": "break-before-avoid",
"all": "break-before-all",
"avoid-page": "break-before-avoid-page",
"page": "break-before-page",
"left": "break-before-left",
"right": "break-before-right",
"column": "break-before-column"
},
"breakInside": {
"auto": "break-inside-auto",
"avoid": "break-inside-avoid",
"avoid-page": "break-inside-avoid-page",
"avoid-column": "break-inside-avoid-column"
},
"clear": {
"left": "clear-left",
"right": "clear-right",
"both": "clear-both",
"none": "clear-none"
},
"display": {
"block": "block",
"inline-block": "inline-block",
"inline": "inline",
"flex": "flex",
"inline-flex": "inline-flex",
"table": "table",
"inline-table": "inline-table",
"table-caption": "table-caption",
"table-cell": "table-cell",
"table-column": "table-column",
"table-column-group": "table-column-group",
"table-footer-group": "table-footer-group",
"table-header-group": "table-header-group",
"table-row-group": "table-row-group",
"table-row": "table-row",
"flow-root": "flow-root",
"grid": "grid",
"inline-grid": "inline-grid",
"contents": "contents",
"list-item": "list-item",
"none": "hidden"
},
"flexDirection": {
"row": "flex-row",
"row-reverse": "flex-row-reverse",
"column": "flex-col",
"column-reverse": "flex-col-reverse"
},
"flexWrap": {
"wrap": "flex-wrap",
"wrap-reverse": "flex-wrap-reverse",
"nowrap": "flex-nowrap"
},
"float": {
"right": "float-right",
"left": "float-left",
"none": "float-none"
},
"fontSmoothing": {
"antialiased": "antialiased",
"grayscale": "antialiased",
"auto": "subpixel-antialiased"
},
"fontStyle": {
"italic": "italic",
"normal": "not-italic"
},
"fontVariantNumeric": {
"normal": "normal-nums",
"ordinal": "ordinal",
"slashed-zero": "slashed-zero",
"lining-nums": "lining-nums",
"oldstyle-nums": "oldstyle-nums",
"proportional-nums": "proportional-nums",
"tabular-nums": "tabular-nums",
"diagonal-fractions": "diagonal-fractions",
"stacked-fractions": "stacked-fractions"
},
"gridAutoFlow": {
"row": "grid-flow-row",
"column": "grid-flow-col",
"dense": "grid-flow-dense",
"row dense": "grid-flow-row-dense",
"column dense": "grid-flow-col-dense"
},
"isolation": {
"isolate": "isolate",
"auto": "isolation-auto"
},
"justifyContent": {
"flex-start": "justify-start",
"flex-end": "justify-end",
"center": "justify-center",
"space-between": "justify-between",
"space-around": "justify-around",
"space-evenly": "justify-evenly"
},
"justifyItems": {
"flex-start": "justify-items-start",
"flex-end": "justify-items-end",
"start": "justify-items-start",
"end": "justify-items-end",
"center": "justify-items-center",
"stretch": "justify-items-stretch"
},
"justifySelf": {
"auto": "justify-self-auto",
"flex-start": "justify-self-start",
"flex-end": "justify-self-end",
"start": "justify-self-start",
"end": "justify-self-end",
"center": "justify-self-center",
"stretch": "justify-self-stretch"
},
"listStylePosition": {
"inside": "list-inside",
"outside": "list-outside"
},
"mixBlendMode": {
"normal": "mix-blend-normal",
"multiply": "mix-blend-multiply",
"screen": "mix-blend-screen",
"overlay": "mix-blend-overlay",
"darken": "mix-blend-darken",
"lighten": "mix-blend-lighten",
"color-dodge": "mix-blend-color-dodge",
"color-burn": "mix-blend-color-burn",
"hard-light": "mix-blend-hard-light",
"soft-light": "mix-blend-soft-light",
"difference": "mix-blend-difference",
"exclusion": "mix-blend-exclusion",
"hue": "mix-blend-hue",
"saturation": "mix-blend-saturation",
"color": "mix-blend-color",
"luminosity": "mix-blend-luminosity",
"plus-lighter": "mix-blend-plus-lighter"
},
"objectFit": {
"contain": "object-contain",
"cover": "object-cover",
"fill": "object-fill",
"none": "object-none",
"scale-down": "object-scale-down"
},
"outlineStyle": {
"solid": "outline",
"dashed": "outline-dashed",
"dotted": "outline-dotted",
"double": "outline-double"
},
"outline": {
"2px solid transparent": "outline-none"
},
"overflow": {
"auto": "overflow-auto",
"hidden": "overflow-hidden",
"clip": "overflow-clip",
"visible": "overflow-visible",
"scroll": "overflow-scroll"
},
"overflowWrap": {
"break-word": "break-words"
},
"overflowX": {
"auto": "overflow-x-auto",
"hidden": "overflow-x-hidden",
"clip": "overflow-x-clip",
"visible": "overflow-x-visible",
"scroll": "overflow-x-scroll"
},
"overflowY": {
"auto": "overflow-y-auto",
"hidden": "overflow-y-hidden",
"clip": "overflow-y-clip",
"visible": "overflow-y-visible",
"scroll": "overflow-y-scroll"
},
"overscrollBehavior": {
"auto": "overscroll-auto",
"contain": "overscroll-contain",
"none": "overscroll-none"
},
"overscrollBehaviorX": {
"auto": "overscroll-x-auto",
"contain": "overscroll-x-contain",
"none": "overscroll-x-none"
},
"overscrollBehaviorY": {
"auto": "overscroll-y-auto",
"contain": "overscroll-y-contain",
"none": "overscroll-y-none"
},
"placeContent": {
"center": "place-content-center",
"start": "place-content-start",
"end": "place-content-end",
"space-between": "place-content-between",
"space-around": "place-content-around",
"space-evenly": "place-content-evenly",
"baseline": "place-content-baseline",
"stretch": "place-content-stretch"
},
"placeItems": {
"start": "place-items-start",
"end": "place-items-end",
"center": "place-items-center",
"baseline": "place-items-baseline",
"stretch": "place-items-stretch"
},
"placeSelf": {
"auto": "place-self-auto",
"start": "place-self-start",
"end": "place-self-end",
"center": "place-self-center",
"stretch": "place-self-stretch"
},
"pointerEvents": {
"none": "pointer-events-none",
"auto": "pointer-events-auto"
},
"position": {
"static": "static",
"fixed": "fixed",
"absolute": "absolute",
"relative": "relative",
"sticky": "sticky"
},
"resize": {
"none": "resize-none",
"vertical": "resize-y",
"horizontal": "resize-x",
"both": "resize"
},
"scrollBehavior": {
"auto": "scroll-auto",
"smooth": "scroll-smooth"
},
"scrollSnapAlign": {
"start": "snap-start",
"end": "snap-end",
"center": "snap-center",
"none": "snap-align-none"
},
"scrollSnapType": {
"none": "snap-none",
"x var(--tw-scroll-snap-strictness)": "snap-x",
"x": "snap-x",
"y var(--tw-scroll-snap-strictness)": "snap-y",
"y": "snap-y",
"both var(--tw-scroll-snap-strictness)": "snap-both",
"both": "snap-both"
},
"scrollSnapStop": {
"normal": "snap-normal",
"always": "snap-always"
},
"tableLayout": {
"auto": "table-auto",
"fixed": "table-fixed"
},
"textAlign": {
"left": "text-left",
"center": "text-center",
"right": "text-right",
"justify": "text-justify",
"start": "text-start",
"end": "text-end"
},
"textDecorationLine": {
"underline": "underline",
"overline": "overline",
"line-through": "line-through",
"none": "no-underline"
},
"textDecorationStyle": {
"solid": "decoration-solid",
"double": "decoration-double",
"dotted": "decoration-dotted",
"dashed": "decoration-dashed",
"wavy": "decoration-wavy"
},
"textTransform": {
"uppercase": "uppercase",
"lowercase": "lowercase",
"capitalize": "capitalize",
"none": "normal-case"
},
"textOverflow": {
"ellipsis": "text-ellipsis",
"clip": "text-clip"
},
"touchAction": {
"auto": "touch-auto",
"none": "touch-none",
"pan-x": "touch-pan-x",
"pan-left": "touch-pan-left",
"pan-right": "touch-pan-right",
"pan-y": "touch-pan-y",
"pan-up": "touch-pan-up",
"pan-down": "touch-pan-down",
"pinch-zoom": "touch-pinch-zoom",
"manipulation": "touch-manipulation"
},
"transform": {
"translateZ(0)": "transform-gpu",
"translate3d(0,0,0)": "transform-gpu",
"none": "transform-none"
},
"userSelect": {
"none": "select-none",
"text": "select-text",
"all": "select-all",
"auto": "select-auto"
},
"verticalAlign": {
"baseline": "align-baseline",
"top": "align-top",
"middle": "align-middle",
"bottom": "align-bottom",
"text-top": "align-text-top",
"text-bottom": "align-text-bottom",
"sub": "align-sub",
"super": "align-super"
},
"visibility": {
"visible": "visible",
"hidden": "invisible",
"collapse": "collapse"
},
"whitespace": {
"normal": "whitespace-normal",
"nowrap": "whitespace-nowrap",
"pre": "whitespace-pre",
"pre-line": "whitespace-pre-line",
"pre-wrap": "whitespace-pre-wrap"
},
"wordBreak": {
"normal": "break-normal",
"break-all": "break-all",
"keep-all": "break-keep"
}
}

View File

@ -1,23 +0,0 @@
import type { ThemeConfig } from "tailwindcss/types/config";
export type RemoveIndex<T> = {
[P in keyof T as string extends P ? never : number extends P ? never : P]: T[P];
};
export type KnownKeys<T> = keyof RemoveIndex<T>;
export type SetPartialProps<Type, Props extends keyof Type> = Partial<Pick<Type, Props>> &
Omit<Type, Props>;
export type SetRequiredProps<Type, Props extends keyof Type> = Required<
Pick<Type, Props>
> &
Type;
export type ConverterMappingKey = Exclude<
KnownKeys<ThemeConfig>,
"keyframes" | "container" | "fontFamily"
>;
export type ConverterMapping = Record<ConverterMappingKey, Record<string, string>> &
Record<"aria" | "data" | "supports", Record<string, string> | undefined>;

View File

@ -1,99 +0,0 @@
import type { AtRule, ChildNode, Container, Node } from "postcss";
import type { Screen } from "tailwindcss/types/config";
export function isCSSVariable(value: string) {
return /^var\((--.+?)\)$/.test(value.trim());
}
export function isAtRuleNode(node: Node | undefined): node is AtRule {
return !!(node?.type === "atrule");
}
export function isChildNode(node: Node | undefined): node is ChildNode {
return !!(
node &&
(node.type === "atrule" ||
node.type === "rule" ||
node.type === "decl" ||
node.type === "comment")
);
}
export function buildMediaQueryByScreen(screens: string | Screen | Screen[]) {
if (typeof screens === "string") {
return `(min-width: ${screens})`;
}
screens = Array.isArray(screens) ? screens : [screens];
return screens
.map(screen => {
if ("raw" in screen && screen["raw"]) {
return screen["raw"];
}
const conditions: string[] = [];
if ("min" in screen && screen["min"]) {
conditions.push(`(min-width: ${screen["min"]})`);
}
if ("max" in screen && screen["max"]) {
conditions.push(`(max-width: ${screen["max"]})`);
}
if (conditions.length) {
return conditions.join(" and ");
}
return null;
})
.filter(Boolean)
.join(", ");
}
export function detectIndent(root: Container<any>) {
if (root.raws.indent) return root.raws.indent;
let detectedIndent = " ";
root.walk(node => {
const p = node.parent;
if (
p &&
p !== root &&
p.parent &&
p.parent === root &&
node.raws.before !== undefined
) {
const parts = node.raws.before.split("\n");
detectedIndent = parts.at(-1)!;
detectedIndent = detectedIndent.replace(/\S/g, "");
return false;
}
});
return detectedIndent;
}
export function flattenObject(
object: Record<string | number | symbol, any>,
separator = "-"
) {
const flatObject: Record<string, any> = {};
for (const [prop, value] of Object.entries(object)) {
if (typeof value === "object" && value !== null) {
const nestedFlatObject = flattenObject(value, separator);
for (const [nestedProp, nestedValue] of Object.entries(nestedFlatObject)) {
flatObject[prop + separator + nestedProp] = nestedValue;
}
} else {
flatObject[prop] = value;
}
}
return flatObject;
}
export function removeUnnecessarySpaces(string: string) {
return string.replace(/(\s+)?([,:;])(\s+)?/gm, "$2");
}

View File

@ -1,216 +0,0 @@
import { colord } from "colord";
import type { Config } from "tailwindcss";
import type {
KeyValuePair,
RecursiveKeyValuePair,
ScreensConfig,
} from "tailwindcss/types/config";
import type { ConverterMapping } from "../types";
import {
buildMediaQueryByScreen,
flattenObject,
removeUnnecessarySpaces,
} from "../utils";
const remValueRegexp = /^(\d+)?\.?\d+rem$/;
function remValueToPx(value: string, remInPx: number) {
if (remValueRegexp.test(value.trim())) {
const number = parseFloat(value);
return isNaN(number) ? value : `${number * remInPx}px`;
}
return value;
}
function normalizeNumbersInString(string: string) {
return string.replace(/(^|[\s*+-/;])(\.\d+)/g, "$10$2");
}
export function normalizeValue(value: string) {
return removeUnnecessarySpaces(normalizeNumbersInString(value));
}
export function normalizeColorValue(colorValue: string) {
const parsed = colord(colorValue);
return parsed.isValid() ? parsed.toHex() : colorValue;
}
export function normalizeZeroSizeValue(value: string) {
return value.trim() === "0px" ? "0" : value;
}
export function normalizeSizeValue(
sizeValue: string,
remInPx: number | undefined | null
) {
return normalizeZeroSizeValue(
normalizeNumbersInString(
remInPx != null ? remValueToPx(sizeValue, remInPx) : sizeValue
)
);
}
export function normalizeAtRuleParams(atRuleParam: string) {
return removeUnnecessarySpaces(atRuleParam.replace(/\(|\)/g, ""));
}
function mapThemeTokens<V>(
tokens: KeyValuePair<string, V>,
valueConverterFn: (tokenValue: V, tokenKey: string) => string | null
) {
const result: Record<string, string> = {};
for (const [tokenKey, tokenValue] of Object.entries(tokens)) {
const convertedTokenValue = valueConverterFn(tokenValue, tokenKey);
if (convertedTokenValue) {
result[convertedTokenValue] = tokenKey;
}
}
return result;
}
function isColorKey(key: string) {
return ["fill", "stroke"].includes(key) || key.toLowerCase().includes("color");
}
function isSizeKey(key: string) {
return [
"backdropBlur",
"backgroundSize",
"blur",
"borderRadius",
"borderSpacing",
"borderWidth",
"columns",
"divideWidth",
"flexBasis",
"gap",
"height",
"inset",
"letterSpacing",
"lineHeight",
"margin",
"maxHeight",
"maxWidth",
"minHeight",
"minWidth",
"outlineOffset",
"outlineWidth",
"padding",
"ringOffsetWidth",
"ringWidth",
"scrollMargin",
"scrollPadding",
"space",
"spacing",
"strokeWidth",
"textDecorationThickness",
"textUnderlineOffset",
"translate",
"width",
].includes(key);
}
function convertFontSizes(
fontSizes: KeyValuePair<
string,
| string
| [fontSize: string, lineHeight: string]
| [
fontSize: string,
configuration: Partial<{
lineHeight: string;
letterSpacing: string;
fontWeight: string | number;
}>,
]
>,
remInPx?: number | null
) {
return mapThemeTokens(fontSizes, fontSizeValue => {
if (!fontSizeValue) {
return null;
}
if (Array.isArray(fontSizeValue)) {
fontSizeValue = fontSizeValue[0];
}
return normalizeSizeValue(fontSizeValue, remInPx);
});
}
function convertScreens(screens: ScreensConfig) {
if (Array.isArray(screens)) {
return {} as Record<string, string>;
}
return mapThemeTokens(screens, screenValue =>
screenValue ? normalizeAtRuleParams(buildMediaQueryByScreen(screenValue)) : null
);
}
function convertColors(colors: RecursiveKeyValuePair) {
const flatColors = flattenObject(colors);
return mapThemeTokens(flatColors, (colorValue: string) => {
colorValue = colorValue?.toString();
return colorValue ? normalizeColorValue(colorValue) : null;
});
}
function convertSizes(sizes: KeyValuePair, remInPx: number | null | undefined) {
return mapThemeTokens(sizes, (sizeValue: string) => {
sizeValue = sizeValue?.toString();
return sizeValue ? normalizeSizeValue(sizeValue, remInPx) : null;
});
}
function convertOtherThemeTokens(tokens: KeyValuePair | null | undefined) {
return tokens
? mapThemeTokens(tokens, (tokenValue: string) => {
tokenValue = tokenValue?.toString();
return tokenValue ? normalizeValue(tokenValue) : null;
})
: tokens;
}
export function converterMappingByTailwindTheme(
resolvedTailwindTheme: Config["theme"],
remInPx?: number | null
) {
const converterMapping = {} as ConverterMapping;
if (!resolvedTailwindTheme) {
return converterMapping;
}
for (const [key, themeItem] of Object.entries(
resolvedTailwindTheme as Record<string, any>
)) {
if (["keyframes", "container", "fontFamily"].includes(key)) {
continue;
}
if (key === "fontSize") {
converterMapping[key] = convertFontSizes(themeItem, remInPx);
} else if (key === "screens") {
converterMapping[key] = convertScreens(themeItem);
} else if (isColorKey(key)) {
(converterMapping as any)[key] = convertColors(themeItem);
} else if (isSizeKey(key)) {
(converterMapping as any)[key] = convertSizes(themeItem, remInPx);
} else {
(converterMapping as any)[key] = convertOtherThemeTokens(themeItem);
}
}
return converterMapping;
}

View File

@ -1,228 +0,0 @@
class TailwindClassesReductionManager {
protected resolvedClasses: string[] = [];
protected map: Record<string, any> = {
m: { mx: { ml: [], mr: [] }, my: { mt: [], mb: [] } },
p: { px: { pl: [], pr: [] }, py: { pt: [], pb: [] } },
"scroll-m": {
"scroll-mx": {
"scroll-ml": [],
"scroll-mr": [],
},
"scroll-my": {
"scroll-mt": [],
"scroll-mb": [],
},
},
"scroll-p": {
"scroll-px": {
"scroll-pl": [],
"scroll-pr": [],
},
"scroll-py": {
"scroll-pt": [],
"scroll-pb": [],
},
},
rounded: {
"rounded-t": {
"rounded-tl": [],
"rounded-tr": [],
},
"rounded-r": {
"rounded-tr": [],
"rounded-br": [],
},
"rounded-b": {
"rounded-bl": [],
"rounded-br": [],
},
"rounded-l": {
"rounded-tl": [],
"rounded-bl": [],
},
},
border: {
"border-x": {
"border-l": [],
"border-r": [],
},
"border-y": {
"border-t": [],
"border-b": [],
},
},
scale: {
"scale-x": [],
"scale-y": [],
},
inset: {
"inset-x": {
left: [],
right: [],
},
"inset-y": {
top: [],
bottom: [],
},
},
};
appendClassName(className: string) {
const { value, classPrefix } = this.parseTailwindClass(className);
if (!value || !this.recursiveSetValue(classPrefix, value, this.map)) {
this.resolvedClasses.push(className);
}
}
reduce() {
for (const [mapKey, mapValue] of Object.entries(this.map)) {
for (const value of this.recursiveResolveClasses(
mapValue,
this.resolvedClasses
).keys()) {
this.resolvedClasses.push(this.toTailwindClass(mapKey, value));
}
}
return this.resolvedClasses;
}
protected recursiveSetValue(
key: string,
value: string,
targetObject: Record<string, any>
) {
for (const objectKey in targetObject) {
if (!Object.prototype.hasOwnProperty.call(targetObject, objectKey)) continue;
const objectValue = targetObject[objectKey];
if (objectKey === key) {
if (isObject(objectValue)) {
this.recursiveSetValueToAllKeys(value, objectValue);
} else if (Array.isArray(objectValue)) {
objectValue.push(value);
}
return true;
}
if (isObject(objectValue)) {
const isSet = this.recursiveSetValue(key, value, objectValue);
if (isSet) {
return true;
}
}
}
return false;
}
protected recursiveSetValueToAllKeys(value: string, targetObject: Record<string, any>) {
for (const key of Object.keys(targetObject)) {
this.recursiveSetValue(key, value, targetObject);
}
}
protected recursiveResolveClasses(
targetObject: Record<string, any>,
resolvedClasses: string[]
) {
let commonValuesMap: null | Map<string, string> = null;
const intersectCommonValues = (valuesMap: Map<string, string>) => {
if (commonValuesMap == null) {
commonValuesMap = valuesMap;
} else {
for (const [commonValue, commonClassPrefix] of commonValuesMap) {
const classPrefix = valuesMap.get(commonValue);
if (classPrefix) {
commonValuesMap?.set(commonValue, classPrefix);
} else {
commonValuesMap?.delete(commonValue);
resolvedClasses.push(this.toTailwindClass(commonClassPrefix, commonValue));
}
}
for (const [value, classPrefix] of valuesMap) {
if (commonValuesMap?.has(value)) {
commonValuesMap.set(value, classPrefix);
} else {
resolvedClasses.push(this.toTailwindClass(classPrefix, value));
}
}
}
};
for (const [currentClassPrefix, objectValue] of Object.entries(targetObject)) {
if (isObject(objectValue)) {
const commonValuesMap = this.recursiveResolveClasses(
objectValue,
resolvedClasses
);
for (const key of commonValuesMap.keys()) {
commonValuesMap.set(key, currentClassPrefix);
}
intersectCommonValues(commonValuesMap);
} else if (Array.isArray(objectValue)) {
intersectCommonValues(
new Map(objectValue.map(value => [value, currentClassPrefix]))
);
}
}
return commonValuesMap || new Map<string, string>();
}
protected toTailwindClass(classPrefix: string, value: any) {
if (typeof value !== "string") {
value = value == null ? "" : value.toString();
}
if (value.startsWith("-")) {
value = value.slice(1);
classPrefix = `-${classPrefix}`;
}
return `${classPrefix}-${value}`;
}
protected parseTailwindClass(tailwindClass: string) {
const isNegativeValue = tailwindClass.startsWith("-");
if (isNegativeValue) {
tailwindClass = tailwindClass.slice(1);
}
const lastDashIndex = tailwindClass.lastIndexOf("-");
if (lastDashIndex === -1) {
return {
value: null,
classPrefix: tailwindClass,
};
}
const classPrefix = tailwindClass.slice(0, lastDashIndex);
const absoluteValue = tailwindClass.slice(lastDashIndex + 1);
return {
value: isNegativeValue ? `-${absoluteValue}` : absoluteValue,
classPrefix,
};
}
}
export function reduceTailwindClasses(tailwindClasses: string[]) {
const manager = new TailwindClassesReductionManager();
for (const className of tailwindClasses) {
manager.appendClassName(className);
}
return manager.reduce();
}
function isObject(value: any): value is Record<keyof any, any> {
return value && typeof value === "object" && !Array.isArray(value);
}

View File

@ -6,7 +6,7 @@ import type * as esbuild from "esbuild";
import { transformSync } from "@babel/core"; import { transformSync } from "@babel/core";
/** /**
* An esbuild plugin that processes files with Babel if `getPlugins` returns any plugins. * An esbuild plugin that processes files with Babel if `plugins` is not empty.
*/ */
export const babelPlugin = ({ export const babelPlugin = ({
filter = /\.[jt]sx?$/, filter = /\.[jt]sx?$/,
@ -36,6 +36,7 @@ export const babelPlugin = ({
const { code } = transformSync(load(), { const { code } = transformSync(load(), {
parserOpts: { parserOpts: {
createImportExpressions: true,
plugins: [ plugins: [
"jsx", "jsx",
"decorators", "decorators",

View File

@ -28,7 +28,7 @@ export const esbuildPlugin = (styleMap: StyleMap, compile: Compile): esbuild.Plu
build.onLoad({ filter: /.*/, namespace: ESBUILD_NAMESPACE }, async ({ path }) => { build.onLoad({ filter: /.*/, namespace: ESBUILD_NAMESPACE }, async ({ path }) => {
if (path === "directive:base") { if (path === "directive:base") {
return { return {
contents: (await compile("@tailwind base")).css, contents: await compile("@tailwind base"),
loader: "css", loader: "css",
}; };
} }
@ -40,7 +40,7 @@ export const esbuildPlugin = (styleMap: StyleMap, compile: Compile): esbuild.Plu
try { try {
const result = await compile(toCSSText(styles)); const result = await compile(toCSSText(styles));
return { return {
contents: result.css, contents: result,
loader: "css", loader: "css",
}; };
} catch (e) { } catch (e) {

View File

@ -1,7 +1,8 @@
/* eslint-disable unicorn/string-content */
import { promises as fs } from "node:fs"; import { promises as fs } from "node:fs";
import { resolve } from "node:path"; import { resolve } from "node:path";
import { type BuildOptions, type OutputFile, build, transformSync } from "esbuild";
import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { afterEach, beforeEach, describe, expect, it } from "vitest";
import * as esbuild from "esbuild";
import dedent from "dedent"; import dedent from "dedent";
import { name } from "../package.json" with { type: "json" }; import { name } from "../package.json" with { type: "json" };
import { import {
@ -24,8 +25,9 @@ describe("babel-tailwind", () => {
}); });
it("supports ESBuild", async () => { it("supports ESBuild", async () => {
const outputFiles = await compileESBuild({ const { files } = await compileESBuild({
clsx: "emotion", clsx: "emotion",
expectFiles: 2,
javascript: /* tsx */ ` javascript: /* tsx */ `
export function Hello() { export function Hello() {
return ( return (
@ -36,20 +38,17 @@ describe("babel-tailwind", () => {
} }
`, `,
}); });
expect(outputFiles).toHaveLength(2);
const js = findByExt(outputFiles, ".js");
const css = findByExt(outputFiles, ".css");
const clsName = getClassName("text-center"); const clsName = getClassName("text-center");
expect(js.text).toContain(`className: "${clsName}"`); expect(files.js.text).toContain(`className: "${clsName}"`);
expect(css.text).toMatch(`.${clsName} {\n text-align: center;\n}`); expect(files.css.text).toMatch(`.${clsName} {\n text-align: center;\n}`);
}); });
it("does not remove the attribute if `preserveAttribute` is true", async () => { it("does not remove the attribute if `preserveAttribute` is true", async () => {
const outputFiles = await compileESBuild({ const { files } = await compileESBuild({
clsx: "emotion", clsx: "emotion",
jsxAttributeAction: "preserve", jsxAttributeAction: "preserve",
expectFiles: 2,
javascript: /* tsx */ ` javascript: /* tsx */ `
export function Hello() { export function Hello() {
return ( return (
@ -60,10 +59,52 @@ describe("babel-tailwind", () => {
} }
`, `,
}); });
expect(outputFiles).toHaveLength(2);
const js = findByExt(outputFiles, ".js"); expect(files.js.text).toContain(`css: "text-center"`);
expect(js.text).toContain(`css: "text-center"`); });
describe('merges with existing "className" attribute', () => {
it("string literal", async () => {
const { files } = await compileESBuild({
clsx: "emotion",
expectFiles: 2,
javascript: /* tsx */ `
export function Hello() {
return (
<div className="text-center" css="text-center">
Hello, world!
</div>
);
}
`,
});
const clsName = getClassName("text-center");
expect(files.js.text).toContain(`className: "text-center ${clsName}"`);
expect(files.css.text).toMatch(`.${clsName} {\n text-align: center;\n}`);
});
it("existing function", async () => {
const { files } = await compileESBuild({
clsx: "emotion",
expectFiles: 2,
javascript: /* tsx */ `
export function Hello() {
return (
<div className={({ isEntering }) => isEntering ? "enter" : "exit"} css="text-center">
Hello, world!
</div>
);
}
`,
});
const clsName = getClassName("text-center");
expect(files.js.text).toContain(
`className: ({\n isEntering\n }) => _cx(isEntering ? "enter" : "exit", "${clsName}")`
);
expect(files.css.text).toMatch(`.${clsName} {\n text-align: center;\n}`);
});
}); });
it("reports errors with correct position", async () => { it("reports errors with correct position", async () => {
@ -98,9 +139,10 @@ describe("babel-tailwind", () => {
}); });
it("supports custom jsxAttributeName", async () => { it("supports custom jsxAttributeName", async () => {
const outputFiles = await compileESBuild({ const { files } = await compileESBuild({
clsx: "emotion", clsx: "emotion",
jsxAttributeName: "tw", jsxAttributeName: "tw",
expectFiles: 2,
javascript: /* tsx */ ` javascript: /* tsx */ `
export function Hello() { export function Hello() {
return ( return (
@ -111,34 +153,29 @@ describe("babel-tailwind", () => {
} }
`, `,
}); });
expect(outputFiles).toHaveLength(2);
const js = findByExt(outputFiles, ".js");
const css = findByExt(outputFiles, ".css");
const clsName = getClassName("text-center"); const clsName = getClassName("text-center");
expect(js.text).toContain(`className: "${clsName}"`); expect(files.js.text).toContain(`className: "${clsName}"`);
expect(css.text).toMatch(`.${clsName} {\n text-align: center;\n}`); expect(files.css.text).toMatch(`.${clsName} {\n text-align: center;\n}`);
}); });
it("supports importing tailwind/base", async () => { it("supports importing tailwind/base", async () => {
const postcss = createPostCSS({ tailwindConfig: {} }); const postcss = createPostCSS({
const base = (await postcss("@tailwind base;")).css; tailwindConfig: {},
const outputFiles = await compileESBuild({ postCSSPlugins: [],
directives: [],
});
const base = await postcss("@tailwind base;");
const { files } = await compileESBuild({
clsx: "emotion", clsx: "emotion",
expectFiles: 2,
javascript: /* tsx */ ` javascript: /* tsx */ `
import "${name}/base"; import "${name}/base";
`, `,
}); });
expect(outputFiles).toHaveLength(2);
const js = findByExt(outputFiles, ".js"); expect(files.js.text).toBe("");
const css = findByExt(outputFiles, ".css"); expect(minCSS(files.css.text)).toContain(minCSS(base));
// expect(js.text).toContain(`import "./base.css";`);
expect(js.text).toBe("");
expect(minCSS(css.text)).toContain(minCSS(base));
}); });
}); });
@ -149,37 +186,48 @@ async function write(path: string, content: string) {
} }
const minCSS = (text: string) => const minCSS = (text: string) =>
transformSync(text, { minify: true, loader: "css" }).code; esbuild.transformSync(text, { minify: true, loader: "css" }).code;
const findByExt = (outputFiles: OutputFile[], ext: string) => const findByExt = (outputFiles: esbuild.OutputFile[], ext: string) =>
outputFiles.find(file => file.path.endsWith(ext))!; outputFiles.find(file => file.path.endsWith(ext))!;
async function compileESBuild({ async function compileESBuild({
javascript, javascript,
esbuild, esbuild: esbuildOptions,
expectFiles,
...options ...options
}: TailwindPluginOptions & { }: TailwindPluginOptions & {
esbuild?: BuildOptions; esbuild?: esbuild.BuildOptions;
javascript: string; javascript: string;
expectFiles?: number;
}) { }) {
const tailwind = getTailwindPlugins({ const tailwind = getTailwindPlugins({
tailwindConfig: {}, tailwindConfig: {},
...options, ...options,
}); });
const result = await build({ const result = await esbuild.build({
bundle: true, bundle: true,
write: false, write: false,
external: ["react/jsx-runtime"], external: ["react/jsx-runtime", "@emotion/css", "clsx"],
outdir: "dist", outdir: "dist",
format: "esm", format: "esm",
entryPoints: [await write("index.tsx", dedent(javascript))], entryPoints: [await write("index.tsx", dedent(javascript))],
plugins: [babelPlugin({ plugins: [tailwind.babel] }), tailwind.esbuild], plugins: [babelPlugin({ plugins: [tailwind.babel()] }), tailwind.esbuild()],
...esbuild, ...esbuildOptions,
}); });
const { errors, warnings, outputFiles } = result; const { errors, warnings, outputFiles } = result;
expect(errors).toHaveLength(0); expect(errors).toHaveLength(0);
expect(warnings).toHaveLength(0); expect(warnings).toHaveLength(0);
return outputFiles!; if (expectFiles != null) {
expect(outputFiles).toHaveLength(expectFiles);
}
return {
outputFiles: outputFiles!,
files: new Proxy({} as Record<string, esbuild.OutputFile>, {
get: (_, ext: string) => findByExt(outputFiles!, ext),
}),
};
} }

View File

@ -2,7 +2,7 @@ import hash from "@emotion/hash";
import type { Config } from "tailwindcss"; import type { Config } from "tailwindcss";
import type postcss from "postcss"; import type postcss from "postcss";
import { memoize } from "lodash"; import { memoize } from "lodash";
import { babelTailwind } from "./babel-tailwind"; import { type ClassNameCollector, babelTailwind } from "./babel-tailwind";
import { esbuildPlugin } from "./esbuild-postcss"; import { esbuildPlugin } from "./esbuild-postcss";
import { vitePlugin } from "./vite-plugin"; import { vitePlugin } from "./vite-plugin";
import { type StyleMap, createPostCSS, type tailwindDirectives } from "./shared"; import { type StyleMap, createPostCSS, type tailwindDirectives } from "./shared";
@ -27,7 +27,7 @@ export interface TailwindPluginOptions {
/** /**
* Directives to prefix to all Tailwind stylesheets * Directives to prefix to all Tailwind stylesheets
*/ */
directives?: (typeof tailwindDirectives)[number][] | "all"; directives?: (typeof tailwindDirectives)[number][] | "all" | undefined;
/** /**
* Additional PostCSS plugins (optional) * Additional PostCSS plugins (optional)
@ -53,7 +53,7 @@ export interface TailwindPluginOptions {
* declare const tw: TaggedTailwindFunction; * declare const tw: TaggedTailwindFunction;
* "tw" => tw`p-2 text-center` * "tw" => tw`p-2 text-center`
*/ */
taggedTemplateName?: string; taggedTemplateName?: string | undefined;
/** /**
* The prefix to use for the generated class names. * The prefix to use for the generated class names.
@ -65,8 +65,22 @@ export interface TailwindPluginOptions {
* Preferred library for classnames * Preferred library for classnames
*/ */
clsx: "clsx" | "classnames" | "emotion"; clsx: "clsx" | "classnames" | "emotion";
/**
* @internal
*/
styleMap?: StyleMap;
/**
* Custom CSS compile function
* @example
* async css => (await postcss.process(css, { plugins: [tailwindcss()] })).css
*/
compile(css: string): Promise<string>;
} }
export type ResolveTailwindOptions = Required<TailwindPluginOptions>;
/** /**
* Hashes and prefixes a string of Tailwind class names. * Hashes and prefixes a string of Tailwind class names.
* @example getClassName("p-2 text-center") // "tw-1r6fxxz" * @example getClassName("p-2 text-center") // "tw-1r6fxxz"
@ -90,15 +104,31 @@ export const getClassName: GetClassName = cls => "tw-" + hash(cls);
* }); * });
*/ */
export function getTailwindPlugins(options: TailwindPluginOptions) { export function getTailwindPlugins(options: TailwindPluginOptions) {
const styleMap: StyleMap = new Map(); const resolvedOptions: ResolveTailwindOptions = {
const compile = memoize(createPostCSS(options)); directives: undefined,
getClassName,
jsxAttributeAction: "delete",
jsxAttributeName: "css",
postCSSPlugins: [],
styleMap: new Map(),
taggedTemplateName: undefined,
tailwindConfig: {},
...options,
};
const getCompiler = () => createPostCSS(resolvedOptions);
const { styleMap } = resolvedOptions;
const compile = options.compile ?? memoize(getCompiler());
return { return {
compile, compile,
babel: babelTailwind(styleMap, options), babel: (onCollect?: ClassNameCollector) => babelTailwind(resolvedOptions, onCollect),
esbuild: esbuildPlugin(styleMap, compile), esbuild: () => esbuildPlugin(styleMap, compile),
vite: vitePlugin(styleMap, compile), vite: () => vitePlugin(styleMap, compile),
styleMap, styleMap,
options,
getCompiler,
[Symbol.dispose]() { [Symbol.dispose]() {
styleMap.clear(); styleMap.clear();
}, },

View File

@ -1,6 +1,6 @@
import tailwind from "tailwindcss"; import tailwind from "tailwindcss";
import postcss from "postcss"; import postcss from "postcss";
import type { TailwindPluginOptions } from "./index"; import type { ResolveTailwindOptions } from "./index";
export const { name: pkgName } = [require][0]( export const { name: pkgName } = [require][0](
process.env.BABEL_TAILWIND_BUILD ? "./package.json" : "../package.json" process.env.BABEL_TAILWIND_BUILD ? "./package.json" : "../package.json"
@ -30,9 +30,9 @@ export type StyleMap = Map</* filename */ string, StyleMapEntry[]>;
export function createPostCSS({ export function createPostCSS({
tailwindConfig, tailwindConfig,
postCSSPlugins = [], postCSSPlugins,
directives, directives,
}: Pick<TailwindPluginOptions, "tailwindConfig" | "postCSSPlugins" | "directives">) { }: Pick<ResolveTailwindOptions, "tailwindConfig" | "postCSSPlugins" | "directives">) {
const post = postcss([ const post = postcss([
tailwind({ tailwind({
...tailwindConfig, ...tailwindConfig,
@ -44,7 +44,10 @@ export function createPostCSS({
const appliedDirectives = directives === "all" ? tailwindDirectives : directives; const appliedDirectives = directives === "all" ? tailwindDirectives : directives;
const directiveTexts = appliedDirectives?.map(d => `@tailwind ${d};\n`).join("") ?? ""; const directiveTexts = appliedDirectives?.map(d => `@tailwind ${d};\n`).join("") ?? "";
return (css: string) => post.process(directiveTexts + css, { from: undefined }); return async (css: string) => {
const result = await post.process(directiveTexts + css, { from: undefined });
return result.css;
};
} }
export type Compile = ReturnType<typeof createPostCSS>; export type Compile = ReturnType<typeof createPostCSS>;

View File

@ -1,6 +1,5 @@
import { dirname, join } from "node:path"; import { dirname, join } from "node:path";
import type * as vite from "vite"; import type * as vite from "vite";
import { memoize } from "lodash";
import { type Compile, type StyleMap, pkgName, toCSSText } from "./shared"; import { type Compile, type StyleMap, pkgName, toCSSText } from "./shared";
const ROLLUP_PREFIX = "\0tailwind:"; const ROLLUP_PREFIX = "\0tailwind:";
@ -29,12 +28,11 @@ export const vitePlugin = (styleMap: StyleMap, compile: Compile): vite.Plugin =>
if (id.startsWith(ROLLUP_PREFIX)) { if (id.startsWith(ROLLUP_PREFIX)) {
const resolved = id.slice(ROLLUP_PREFIX.length); const resolved = id.slice(ROLLUP_PREFIX.length);
if (resolved === "directive:base") { if (resolved === "directive:base") {
return (await compile("@tailwind base")).css; return await compile("@tailwind base");
} }
if (styleMap.has(resolved)) { if (styleMap.has(resolved)) {
const result = await compile(toCSSText(styleMap.get(resolved)!)); return await compile(toCSSText(styleMap.get(resolved)!));
return result.css;
} }
} }
}, },

View File

@ -4,6 +4,7 @@
"allowArbitraryExtensions": true, "allowArbitraryExtensions": true,
"esModuleInterop": true, "esModuleInterop": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"exactOptionalPropertyTypes": true,
"jsx": "react-jsx", "jsx": "react-jsx",
"module": "esnext", "module": "esnext",
"moduleResolution": "node", "moduleResolution": "node",