Skip to content

Commit

Permalink
fix(svelte-scoped): handle expressions in class attribute correctly (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
devunt committed Apr 15, 2023
1 parent f3d5afc commit 52327b8
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 15 deletions.
42 changes: 27 additions & 15 deletions packages/vite/src/modes/svelte-scoped/transform.ts
Expand Up @@ -3,10 +3,11 @@ import { type UnoGenerator, attributifyRE, escapeRegExp, expandVariantGroup } fr
import { wrapSelectorsWithGlobal } from './wrap-global'
import { hash } from './hash'

const classesRE = /class=(["'\`])([\S\s]+?)\1/g // class="mb-1"
const classesRE = /class=(["'\`])([^\{][\S\s]*?)\1/g // class="mb-1"
const classesExpressionsRE = /class=(["'\`])?(\{[\S\s]+?\})\1/g // class={clsx('mb-1')} or class="{clsx('mb-1')}"
const classesDirectivesRE = /class:([\S]+?)={/g // class:mb-1={foo}
const classDirectivesShorthandRE = /class:([^=>\s/]+)[{>\s/]/g // class:mb-1 (compiled to class:uno-1hashz={mb-1})
const classesFromInlineConditionalsRE = /'([\S\s]+?)'/g // { foo ? 'mt-1' : 'mt-2'}
const classesDirectivesShorthandRE = /class:([^=>\s/]+)[{>\s/]/g // class:mb-1 (compiled to class:uno-1hashz={mb-1})
const classesInsideExpressionsRE = /(["'\`])([\S\s]+?)\1/g // { foo ? 'mt-1' : "mt-2"}

export interface TransformSFCOptions {
/**
Expand Down Expand Up @@ -44,9 +45,9 @@ export async function transformSvelteSFC(code: string, id: string, uno: UnoGener
styles = css
}

const classes = [...code.matchAll(classesRE)]
const classes = [...code.matchAll(classesRE), ...code.matchAll(classesExpressionsRE)]
const classDirectives = [...code.matchAll(classesDirectivesRE)]
const classDirectivesShorthand = [...code.matchAll(classDirectivesShorthandRE)]
const classDirectivesShorthand = [...code.matchAll(classesDirectivesShorthandRE)]

const originalShortcuts = uno.config.shortcuts
const shortcuts: Record<string, string[]> = {}
Expand Down Expand Up @@ -103,24 +104,35 @@ export async function transformSvelteSFC(code: string, id: string, uno: UnoGener
const className = queueCompiledClass(known)
return [className, ...replacements].join(' ')
}

const processedMap = new Set()

for (const match of classes) {
let body = expandVariantGroup(match[2].trim())

const inlineConditionals = [...body.matchAll(classesFromInlineConditionalsRE)]
for (const conditional of inlineConditionals) {
const replacement = await sortKnownAndUnknownClasses(conditional[1].trim())
if (replacement)
body = body.replace(conditional[0], `'${replacement}'`)
let replaced = false

const expressions = [...body.matchAll(classesInsideExpressionsRE)]
for (const expression of expressions) {
const replacement = await sortKnownAndUnknownClasses(expression[2].trim())
if (replacement) {
body = body.replace(expression[2], replacement)
replaced = true
}
}

const replacement = await sortKnownAndUnknownClasses(body)
if (replacement) {
const start = match.index! + 7
const end = match.index! + match[0].length - 1
processedMap.add(start)
s.overwrite(start, end, replacement)
body = body.replace(body, replacement)
replaced = true
}

if (!replaced)
continue

const start = match.index! + (match[1] ? 7 : 6)
const end = match.index! + match[0].length - (match[1] ? 1 : 0)
processedMap.add(start)
s.overwrite(start, end, body)
}

for (const match of classDirectives) {
Expand Down
70 changes: 70 additions & 0 deletions test/svelte-scoped.test.ts
Expand Up @@ -281,6 +281,76 @@ describe('svelte-scoped', () => {
`)
})

test('handles classes in inline expressions', async () => {
const result = await transform(`
<span class={classnames('text-red-500', foo ? "font-bold" : 'font-medium', 'foo', bar && 'hover:(bg-blue-500 text-white) baz')}>Hello</span>`.trim())
expect(result).toMatchInlineSnapshot(`
"<span
class={classnames(
\\"uno-ik91av\\",
foo ? \\"uno-k2ufqh\\" : \\"uno-wgrcwx\\",
\\"foo\\",
bar && \\"uno-484hzz baz\\"
)}>Hello</span
>
<style>
:global(.uno-484hzz:hover) {
--un-bg-opacity: 1;
background-color: rgba(59, 130, 246, var(--un-bg-opacity));
--un-text-opacity: 1;
color: rgba(255, 255, 255, var(--un-text-opacity));
}
:global(.uno-k2ufqh) {
font-weight: 700;
}
:global(.uno-wgrcwx) {
font-weight: 500;
}
:global(.uno-ik91av) {
--un-text-opacity: 1;
color: rgba(239, 68, 68, var(--un-text-opacity));
}
</style>
"
`)
})

test('handles classes in quoted inline expressions', async () => {
const result = await transform(`
<span class="{classnames('text-red-500', foo ? 'font-bold' : 'font-medium', 'foo', bar && 'hover:(bg-blue-500 text-white) baz')}">Hello</span>`.trim())
expect(result).toMatchInlineSnapshot(`
"<span
class={classnames(
\\"uno-ik91av\\",
foo ? \\"uno-k2ufqh\\" : \\"uno-wgrcwx\\",
\\"foo\\",
bar && \\"uno-484hzz baz\\"
)}>Hello</span
>
<style>
:global(.uno-484hzz:hover) {
--un-bg-opacity: 1;
background-color: rgba(59, 130, 246, var(--un-bg-opacity));
--un-text-opacity: 1;
color: rgba(255, 255, 255, var(--un-text-opacity));
}
:global(.uno-k2ufqh) {
font-weight: 700;
}
:global(.uno-wgrcwx) {
font-weight: 500;
}
:global(.uno-ik91av) {
--un-text-opacity: 1;
color: rgba(239, 68, 68, var(--un-text-opacity));
}
</style>
"
`)
})

test('no tokens found returns undefined', async () => {
const result = await transform(`
<div class="foo" />
Expand Down

0 comments on commit 52327b8

Please sign in to comment.