Skip to content

Commit 313eb1f

Browse files
committed
perf(weapp-tailwindcss): 复用 eval 选项并收紧替换扫描
1 parent 411e993 commit 313eb1f

4 files changed

Lines changed: 106 additions & 19 deletions

File tree

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

Lines changed: 62 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ import { jsStringEscape } from '@ast-core/escape'
77

88
export type EvalHandler = (source: string, opts: IJsHandlerOptions) => JsHandlerResult
99

10+
const evalHandlerOptionsCache = new WeakMap<IJsHandlerOptions, {
11+
stringLiteralOptions?: IJsHandlerOptions
12+
templateLiteralOptions?: IJsHandlerOptions
13+
}>()
14+
const EVAL_SCOPE_ERROR_REGEXP = /pass a scope and parentPath|traversing a Program\/File/i
15+
1016
export function isEvalPath(path: NodePath<Node>) {
1117
if (path.isCallExpression()) {
1218
const calleePath = path.get('callee')
@@ -60,15 +66,11 @@ function createEvalReplacementToken(
6066

6167
function handleEvalStringLiteral(
6268
path: NodePath<StringLiteral>,
63-
options: IJsHandlerOptions,
69+
handlerOptions: IJsHandlerOptions,
6470
updater: JsTokenUpdater,
6571
handler: EvalHandler,
6672
) {
67-
const { code } = handler(path.node.value, {
68-
...options,
69-
needEscaped: false,
70-
generateMap: false,
71-
})
73+
const { code } = handler(path.node.value, handlerOptions)
7274

7375
if (!code) {
7476
return
@@ -82,14 +84,11 @@ function handleEvalStringLiteral(
8284

8385
function handleEvalTemplateElement(
8486
path: NodePath<TemplateElement>,
85-
options: IJsHandlerOptions,
87+
handlerOptions: IJsHandlerOptions,
8688
updater: JsTokenUpdater,
8789
handler: EvalHandler,
8890
) {
89-
const { code } = handler(path.node.value.raw, {
90-
...options,
91-
generateMap: false,
92-
})
91+
const { code } = handler(path.node.value.raw, handlerOptions)
9392

9493
if (!code) {
9594
return
@@ -101,29 +100,74 @@ function handleEvalTemplateElement(
101100
}
102101
}
103102

103+
function getEvalStringHandlerOptions(options: IJsHandlerOptions) {
104+
if (options.needEscaped === false && options.generateMap === false) {
105+
return options
106+
}
107+
108+
let cached = evalHandlerOptionsCache.get(options)
109+
if (!cached) {
110+
cached = {}
111+
evalHandlerOptionsCache.set(options, cached)
112+
}
113+
114+
if (!cached.stringLiteralOptions) {
115+
cached.stringLiteralOptions = {
116+
...options,
117+
needEscaped: false,
118+
generateMap: false,
119+
}
120+
}
121+
122+
return cached.stringLiteralOptions
123+
}
124+
125+
function getEvalTemplateHandlerOptions(options: IJsHandlerOptions) {
126+
if (options.generateMap === false) {
127+
return options
128+
}
129+
130+
let cached = evalHandlerOptionsCache.get(options)
131+
if (!cached) {
132+
cached = {}
133+
evalHandlerOptionsCache.set(options, cached)
134+
}
135+
136+
if (!cached.templateLiteralOptions) {
137+
cached.templateLiteralOptions = {
138+
...options,
139+
generateMap: false,
140+
}
141+
}
142+
143+
return cached.templateLiteralOptions
144+
}
145+
104146
export function walkEvalExpression(
105147
path: NodePath<CallExpression>,
106148
options: IJsHandlerOptions,
107149
updater: JsTokenUpdater,
108150
handler: EvalHandler,
109151
) {
152+
const stringHandlerOptions = getEvalStringHandlerOptions(options)
153+
const templateHandlerOptions = getEvalTemplateHandlerOptions(options)
110154
// 优先走 NodePath 的 traverse(测试桩会用到),若因 noScope 缺少作用域报错则降级到手工参数遍历。
111155
const maybeTraverse = (path as any)?.traverse as ((v: any) => void) | undefined
112156
if (typeof maybeTraverse === 'function') {
113157
try {
114158
return maybeTraverse.call(path, {
115159
StringLiteral(innerPath: NodePath<StringLiteral>) {
116-
handleEvalStringLiteral(innerPath, options, updater, handler)
160+
handleEvalStringLiteral(innerPath, stringHandlerOptions, updater, handler)
117161
},
118162
TemplateElement(innerPath: NodePath<TemplateElement>) {
119-
handleEvalTemplateElement(innerPath, options, updater, handler)
163+
handleEvalTemplateElement(innerPath, templateHandlerOptions, updater, handler)
120164
},
121165
})
122166
}
123167
catch (error) {
124168
// 若是因为缺少 scope/parentPath 的错误,则继续走手工参数遍历;其他错误透出。
125169
const msg = (error as Error)?.message ?? ''
126-
const scopeError = /pass a scope and parentPath|traversing a Program\/File/i.test(msg)
170+
const scopeError = EVAL_SCOPE_ERROR_REGEXP.test(msg)
127171
if (!scopeError) {
128172
throw error
129173
}
@@ -135,12 +179,12 @@ export function walkEvalExpression(
135179
if (Array.isArray(getArgs)) {
136180
for (const arg of getArgs as Array<NodePath<Node>>) {
137181
if ((arg as any)?.isStringLiteral?.()) {
138-
handleEvalStringLiteral(arg as unknown as NodePath<StringLiteral>, options, updater, handler)
182+
handleEvalStringLiteral(arg as unknown as NodePath<StringLiteral>, stringHandlerOptions, updater, handler)
139183
continue
140184
}
141185
if ((arg as any)?.isTemplateLiteral?.()) {
142186
for (const quasi of (arg as any).get('quasis') as Array<NodePath<TemplateElement>>) {
143-
handleEvalTemplateElement(quasi, options, updater, handler)
187+
handleEvalTemplateElement(quasi, templateHandlerOptions, updater, handler)
144188
}
145189
}
146190
}
@@ -156,15 +200,15 @@ export function walkEvalExpression(
156200
node: n as StringLiteral,
157201
isStringLiteral: () => true,
158202
} as unknown as NodePath<StringLiteral>
159-
handleEvalStringLiteral(stub, options, updater, handler)
203+
handleEvalStringLiteral(stub, stringHandlerOptions, updater, handler)
160204
}
161205
else if (n?.type === 'TemplateLiteral' && Array.isArray(n.quasis)) {
162206
for (const q of n.quasis as any[]) {
163207
const stub = {
164208
node: q as TemplateElement,
165209
isTemplateElement: () => true,
166210
} as unknown as NodePath<TemplateElement>
167-
handleEvalTemplateElement(stub, options, updater, handler)
211+
handleEvalTemplateElement(stub, templateHandlerOptions, updater, handler)
168212
}
169213
}
170214
}

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,20 @@ export function collectModuleSpecifierReplacementTokens(
4747
analysis: SourceAnalysis,
4848
replacements: Record<string, string>,
4949
) {
50+
const replacementKeys = Object.keys(replacements)
51+
if (replacementKeys.length === 0) {
52+
return []
53+
}
54+
55+
if (
56+
analysis.importDeclarations.size === 0
57+
&& analysis.exportDeclarations.size === 0
58+
&& analysis.requireCallPaths.length === 0
59+
&& analysis.walker.imports.length === 0
60+
) {
61+
return []
62+
}
63+
5064
const tokens: JsToken[] = []
5165

5266
const applyReplacement = (path: NodePath<StringLiteral>) => {

packages/weapp-tailwindcss/test/js/evalTransforms.test.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { NodePath } from '@babel/traverse'
22
import type { CallExpression, Node } from '@babel/types'
33
import type { IJsHandlerOptions, JsHandlerResult } from '@/types'
44
import MagicString from 'magic-string'
5-
import { describe, expect, it } from 'vitest'
5+
import { describe, expect, it, vi } from 'vitest'
66
import { parse, traverse } from '@/babel'
77
import { isEvalPath, walkEvalExpression } from '@/js/evalTransforms'
88
import { JsTokenUpdater } from '@/js/JsTokenUpdater'
@@ -54,6 +54,21 @@ describe('evalTransforms', () => {
5454
expect(rewritten).toBe('eval(`foo bar`);')
5555
})
5656

57+
it('reuses cached handler options for repeated eval string literals', () => {
58+
const source = 'eval("foo", "bar")'
59+
const updater = new JsTokenUpdater()
60+
const handler = vi.fn((input: string): JsHandlerResult => ({ code: input.toUpperCase() }))
61+
62+
walkEvalExpression(getEvalCall(source), baseOptions, updater, handler)
63+
64+
expect(handler).toHaveBeenCalledTimes(2)
65+
expect(handler.mock.calls[0]?.[1]).toBe(handler.mock.calls[1]?.[1])
66+
expect(handler.mock.calls[0]?.[1]).toMatchObject({
67+
needEscaped: false,
68+
generateMap: false,
69+
})
70+
})
71+
5772
it('detects eval calls via isEvalPath helper', () => {
5873
const ast = parse('eval("foo"); other("bar")', { sourceType: 'module' as const })
5974
const calls: NodePath<CallExpression>[] = []

packages/weapp-tailwindcss/test/js/sourceAnalysis.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,18 @@ describe('sourceAnalysis helpers', () => {
8181
expect(reExports).toHaveLength(1)
8282
expect(reExports[0]!.source).toBe('./foo?transformed')
8383
})
84+
85+
it('returns early when no replacement entries are provided', () => {
86+
const source = `import foo from './foo'`
87+
const ast = babelParse(source, { sourceType: 'module' as const })
88+
const analysis = analyzeSource(ast, {})
89+
const originalImports = analysis.walker.imports
90+
const originalImportCount = analysis.walker.imports.size
91+
92+
const tokens = collectModuleSpecifierReplacementTokens(analysis, {})
93+
94+
expect(tokens).toEqual([])
95+
expect(analysis.walker.imports).toBe(originalImports)
96+
expect(analysis.walker.imports.size).toBe(originalImportCount)
97+
})
8498
})

0 commit comments

Comments
 (0)