diff --git a/alias.ts b/alias.ts index 5ea00bddab..4350a7e22a 100644 --- a/alias.ts +++ b/alias.ts @@ -23,6 +23,7 @@ export const alias: Record = { '@unocss/transformer-directives': r('./packages/transformer-directives/src/'), '@unocss/transformer-variant-group': r('./packages/transformer-variant-group/src/'), '@unocss/transformer-compile-class': r('./packages/transformer-compile-class/src/'), + '@unocss/transformer-attributify-jsx': r('./packages/transformer-attributify-jsx/src/'), '@unocss/vite': r('./packages/vite/src/'), 'unocss': r('./packages/unocss/src/'), } diff --git a/interactive/package.json b/interactive/package.json index bfe2578ad6..ac0c6ad312 100644 --- a/interactive/package.json +++ b/interactive/package.json @@ -3,10 +3,9 @@ "type": "module", "private": true, "scripts": { - "build": "nuxi generate . && esno scripts/verify-build.ts", - "dev": "nuxi dev .", + "build": "nuxi prepare && esno scripts/prepare.ts && nuxi generate . && esno scripts/verify-build.ts", + "dev": "nuxi prepare && esno scripts/prepare.ts && nuxi dev .", "start": "node .output/server/index.mjs", - "postinstall": "esno scripts/prepare.ts", "fetch": "esno scripts/fetch.ts" }, "devDependencies": { diff --git a/packages/README.md b/packages/README.md index 5b06eb9bef..c6599381c8 100644 --- a/packages/README.md +++ b/packages/README.md @@ -17,6 +17,7 @@ | [@unocss/transformer-variant-group](./transformer-variant-group) | Transformer for Windi CSS's variant group feature | ✅ | No | | [@unocss/transformer-directives](./transformer-directives) | Transformer for CSS directives like `@apply` | ✅ | No | | [@unocss/transformer-compile-class](./transformer-compile-class) | Compile group of classes into one class | ✅ | No | +| [@unocss/transformer-attributify-jsx](./transformer-attributify-jsx) | Support valueless attributify in JSX/TSX | ✅ | No | | [@unocss/extractor-pug](./extractor-pug) | Extractor for Pug | No | - | | [@unocss/autocomplete](./autocomplete) | Utils for autocomplete | No | - | | [@unocss/config](./config) | Configuration file loader | ✅ | - | diff --git a/packages/preset-attributify/README.md b/packages/preset-attributify/README.md index 8871c57aed..9bd7c39c07 100644 --- a/packages/preset-attributify/README.md +++ b/packages/preset-attributify/README.md @@ -71,7 +71,7 @@ Can be written as In addition to Windi CSS's Attributify Mode, this presets also supports valueless attributes. -For example, +For example, ```html
@@ -83,6 +83,8 @@ now can be
``` +> Note: If you are using JSX, `
` might be transformed to `
` which will make the generate CSS from UnoCSS failed to match the attributes. To solve this, you might want to try [`transformer-attributify-jsx`](https://github.com/unocss/unocss/tree/main/packages/transformer-attributify-jsx) along with this preset. + ### Properties Conflicts If the name of the attributes mode ever conflicts with the elements' or components' properties, you can add `un-` prefix to be specific to UnoCSS's attributify mode. diff --git a/packages/preset-attributify/src/extractor.ts b/packages/preset-attributify/src/extractor.ts index f2b1c59d63..7dbd6a27bf 100644 --- a/packages/preset-attributify/src/extractor.ts +++ b/packages/preset-attributify/src/extractor.ts @@ -9,7 +9,8 @@ const strippedPrefixes = [ const splitterRE = /[\s'"`;]+/g const elementRE = /<\w(?=.*>)[\w:\.$-]*\s((?:['"`\{].*?['"`\}]|.*?)*?)>/gs -const valuedAttributeRE = /([?]|(?!\d|-{2}|-\d)[a-zA-Z0-9\u00A0-\uFFFF-_:!%-]+)(?:=(["'])([^\2]*?)\2)?/g +const valuedAttributeRE = /([?]|(?!\d|-{2}|-\d)[a-zA-Z0-9\u00A0-\uFFFF-_:!%-]+)(?:={?(["'])([^\2]*?)\2}?)?/g + export const defaultIgnoreAttributes = ['placeholder'] export const extractorAttributify = (options?: AttributifyOptions): Extractor => { diff --git a/packages/transformer-attributify-jsx/README.md b/packages/transformer-attributify-jsx/README.md new file mode 100644 index 0000000000..ad5111b09d --- /dev/null +++ b/packages/transformer-attributify-jsx/README.md @@ -0,0 +1,118 @@ +# @unocss/transformer-attributify-jsx + + + +Support [valueless attributify](https://github.com/unocss/unocss/tree/main/packages/preset-attributify#valueless-attributify) in JSX/TSX. + +```jsx +export function Component() { + return ( +
+ unocss +
+ ) +} +``` + +Will be transformed to: + +```jsx +export function Component() { + return ( +
+ unocss +
+ ) +} +``` + +
+Without this transformer + +JSX by default will treat valueless attributes as boolean attributes. + +```jsx +export function Component() { + return ( +
+ unocss +
+ ) +} +``` + +
+ +## Install + +```bash +npm i -D @unocss/transformer-attributify-jsx +``` + +```ts +// uno.config.js +import { defineConfig, presetAttributify } from 'unocss' +import transformerAttributifyJsx from '@unocss/transformer-attributify-jsx' + +export default defineConfig({ + // ... + presets: [ + // ... + presetAttributify() + ], + transformers: [ + transformerAttributifyJsx(), // <-- + ], +}) +``` + +## Caveats + +> ⚠️ The rules are almost the same as those of `preset-attributify`, but there are several precautions + +```html +
+ +
+ +
+``` + +Instead, you may want to use valued attributes instead: + +```html +
+ +
+ +
+``` + +## Blocklist + +This transformer will only transform attrubutes that are valid UnoCSS utilities. +You can also `blocklist` bypass some attributes from been transformed. + +```js +transformerAttributifyJsx({ + blocklist: [/text-[a-zA-Z]*/, 'text-5xl'] +}) +``` + +```jsx +
+ unocss +
+``` + +Will be compiled to: + +```html +
+ unocss +
+``` + +## License + +MIT License © 2022-PRESENT [Anthony Fu](https://github.com/antfu) diff --git a/packages/transformer-attributify-jsx/build.config.ts b/packages/transformer-attributify-jsx/build.config.ts new file mode 100644 index 0000000000..d20739e914 --- /dev/null +++ b/packages/transformer-attributify-jsx/build.config.ts @@ -0,0 +1,15 @@ +import { defineBuildConfig } from 'unbuild' + +export default defineBuildConfig({ + entries: [ + 'src/index', + ], + clean: true, + declaration: true, + externals: [ + 'magic-string', + ], + rollup: { + emitCJS: true, + }, +}) diff --git a/packages/transformer-attributify-jsx/package.json b/packages/transformer-attributify-jsx/package.json new file mode 100644 index 0000000000..04ee0212c2 --- /dev/null +++ b/packages/transformer-attributify-jsx/package.json @@ -0,0 +1,45 @@ +{ + "name": "@unocss/transformer-attributify-jsx", + "version": "0.0.1", + "description": "Support valueless attributify in JSX/TSX.", + "author": "Anthony Fu ", + "license": "MIT", + "funding": "https://github.com/sponsors/antfu", + "homepage": "https://github.com/unocss/unocss/tree/main/packages/transformer-attributify-jsx#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/unocss/unocss.git", + "directory": "packages/transformer-attributify-jsx" + }, + "bugs": { + "url": "https://github.com/unocss/unocss/issues" + }, + "keywords": [ + "unocss", + "unocss-transformer" + ], + "sideEffects": false, + "exports": { + ".": { + "require": "./dist/index.cjs", + "import": "./dist/index.mjs" + } + }, + "main": "./dist/index.cjs", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "unbuild", + "stub": "unbuild --stub" + }, + "dependencies": { + "@unocss/core": "workspace:*" + }, + "devDependencies": { + "@rollup/pluginutils": "^4.2.1", + "magic-string": "^0.26.2" + } +} diff --git a/packages/transformer-attributify-jsx/src/index.ts b/packages/transformer-attributify-jsx/src/index.ts new file mode 100644 index 0000000000..525fe821e7 --- /dev/null +++ b/packages/transformer-attributify-jsx/src/index.ts @@ -0,0 +1,81 @@ +import type { SourceCodeTransformer } from '@unocss/core' +import { createFilter } from '@rollup/pluginutils' + +export type FilterPattern = ReadonlyArray | string | RegExp | null + +export interface TransformerAttributifyJsxOptions { + /** + * the list of attributes to ignore + * @default [] + */ + blocklist?: (string | RegExp)[] + + /** + * Regex of modules to be included from processing + * @default [/\.[jt]sx$/, /\.mdx$/] + */ + include?: FilterPattern + + /** + * Regex of modules to exclude from processing + * + * @default [] + */ + exclude?: FilterPattern +} + +const elementRE = /|<(\/?)([a-zA-Z][-.:0-9_a-zA-Z]*)((?:\s+[^>]*?(?:(?:'[^']*')|(?:"[^"]*"))?)*)\s*(\/?)>/gs +const attributeRE = /([a-zA-Z()#][\[?a-zA-Z0-9-_:()#%\]?]*)(?:\s*=\s*((?:'[^']*')|(?:"[^"]*")|\S+))?/g + +export default function transformerAttributifyJsx(options: TransformerAttributifyJsxOptions = {}): SourceCodeTransformer { + const { + blocklist = [], + } = options + + const isBlocked = (matchedRule: string) => { + for (const blockedRule of blocklist) { + if (blockedRule instanceof RegExp) { + if (blockedRule.test(matchedRule)) + return true + } + else if (matchedRule === blockedRule) { + return true + } + } + + return false + } + + const idFilter = createFilter( + options.include || [/\.[jt]sx$/, /\.mdx$/], + options.exclude || [], + ) + + return { + name: 'transformer-jsx', + enforce: 'pre', + idFilter, + async transform(code, _, { uno }) { + const tasks: Promise[] = [] + + for (const item of Array.from(code.original.matchAll(elementRE))) { + for (const attr of item[3].matchAll(attributeRE)) { + const matchedRule = attr[0] + if (matchedRule.includes('=') || isBlocked(matchedRule)) + continue + + tasks.push(uno.parseToken(matchedRule).then((matched) => { + if (matched) { + const tag = item[2] + const startIdx = (item.index || 0) + (attr.index || 0) + tag.length + 1 + const endIdx = startIdx + matchedRule.length + code.overwrite(startIdx, endIdx, `${matchedRule}=""`) + } + })) + } + } + + await Promise.all(tasks) + }, + } +} diff --git a/packages/unocss/package.json b/packages/unocss/package.json index 12414e1c5b..bbc780042e 100644 --- a/packages/unocss/package.json +++ b/packages/unocss/package.json @@ -116,6 +116,7 @@ "@unocss/preset-web-fonts": "workspace:*", "@unocss/preset-wind": "workspace:*", "@unocss/reset": "workspace:*", + "@unocss/transformer-attributify-jsx": "workspace:*", "@unocss/transformer-compile-class": "workspace:*", "@unocss/transformer-directives": "workspace:*", "@unocss/transformer-variant-group": "workspace:*", diff --git a/packages/unocss/src/index.ts b/packages/unocss/src/index.ts index f154573d11..c2bccfabc7 100644 --- a/packages/unocss/src/index.ts +++ b/packages/unocss/src/index.ts @@ -13,6 +13,7 @@ export { default as presetWind } from '@unocss/preset-wind' export { default as transformerDirectives } from '@unocss/transformer-directives' export { default as transformerVariantGroup } from '@unocss/transformer-variant-group' export { default as transformerCompileClass } from '@unocss/transformer-compile-class' +export { default as transformerAttributifyJsx } from '@unocss/transformer-attributify-jsx' export function defineConfig(config: UserConfig) { return config diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c3e037a2e0..c99e1db2d7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -458,6 +458,17 @@ importers: '@unocss/core': link:../core '@unocss/scope': link:../scope + packages/transformer-attributify-jsx: + specifiers: + '@rollup/pluginutils': ^4.2.1 + '@unocss/core': workspace:* + magic-string: ^0.26.2 + dependencies: + '@unocss/core': link:../core + devDependencies: + '@rollup/pluginutils': 4.2.1 + magic-string: 0.26.2 + packages/transformer-compile-class: specifiers: '@unocss/core': workspace:* @@ -500,6 +511,7 @@ importers: '@unocss/preset-web-fonts': workspace:* '@unocss/preset-wind': workspace:* '@unocss/reset': workspace:* + '@unocss/transformer-attributify-jsx': workspace:* '@unocss/transformer-compile-class': workspace:* '@unocss/transformer-directives': workspace:* '@unocss/transformer-variant-group': workspace:* @@ -517,6 +529,7 @@ importers: '@unocss/preset-web-fonts': link:../preset-web-fonts '@unocss/preset-wind': link:../preset-wind '@unocss/reset': link:../reset + '@unocss/transformer-attributify-jsx': link:../transformer-attributify-jsx '@unocss/transformer-compile-class': link:../transformer-compile-class '@unocss/transformer-directives': link:../transformer-directives '@unocss/transformer-variant-group': link:../transformer-variant-group diff --git a/test/__snapshots__/preset-attributify.test.ts.snap b/test/__snapshots__/preset-attributify.test.ts.snap index 9c1c1c0c8d..0cb2b5c502 100644 --- a/test/__snapshots__/preset-attributify.test.ts.snap +++ b/test/__snapshots__/preset-attributify.test.ts.snap @@ -1,45 +1,5 @@ // Vitest Snapshot v1 -exports[`attributify > compatible with full controlled rules 1`] = ` -"/* layer: default */ - - .custom-2 { - font-size: 2px; - } - /* you can have multiple rules */ - .custom-2::after { - content: 'after'; - } - .foo > .custom-2 { - color: red; - } - /* or media queries */ - @media (min-width: 680px) { - .custom-2 { - font-size: 16px; - } - } - - - [custom~=\\"\\\\31 \\"] { - font-size: 1px; - } - /* you can have multiple rules */ - [custom~=\\"\\\\31 \\"]::after { - content: 'after'; - } - .foo > [custom~=\\"\\\\31 \\"] { - color: red; - } - /* or media queries */ - @media (min-width: 680px) { - [custom~=\\"\\\\31 \\"] { - font-size: 16px; - } - } - " -`; - exports[`attributify > extractor1 1`] = ` Set { "[uno-layer-base~=\\"c-white/10\\"]", @@ -157,6 +117,49 @@ Set { } `; +exports[`attributify > extractor4 1`] = ` +Set { + "[uno-layer-base~=\\"c-white/10\\"]", + "[uno-layer-base~=\\"hover:c-black/20\\"]", + "[sm~=\\"[color:red]\\"]", + "[md~=\\"[--var:var(--another)]\\"]", + "[lg~=\\"bg-blue-600\\"]", + "absolute", + "fixed", + "[important~=\\"text-red\\"]", + "[important~=\\"bg-red\\"]", + "[bg~=\\"blue-400\\"]", + "[bg~=\\"hover:blue-500\\"]", + "[bg~=\\"dark:!blue-500\\"]", + "[bg~=\\"dark:hover:blue-600\\"]", + "[text~=\\"sm\\"]", + "[text~=\\"white\\"]", + "[flex~=\\"!~\\"]", + "[flex~=\\"col\\"]", + "[p~=\\"t-2\\"]", + "[pt~=\\"2\\"]", + "[border~=\\"rounded-xl\\"]", + "[border~=\\"x-1\\"]", + "[border~=\\"x-style-dashed\\"]", + "[border~=\\"2\\"]", + "[border~=\\"rounded\\"]", + "[border~=\\"blue-200\\"]", + "[un-children~=\\"m-auto\\"]", + "[pt2=\\"\\"]", + "[rounded-sm=\\"\\"]", + "[inline-block=\\"\\"]", + "[transform=\\"\\"]", + "[translate-x-100=\\"\\"]", + "[rotate-30=\\"\\"]", + "[after~=\\"content-[unocss]\\"]", + "[rotate-60=\\"\\"]", + "[ma=\\"\\"]", + "[m~=\\"1\\"]", + "[m~=\\"2\\"]", + "[m~=\\"3\\"]", +} +`; + exports[`attributify > fixture1 1`] = ` "/* layer: base */ [uno-layer-base~=\\"c-white\\\\/10\\"]{color:rgba(255,255,255,0.1);} @@ -267,6 +270,55 @@ exports[`attributify > fixture2 1`] = ` }" `; +exports[`attributify > fixture4 1`] = ` +"/* layer: base */ +[uno-layer-base~=\\"c-white\\\\/10\\"]{color:rgba(255,255,255,0.1);} +[uno-layer-base~=\\"hover\\\\:c-black\\\\/20\\"]:hover{color:rgba(0,0,0,0.2);} +/* layer: default */ +.absolute{position:absolute;} +.fixed{position:fixed;} +[m~=\\"\\\\31 \\"]{margin:0.25rem;} +[m~=\\"\\\\32 \\"]{margin:0.5rem;} +[m~=\\"\\\\33 \\"]{margin:0.75rem;} +[ma=\\"\\"], +[un-children~=\\"m-auto\\"]>*{margin:auto;} +[inline-block=\\"\\"]{display:inline-block;} +[flex~=\\"\\\\!\\\\~\\"]{display:flex !important;} +[flex~=\\"col\\"]{flex-direction:column;} +[translate-x-100=\\"\\"]{--un-translate-x:25rem;transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z));} +[rotate-30=\\"\\"]{--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-rotate:30deg;transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z));} +[rotate-60=\\"\\"]{--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-rotate:60deg;transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z));} +[transform=\\"\\"]{transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z));} +[border~=\\"\\\\32 \\"]{border-width:2px;border-style:solid;} +[border~=\\"x-1\\"]{border-left-width:1px;border-right-width:1px;border-left-style:solid;border-right-style:solid;} +[border~=\\"blue-200\\"]{--un-border-opacity:1;border-color:rgba(191,219,254,var(--un-border-opacity));} +[border~=\\"rounded-xl\\"]{border-radius:0.75rem;} +[border~=\\"rounded\\"]{border-radius:0.25rem;} +[rounded-sm=\\"\\"]{border-radius:0.125rem;} +[border~=\\"x-style-dashed\\"]{border-left-style:dashed;border-right-style:dashed;} +.dark [bg~=\\"dark\\\\:\\\\!blue-500\\"]{--un-bg-opacity:1 !important;background-color:rgba(59,130,246,var(--un-bg-opacity)) !important;} +.dark [bg~=\\"dark\\\\:hover\\\\:blue-600\\"]:hover{--un-bg-opacity:1;background-color:rgba(37,99,235,var(--un-bg-opacity));} +[bg~=\\"blue-400\\"]{--un-bg-opacity:1;background-color:rgba(96,165,250,var(--un-bg-opacity));} +[bg~=\\"hover\\\\:blue-500\\"]:hover{--un-bg-opacity:1;background-color:rgba(59,130,246,var(--un-bg-opacity));} +[important~=\\"bg-red\\"]{--un-bg-opacity:1 !important;background-color:rgba(248,113,113,var(--un-bg-opacity)) !important;} +[p~=\\"t-2\\"], +[pt~=\\"\\\\32 \\"], +[pt2=\\"\\"]{padding-top:0.5rem;} +[text~=\\"sm\\"]{font-size:0.875rem;line-height:1.25rem;} +[important~=\\"text-red\\"]{--un-text-opacity:1 !important;color:rgba(248,113,113,var(--un-text-opacity)) !important;} +[text~=\\"white\\"]{--un-text-opacity:1;color:rgba(255,255,255,var(--un-text-opacity));} +[after~=\\"content-\\\\[unocss\\\\]\\"]::after{content:\\"unocss\\";} +@media (min-width: 640px){ +[sm~=\\"\\\\[color\\\\:red\\\\]\\"]{color:red;} +} +@media (min-width: 768px){ +[md~=\\"\\\\[--var\\\\:var\\\\(--another\\\\)\\\\]\\"]{--var:var(--another);} +} +@media (min-width: 1024px){ +[lg~=\\"bg-blue-600\\"]{--un-bg-opacity:1;background-color:rgba(37,99,235,var(--un-bg-opacity));} +}" +`; + exports[`attributify > variant 1`] = ` [ "uno-layer-base-c-white/10", @@ -321,12 +373,3 @@ exports[`attributify > variant 1`] = ` "m-3", ] `; - -exports[`attributify > with trueToNonValued 1`] = ` -"/* layer: default */ -.grid, -[grid~=\\"\\\\~\\"]{display:grid;} -[grid~=\\"cols-2\\"]{grid-template-columns:repeat(2,minmax(0,1fr));} -.flex, -[flex~=\\"true\\"]{display:flex;}" -`; diff --git a/test/cli.test.ts b/test/cli.test.ts index 1f97db2de5..ebc1680b8c 100644 --- a/test/cli.test.ts +++ b/test/cli.test.ts @@ -44,7 +44,7 @@ export default defineConfig({ const testDir = getTestDir() const absolutePathOfFile = resolve(testDir, fileName) await fs.outputFile(absolutePathOfFile, initializedContent) - runAsyncChildProcess(testDir, './views/**/*', '-w') + await runAsyncChildProcess(testDir, './views/**/*', '-w') const outputPath = resolve(testDir, 'uno.css') for (let i = 50; i >= 0; i--) { await sleep(50) diff --git a/test/preset-attributify.test.ts b/test/preset-attributify.test.ts index 82da67b84f..8c3f121562 100644 --- a/test/preset-attributify.test.ts +++ b/test/preset-attributify.test.ts @@ -78,6 +78,39 @@ describe('attributify', () => {
` + // jsx + const fixture4 = ` + + ` + const uno = createGenerator({ presets: [ presetAttributify({ strict: true }), @@ -117,6 +150,10 @@ describe('attributify', () => { expect(await uno.applyExtractors(fixture2)).toMatchSnapshot() }) + test('extractor4', async () => { + expect(await uno.applyExtractors(fixture4)).toMatchSnapshot() + }) + test('variant', async () => { const variant = variantAttributify({ prefix: 'un-', @@ -142,25 +179,29 @@ describe('attributify', () => { expect(css).toMatchSnapshot() }) - test('autocomplete extractor', async () => { - const res = await autocompleteExtractorAttributify.extract({ - content: fixture1, - cursor: 187, - }) + test('fixture4', async () => { + const { css } = await uno.generate(fixture4, { preflights: false }) + expect(css).toMatchSnapshot() + + test('autocomplete extractor', async () => { + const res = await autocompleteExtractorAttributify.extract({ + content: fixture1, + cursor: 187, + }) - expect(res).not.toBeNull() + expect(res).not.toBeNull() - expect(res!.extracted).toMatchInlineSnapshot('"bg-blue-400"') - expect(res!.transformSuggestions!([`${res!.extracted}1`, `${res!.extracted}2`])) - .toMatchInlineSnapshot(` + expect(res!.extracted).toMatchInlineSnapshot('"bg-blue-400"') + expect(res!.transformSuggestions!([`${res!.extracted}1`, `${res!.extracted}2`])) + .toMatchInlineSnapshot(` [ "blue-4001", "blue-4002", ] `) - const reversed = res!.resolveReplacement(`${res!.extracted}1`) - expect(reversed).toMatchInlineSnapshot(` + const reversed = res!.resolveReplacement(`${res!.extracted}1`) + expect(reversed).toMatchInlineSnapshot(` { "end": 193, "replacement": "blue-4001", @@ -168,30 +209,31 @@ describe('attributify', () => { } `) - expect(fixture1.slice(reversed.start, reversed.end)) - .toMatchInlineSnapshot('"blue-400"') - }) - - test('compatible with full controlled rules', async () => { - const { css } = await uno.generate(fixture3, { preflights: false }) - expect(css).toMatchSnapshot() - }) + expect(fixture1.slice(reversed.start, reversed.end)) + .toMatchInlineSnapshot('"blue-400"') + }) - test('with trueToNonValued', async () => { - const uno = createGenerator({ - presets: [ - presetAttributify({ trueToNonValued: true }), - presetUno(), - ], + test('compatible with full controlled rules', async () => { + const { css } = await uno.generate(fixture3, { preflights: false }) + expect(css).toMatchSnapshot() }) - const { css } = await uno.generate(` + + test('with trueToNonValued', async () => { + const uno = createGenerator({ + presets: [ + presetAttributify({ trueToNonValued: true }), + presetUno(), + ], + }) + const { css } = await uno.generate(`
`, { preflights: false }) - expect(css).toMatchSnapshot() - }) + expect(css).toMatchSnapshot() + }) - test('with incomplete element', async () => { - await uno.generate('
{ + await uno.generate('
{ + const originalCode = ` +
+
+
+ unocss +
+
+ The instant on-demand Atomic CSS engine. +
+
+ +
+
+
+
+ on-demand · instant · fully customizable +
+ `.trim() + + const uno = createGenerator({ + presets: [ + presetUno(), + presetAttributify(), + ], + }) + + test('transform', async () => { + const code = new MagicString(originalCode) + await transformerAttributifyJsx().transform(code, 'app.tsx', { uno, tokens: new Set() } as any) + + expect(code.toString()).toMatchInlineSnapshot(` + "
+
+
+ unocss +
+
+ The instant on-demand Atomic CSS engine. +
+
+ +
+
+
+
+ on-demand · instant · fully customizable +
" + `) + }) + + test('blocklist', async () => { + const code = new MagicString(originalCode) + const blocklist = ['flex', 'absolute', /op[0-9]+/] + + await transformerAttributifyJsx({ + blocklist, + }).transform(code, 'app.jsx', { uno, tokens: new Set() } as any) + + expect(code.toString()).toMatchInlineSnapshot(` + "
+
+
+ unocss +
+
+ The instant on-demand Atomic CSS engine. +
+
+ +
+
+
+
+ on-demand · instant · fully customizable +
" + `) + + const codeToString = code.toString() + blocklist.forEach((rule) => { + if (rule instanceof RegExp) + expect(new RegExp(`${rule.source}=""`).test(codeToString)).not.true + else + expect(codeToString).not.toMatch(`${rule}=""`) + }) + }) +}) diff --git a/tsconfig.json b/tsconfig.json index e7a0d7e08d..529ca480d0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -38,6 +38,7 @@ "@unocss/transformer-directives": ["./packages/transformer-directives/src/index.ts"], "@unocss/transformer-variant-group": ["./packages/transformer-variant-group/src/index.ts"], "@unocss/transformer-compile-class": ["./packages/transformer-compile-class/src/index.ts"], + "@unocss/transformer-attributify-jsx": ["./packages/transformer-attributify-jsx/src/index.ts"], "@unocss/vite": ["./packages/vite/src/index.ts"], "unocss": ["./packages/unocss/src/index.ts"], "unocss/vite": ["./packages/unocss/src/vite.ts"]