Skip to content

Commit 9cf471d

Browse files
committed
fix: preProcessJs clsx issue
1 parent 8f55522 commit 9cf471d

File tree

7 files changed

+153
-45
lines changed

7 files changed

+153
-45
lines changed

apps/vite-react/src/App.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,24 @@ import { useState } from 'react'
22
import { twMerge } from 'tailwind-merge'
33
import reactLogo from './assets/react.svg'
44
import './App.css'
5+
import { cn } from './utils'
56
// 'hover:bg-dark-red p-3 bg-[#B91C1C]'
67
const aaa = twMerge('px-2 py-1 bg-red-100 hover:bg-red-800', 'p-3 bg-[#B91C1C]')
7-
8+
const bbb = cn(
9+
{
10+
'p-3': true
11+
},
12+
'p-1',
13+
['p-2', true && 'p-4']
14+
)
815
function App() {
916
const [count, setCount] = useState(0)
1017

1118
return (
1219
<main className="px-2 py-1 bg-red-100 hover:bg-red-800 flex min-h-screen flex-col items-center justify-between p-24">
1320
<nav className={aaa}>{aaa}</nav>
21+
<nav className={bbb}>{bbb}</nav>
22+
<nav className="p-3">ccc</nav>
1423
<div className="z-10 w-full max-w-5xl items-center justify-between font-mono text-sm lg:flex">
1524
<p className="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">
1625
Get started by editing&nbsp;

apps/vite-react/src/utils.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { type ClassValue, clsx } from 'clsx'
2+
import { twMerge } from 'tailwind-merge'
3+
4+
export function cn(...inputs: ClassValue[]) {
5+
return twMerge(clsx(inputs))
6+
}

apps/vite-react/tailwindcss-mangle.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@ import { defineConfig } from 'tailwindcss-patch'
22

33
export default defineConfig({
44
mangle: {
5-
preserveFunction: ['twMerge']
5+
preserveFunction: ['twMerge', 'cn']
66
}
77
})

packages/core/src/js/pre.ts

Lines changed: 95 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -6,47 +6,50 @@ import { sort } from 'fast-sort'
66
import { jsStringEscape } from '@ast-core/escape'
77
import { parse, ParseResult } from '@babel/parser'
88
import traverse from '@babel/traverse'
9-
import { getStringLiteralCalleeName, getTemplateElementCalleeName } from './utils'
109
import { escapeStringRegexp } from '@/utils'
1110
import type { Context } from '@/ctx'
11+
import { between } from '@/math'
1212

1313
interface Options {
1414
replaceMap: Map<string, string>
1515
magicString: MagicString
1616
id: string
1717
ctx: Context
18+
markedArray: [number, number][]
1819
}
1920

2021
type HandleValueOptions = {
2122
raw: string
2223
path: babel.NodePath<babel.types.StringLiteral | babel.types.TemplateElement>
2324
offset: number
2425
escape: boolean
25-
preserve: boolean
2626
} & Options
2727

2828
export function handleValue(options: HandleValueOptions) {
29-
const { ctx, id, path, magicString, raw, replaceMap, offset = 0, escape = false, preserve = false } = options
29+
const { ctx, id, path, magicString, raw, replaceMap, offset = 0, escape = false, markedArray } = options
3030
const node = path.node
3131
let value = raw
32+
// why 字符串字面量只要开始和结束 在 方法节点内就保留, 另外不可能出现 字符串字面量 开始和结束的下标整个包括 方法体,这是不可能出现的事情
33+
for (const [s, e] of markedArray) {
34+
if (between(node.start, s, e) || between(node.end, s, e)) {
35+
return
36+
}
37+
}
38+
3239
const arr = sort(splitCode(value)).desc((x) => x.length)
3340

3441
for (const str of arr) {
3542
if (replaceMap.has(str)) {
3643
ctx.addToUsedBy(str, id)
37-
if (preserve) {
38-
ctx.addPreserveClass(str)
39-
}
44+
4045
// replace
4146
const v = replaceMap.get(str)
4247
if (v) {
4348
value = value.replaceAll(str, v)
4449
}
4550
}
4651
}
47-
if (preserve) {
48-
return
49-
}
52+
5053
if (typeof node.start === 'number' && typeof node.end === 'number' && value) {
5154
const start = node.start + offset
5255
const end = node.end - offset
@@ -58,11 +61,11 @@ export function handleValue(options: HandleValueOptions) {
5861

5962
export const JsPlugin = declare((api, options: Options) => {
6063
api.assertVersion(7)
61-
const { magicString, replaceMap, id, ctx } = options
64+
const { magicString, replaceMap, id, ctx, markedArray } = options
6265
return {
6366
visitor: {
6467
StringLiteral: {
65-
enter(p) {
68+
exit(p) {
6669
const opts: HandleValueOptions = {
6770
ctx,
6871
id,
@@ -72,17 +75,14 @@ export const JsPlugin = declare((api, options: Options) => {
7275
replaceMap,
7376
offset: 1,
7477
escape: true,
75-
preserve: false
76-
}
77-
const calleeName = getStringLiteralCalleeName(p)
78-
if (calleeName && ctx.isPreserveFunction(calleeName)) {
79-
opts.preserve = true
78+
markedArray
8079
}
80+
8181
handleValue(opts)
8282
}
8383
},
8484
TemplateElement: {
85-
enter(p) {
85+
exit(p) {
8686
const opts: HandleValueOptions = {
8787
ctx,
8888
id,
@@ -92,12 +92,9 @@ export const JsPlugin = declare((api, options: Options) => {
9292
replaceMap,
9393
offset: 0,
9494
escape: false,
95-
preserve: false
96-
}
97-
const calleeName = getTemplateElementCalleeName(p)
98-
if (calleeName && ctx.isPreserveFunction(calleeName)) {
99-
opts.preserve = true
95+
markedArray
10096
}
97+
10198
handleValue(opts)
10299
}
103100
}
@@ -112,27 +109,88 @@ interface IPreProcessJsOptions {
112109
ctx: Context
113110
}
114111

115-
function transformSync(code: string, plugins: babel.PluginItem[] | null | undefined, filename: string | null | undefined) {
116-
babel.transformSync(code, {
117-
presets: [
118-
// ['@babel/preset-react', {}],
119-
[
120-
require('@babel/preset-typescript'),
121-
{
122-
allExtensions: true,
123-
isTSX: true
124-
}
125-
]
126-
],
112+
function transformSync(ast: babel.types.Node, code: string, plugins: babel.PluginItem[] | null | undefined, filename: string | null | undefined) {
113+
babel.transformFromAstSync(ast, code, {
114+
presets: loadPresets(),
127115
plugins,
128116
filename
129117
})
130118
}
131119

120+
export function loadPresets() {
121+
return [
122+
[
123+
require('@babel/preset-typescript'),
124+
{
125+
allExtensions: true,
126+
isTSX: true
127+
}
128+
]
129+
]
130+
}
131+
132132
export function preProcessJs(options: IPreProcessJsOptions) {
133133
const { code, replaceMap, id, ctx } = options
134134
const magicString = typeof code === 'string' ? new MagicString(code) : code
135+
let ast: ParseResult<babel.types.File>
136+
try {
137+
const file = babel.parseSync(magicString.original, {
138+
sourceType: 'unambiguous',
139+
presets: loadPresets()
140+
})
141+
if (file) {
142+
ast = file
143+
} else {
144+
return code
145+
}
146+
} catch {
147+
return code
148+
}
149+
const markedArray: [number, number][] = []
150+
traverse(ast, {
151+
CallExpression: {
152+
enter(p) {
153+
const callee = p.get('callee')
154+
if (callee.isIdentifier() && ctx.isPreserveFunction(callee.node.name)) {
155+
if (p.node.start && p.node.end) {
156+
markedArray.push([p.node.start, p.node.end])
157+
}
158+
159+
p.traverse({
160+
StringLiteral: {
161+
enter(path) {
162+
const node = path.node
163+
const value = node.value
164+
const arr = sort(splitCode(value)).desc((x) => x.length)
165+
166+
for (const str of arr) {
167+
if (replaceMap.has(str)) {
168+
ctx.addPreserveClass(str)
169+
}
170+
}
171+
}
172+
},
173+
TemplateElement: {
174+
enter(path) {
175+
const node = path.node
176+
const value = node.value.raw
177+
const arr = sort(splitCode(value)).desc((x) => x.length)
178+
179+
for (const str of arr) {
180+
if (replaceMap.has(str)) {
181+
ctx.addPreserveClass(str)
182+
}
183+
}
184+
}
185+
}
186+
})
187+
}
188+
}
189+
}
190+
})
191+
135192
transformSync(
193+
ast,
136194
magicString.original,
137195
[
138196
[
@@ -141,7 +199,8 @@ export function preProcessJs(options: IPreProcessJsOptions) {
141199
magicString,
142200
replaceMap,
143201
id,
144-
ctx
202+
ctx,
203+
markedArray
145204
}
146205
]
147206
],
@@ -203,10 +262,8 @@ export function preProcessRawCode(options: IPreProcessRawCodeOptions) {
203262
continue
204263
}
205264
}
206-
// console.log(arr, regex.lastIndex)
207265
}
208266
for (const [key, value] of replaceMap) {
209-
// if (!ctx.isPreserveClass(key)) {
210267
// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec
211268
const regex = new RegExp(escapeStringRegexp(key), 'g')
212269
let arr: RegExpExecArray | null = null
@@ -215,7 +272,7 @@ export function preProcessRawCode(options: IPreProcessRawCodeOptions) {
215272
const end = arr.index + arr[0].length
216273
let shouldUpdate = true
217274
for (const [ps, pe] of markArr) {
218-
if ((start > ps && start < pe) || (end < pe && end > ps)) {
275+
if (between(start, ps, pe) || between(end, ps, pe)) {
219276
shouldUpdate = false
220277
break
221278
}
@@ -228,8 +285,4 @@ export function preProcessRawCode(options: IPreProcessRawCodeOptions) {
228285
}
229286

230287
return magicString.toString()
231-
// for (const [key, value] of replaceMap) {
232-
// code = code.replaceAll(key, value)
233-
// }
234-
// return code
235288
}

packages/core/src/math.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export function between(x: number | null | undefined, min: number, max: number, included: boolean = false) {
2+
if (typeof x !== 'number') {
3+
return false
4+
}
5+
return included ? x >= min && x <= max : x > min && x < max
6+
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
22

3+
exports[`js handler > cn 1`] = `
4+
"const bbb = cn(
5+
{
6+
'p-3': true
7+
},
8+
'p-1',
9+
['p-2', true && 'p-4']
10+
)"
11+
`;
12+
313
exports[`js handler > comment-ignore case 1`] = `
414
"const clipPath = [\`circle()\`];
515
document.documentElement.animate({

packages/core/test/js.test.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { Context } from '@/index'
1010
// return _jsHandler(str, options)
1111
// }
1212

13-
describe('js handler', () => {
13+
describe('js handler', async () => {
1414
// let classGenerator: ClassGenerator
1515
let ctx: Context
1616
beforeEach(() => {
@@ -464,4 +464,28 @@ describe('js handler', () => {
464464
})
465465
expect(res).toMatchSnapshot()
466466
})
467+
468+
it('cn', async () => {
469+
const code = `const bbb = cn(
470+
{
471+
'p-3': true
472+
},
473+
'p-1',
474+
['p-2', true && 'p-4']
475+
)`
476+
await ctx.initConfig({
477+
classList: 'p-1 p-2 p-3 p-4'.split(' '),
478+
mangleOptions: {
479+
preserveFunction: ['cn']
480+
}
481+
})
482+
const replaceMap = ctx.getReplaceMap()
483+
const res = preProcessJs({
484+
code,
485+
replaceMap,
486+
ctx,
487+
id: 'xxx'
488+
})
489+
expect(res).toMatchSnapshot()
490+
})
467491
})

0 commit comments

Comments
 (0)