Skip to content

Commit 3a02441

Browse files
committed
chore(demo): twMerge
1 parent ee92caf commit 3a02441

File tree

9 files changed

+575
-15
lines changed

9 files changed

+575
-15
lines changed

apps/vite-vue/.tw-patch/tw-class-list.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"bg-[#B91C1C]",
2828
"bg-gradient-to-b",
2929
"bg-gradient-to-t",
30+
"bg-red-100",
3031
"bg-red-200",
3132
"bg-red-200/40",
3233
"bg-red-200/70",
@@ -59,6 +60,7 @@
5960
"group-hover:translate-x-1",
6061
"h-48",
6162
"hover:bg-gray-100",
63+
"hover:bg-red-800",
6264
"hover:border-gray-300",
6365
"hover:dark:bg-neutral-800/30",
6466
"hover:dark:border-neutral-700",

apps/vite-vue/src/App.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
<script setup lang="ts">
22
import { twMerge } from 'tailwind-merge'
3-
4-
const aaa = twMerge('px-2 py-1 bg-red hover:bg-dark-red', 'p-3 bg-[#B91C1C]')
3+
// 'hover:bg-dark-red p-3 bg-[#B91C1C]'
4+
const aaa = twMerge('px-2 py-1 bg-red-100 hover:bg-red-800', 'p-3 bg-[#B91C1C]')
55
66
</script>
77

88
<template>
99
<main class="flex min-h-screen flex-col items-center justify-between p-24 ">
10-
<nav :class="aaa">aaa</nav>
10+
<nav :class="aaa">{{ aaa }}</nav>
1111
<div class="z-10 w-full max-w-5xl items-center justify-between font-mono text-sm lg:flex">
1212
<p
1313
class="fixed left-0 top-0 flex w-full justify-center border-b border-gray-300 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl dark:border-neutral-800 dark:bg-zinc-800/30 dark:from-inherit lg:static lg:w-auto lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30">

packages/core/src/ctx/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export class Context {
7272

7373
getReplaceMap() {
7474
const map = new Map<string, string>()
75-
for (const [key, value] of this.replaceMap) {
75+
for (const [key, value] of sort([...this.replaceMap.entries()]).desc((x) => x[0].length)) {
7676
if (!this.isPreserveClass(key)) {
7777
map.set(key, value)
7878
}

packages/core/src/js/pre.ts

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { jsStringEscape } from '@ast-core/escape'
77
import { parse, ParseResult } from '@babel/parser'
88
import traverse from '@babel/traverse'
99
import { getStringLiteralCalleeName, getTemplateElementCalleeName } from './utils'
10+
import { escapeStringRegexp } from '@/utils'
1011
import type { Context } from '@/ctx'
1112

1213
interface Options {
@@ -160,8 +161,16 @@ interface IPreProcessRawCodeOptions {
160161
export function preProcessRawCode(options: IPreProcessRawCodeOptions) {
161162
const { code, replaceMap, ctx } = options
162163
const magicString = typeof code === 'string' ? new MagicString(code) : code
164+
const markArr: [number, number][] = []
163165
for (const regex of ctx.preserveFunctionRegexs) {
164-
for (const regExpMatch of magicString.original.matchAll(regex)) {
166+
const allArr: RegExpExecArray[] = []
167+
let arr: RegExpExecArray | null = null
168+
while ((arr = regex.exec(magicString.original)) !== null) {
169+
allArr.push(arr)
170+
markArr.push([arr.index, arr.index + arr[0].length])
171+
}
172+
// magicString.original.matchAll(regex)
173+
for (const regExpMatch of allArr) {
165174
let ast: ParseResult<babel.types.File>
166175
try {
167176
ast = parse(regExpMatch[0], {
@@ -170,8 +179,9 @@ export function preProcessRawCode(options: IPreProcessRawCodeOptions) {
170179
traverse(ast, {
171180
StringLiteral: {
172181
enter(p) {
173-
const array = splitCode(p.node.value)
174-
for (const v of array) {
182+
const arr = sort(splitCode(p.node.value)).desc((x) => x.length)
183+
184+
for (const v of arr) {
175185
if (replaceMap.has(v)) {
176186
ctx.addPreserveClass(v)
177187
}
@@ -180,8 +190,8 @@ export function preProcessRawCode(options: IPreProcessRawCodeOptions) {
180190
},
181191
TemplateElement: {
182192
enter(p) {
183-
const array = splitCode(p.node.value.raw)
184-
for (const v of array) {
193+
const arr = sort(splitCode(p.node.value.raw)).desc((x) => x.length)
194+
for (const v of arr) {
185195
if (replaceMap.has(v)) {
186196
ctx.addPreserveClass(v)
187197
}
@@ -196,10 +206,27 @@ export function preProcessRawCode(options: IPreProcessRawCodeOptions) {
196206
// console.log(arr, regex.lastIndex)
197207
}
198208
for (const [key, value] of replaceMap) {
199-
if (!ctx.isPreserveClass(key)) {
200-
magicString.replaceAll(key, value)
209+
// if (!ctx.isPreserveClass(key)) {
210+
// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec
211+
const regex = new RegExp(escapeStringRegexp(key), 'g')
212+
let arr: RegExpExecArray | null = null
213+
while ((arr = regex.exec(magicString.original)) !== null) {
214+
const start = arr.index
215+
const end = arr.index + arr[0].length
216+
let shouldUpdate = true
217+
for (const [ps, pe] of markArr) {
218+
if ((start > ps && start < pe) || (end < pe && end > ps)) {
219+
shouldUpdate = false
220+
break
221+
}
222+
}
223+
if (shouldUpdate) {
224+
magicString.update(start, end, value)
225+
markArr.push([start, end])
226+
}
201227
}
202228
}
229+
203230
return magicString.toString()
204231
// for (const [key, value] of replaceMap) {
205232
// code = code.replaceAll(key, value)

packages/core/test/__snapshots__/js.test.ts.snap

Lines changed: 254 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -320,15 +320,266 @@ export default defineComponent({
320320
321321
exports[`js handler > preProcessRawCode case 0 2`] = `
322322
Map {
323-
"flex" => "u",
323+
"justify-between" => "a",
324324
"min-h-screen" => "i",
325-
"flex-col" => "o",
326325
"items-center" => "p",
327-
"justify-between" => "a",
326+
"flex-col" => "o",
327+
"flex" => "u",
328328
"p-24" => "s",
329329
}
330330
`;
331331
332+
exports[`js handler > preProcessRawCode case 1 1`] = `
333+
"<script setup lang=\\"ts\\">
334+
import { twMerge } from 'tailwind-merge'
335+
// 'hover:bg-dark-red i i'
336+
const aaa = twMerge('px-2 py-1 bg-red-100 hover:bg-red-800', 'p-3 bg-[#B91C1C]')
337+
338+
</script>
339+
340+
<template>
341+
<main class=\\"i i i i i i \\">
342+
<nav :class=\\"aaa\\">{{ aaa }}</nav>
343+
<div class=\\"i i i i i i i i\\">
344+
<p
345+
class=\\"i i i i i i i i i i i i i i i i i i i i i i i\\">
346+
Get started by editing&nbsp;
347+
<code class=\\"i i\\">pages/index.tsx</code>
348+
</p>
349+
<div
350+
class=\\"i i i i i i i i i i i i i i i i i\\">
351+
<a class=\\"i i i i i i i\\"
352+
href=\\"https://vercel.com?utm_source=create-next-app&utm_medium=default-template-tw&utm_campaign=create-next-app\\"
353+
target=\\"_blank\\" rel=\\"noopener noreferrer\\">
354+
By
355+
<img src=\\"/vite.svg\\" alt=\\"Vercel Logo\\" class=\\"i\\" priority />
356+
</a>
357+
</div>
358+
</div>
359+
360+
<div
361+
class=\\"i i i i i i i i before:bg-gradient-radial i i i i i i i i i after:bg-gradient-conic i i i i i i i i i i\\">
362+
<img class=\\"i i i\\" src=\\"./assets/vue.svg\\" alt=\\"Next.js Logo\\"
363+
priority />
364+
</div>
365+
366+
<div class=\\"i i i i i i \\">
367+
<a href=\\"https://nextjs.org/docs?utm_source=create-next-app&utm_medium=default-template-tw&utm_campaign=create-next-app\\"
368+
class=\\"group i i i i i i i i i i\\"
369+
target=\\"_blank\\" rel=\\"noopener noreferrer\\">
370+
<h2 class=\\"i i i i\\">
371+
Docs <span
372+
class=\\"i i i i\\">-&gt;</span>
373+
</h2>
374+
<p class=\\"i i i i\\">Find in-depth information about Next.js
375+
features and API.</p>
376+
</a>
377+
378+
<a href=\\"https://nextjs.org/learn?utm_source=create-next-app&utm_medium=default-template-tw&utm_campaign=create-next-app\\"
379+
class=\\"group i i i i i i i i i i\\"
380+
target=\\"_blank\\" rel=\\"noopener noreferrer\\">
381+
<h2 class=\\"i i i i\\">
382+
Learn <span
383+
class=\\"i i i i\\">-&gt;</span>
384+
</h2>
385+
<p class=\\"i i i i\\">Learn about Next.js in an interactive
386+
course with&nbsp;quizzes!</p>
387+
</a>
388+
389+
<a href=\\"https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=default-template-tw&utm_campaign=create-next-app\\"
390+
class=\\"group i i i i i i i i i i\\"
391+
target=\\"_blank\\" rel=\\"noopener noreferrer\\">
392+
<h2 class=\\"i i i i\\">
393+
Templates <span
394+
class=\\"i i i i\\">-&gt;</span>
395+
</h2>
396+
<p class=\\"i i i i\\">Discover and deploy boilerplate example
397+
Next.js&nbsp;projects.</p>
398+
</a>
399+
400+
<a href=\\"https://vercel.com/new?utm_source=create-next-app&utm_medium=default-template-tw&utm_campaign=create-next-app\\"
401+
class=\\"group i i i i i i i i i i\\"
402+
target=\\"_blank\\" rel=\\"noopener noreferrer\\">
403+
<h2 class=\\"i i i i\\">
404+
Deploy <span
405+
class=\\"i i i i\\">-&gt;</span>
406+
</h2>
407+
<p class=\\"i i i i\\">Instantly deploy your Next.js site to a
408+
shareable URL with Vercel.</p>
409+
</a>
410+
</div>
411+
</main>
412+
</template>
413+
414+
<script lang=\\"ts\\">
415+
import { onMounted, defineComponent } from 'vue'
416+
417+
export default defineComponent({
418+
setup() {
419+
onMounted(() => {
420+
const clipPath = '';
421+
document.documentElement.animate(
422+
{
423+
clipPath
424+
},
425+
{
426+
duration: 500,
427+
easing: /* tw-mangle ignore */ 'i',
428+
pseudoElement: '::view-i-new(root)'
429+
})
430+
document.documentElement.animate(
431+
{
432+
clipPath
433+
},
434+
{
435+
duration: 500,
436+
easing: 'i',
437+
pseudoElement: '::view-i-new(root)'
438+
}
439+
)
440+
})
441+
return {}
442+
}
443+
})
444+
445+
</script>
446+
447+
<style scoped>
448+
.logo {
449+
height: 6em;
450+
padding: 1.5em;
451+
will-change: i;
452+
i: i 300ms;
453+
}
454+
455+
.logo:hover {
456+
i: i(0 0 2em #646cffaa);
457+
}
458+
459+
.logo.vue:hover {
460+
i: i(0 0 2em #42b883aa);
461+
}
462+
</style>
463+
"
464+
`;
465+
466+
exports[`js handler > preProcessRawCode case 1 2`] = `
467+
Map {
468+
"dark:drop-shadow-[0_0_0.3rem_#ffffff70]" => "i",
469+
"before:dark:bg-gradient-to-br" => "i",
470+
"hover:dark:border-neutral-700" => "i",
471+
"before:dark:from-transparent" => "i",
472+
"hover:dark:bg-neutral-800/30" => "i",
473+
"motion-reduce:transform-none" => "i",
474+
"after:dark:via-[#0141ff]/40" => "i",
475+
"before:dark:to-blue-700/10" => "i",
476+
"group-hover:translate-x-1" => "i",
477+
"after:dark:from-sky-900" => "i",
478+
"before:-translate-x-1/2" => "i",
479+
"dark:border-neutral-800" => "i",
480+
"lg:dark:bg-zinc-800/30" => "i",
481+
"lg:pointer-events-auto" => "i",
482+
"after:translate-x-1/3" => "i",
483+
"before:to-transparent" => "i",
484+
"hover:border-gray-300" => "i",
485+
"transition-transform" => "i",
486+
"before:content-['']" => "i",
487+
"before:lg:h-[360px]" => "i",
488+
"before:rounded-full" => "i",
489+
"dark:bg-zinc-800/30" => "i",
490+
"pointer-events-none" => "i",
491+
"after:content-['']" => "i",
492+
"after:from-sky-200" => "i",
493+
"after:via-blue-200" => "i",
494+
"border-transparent" => "i",
495+
"place-items-center" => "i",
496+
"backdrop-blur-2xl" => "i",
497+
"before:from-white" => "i",
498+
"dark:from-inherit" => "i",
499+
"hover:bg-gray-100" => "i",
500+
"transition-colors" => "i",
501+
"before:h-[300px]" => "i",
502+
"before:w-[480px]" => "i",
503+
"bg-gradient-to-b" => "i",
504+
"bg-gradient-to-t" => "i",
505+
"after:h-[180px]" => "i",
506+
"after:w-[240px]" => "i",
507+
"before:absolute" => "i",
508+
"before:blur-2xl" => "i",
509+
"border-gray-300" => "i",
510+
"dark:from-black" => "i",
511+
"justify-between" => "i",
512+
"after:absolute" => "i",
513+
"after:blur-2xl" => "i",
514+
"dark:via-black" => "i",
515+
"justify-center" => "i",
516+
"lg:bg-gray-200" => "i",
517+
"lg:grid-cols-4" => "i",
518+
"bg-red-200/40" => "i",
519+
"bg-red-200/70" => "i",
520+
"bg-red-200/90" => "i",
521+
"font-semibold" => "i",
522+
"from-zinc-200" => "i",
523+
"lg:rounded-xl" => "i",
524+
"inline-block" => "i",
525+
"items-center" => "i",
526+
"lg:text-left" => "i",
527+
"max-w-[30ch]" => "i",
528+
"min-h-screen" => "i",
529+
"after:-z-20" => "i",
530+
"dark:invert" => "i",
531+
"drop-shadow" => "i",
532+
"text-[16px]" => "i",
533+
"text-center" => "i",
534+
"bg-red-200" => "i",
535+
"from-white" => "i",
536+
"lg:bg-none" => "i",
537+
"opacity-50" => "i",
538+
"rounded-lg" => "i",
539+
"transition" => "i",
540+
"font-bold" => "i",
541+
"font-mono" => "i",
542+
"items-end" => "i",
543+
"lg:border" => "i",
544+
"lg:h-auto" => "i",
545+
"lg:static" => "i",
546+
"lg:w-auto" => "i",
547+
"max-w-5xl" => "i",
548+
"via-white" => "i",
549+
"border-b" => "i",
550+
"bottom-0" => "i",
551+
"ease-out" => "i",
552+
"flex-col" => "i",
553+
"relative" => "i",
554+
"text-2xl" => "i",
555+
"lg:flex" => "i",
556+
"lg:mb-0" => "i",
557+
"text-sm" => "i",
558+
"border" => "i",
559+
"filter" => "i",
560+
"left-0" => "i",
561+
"lg:p-0" => "i",
562+
"lg:p-4" => "i",
563+
"w-full" => "i",
564+
"fixed" => "i",
565+
"gap-2" => "i",
566+
"mb-32" => "i",
567+
"top-0" => "i",
568+
"flex" => "i",
569+
"grid" => "i",
570+
"h-48" => "i",
571+
"mb-3" => "i",
572+
"p-24" => "i",
573+
"pb-6" => "i",
574+
"pt-8" => "i",
575+
"px-5" => "i",
576+
"py-4" => "i",
577+
"z-10" => "i",
578+
"m-0" => "i",
579+
"p-8" => "i",
580+
}
581+
`;
582+
332583
exports[`js handler > preserve-fn-case0.js case 0 1`] = `
333584
"import { clsx } from \\"clsx\\";
334585
import { twMerge } from \\"tailwind-merge\\";

0 commit comments

Comments
 (0)