Skip to content

Commit

Permalink
feat(attributify-jsx): new transformer transformer-attributify-jsx (#…
Browse files Browse the repository at this point in the history
…1334)


Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>
  • Loading branch information
SnowingFox and antfu committed Jul 29, 2022
1 parent 719cdab commit 3a931b8
Show file tree
Hide file tree
Showing 17 changed files with 559 additions and 86 deletions.
1 change: 1 addition & 0 deletions alias.ts
Expand Up @@ -23,6 +23,7 @@ export const alias: Record<string, string> = {
'@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/'),
}
5 changes: 2 additions & 3 deletions interactive/package.json
Expand Up @@ -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": {
Expand Down
1 change: 1 addition & 0 deletions packages/README.md
Expand Up @@ -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 || - |
Expand Down
4 changes: 3 additions & 1 deletion packages/preset-attributify/README.md
Expand Up @@ -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
<div class="m-2 rounded text-teal-400" />
Expand All @@ -83,6 +83,8 @@ now can be
<div m-2 rounded text-teal-400 />
```

> Note: If you are using JSX, `<div foo>` might be transformed to `<div foo={true}>` 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.
Expand Down
3 changes: 2 additions & 1 deletion packages/preset-attributify/src/extractor.ts
Expand Up @@ -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 => {
Expand Down
118 changes: 118 additions & 0 deletions packages/transformer-attributify-jsx/README.md
@@ -0,0 +1,118 @@
# @unocss/transformer-attributify-jsx

<!-- @unocss-ignore -->

Support [valueless attributify](https://github.com/unocss/unocss/tree/main/packages/preset-attributify#valueless-attributify) in JSX/TSX.

```jsx
export function Component() {
return (
<div text-red text-center text-5xl animate-bounce>
unocss
</div>
)
}
```

Will be transformed to:

```jsx
export function Component() {
return (
<div text-red="" text-center="" text-5xl="" animate-bounce="">
unocss
</div>
)
}
```

<details>
<summary>Without this transformer</summary>

JSX by default will treat valueless attributes as boolean attributes.

```jsx
export function Component() {
return (
<div text-red={true} text-center={true} text-5xl={true} animate-bounce={true}>
unocss
</div>
)
}
```

</details>

## 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
<div translate-x-100% /> <!-- cannot end with `%` -->

<div hover:text-2xl /> <!-- cannot contain `:` -->

<div translate-x-[100px] /> <!-- cannot contain `[` or `]` -->
```

Instead, you may want to use valued attributes instead:

```html
<div translate="x-100%" />

<div hover="text-2xl" />

<div translate="x-[100px]" />
```

## 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
<div text-red text-center text-5xl animate-bounce>
unocss
</div>
```

Will be compiled to:

```html
<div text-red text-center text-5xl animate-bounce="">
unocss
</div>
```

## License

MIT License &copy; 2022-PRESENT [Anthony Fu](https://github.com/antfu)
15 changes: 15 additions & 0 deletions 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,
},
})
45 changes: 45 additions & 0 deletions 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 <anthonyfu117@hotmail.com>",
"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"
}
}
81 changes: 81 additions & 0 deletions 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> | 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 = /<!--[\s\S]*?-->|<(\/?)([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<void>[] = []

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)
},
}
}
1 change: 1 addition & 0 deletions packages/unocss/package.json
Expand Up @@ -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:*",
Expand Down
1 change: 1 addition & 0 deletions packages/unocss/src/index.ts
Expand Up @@ -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<Theme extends {} = PresetUnoTheme>(config: UserConfig<Theme>) {
return config
Expand Down
13 changes: 13 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 3a931b8

Please sign in to comment.