From 96d15e9dc9f37ef3146657f3ff9b6e0768b0097d Mon Sep 17 00:00:00 2001 From: Techassi Date: Fri, 5 May 2023 16:57:37 +0200 Subject: [PATCH 1/6] Add support for custom class name in compile class transformer --- .../transformer-compile-class/src/index.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/transformer-compile-class/src/index.ts b/packages/transformer-compile-class/src/index.ts index dfcae5ec9c..0c638a89e7 100644 --- a/packages/transformer-compile-class/src/index.ts +++ b/packages/transformer-compile-class/src/index.ts @@ -34,12 +34,12 @@ export interface CompileClassOptions { export default function transformerCompileClass(options: CompileClassOptions = {}): SourceCodeTransformer { const { - trigger = ':uno:', + trigger = 'uno', classPrefix = 'uno-', hashFn = hash, keepUnknown = true, } = options - const regex = new RegExp(`(["'\`])${escapeRegExp(trigger)}\\s([^\\1]*?)\\1`, 'g') + const regex = new RegExp(`(["'\`]):${escapeRegExp(trigger)}(?:-)?([^\\1]+)?:\\s([^\\1]*?)\\1`, 'g') return { name: '@unocss/transformer-compile-class', @@ -50,7 +50,7 @@ export default function transformerCompileClass(options: CompileClassOptions = { return for (const match of matches) { - let body = expandVariantGroup(match[2].trim()) + let body = expandVariantGroup(match[3].trim()) const start = match.index! const replacements = [] if (keepUnknown) { @@ -62,14 +62,21 @@ export default function transformerCompileClass(options: CompileClassOptions = { } if (body) { body = body.split(/\s+/).sort().join(' ') - const hash = hashFn(body) - const className = `${classPrefix}${hash}` + const className = match[2] ? `${classPrefix}${match[2]}` : `${classPrefix}${hashFn(body)}` + + // FIXME: Ideally we should also check that the hash doesn't match. If the hash is the same, the same class + // name is allowed, as the applied styles are the same. + if (tokens && tokens.has(className)) + throw new Error(`duplicate compile class name '${className}', please choose different class name`) + replacements.unshift(className) if (options.layer) uno.config.shortcuts.push([className, body, { layer: options.layer }]) else uno.config.shortcuts.push([className, body]) - tokens.add(className) + + if (tokens) + tokens.add(className) } s.overwrite(start + 1, start + match[0].length - 1, replacements.join(' ')) } From 2e0cf279b5f57bdcab89a992666d9c520a0d44a2 Mon Sep 17 00:00:00 2001 From: Techassi Date: Mon, 8 May 2023 17:42:25 +0200 Subject: [PATCH 2/6] Add custom compile class name test --- test/transformer-compile-class.test.ts | 40 +++++++++++++++++++------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/test/transformer-compile-class.test.ts b/test/transformer-compile-class.test.ts index 6e7fe2184a..6cb15e382f 100644 --- a/test/transformer-compile-class.test.ts +++ b/test/transformer-compile-class.test.ts @@ -34,22 +34,26 @@ describe('transformer-compile-class', () => { `.trim()) expect(result.code.trim()).toMatchInlineSnapshot(` - "
-
+ "
+
-
-
+
+
" `) expect(result.css).toMatchInlineSnapshot(` - "/* layer: shortcuts */ - .uno-pe1esh{--un-scale-x:0.05;--un-scale-y:0.05;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-width:1px;--un-border-opacity:1;border-color:rgba(229,231,235,var(--un-border-opacity));--un-bg-opacity:1;background-color:rgba(239,68,68,var(--un-bg-opacity));font-size:1.25rem;line-height:1.75rem;font-weight:700;} - .dark .uno-pe1esh:hover{--un-bg-opacity:1;background-color:rgba(34,197,94,var(--un-bg-opacity));} - .uno-cbgd7b{text-align:center;} - .uno-s9yxer{font-size:0.875rem;line-height:1.25rem;font-weight:700;} - .uno-s9yxer:hover{--un-text-opacity:1;color:rgba(248,113,113,var(--un-text-opacity));} + "/* layer: default */ + .scale-5{--un-scale-x:0.05;--un-scale-y:0.05;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{border-width:1px;} + .border-gray-200{--un-border-opacity:1;border-color:rgba(229,231,235,var(--un-border-opacity));} + .bg-red-500{--un-bg-opacity:1;background-color:rgba(239,68,68,var(--un-bg-opacity));} + .dark .dark\\\\:hover\\\\:bg-green-500:hover{--un-bg-opacity:1;background-color:rgba(34,197,94,var(--un-bg-opacity));} + .text-center{text-align:center;} + .text-xl{font-size:1.25rem;line-height:1.75rem;} + .font-bold{font-weight:700;} @media (min-width: 640px){ - .uno-cbgd7b{text-align:left;} + .sm\\\\:text-left{text-align:left;} }" `) }) @@ -61,4 +65,18 @@ describe('transformer-compile-class', () => { expect(order1.css).toBe(order2.css) expect(order1.code).toBe(order2.code) }) + + test('custom compile class name', async () => { + const result = await transform(` +
`.trim()) + + expect(result.code.trim()).toMatchInlineSnapshot(` + "
" + `) + + expect(result.css).toMatchInlineSnapshot(` + "/* layer: shortcuts */ + .uno-foo{--un-bg-opacity:1;background-color:rgba(239,68,68,var(--un-bg-opacity));font-size:1.25rem;line-height:1.75rem;}" + `) + }) }) From f8493bf8dc60d994aec2f3041406467d1178845d Mon Sep 17 00:00:00 2001 From: Techassi Date: Tue, 9 May 2023 13:40:18 +0200 Subject: [PATCH 3/6] Fix regression --- .../transformer-compile-class/src/index.ts | 2 +- test/transformer-compile-class.test.ts | 26 ++++++++----------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/packages/transformer-compile-class/src/index.ts b/packages/transformer-compile-class/src/index.ts index 0c638a89e7..5fda1d761e 100644 --- a/packages/transformer-compile-class/src/index.ts +++ b/packages/transformer-compile-class/src/index.ts @@ -39,7 +39,7 @@ export default function transformerCompileClass(options: CompileClassOptions = { hashFn = hash, keepUnknown = true, } = options - const regex = new RegExp(`(["'\`]):${escapeRegExp(trigger)}(?:-)?([^\\1]+)?:\\s([^\\1]*?)\\1`, 'g') + const regex = new RegExp(`(["'\`]):${escapeRegExp(trigger)}(?:-)?([^\\s\\1]+)?:\\s([^\\1]*?)\\1`, 'g') return { name: '@unocss/transformer-compile-class', diff --git a/test/transformer-compile-class.test.ts b/test/transformer-compile-class.test.ts index 6cb15e382f..fee46c9204 100644 --- a/test/transformer-compile-class.test.ts +++ b/test/transformer-compile-class.test.ts @@ -34,26 +34,22 @@ describe('transformer-compile-class', () => {
`.trim()) expect(result.code.trim()).toMatchInlineSnapshot(` - "
-
+ "
+
-
-
+
+
" `) expect(result.css).toMatchInlineSnapshot(` - "/* layer: default */ - .scale-5{--un-scale-x:0.05;--un-scale-y:0.05;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{border-width:1px;} - .border-gray-200{--un-border-opacity:1;border-color:rgba(229,231,235,var(--un-border-opacity));} - .bg-red-500{--un-bg-opacity:1;background-color:rgba(239,68,68,var(--un-bg-opacity));} - .dark .dark\\\\:hover\\\\:bg-green-500:hover{--un-bg-opacity:1;background-color:rgba(34,197,94,var(--un-bg-opacity));} - .text-center{text-align:center;} - .text-xl{font-size:1.25rem;line-height:1.75rem;} - .font-bold{font-weight:700;} + "/* layer: shortcuts */ + .uno-pe1esh{--un-scale-x:0.05;--un-scale-y:0.05;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-width:1px;--un-border-opacity:1;border-color:rgba(229,231,235,var(--un-border-opacity));--un-bg-opacity:1;background-color:rgba(239,68,68,var(--un-bg-opacity));font-size:1.25rem;line-height:1.75rem;font-weight:700;} + .dark .uno-pe1esh:hover{--un-bg-opacity:1;background-color:rgba(34,197,94,var(--un-bg-opacity));} + .uno-cbgd7b{text-align:center;} + .uno-s9yxer{font-size:0.875rem;line-height:1.25rem;font-weight:700;} + .uno-s9yxer:hover{--un-text-opacity:1;color:rgba(248,113,113,var(--un-text-opacity));} @media (min-width: 640px){ - .sm\\\\:text-left{text-align:left;} + .uno-cbgd7b{text-align:left;} }" `) }) From cbec397d7236da5f73532466950b720dde39ecd3 Mon Sep 17 00:00:00 2001 From: Techassi Date: Mon, 22 May 2023 16:18:02 +0200 Subject: [PATCH 4/6] Use regex literal for trigger --- .../transformer-compile-class/src/index.ts | 43 +++++++++++++++---- playground/src/auto-imports.d.ts | 20 ++++++++- test/__snapshots__/cli.test.ts.snap | 5 +++ test/transformer-compile-class.test.ts | 14 ------ 4 files changed, 57 insertions(+), 25 deletions(-) diff --git a/packages/transformer-compile-class/src/index.ts b/packages/transformer-compile-class/src/index.ts index 5fda1d761e..e0de165aee 100644 --- a/packages/transformer-compile-class/src/index.ts +++ b/packages/transformer-compile-class/src/index.ts @@ -1,12 +1,33 @@ import type { SourceCodeTransformer } from '@unocss/core' -import { escapeRegExp, expandVariantGroup } from '@unocss/core' +import { expandVariantGroup } from '@unocss/core' export interface CompileClassOptions { /** - * Trigger string - * @default ':uno:' + * Trigger regex literal. The default trigger regex literal matches `:uno:`, + * for example: `
`. + * + * @example + * The trigger additionally allows defining a capture group named `name`, which + * allows custom class names. One possible regex would be: + * + * ``` + * export default defineConfig({ + * transformers: [ + * transformerCompileClass({ + * trigger: /(["'`]):uno(?:-)?(?[^\s\1]+)?:\s([^\1]*?)\1/g + * }), + * ], + * }) + * ``` + * + * This regular expression matches `:uno-MYNAME:` and uses `MYNAME` in + * combination with the class prefix as the final class name, for example: + * `.uno-MYNAME`. It should be noted that the regex literal needs to include + * the global flag `/g`. + * + * @default `/(["'`]):uno:\s([^\1]*?)\1/g` */ - trigger?: string + trigger?: RegExp /** * Prefix for compile class name @@ -34,23 +55,25 @@ export interface CompileClassOptions { export default function transformerCompileClass(options: CompileClassOptions = {}): SourceCodeTransformer { const { - trigger = 'uno', + trigger = /(["'`]):uno:\s([^\1]*?)\1/g, classPrefix = 'uno-', hashFn = hash, keepUnknown = true, } = options - const regex = new RegExp(`(["'\`]):${escapeRegExp(trigger)}(?:-)?([^\\s\\1]+)?:\\s([^\\1]*?)\\1`, 'g') return { name: '@unocss/transformer-compile-class', enforce: 'pre', async transform(s, _, { uno, tokens }) { - const matches = [...s.original.matchAll(regex)] + const matches = [...s.original.matchAll(trigger)] if (!matches.length) return for (const match of matches) { - let body = expandVariantGroup(match[3].trim()) + let body = (match.length === 4 && match.groups) + ? expandVariantGroup(match[3].trim()) + : expandVariantGroup(match[2].trim()) + const start = match.index! const replacements = [] if (keepUnknown) { @@ -62,7 +85,9 @@ export default function transformerCompileClass(options: CompileClassOptions = { } if (body) { body = body.split(/\s+/).sort().join(' ') - const className = match[2] ? `${classPrefix}${match[2]}` : `${classPrefix}${hashFn(body)}` + const className = (match.groups && match.groups.name) + ? `${classPrefix}${match.groups.name}` + : `${classPrefix}${hashFn(body)}` // FIXME: Ideally we should also check that the hash doesn't match. If the hash is the same, the same class // name is allowed, as the applied styles are the same. diff --git a/playground/src/auto-imports.d.ts b/playground/src/auto-imports.d.ts index f961d20ef7..d5b24ee1a0 100644 --- a/playground/src/auto-imports.d.ts +++ b/playground/src/auto-imports.d.ts @@ -22,7 +22,9 @@ declare global { const createInjectionState: typeof import('@vueuse/core')['createInjectionState'] const createProjection: typeof import('@vueuse/math')['createProjection'] const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn'] + const createReusableTemplate: typeof import('@vueuse/core')['createReusableTemplate'] const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable'] + const createTemplatePromise: typeof import('@vueuse/core')['createTemplatePromise'] const createUnrefFn: typeof import('@vueuse/core')['createUnrefFn'] const cssFormatted: typeof import('./composables/prettier')['cssFormatted'] const customCSS: typeof import('./composables/url')['customCSS'] @@ -139,11 +141,14 @@ declare global { const until: typeof import('@vueuse/core')['until'] const useAbs: typeof import('@vueuse/math')['useAbs'] const useActiveElement: typeof import('@vueuse/core')['useActiveElement'] + const useAnimate: typeof import('@vueuse/core')['useAnimate'] + const useArrayDifference: typeof import('@vueuse/core')['useArrayDifference'] const useArrayEvery: typeof import('@vueuse/core')['useArrayEvery'] const useArrayFilter: typeof import('@vueuse/core')['useArrayFilter'] const useArrayFind: typeof import('@vueuse/core')['useArrayFind'] const useArrayFindIndex: typeof import('@vueuse/core')['useArrayFindIndex'] const useArrayFindLast: typeof import('@vueuse/core')['useArrayFindLast'] + const useArrayIncludes: typeof import('@vueuse/core')['useArrayIncludes'] const useArrayJoin: typeof import('@vueuse/core')['useArrayJoin'] const useArrayMap: typeof import('@vueuse/core')['useArrayMap'] const useArrayReduce: typeof import('@vueuse/core')['useArrayReduce'] @@ -236,6 +241,8 @@ declare global { const useOnline: typeof import('@vueuse/core')['useOnline'] const usePageLeave: typeof import('@vueuse/core')['usePageLeave'] const useParallax: typeof import('@vueuse/core')['useParallax'] + const useParentElement: typeof import('@vueuse/core')['useParentElement'] + const usePerformanceObserver: typeof import('@vueuse/core')['usePerformanceObserver'] const usePermission: typeof import('@vueuse/core')['usePermission'] const usePointer: typeof import('@vueuse/core')['usePointer'] const usePointerLock: typeof import('@vueuse/core')['usePointerLock'] @@ -284,7 +291,6 @@ declare global { const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll'] const useTimestamp: typeof import('@vueuse/core')['useTimestamp'] const useTitle: typeof import('@vueuse/core')['useTitle'] - const useToFixed: typeof import('@vueuse/math')['useToFixed'] const useToNumber: typeof import('@vueuse/core')['useToNumber'] const useToString: typeof import('@vueuse/core')['useToString'] const useToggle: typeof import('@vueuse/core')['useToggle'] @@ -309,8 +315,10 @@ declare global { const watchArray: typeof import('@vueuse/core')['watchArray'] const watchAtMost: typeof import('@vueuse/core')['watchAtMost'] const watchDebounced: typeof import('@vueuse/core')['watchDebounced'] + const watchDeep: typeof import('@vueuse/core')['watchDeep'] const watchEffect: typeof import('vue')['watchEffect'] const watchIgnorable: typeof import('@vueuse/core')['watchIgnorable'] + const watchImmediate: typeof import('@vueuse/core')['watchImmediate'] const watchOnce: typeof import('@vueuse/core')['watchOnce'] const watchPausable: typeof import('@vueuse/core')['watchPausable'] const watchPostEffect: typeof import('vue')['watchPostEffect'] @@ -347,7 +355,9 @@ declare module 'vue' { readonly createInjectionState: UnwrapRef readonly createProjection: UnwrapRef readonly createReactiveFn: UnwrapRef + readonly createReusableTemplate: UnwrapRef readonly createSharedComposable: UnwrapRef + readonly createTemplatePromise: UnwrapRef readonly createUnrefFn: UnwrapRef readonly cssFormatted: UnwrapRef readonly customCSS: UnwrapRef @@ -464,11 +474,14 @@ declare module 'vue' { readonly until: UnwrapRef readonly useAbs: UnwrapRef readonly useActiveElement: UnwrapRef + readonly useAnimate: UnwrapRef + readonly useArrayDifference: UnwrapRef readonly useArrayEvery: UnwrapRef readonly useArrayFilter: UnwrapRef readonly useArrayFind: UnwrapRef readonly useArrayFindIndex: UnwrapRef readonly useArrayFindLast: UnwrapRef + readonly useArrayIncludes: UnwrapRef readonly useArrayJoin: UnwrapRef readonly useArrayMap: UnwrapRef readonly useArrayReduce: UnwrapRef @@ -561,6 +574,8 @@ declare module 'vue' { readonly useOnline: UnwrapRef readonly usePageLeave: UnwrapRef readonly useParallax: UnwrapRef + readonly useParentElement: UnwrapRef + readonly usePerformanceObserver: UnwrapRef readonly usePermission: UnwrapRef readonly usePointer: UnwrapRef readonly usePointerLock: UnwrapRef @@ -609,7 +624,6 @@ declare module 'vue' { readonly useTimeoutPoll: UnwrapRef readonly useTimestamp: UnwrapRef readonly useTitle: UnwrapRef - readonly useToFixed: UnwrapRef readonly useToNumber: UnwrapRef readonly useToString: UnwrapRef readonly useToggle: UnwrapRef @@ -634,8 +648,10 @@ declare module 'vue' { readonly watchArray: UnwrapRef readonly watchAtMost: UnwrapRef readonly watchDebounced: UnwrapRef + readonly watchDeep: UnwrapRef readonly watchEffect: UnwrapRef readonly watchIgnorable: UnwrapRef + readonly watchImmediate: UnwrapRef readonly watchOnce: UnwrapRef readonly watchPausable: UnwrapRef readonly watchPostEffect: UnwrapRef diff --git a/test/__snapshots__/cli.test.ts.snap b/test/__snapshots__/cli.test.ts.snap index d7dd937503..6d1cd6bc50 100644 --- a/test/__snapshots__/cli.test.ts.snap +++ b/test/__snapshots__/cli.test.ts.snap @@ -10,6 +10,11 @@ exports[`cli > supports directives transformer 1`] = `""`; exports[`cli > supports directives transformer 2`] = `".btn-center{margin-top:0;margin-bottom:0;text-align:center;font-weight:500;}"`; +exports[`cli > supports uno.config.ts changed rebuild 1`] = ` +"/* layer: shortcuts */ +.box{margin-left:auto;margin-right:auto;max-width:80rem;border-radius:0.375rem;--un-bg-opacity:1;background-color:rgba(243,244,246,var(--un-bg-opacity));padding:1rem;--un-shadow:var(--un-shadow-inset) 0 1px 2px 0 var(--un-shadow-color, rgba(0,0,0,0.05));box-shadow:var(--un-ring-offset-shadow), var(--un-ring-shadow), var(--un-shadow);}" +`; + exports[`cli > supports unocss.config.js 1`] = ` "/* layer: shortcuts */ .box{margin-left:auto;margin-right:auto;max-width:80rem;border-radius:0.375rem;--un-bg-opacity:1;background-color:rgba(243,244,246,var(--un-bg-opacity));padding:1rem;--un-shadow:var(--un-shadow-inset) 0 1px 2px 0 var(--un-shadow-color, rgba(0,0,0,0.05));box-shadow:var(--un-ring-offset-shadow), var(--un-ring-shadow), var(--un-shadow);}" diff --git a/test/transformer-compile-class.test.ts b/test/transformer-compile-class.test.ts index fee46c9204..6e7fe2184a 100644 --- a/test/transformer-compile-class.test.ts +++ b/test/transformer-compile-class.test.ts @@ -61,18 +61,4 @@ describe('transformer-compile-class', () => { expect(order1.css).toBe(order2.css) expect(order1.code).toBe(order2.code) }) - - test('custom compile class name', async () => { - const result = await transform(` -
`.trim()) - - expect(result.code.trim()).toMatchInlineSnapshot(` - "
" - `) - - expect(result.css).toMatchInlineSnapshot(` - "/* layer: shortcuts */ - .uno-foo{--un-bg-opacity:1;background-color:rgba(239,68,68,var(--un-bg-opacity));font-size:1.25rem;line-height:1.75rem;}" - `) - }) }) From c00c5e5ee9c6972ce217e500f38955941c9dab02 Mon Sep 17 00:00:00 2001 From: Techassi Date: Tue, 23 May 2023 10:03:23 +0200 Subject: [PATCH 5/6] Add backwards compatibility --- .../transformer-compile-class/src/index.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/transformer-compile-class/src/index.ts b/packages/transformer-compile-class/src/index.ts index e0de165aee..9de6cebf2f 100644 --- a/packages/transformer-compile-class/src/index.ts +++ b/packages/transformer-compile-class/src/index.ts @@ -1,5 +1,5 @@ import type { SourceCodeTransformer } from '@unocss/core' -import { expandVariantGroup } from '@unocss/core' +import { escapeRegExp, expandVariantGroup } from '@unocss/core' export interface CompileClassOptions { /** @@ -25,9 +25,13 @@ export interface CompileClassOptions { * `.uno-MYNAME`. It should be noted that the regex literal needs to include * the global flag `/g`. * + * @note + * This parameter is backwards compatible. It accepts string only trigger + * words, like `:uno:` or a regex literal. + * * @default `/(["'`]):uno:\s([^\1]*?)\1/g` */ - trigger?: RegExp + trigger?: string | RegExp /** * Prefix for compile class name @@ -61,11 +65,17 @@ export default function transformerCompileClass(options: CompileClassOptions = { keepUnknown = true, } = options + // Provides backwards compatibility. We either accept a trigger string which + // gets turned into a regexp (like previously) or a regex literal directly. + const regexp = typeof trigger === 'string' + ? RegExp(`(["'\`])${escapeRegExp(trigger)}\\s([^\\1]*?)\\1`, 'g') + : trigger + return { name: '@unocss/transformer-compile-class', enforce: 'pre', async transform(s, _, { uno, tokens }) { - const matches = [...s.original.matchAll(trigger)] + const matches = [...s.original.matchAll(regexp)] if (!matches.length) return @@ -76,6 +86,7 @@ export default function transformerCompileClass(options: CompileClassOptions = { const start = match.index! const replacements = [] + if (keepUnknown) { const result = await Promise.all(body.split(/\s+/).filter(Boolean).map(async i => [i, !!await uno.parseToken(i)] as const)) const known = result.filter(([, matched]) => matched).map(([i]) => i) @@ -83,6 +94,7 @@ export default function transformerCompileClass(options: CompileClassOptions = { replacements.push(...unknown) body = known.join(' ') } + if (body) { body = body.split(/\s+/).sort().join(' ') const className = (match.groups && match.groups.name) @@ -103,6 +115,7 @@ export default function transformerCompileClass(options: CompileClassOptions = { if (tokens) tokens.add(className) } + s.overwrite(start + 1, start + match[0].length - 1, replacements.join(' ')) } }, From abc2bacab3d806dfed256e3b8ca3ac973baddb4c Mon Sep 17 00:00:00 2001 From: Techassi Date: Tue, 23 May 2023 10:03:32 +0200 Subject: [PATCH 6/6] Add tests --- test/transformer-compile-class.test.ts | 54 ++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/test/transformer-compile-class.test.ts b/test/transformer-compile-class.test.ts index 6e7fe2184a..974912fe56 100644 --- a/test/transformer-compile-class.test.ts +++ b/test/transformer-compile-class.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from 'vitest' -import type { UnoGenerator } from '@unocss/core' +import type { SourceCodeTransformer, UnoGenerator } from '@unocss/core' import { createGenerator } from '@unocss/core' import MagicString from 'magic-string' import transformerCompileClass from '@unocss/transformer-compile-class' @@ -11,11 +11,15 @@ describe('transformer-compile-class', () => { presetUno(), ], }) - const transformer = transformerCompileClass() - async function transform(code: string, _uno: UnoGenerator = uno) { + const defaultTransformer = transformerCompileClass() + const customClassNameTransformer = transformerCompileClass({ + trigger: /(["'`]):uno(?:-)?(?[^\s\1]+)?:\s([^\1]*?)\1/g, + }) + + async function transform(code: string, _uno: UnoGenerator = uno, _tranformer: SourceCodeTransformer = defaultTransformer) { const s = new MagicString(code) - await transformer.transform(s, 'foo.js', { uno: _uno, tokens: new Set() } as any) + await _tranformer.transform(s, 'foo.js', { uno: _uno, tokens: new Set() } as any) const result = s.toString() const { css } = await uno.generate(result, { preflights: false }) return { @@ -61,4 +65,46 @@ describe('transformer-compile-class', () => { expect(order1.css).toBe(order2.css) expect(order1.code).toBe(order2.code) }) + + test('custom class name trigger (without class name)', async () => { + const result = await transform(` +
`.trim(), uno, customClassNameTransformer) + + expect(result.code.trim()).toMatchInlineSnapshot(` + "
" + `) + + expect(result.css).toMatchInlineSnapshot(` + "/* layer: shortcuts */ + .uno-trmz0g{--un-bg-opacity:1;background-color:rgba(239,68,68,var(--un-bg-opacity));font-size:1.25rem;line-height:1.75rem;}" + `) + }) + + test('custom class name trigger (with basic class name)', async () => { + const result = await transform(` +
`.trim(), uno, customClassNameTransformer) + + expect(result.code.trim()).toMatchInlineSnapshot(` + "
" + `) + + expect(result.css).toMatchInlineSnapshot(` + "/* layer: shortcuts */ + .uno-foo{--un-bg-opacity:1;background-color:rgba(239,68,68,var(--un-bg-opacity));font-size:1.25rem;line-height:1.75rem;}" + `) + }) + + test('custom class name trigger (with complex class name)', async () => { + const result = await transform(` +
`.trim(), uno, customClassNameTransformer) + + expect(result.code.trim()).toMatchInlineSnapshot(` + "
" + `) + + expect(result.css).toMatchInlineSnapshot(` + "/* layer: shortcuts */ + .uno-foo_bar-baz{--un-bg-opacity:1;background-color:rgba(239,68,68,var(--un-bg-opacity));font-size:1.25rem;line-height:1.75rem;}" + `) + }) })