Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(attributify-jsx): new transformer transformer-attributify-jsx #1334

Merged
merged 21 commits into from Jul 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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.