diff --git a/.vscode/settings.json b/.vscode/settings.json index 310e37bb12..bafd48ca3e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,5 +4,8 @@ "editor.codeActionsOnSave": { "source.fixAll.eslint": true }, - "editor.formatOnSave": false + "editor.formatOnSave": false, + "cSpell.words": [ + "tagify" + ] } diff --git a/alias.ts b/alias.ts index ff8b724fa2..1110c09ccb 100644 --- a/alias.ts +++ b/alias.ts @@ -13,6 +13,7 @@ export const alias: Record = { '@unocss/preset-attributify': r('./packages/preset-attributify/src/'), '@unocss/preset-icons': r('./packages/preset-icons/src/'), '@unocss/preset-mini': r('./packages/preset-mini/src/'), + '@unocss/preset-tagify': r('./packages/preset-tagify/src/'), '@unocss/preset-typography': r('./packages/preset-typography/src/'), '@unocss/preset-uno': r('./packages/preset-uno/src/'), '@unocss/preset-web-fonts': r('./packages/preset-web-fonts/src/'), diff --git a/interactive/guides/packages.md b/interactive/guides/packages.md index 8678ce16dd..21d5487e73 100644 --- a/interactive/guides/packages.md +++ b/interactive/guides/packages.md @@ -7,7 +7,8 @@ | [@unocss/preset-uno](https://github.com/unocss/unocss/tree/main/packages/preset-uno) | The default preset | | [@unocss/preset-mini](https://github.com/unocss/unocss/tree/main/packages/preset-mini) | The minimal but essential rules and variants | | [@unocss/preset-wind](https://github.com/unocss/unocss/tree/main/packages/preset-wind) | Tailwind / Windi CSS compact preset | -| [@unocss/preset-attributify](https://github.com/unocss/unocss/tree/main/packages/preset-attributify) | Enables Attributify Mode for other rules | +| [@unocss/preset-attributify](https://github.com/unocss/unocss/tree/main/packages/preset-attributify) | Enables Attributify Mode for other rules | +| [@unocss/preset-tagify](https://github.com/unocss/unocss/tree/main/packages/preset-tagify) | Enables Tagify Mode for other rules | | [@unocss/preset-icons](https://github.com/unocss/unocss/tree/main/packages/preset-icons) | Pure CSS Icons solution powered by Iconify | | [@unocss/preset-web-fonts](https://github.com/unocss/unocss/tree/main/packages/preset-web-fonts) | Web fonts (Google Fonts, etc.) support | | [@unocss/preset-typography](https://github.com/unocss/unocss/tree/main/packages/preset-typography) | The typography preset | diff --git a/package.json b/package.json index 67a8e02269..2e65e3f507 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "@unocss/preset-attributify": "workspace:*", "@unocss/preset-icons": "workspace:*", "@unocss/preset-mini": "workspace:*", + "@unocss/preset-tagify": "workspace:*", "@unocss/preset-typography": "workspace:*", "@unocss/preset-uno": "workspace:*", "@unocss/preset-web-fonts": "workspace:*", diff --git a/packages/README.md b/packages/README.md index cd576d4cd9..0048121f03 100644 --- a/packages/README.md +++ b/packages/README.md @@ -9,6 +9,7 @@ | [@unocss/preset-mini](./preset-mini) | The minimal but essential rules and variants | ✅ | ✅ | | [@unocss/preset-wind](./preset-wind) | Tailwind / Windi CSS compact preset | ✅ | ✅ | | [@unocss/preset-attributify](./preset-attributify) | Enables Attributify Mode for other rules | ✅ | No | +| [@unocss/preset-tagify](./preset-tagify) | Enables Tagify Mode for other rules | ✅ | No | | [@unocss/preset-icons](./preset-icons) | Pure CSS Icons solution powered by Iconify | ✅ | No | | [@unocss/preset-web-fonts](./preset-web-fonts) | Web fonts (Google Fonts, etc.) support | ✅ | No | | [@unocss/preset-typography](./preset-typography) | The typography preset | ✅ | No | diff --git a/packages/nuxt/package.json b/packages/nuxt/package.json index fc98500236..5e789fee45 100644 --- a/packages/nuxt/package.json +++ b/packages/nuxt/package.json @@ -44,6 +44,7 @@ "@unocss/core": "workspace:*", "@unocss/preset-attributify": "workspace:*", "@unocss/preset-icons": "workspace:*", + "@unocss/preset-tagify": "workspace:*", "@unocss/preset-typography": "workspace:*", "@unocss/preset-uno": "workspace:*", "@unocss/preset-web-fonts": "workspace:*", diff --git a/packages/nuxt/src/options.ts b/packages/nuxt/src/options.ts index 54977c68f3..df712dba55 100644 --- a/packages/nuxt/src/options.ts +++ b/packages/nuxt/src/options.ts @@ -3,6 +3,7 @@ import presetAttributify from '@unocss/preset-attributify' import presetIcons from '@unocss/preset-icons' import presetWebFonts from '@unocss/preset-web-fonts' import presetTypography from '@unocss/preset-typography' +import presetTagify from '@unocss/preset-tagify' import presetWind from '@unocss/preset-wind' import type { UnocssNuxtOptions } from './types' @@ -12,6 +13,7 @@ export function resolveOptions(options: UnocssNuxtOptions) { const presetMap = { uno: presetUno, attributify: presetAttributify, + tagify: presetTagify, icons: presetIcons, webFonts: presetWebFonts, typography: presetTypography, diff --git a/packages/nuxt/src/types.ts b/packages/nuxt/src/types.ts index 993565f600..dd5455ac49 100644 --- a/packages/nuxt/src/types.ts +++ b/packages/nuxt/src/types.ts @@ -4,6 +4,7 @@ import type { AttributifyOptions } from '@unocss/preset-attributify' import type { IconsOptions } from '@unocss/preset-icons' import type { WebFontsOptions } from '@unocss/preset-web-fonts' import type { TypographyOptions } from '@unocss/preset-typography' +import type { TagifyOptions } from '@unocss/preset-tagify' import type { PresetWindOptions } from '@unocss/preset-wind' export interface UnocssNuxtOptions extends UserConfig { @@ -43,6 +44,13 @@ export interface UnocssNuxtOptions extends UserConfig { */ attributify?: boolean | AttributifyOptions + /** + * Enable tagify mode and the options of it + * Only works when `presets` is not specified + * @default false + */ + tagify?: boolean | TagifyOptions + /** * Enable icons preset and the options of it * Only works when `presets` is not specified diff --git a/packages/preset-tagify/LICENSE b/packages/preset-tagify/LICENSE new file mode 100644 index 0000000000..39da18e726 --- /dev/null +++ b/packages/preset-tagify/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2022-PRESENT Jeff Zou +Copyright (c) 2022-PRESENT Anthony Fu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/preset-tagify/README.md b/packages/preset-tagify/README.md new file mode 100644 index 0000000000..373e9ee932 --- /dev/null +++ b/packages/preset-tagify/README.md @@ -0,0 +1,77 @@ +# @unocss/preset-tagify + +Tagify Mode for [UnoCSS](https://github.com/unocss/unocss). + +## Installation + +```bash +npm i -D @unocss/preset-tagify +``` + +```ts +import preseTagify from '@unocss/preset-tagify' + +Unocss({ + presets: [ + presetTagify({ /* options */ }), + // ...other presets + ], +}) +``` + +## Tagify Mode + +This preset can come in handy when you only need a single unocss rule to be apply on an element. + +```html + red text +
flexbox
+I'm feeling today! +``` + +With tagify mode, you can embed CSS styles into HTML tags: + +```html + red text + flexbox +I'm feeling today! +``` + +The HTML above works exactly as you would expect. + +## With Prefix + +```js +presetTagify({ + prefix: 'un-' +}) +``` + +```html + + + + +``` + +## Extra Properties + +You can inject extra properties to the matched rules: + +```js +presetTagify({ + // adds display: inline-block to matched icons + extraProperties: matched => matched.startsWith('i-') + ? { display: 'inline-block' } + : { } +}) +presetTagify({ + // extraProperties can also be a plain object + extraProperties: { display: 'block' } +}) +``` + +## License + +MIT License © 2022-PRESENT [Jeff Zou](https://github.com/zojize) +MIT License © 2022-PRESENT [Anthony Fu](https://github.com/antfu) diff --git a/packages/preset-tagify/build.config.ts b/packages/preset-tagify/build.config.ts new file mode 100644 index 0000000000..868e79fab0 --- /dev/null +++ b/packages/preset-tagify/build.config.ts @@ -0,0 +1,12 @@ +import { defineBuildConfig } from 'unbuild' + +export default defineBuildConfig({ + entries: [ + 'src/index', + ], + clean: true, + declaration: true, + rollup: { + emitCJS: true, + }, +}) diff --git a/packages/preset-tagify/package.json b/packages/preset-tagify/package.json new file mode 100644 index 0000000000..48b0a04894 --- /dev/null +++ b/packages/preset-tagify/package.json @@ -0,0 +1,42 @@ +{ + "name": "@unocss/preset-tagify", + "version": "0.34.1", + "description": "Tagify preset for UnoCSS", + "author": "Anthony Fu ", + "license": "MIT", + "funding": "https://github.com/sponsors/antfu", + "homepage": "https://github.com/unocss/unocss/tree/main/packages/transformer-variant-group#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/unocss/unocss.git", + "directory": "packages/transformer-variant-group" + }, + "bugs": { + "url": "https://github.com/unocss/unocss/issues" + }, + "keywords": [ + "unocss", + "unocss-preset" + ], + "sideEffects": false, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.cjs" + } + }, + "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:*" + } +} diff --git a/packages/preset-tagify/src/extractor.ts b/packages/preset-tagify/src/extractor.ts new file mode 100644 index 0000000000..ce8e97eaf4 --- /dev/null +++ b/packages/preset-tagify/src/extractor.ts @@ -0,0 +1,22 @@ +import type { Extractor } from '@unocss/core' +import type { TagifyOptions } from './types' + +export const MARKER = '__TAGIFY__' +export const htmlTagRE = /<([\w\d-:]+)/g + +export const extractorTagify = (options: TagifyOptions): Extractor => { + const { + prefix = '', + } = options + + return { + name: 'tagify', + extract({ code }) { + return new Set( + Array.from(code.matchAll(htmlTagRE)) + .filter(({ 1: match }) => match.startsWith(prefix)) + .map(([, matched]) => `${MARKER}${matched}`), + ) + }, + } +} diff --git a/packages/preset-tagify/src/index.ts b/packages/preset-tagify/src/index.ts new file mode 100644 index 0000000000..1e45fe6437 --- /dev/null +++ b/packages/preset-tagify/src/index.ts @@ -0,0 +1,33 @@ +import type { Preset } from '@unocss/core' +import { extractorSplit } from '@unocss/core' +import type { TagifyOptions } from './types' +import { extractorTagify } from './extractor' +import { variantTagify } from './variant' + +export * from './extractor' +export * from './types' +export * from './variant' + +const preset = (options: TagifyOptions = {}): Preset => { + const { + defaultExtractor = true, + } = options + + const variants = [ + variantTagify(options), + ] + const extractors = [ + extractorTagify(options), + ] + + if (defaultExtractor) + extractors.push(extractorSplit) + + return { + name: '@unocss/preset-tagify', + variants, + extractors, + } +} + +export default preset diff --git a/packages/preset-tagify/src/types.ts b/packages/preset-tagify/src/types.ts new file mode 100644 index 0000000000..d28f5658e1 --- /dev/null +++ b/packages/preset-tagify/src/types.ts @@ -0,0 +1,19 @@ +export interface TagifyOptions { + /** + * The prefix to use for the tagify variant. + */ + prefix?: string + + /** + * Extra CSS properties to apply to matched rules + */ + extraProperties?: + | Record + | ((matched: string) => Partial>) + + /** + * Enable default extractor + * @default true + */ + defaultExtractor?: boolean +} diff --git a/packages/preset-tagify/src/variant.ts b/packages/preset-tagify/src/variant.ts new file mode 100644 index 0000000000..0bf42e1ea4 --- /dev/null +++ b/packages/preset-tagify/src/variant.ts @@ -0,0 +1,31 @@ +import type { VariantHandler, VariantObject } from '@unocss/core' +import type { TagifyOptions } from './types' +import { MARKER } from './extractor' + +export const variantTagify = (options: TagifyOptions): VariantObject => { + const { extraProperties } = options + const prefix = `${MARKER}${options.prefix ?? ''}` + + return { + name: 'tagify', + match(input) { + if (!input.startsWith(prefix)) + return + + const matcher = input.slice(prefix.length) + const handler: VariantHandler = { + matcher, + selector: i => i.slice(MARKER.length + 1), + } + + if (extraProperties) { + if (typeof extraProperties === 'function') + handler.body = entries => [...entries, ...Object.entries(extraProperties(matcher) ?? {})] + else + handler.body = entries => [...entries, ...Object.entries(extraProperties)] + } + + return handler + }, + } +} diff --git a/packages/unocss/build.config.ts b/packages/unocss/build.config.ts index e98319732f..3c021bf780 100644 --- a/packages/unocss/build.config.ts +++ b/packages/unocss/build.config.ts @@ -7,6 +7,7 @@ export default defineBuildConfig({ 'src/preset-uno', 'src/preset-icons', 'src/preset-attributify', + 'src/preset-tagify', 'src/preset-web-fonts', 'src/preset-typography', 'src/preset-wind', diff --git a/packages/unocss/package.json b/packages/unocss/package.json index 53deea04e3..b2cc88aaf7 100644 --- a/packages/unocss/package.json +++ b/packages/unocss/package.json @@ -33,6 +33,11 @@ "import": "./dist/preset-attributify.mjs", "require": "./dist/preset-attributify.cjs" }, + "./preset-tagify": { + "types": "./dist/preset-tagify.d.ts", + "import": "./dist/preset-tagify.mjs", + "require": "./dist/preset-tagify.cjs" + }, "./preset-icons": { "types": "./dist/preset-icons.d.ts", "import": "./dist/preset-icons.mjs", @@ -89,6 +94,7 @@ "@unocss/preset-attributify": "workspace:*", "@unocss/preset-icons": "workspace:*", "@unocss/preset-mini": "workspace:*", + "@unocss/preset-tagify": "workspace:*", "@unocss/preset-typography": "workspace:*", "@unocss/preset-uno": "workspace:*", "@unocss/preset-web-fonts": "workspace:*", diff --git a/packages/unocss/src/index.ts b/packages/unocss/src/index.ts index e375208d37..677cf873e7 100644 --- a/packages/unocss/src/index.ts +++ b/packages/unocss/src/index.ts @@ -3,6 +3,7 @@ import type { UserConfig } from '@unocss/core' export * from '@unocss/core' export { default as presetUno } from '@unocss/preset-uno' export { default as presetAttributify } from '@unocss/preset-attributify' +export { default as presetTagify } from '@unocss/preset-tagify' export { default as presetIcons } from '@unocss/preset-icons' export { default as presetWebFonts } from '@unocss/preset-web-fonts' export { default as presetTypography } from '@unocss/preset-typography' diff --git a/packages/unocss/src/preset-tagify.ts b/packages/unocss/src/preset-tagify.ts new file mode 100644 index 0000000000..c9421e55b7 --- /dev/null +++ b/packages/unocss/src/preset-tagify.ts @@ -0,0 +1 @@ +export * from '@unocss/preset-attributify' diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d75ee9516f..448b707687 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -40,6 +40,7 @@ importers: '@unocss/preset-attributify': workspace:* '@unocss/preset-icons': workspace:* '@unocss/preset-mini': workspace:* + '@unocss/preset-tagify': workspace:* '@unocss/preset-typography': workspace:* '@unocss/preset-uno': workspace:* '@unocss/preset-web-fonts': workspace:* @@ -121,6 +122,7 @@ importers: '@unocss/preset-attributify': link:packages/preset-attributify '@unocss/preset-icons': link:packages/preset-icons '@unocss/preset-mini': link:packages/preset-mini + '@unocss/preset-tagify': link:packages/preset-tagify '@unocss/preset-typography': link:packages/preset-typography '@unocss/preset-uno': link:packages/preset-uno '@unocss/preset-web-fonts': link:packages/preset-web-fonts @@ -314,6 +316,7 @@ importers: '@unocss/core': workspace:* '@unocss/preset-attributify': workspace:* '@unocss/preset-icons': workspace:* + '@unocss/preset-tagify': workspace:* '@unocss/preset-typography': workspace:* '@unocss/preset-uno': workspace:* '@unocss/preset-web-fonts': workspace:* @@ -327,6 +330,7 @@ importers: '@unocss/core': link:../core '@unocss/preset-attributify': link:../preset-attributify '@unocss/preset-icons': link:../preset-icons + '@unocss/preset-tagify': link:../preset-tagify '@unocss/preset-typography': link:../preset-typography '@unocss/preset-uno': link:../preset-uno '@unocss/preset-web-fonts': link:../preset-web-fonts @@ -363,6 +367,12 @@ importers: dependencies: '@unocss/core': link:../core + packages/preset-tagify: + specifiers: + '@unocss/core': workspace:* + dependencies: + '@unocss/core': link:../core + packages/preset-typography: specifiers: '@unocss/core': workspace:* @@ -480,6 +490,7 @@ importers: '@unocss/preset-attributify': workspace:* '@unocss/preset-icons': workspace:* '@unocss/preset-mini': workspace:* + '@unocss/preset-tagify': workspace:* '@unocss/preset-typography': workspace:* '@unocss/preset-uno': workspace:* '@unocss/preset-web-fonts': workspace:* @@ -495,6 +506,7 @@ importers: '@unocss/preset-attributify': link:../preset-attributify '@unocss/preset-icons': link:../preset-icons '@unocss/preset-mini': link:../preset-mini + '@unocss/preset-tagify': link:../preset-tagify '@unocss/preset-typography': link:../preset-typography '@unocss/preset-uno': link:../preset-uno '@unocss/preset-web-fonts': link:../preset-web-fonts diff --git a/test/preset-tagify.test.ts b/test/preset-tagify.test.ts new file mode 100644 index 0000000000..31f6a79828 --- /dev/null +++ b/test/preset-tagify.test.ts @@ -0,0 +1,106 @@ +import presetIcons from '@unocss/preset-icons' +import { type ExtractorContext, createGenerator } from '@unocss/core' +import { describe, expect, test } from 'vitest' +import presetTagify, { extractorTagify } from '@unocss/preset-tagify' +import presetMini from '@unocss/preset-mini' + +describe('tagify', () => { + test('extractor', async () => { + const extractor = extractorTagify({}) + + const code = ` + + spam + + + ` + + expect(extractor.extract({ code } as ExtractorContext)).toMatchInlineSnapshot(` + Set { + "__TAGIFY__foo", + "__TAGIFY__bar", + "__TAGIFY__baz", + } + `) + }) + + test('preset', async () => { + const uno = createGenerator({ + shortcuts: [ + ['btn', 'px-4 py-1 rounded inline-block bg-teal-600 text-white cursor-pointer hover:bg-teal-700 disabled:cursor-default disabled:bg-gray-600 disabled:opacity-50'], + ], + rules: [ + ['custom-rule', { 'background-color': 'pink' }], + ], + presets: [ + presetMini(), + presetTagify(), + ], + }) + + const code = ` + + red text + + margin + custom + shortcut + variant + + ` + + expect((await uno.generate(code)).css).toMatchInlineSnapshot(` + "/* layer: shortcuts */ + btn{padding-left:1rem;padding-right:1rem;padding-top:0.25rem;padding-bottom:0.25rem;display:inline-block;--un-bg-opacity:1;background-color:rgba(13,148,136,var(--un-bg-opacity));border-radius:0.25rem;--un-text-opacity:1;color:rgba(255,255,255,var(--un-text-opacity));cursor:pointer;} + btn:disabled{opacity:0.5;--un-bg-opacity:1;background-color:rgba(75,85,99,var(--un-bg-opacity));cursor:default;} + btn:hover{--un-bg-opacity:1;background-color:rgba(15,118,110,var(--un-bg-opacity));} + /* layer: default */ + .p2{padding:0.5rem;} + m-1{margin:0.25rem;} + hover\\\\:color-red:hover, + text-red{--un-text-opacity:1;color:rgba(248,113,113,var(--un-text-opacity));} + text-green5\\\\:10{color:rgba(34,197,94,0.1);} + flex{display:flex;} + custom-rule{background-color:pink;}" + `) + }) + + test('extraProperties', async () => { + const uno = createGenerator({ + presets: [ + presetIcons(), + presetTagify({ + extraProperties: + matched => matched.startsWith('i-') ? { display: 'inline-block' } : {}, + }), + ], + }) + + const code = ` + + ` + + expect((await uno.generate(code)).css).toContain('display:inline-block') + }) + + test('prefix', async () => { + const uno = createGenerator({ + presets: [ + presetMini(), + presetTagify({ + prefix: 'un-', + }), + ], + }) + + const code = ` + + + ` + + expect((await uno.generate(code)).css).toMatchInlineSnapshot(` + "/* layer: default */ + un-flex{display:flex;}" + `) + }) +}) diff --git a/tsconfig.json b/tsconfig.json index 134bfc861d..57538f032b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,6 +23,7 @@ "@unocss/preset-attributify": ["./packages/preset-attributify/src/index.ts"], "@unocss/preset-icons": ["./packages/preset-icons/src/index.ts"], "@unocss/preset-mini": ["./packages/preset-mini/src/index.ts"], + "@unocss/preset-tagify": ["./packages/preset-tagify/src/index.ts"], "@unocss/preset-mini/rules": ["./packages/preset-mini/src/rules.ts"], "@unocss/preset-mini/utils": ["./packages/preset-mini/src/utils.ts"], "@unocss/preset-mini/variants": ["./packages/preset-mini/src/variants.ts"],