Skip to content

Commit e5f8946

Browse files
feat(svelte-scoped): add hashSafelistClasses boolean, by default do not hash safelisted classes (#5157)
1 parent 17513da commit e5f8946

File tree

3 files changed

+68
-3
lines changed

3 files changed

+68
-3
lines changed

packages-integrations/svelte-scoped/src/_preprocess/transformClasses/index.test.ts

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ describe('transform', async () => {
2828
safelist: [safelistClassToSkip],
2929
})
3030

31-
async function transform(content: string, { combine = true, format = true } = {}) {
32-
const transformed = (await transformClasses({ content, filename: 'Foo.svelte', uno, options: { combine } }))?.code
31+
async function transform(content: string, { combine = true, format = true, hashSafelistClasses }: { combine?: boolean, format?: boolean, hashSafelistClasses?: boolean } = {}) {
32+
const transformed = (await transformClasses({ content, filename: 'Foo.svelte', uno, options: { combine, hashSafelistClasses } }))?.code
3333
if (transformed && format) {
3434
return prettier(transformed, {
3535
parser: 'svelte',
@@ -403,3 +403,56 @@ describe('transform', async () => {
403403
`)
404404
})
405405
})
406+
407+
describe('safelist shortcut handling', async () => {
408+
const uno = await createGenerator({
409+
presets: [presetWind3()],
410+
shortcuts: [
411+
{ btn: 'px-4 py-2 font-bold' },
412+
],
413+
safelist: ['btn', 'mr-7'],
414+
})
415+
416+
async function transform(content: string, { combine = true, hashSafelistClasses }: { combine?: boolean, hashSafelistClasses?: boolean } = {}) {
417+
const result = await transformClasses({ content, filename: 'Foo.svelte', uno, options: { combine, hashSafelistClasses } })
418+
return result?.code
419+
}
420+
421+
it('does not hash shortcut classes in safelist by default', async () => {
422+
const code = '<div class="btn mb-1" />'
423+
const output = await transform(code)
424+
// btn is passed through as-is, only mb-1 gets hashed
425+
expect(output).toContain('btn')
426+
expect(output).toMatch(/class="[^ ]+ btn"/)
427+
})
428+
429+
it('does not hash utility classes in safelist by default', async () => {
430+
const code = '<div class="bg-red-500 mr-7" />'
431+
const output = await transform(code)
432+
expect(output).toContain('mr-7')
433+
})
434+
435+
it('does not hash shortcut classes in safelist in dev mode (combine: false)', async () => {
436+
const code = '<div class="btn mb-1" />'
437+
const output = await transform(code, { combine: false })
438+
// btn is passed through as-is, mb-1 gets its own hashed class
439+
expect(output).toContain('btn')
440+
expect(output).toMatch(/_mb-1_\w+/)
441+
expect(output).not.toMatch(/_btn_\w+/)
442+
})
443+
444+
it('hashes shortcut classes in safelist when hashSafelistClasses is true', async () => {
445+
const code = '<div class="btn mb-1" />'
446+
const output = await transform(code, { hashSafelistClasses: true })
447+
// btn should be hashed along with mb-1
448+
expect(output).not.toMatch(/\bbtn\b/)
449+
})
450+
451+
it('hashes shortcut classes in safelist in dev mode when hashSafelistClasses is true', async () => {
452+
const code = '<div class="btn mb-1" />'
453+
const output = await transform(code, { combine: false, hashSafelistClasses: true })
454+
// btn should get its own hashed class
455+
expect(output).toMatch(/_btn_\w+/)
456+
expect(output).not.toMatch(/\bbtn\b/)
457+
})
458+
})

packages-integrations/svelte-scoped/src/_preprocess/transformClasses/sortClassesIntoCategories.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export async function sortClassesIntoCategories(
1111
uno: UnoGenerator<object>,
1212
filename: string,
1313
) {
14-
const { combine = true } = options
14+
const { combine = true, hashSafelistClasses = false } = options
1515

1616
const rulesToGenerate: ProcessResult['rulesToGenerate'] = {}
1717
const ignore: string[] = []
@@ -20,6 +20,11 @@ export async function sortClassesIntoCategories(
2020
const knownClassesToCombine: string[] = []
2121

2222
for (const token of classes) {
23+
if (!hashSafelistClasses && uno.config.safelist.includes(token)) {
24+
ignore.push(token)
25+
continue
26+
}
27+
2328
const isShortcutOrUtility = isShortcut(token, uno.config.shortcuts) || await needsGenerated(token, uno)
2429

2530
if (!isShortcutOrUtility) {

packages-integrations/svelte-scoped/src/_preprocess/types.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ export interface TransformClassesOptions {
2424
* Used to generate hash for compiled class names
2525
*/
2626
hashFn?: (str: string) => string
27+
/**
28+
* Hash safelist classes (including shortcuts in the safelist) instead of passing them through as-is.
29+
* When false (default), safelist classes are left unhashed so they match the globally generated safelist CSS.
30+
* Set to true to restore the legacy behavior where shortcut classes in the safelist were still hashed.
31+
* @default false
32+
*/
33+
hashSafelistClasses?: boolean
2734
}
2835

2936
export interface TransformApplyOptions {

0 commit comments

Comments
 (0)