Skip to content

Commit 8767581

Browse files
committed
perf(weapp-tailwindcss): 复用 js 与模板处理默认分支
1 parent 03320ce commit 8767581

5 files changed

Lines changed: 146 additions & 23 deletions

File tree

packages/weapp-tailwindcss/src/js/babel/process.ts

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,50 @@ import MagicString from 'magic-string'
55
import { replaceHandleValue } from '../handlers'
66
import { collectModuleSpecifierReplacementTokens } from '../sourceAnalysis'
77

8+
const optionVariantsCache = new WeakMap<IJsHandlerOptions, {
9+
stringLiteralOptions?: IJsHandlerOptions
10+
templateLiteralOptions?: IJsHandlerOptions
11+
}>()
12+
13+
function getNeedEscapedOptions(
14+
options: IJsHandlerOptions,
15+
needEscaped: boolean,
16+
) {
17+
if (options.needEscaped === needEscaped) {
18+
return options
19+
}
20+
21+
let cached = optionVariantsCache.get(options)
22+
if (!cached) {
23+
cached = {}
24+
optionVariantsCache.set(options, cached)
25+
}
26+
27+
if (needEscaped) {
28+
if (!cached.stringLiteralOptions) {
29+
cached.stringLiteralOptions = {
30+
...options,
31+
needEscaped: true,
32+
}
33+
}
34+
return cached.stringLiteralOptions
35+
}
36+
37+
if (!cached.templateLiteralOptions) {
38+
cached.templateLiteralOptions = {
39+
...options,
40+
needEscaped: false,
41+
}
42+
}
43+
return cached.templateLiteralOptions
44+
}
45+
846
export function processUpdatedSource(
947
rawSource: string,
1048
options: IJsHandlerOptions,
1149
analysis: SourceAnalysis,
1250
) {
1351
const { targetPaths, jsTokenUpdater, ignoredPaths } = analysis
14-
const stringLiteralOptions = options.needEscaped === undefined
15-
? { ...options, needEscaped: true }
16-
: options
17-
const templateLiteralOptions = options.needEscaped === false
18-
? options
19-
: { ...options, needEscaped: false }
2052

2153
// 为前面收集到的所有字符串节点生成替换 token。
2254
const replacementTokens: JsToken[] = []
@@ -27,7 +59,9 @@ export function processUpdatedSource(
2759

2860
const token = replaceHandleValue(
2961
path,
30-
path.isStringLiteral() ? stringLiteralOptions : templateLiteralOptions,
62+
path.isStringLiteral()
63+
? getNeedEscapedOptions(options, true)
64+
: getNeedEscapedOptions(options, false),
3165
)
3266

3367
if (token) {

packages/weapp-tailwindcss/src/js/index.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,20 @@ export {
66
jsHandler,
77
}
88

9+
function hasDefinedOverrides(options?: CreateJsHandlerOptions) {
10+
if (!options) {
11+
return false
12+
}
13+
14+
for (const key in options) {
15+
if (options[key as keyof CreateJsHandlerOptions] !== undefined) {
16+
return true
17+
}
18+
}
19+
20+
return false
21+
}
22+
923
export function createJsHandler(options: CreateJsHandlerOptions): JsHandler {
1024
// 预构建不可变的默认选项对象,避免每次调用都重新创建字面量。
1125
const defaults: IJsHandlerOptions = {
@@ -53,8 +67,8 @@ export function createJsHandler(options: CreateJsHandlerOptions): JsHandler {
5367
}
5468

5569
function handler(rawSource: string, classNameSet?: Set<string>, options?: CreateJsHandlerOptions) {
56-
// 快路径:无覆盖选项时跳过 defuOverrideArray,直接合并 classNameSet
57-
if (!options || Object.keys(options).length === 0) {
70+
// 快路径:无有效覆盖选项时跳过 defuOverrideArray,直接合并 classNameSet
71+
if (!hasDefinedOverrides(options)) {
5872
return jsHandler(rawSource, resolveDefaultOptions(classNameSet))
5973
}
6074

packages/weapp-tailwindcss/src/wxml/utils/codegen.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
1-
import type { ITemplateHandlerOptions } from '../../types'
1+
import type { CreateJsHandlerOptions, ITemplateHandlerOptions } from '../../types'
22
import { rewriteLegacyExpression } from './codegen/legacy-rewriter'
33

4+
const WRAP_EXPRESSION_HANDLER_OPTIONS: CreateJsHandlerOptions = Object.freeze({
5+
wrapExpression: true,
6+
})
7+
48
export function generateCode(match: string, options: ITemplateHandlerOptions = {}) {
59
try {
6-
const { jsHandler, runtimeSet } = options
10+
const { jsHandler, runtimeSet, wrapExpression } = options
711
if (jsHandler && runtimeSet) {
8-
const runHandler = (wrap?: boolean) => jsHandler(match, runtimeSet, wrap ? { wrapExpression: true } : undefined)
9-
const initial = runHandler(options.wrapExpression)
10-
if (!initial.error || options.wrapExpression) {
12+
const initial = jsHandler(
13+
match,
14+
runtimeSet,
15+
wrapExpression ? WRAP_EXPRESSION_HANDLER_OPTIONS : undefined,
16+
)
17+
if (!initial.error || wrapExpression) {
1118
return initial.code
1219
}
13-
const fallback = runHandler(true)
20+
const fallback = jsHandler(match, runtimeSet, WRAP_EXPRESSION_HANDLER_OPTIONS)
1421
return fallback.code
1522
}
1623
else {

packages/weapp-tailwindcss/test/js/index-handler.test.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import { afterEach, describe, expect, it, vi } from 'vitest'
33
import { createJsHandler } from '@/js'
44
import * as babel from '@/js/babel'
55

6+
const STYLED_TAG_REGEXP = /^styled$/
7+
const OVERRIDE_CALL_REGEXP = /^override$/
8+
69
function createBaseOptions() {
710
return {
811
escapeMap: { base: '_' },
@@ -14,7 +17,7 @@ function createBaseOptions() {
1417
unescapeUnicode: true,
1518
babelParserOptions: { sourceType: 'module' as const },
1619
ignoreCallExpressionIdentifiers: ['cn'],
17-
ignoreTaggedTemplateExpressionIdentifiers: [/^styled$/],
20+
ignoreTaggedTemplateExpressionIdentifiers: [STYLED_TAG_REGEXP],
1821
uniAppX: true,
1922
}
2023
}
@@ -31,7 +34,7 @@ describe('createJsHandler', () => {
3134
const override = {
3235
needEscaped: false,
3336
escapeMap: { override: '_' },
34-
ignoreCallExpressionIdentifiers: [/^override$/],
37+
ignoreCallExpressionIdentifiers: [OVERRIDE_CALL_REGEXP],
3538
}
3639
const classNameSet = new Set(['foo'])
3740

@@ -80,6 +83,23 @@ describe('createJsHandler', () => {
8083
expect(resolved.unescapeUnicode).toBe(true)
8184
})
8285

86+
it('reuses the cached configuration when overrides only contain undefined values', () => {
87+
const spy = vi.spyOn(babel, 'jsHandler').mockReturnValue({ code: 'undefined-cache' })
88+
const base = createBaseOptions()
89+
const handler = createJsHandler(base)
90+
const classNameSet = new Set(['foo'])
91+
92+
handler('source-a', classNameSet)
93+
handler('source-b', classNameSet, {
94+
needEscaped: undefined,
95+
alwaysEscape: undefined,
96+
})
97+
98+
expect(spy).toHaveBeenCalledTimes(2)
99+
expect(spy.mock.calls[0][1]).toBe(spy.mock.calls[1][1])
100+
expect(spy.mock.calls[1][1].classNameSet).toBe(classNameSet)
101+
})
102+
83103
it('reuses the cached configuration for the same runtime classNameSet when no overrides are supplied', () => {
84104
const spy = vi.spyOn(babel, 'jsHandler').mockReturnValue({ code: 'set-cache' })
85105
const base = createBaseOptions()

packages/weapp-tailwindcss/test/wxml/utils.test.ts

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,25 @@
1+
import { describe, expect, it, vi } from 'vitest'
12
import { getCompilerContext } from '@/context'
23
import { replaceWxml } from '@/wxml'
34
import { generateCode, isPropsMatch } from '@/wxml/utils'
45
import { removeWxmlId } from '../util'
56

7+
const EXACT_RD_BTN_CLASS_REGEXP = /^rd-btn-class$/
8+
const RD_WORD_CLASS_REGEXP = /^rd-\w+-class$/
9+
const RD_ALPHA_CLASS_REGEXP = /^rd-[A-Za-z]+-class$/
10+
611
describe('utils', () => {
712
it('isPropsMatch', () => {
813
expect(isPropsMatch('a', 'a')).toBe(true)
914
expect(isPropsMatch(['a'], 'a')).toBe(true)
1015
expect(isPropsMatch('rd-btn-class', 'rd-btn-class')).toBe(true)
1116
expect(isPropsMatch(['rd-btn-class'], 'rd-btn-class')).toBe(true)
12-
expect(isPropsMatch(/^rd-btn-class$/, 'rd-btn-class')).toBe(true)
13-
expect(isPropsMatch([/^rd-btn-class$/], 'rd-btn-class')).toBe(true)
14-
expect(isPropsMatch(/^rd-\w+-class$/, 'rd-btn-class')).toBe(true)
15-
expect(isPropsMatch([/^rd-\w+-class$/], 'rd-btn-class')).toBe(true)
16-
expect(isPropsMatch(/^rd-[A-Za-z]+-class$/, 'rd-btn-class')).toBe(true)
17-
expect(isPropsMatch([/^rd-[A-Za-z]+-class$/], 'rd-btn-class')).toBe(true)
17+
expect(isPropsMatch(EXACT_RD_BTN_CLASS_REGEXP, 'rd-btn-class')).toBe(true)
18+
expect(isPropsMatch([EXACT_RD_BTN_CLASS_REGEXP], 'rd-btn-class')).toBe(true)
19+
expect(isPropsMatch(RD_WORD_CLASS_REGEXP, 'rd-btn-class')).toBe(true)
20+
expect(isPropsMatch([RD_WORD_CLASS_REGEXP], 'rd-btn-class')).toBe(true)
21+
expect(isPropsMatch(RD_ALPHA_CLASS_REGEXP, 'rd-btn-class')).toBe(true)
22+
expect(isPropsMatch([RD_ALPHA_CLASS_REGEXP], 'rd-btn-class')).toBe(true)
1823
})
1924

2025
it('remove all id', () => {
@@ -54,4 +59,47 @@ describe('utils', () => {
5459
})
5560
expect(code).toContain(replaceWxml('border-[#ff0000] bg-blue-600/50'))
5661
})
62+
63+
it('does not retry when wrapExpression is already enabled', () => {
64+
const jsHandler = vi.fn(() => ({
65+
code: 'wrapped',
66+
error: new Error('already wrapped'),
67+
}))
68+
const runtimeSet = new Set<string>()
69+
const code = generateCode(`{'foo': flag}`, {
70+
jsHandler,
71+
runtimeSet,
72+
wrapExpression: true,
73+
})
74+
75+
expect(code).toBe('wrapped')
76+
expect(jsHandler).toHaveBeenCalledTimes(1)
77+
expect(jsHandler.mock.calls[0]?.[2]).toEqual({
78+
wrapExpression: true,
79+
})
80+
})
81+
82+
it('retries once with wrapExpression when the initial parse fails', () => {
83+
const jsHandler = vi
84+
.fn()
85+
.mockReturnValueOnce({
86+
code: 'initial',
87+
error: new Error('parse error'),
88+
})
89+
.mockReturnValueOnce({
90+
code: 'fallback',
91+
})
92+
const runtimeSet = new Set<string>()
93+
const code = generateCode(`{'foo': flag}`, {
94+
jsHandler,
95+
runtimeSet,
96+
})
97+
98+
expect(code).toBe('fallback')
99+
expect(jsHandler).toHaveBeenCalledTimes(2)
100+
expect(jsHandler.mock.calls[0]?.[2]).toBeUndefined()
101+
expect(jsHandler.mock.calls[1]?.[2]).toEqual({
102+
wrapExpression: true,
103+
})
104+
})
57105
})

0 commit comments

Comments
 (0)