Skip to content

Commit

Permalink
feat(transformer-attributify-jsx-babel): new transformer (#2337)
Browse files Browse the repository at this point in the history
  • Loading branch information
sibbng committed Apr 8, 2023
1 parent 4b29c98 commit cf1b1d4
Show file tree
Hide file tree
Showing 11 changed files with 848 additions and 467 deletions.
1 change: 1 addition & 0 deletions alias.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const alias: Record<string, string> = {
'@unocss/shared-docs': r('./packages/shared-docs/src/'),
'@unocss/shared-integration': r('./packages/shared-integration/src/'),
'@unocss/transformer-attributify-jsx': r('./packages/transformer-attributify-jsx/src/'),
'@unocss/transformer-attributify-jsx-babel': r('./packages/transformer-attributify-jsx-babel/src/'),
'@unocss/transformer-compile-class': r('./packages/transformer-compile-class/src/'),
'@unocss/transformer-directives': r('./packages/transformer-directives/src/'),
'@unocss/transformer-variant-group': r('./packages/transformer-variant-group/src/'),
Expand Down
1 change: 1 addition & 0 deletions packages/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
| [@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/transformer-attributify-jsx-babel](./transformer-attributify-jsx) | Support valueless attributify in JSX/TSX (Babel) | No | 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
114 changes: 114 additions & 0 deletions packages/transformer-attributify-jsx-babel/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# @unocss/transformer-attributify-jsx-babel

<!-- @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-babel
```

```ts
// uno.config.js
import { defineConfig, presetAttributify } from 'unocss'
import transformerAttributifyJsx from '@unocss/transformer-attributify-jsx-babel'

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 translate-x-[100px] /> <!-- cannot contain `[` or `]` -->
```

Instead, you may want to use valued attributes instead:

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

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

## Blocklist

This transformer will only transform attributes 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)
18 changes: 18 additions & 0 deletions packages/transformer-attributify-jsx-babel/build.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { defineBuildConfig } from 'unbuild'

export default defineBuildConfig({
entries: [
'src/index',
],
clean: true,
declaration: true,
externals: [
'magic-string',
'@babel/core',
'@babel/preset-typescript',
'@babel/plugin-syntax-jsx',
],
rollup: {
emitCJS: true,
},
})
48 changes: 48 additions & 0 deletions packages/transformer-attributify-jsx-babel/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"name": "@unocss/transformer-attributify-jsx-babel",
"version": "0.50.4",
"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-babel#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/unocss/unocss.git",
"directory": "packages/transformer-attributify-jsx-babel"
},
"bugs": {
"url": "https://github.com/unocss/unocss/issues"
},
"keywords": [
"unocss",
"unocss-transformer"
],
"sideEffects": false,
"exports": {
".": {
"types": "./dist/index.d.ts",
"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": {
"@babel/core": "^7.21.0",
"@babel/plugin-syntax-jsx": "^7.18.6",
"@babel/preset-typescript": "^7.21.0",
"magic-string": "^0.30.0"
}
}
130 changes: 130 additions & 0 deletions packages/transformer-attributify-jsx-babel/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import path from 'node:path'
import type { SourceCodeTransformer, UnoGenerator } from '@unocss/core'
import { toArray } from '@unocss/core'
import * as babel from '@babel/core'
// @ts-expect-error no types
import ts from '@babel/preset-typescript'
// @ts-expect-error no types
import jsx from '@babel/plugin-syntax-jsx'

export type FilterPattern = Array<string | RegExp> | string | RegExp | null

function createFilter(
include: FilterPattern,
exclude: FilterPattern,
): (id: string) => boolean {
const includePattern = toArray(include || [])
const excludePattern = toArray(exclude || [])
return (id: string) => {
if (excludePattern.some(p => id.match(p)))
return false
return includePattern.some(p => id.match(p))
}
}

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
}

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 || [],
)

interface InternaalBabelContext {
tasks: Promise<void>[]
matched: string[]
}

function babelPlugin(uno: UnoGenerator, ctx: InternaalBabelContext): babel.PluginObj {
return {
name: '@unocss/transformer-attributify-jsx-babel',
visitor: {
JSXAttribute(path) {
if (path.node.value === null) {
const attr = babel.types.isJSXNamespacedName(path.node.name)
? `${path.node.name.namespace.name}:${path.node.name.name.name}`
: path.node.name.name

if (isBlocked(attr))
return

if (ctx.matched.includes(attr)) {
path.node.value = babel.types.stringLiteral('')
}
else {
ctx.tasks.push(
uno.parseToken(attr).then((matched) => {
if (matched)
ctx.matched.push(attr)
}),
)
}
}
},
},
}
}

return {
name: '@unocss/transformer-attributify-jsx-babel',
enforce: 'pre',
idFilter,
async transform(code, id, { uno }) {
const ctx: InternaalBabelContext = { tasks: [], matched: [] }
const babelOptions = {
presets: [ts],
plugins: [
babelPlugin(uno, ctx),
jsx,
],
filename: path.basename(id),
}
// extract attributes without a value
await babel.transformAsync(code.toString(), babelOptions)
// parse extracted attributes
await Promise.all(ctx.tasks)
// add empty value to matched attributes
const result = await babel.transformAsync(code.toString(), babelOptions)

if (result)
code.overwrite(0, code.original.length, result.code || '')
},
}
}
2 changes: 2 additions & 0 deletions packages/transformer-attributify-jsx/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ export default defineConfig({

## Caveats

If you encounter any issues with this package, there is [@unocss/transformer-attributify-jsx-babel](https://github.com/unocss/unocss/tree/main/packages/transformer-attributify-jsx-babel) package that uses Babel to transform JSX.

> ⚠️ The rules are almost the same as those of `preset-attributify`, but there are several precautions
```html
Expand Down
1 change: 1 addition & 0 deletions packages/unocss/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@
"@unocss/preset-wind": "workspace:*",
"@unocss/reset": "workspace:*",
"@unocss/transformer-attributify-jsx": "workspace:*",
"@unocss/transformer-attributify-jsx-babel": "workspace:*",
"@unocss/transformer-compile-class": "workspace:*",
"@unocss/transformer-directives": "workspace:*",
"@unocss/transformer-variant-group": "workspace:*",
Expand Down

0 comments on commit cf1b1d4

Please sign in to comment.