Skip to content

Commit ab84953

Browse files
committed
feat: add style processing performance optimization
- Introduce style caching, batch updates, and performance monitoring mechanisms to optimize style processing performance. Main changes include: - Added a configuration system to support dynamic adjustment of parameters such as cache and batch updates - Implemented LRU caching strategy to reduce redundant style computations - Added batch update mechanism to merge DOM operations - Integrated performance monitoring to provide optimization suggestions - Improved test coverage to ensure functional stability
1 parent 1e03ae7 commit ab84953

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+7167
-231
lines changed

commitlint.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ export default {
22
extends: ['@commitlint/config-conventional'],
33
rules: {
44
'header-max-length': [0, 'always', 125],
5+
'body-max-line-length': [0, 'always', 250],
56
},
67
}
Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
2+
import { configureStyleProcessing, resetStyleConfig } from '../src/config'
3+
import { batchUpdater } from '../src/utils/batchUpdater'
4+
5+
describe('batchUpdater', () => {
6+
let mockInsertFunction: ReturnType<typeof vi.fn>
7+
8+
beforeAll(() => {
9+
window.requestAnimationFrame = vi.fn(callback => callback())
10+
})
11+
12+
beforeEach(() => {
13+
resetStyleConfig()
14+
batchUpdater.clear()
15+
mockInsertFunction = vi.fn()
16+
batchUpdater.setInsertFunction(mockInsertFunction)
17+
})
18+
19+
afterEach(() => {
20+
resetStyleConfig()
21+
batchUpdater.clear()
22+
vi.clearAllTimers()
23+
vi.useRealTimers()
24+
})
25+
26+
describe('immediate execution mode', () => {
27+
it('should execute updates immediately when batch updates are disabled', () => {
28+
configureStyleProcessing({ enableBatchUpdates: false })
29+
30+
batchUpdater.scheduleUpdate('test-class', '.test { color: red; }')
31+
32+
expect(mockInsertFunction).toHaveBeenCalledWith('test-class', '.test { color: red; }')
33+
expect(batchUpdater.getPendingCount()).toBe(0)
34+
})
35+
36+
it('should handle multiple immediate updates', () => {
37+
configureStyleProcessing({ enableBatchUpdates: false })
38+
39+
batchUpdater.scheduleUpdate('class1', '.class1 { color: red; }')
40+
batchUpdater.scheduleUpdate('class2', '.class2 { color: blue; }')
41+
42+
expect(mockInsertFunction).toHaveBeenCalledTimes(2)
43+
expect(mockInsertFunction).toHaveBeenNthCalledWith(1, 'class1', '.class1 { color: red; }')
44+
expect(mockInsertFunction).toHaveBeenNthCalledWith(2, 'class2', '.class2 { color: blue; }')
45+
})
46+
})
47+
48+
describe('batch mode', () => {
49+
beforeEach(() => {
50+
vi.useFakeTimers()
51+
configureStyleProcessing({ enableBatchUpdates: true, batchDelay: 16 })
52+
})
53+
54+
it('should schedule updates for batch execution', () => {
55+
batchUpdater.scheduleUpdate('test-class', '.test { color: red; }')
56+
57+
expect(mockInsertFunction).not.toHaveBeenCalled()
58+
expect(batchUpdater.getPendingCount()).toBe(1)
59+
})
60+
61+
it('should execute batched updates after delay', () => {
62+
batchUpdater.scheduleUpdate('test-class', '.test { color: red; }')
63+
64+
expect(mockInsertFunction).not.toHaveBeenCalled()
65+
66+
// 模拟 requestAnimationFrame 和 setTimeout
67+
vi.advanceTimersByTime(16)
68+
vi.runAllTimers()
69+
70+
expect(mockInsertFunction).toHaveBeenCalledWith('test-class', '.test { color: red; }')
71+
expect(batchUpdater.getPendingCount()).toBe(0)
72+
})
73+
74+
it('should batch multiple updates together', () => {
75+
batchUpdater.scheduleUpdate('class1', '.class1 { color: red; }')
76+
batchUpdater.scheduleUpdate('class2', '.class2 { color: blue; }')
77+
batchUpdater.scheduleUpdate('class3', '.class3 { color: green; }')
78+
79+
expect(batchUpdater.getPendingCount()).toBe(3)
80+
expect(mockInsertFunction).not.toHaveBeenCalled()
81+
82+
vi.advanceTimersByTime(16)
83+
vi.runAllTimers()
84+
85+
expect(mockInsertFunction).toHaveBeenCalledTimes(3)
86+
expect(batchUpdater.getPendingCount()).toBe(0)
87+
})
88+
89+
it('should replace duplicate class updates', () => {
90+
batchUpdater.scheduleUpdate('test-class', '.test { color: red; }')
91+
batchUpdater.scheduleUpdate('test-class', '.test { color: blue; }') // 应该替换前一个
92+
batchUpdater.scheduleUpdate('other-class', '.other { color: green; }')
93+
94+
expect(batchUpdater.getPendingCount()).toBe(2) // 只有两个不同的类
95+
96+
vi.advanceTimersByTime(16)
97+
vi.runAllTimers()
98+
99+
expect(mockInsertFunction).toHaveBeenCalledTimes(2)
100+
expect(mockInsertFunction).toHaveBeenCalledWith('test-class', '.test { color: blue; }') // 使用最新的样式
101+
expect(mockInsertFunction).toHaveBeenCalledWith('other-class', '.other { color: green; }')
102+
})
103+
104+
it('should handle priority ordering', () => {
105+
batchUpdater.scheduleUpdate('low-priority', '.low { color: red; }', 1)
106+
batchUpdater.scheduleUpdate('high-priority', '.high { color: blue; }', 10)
107+
batchUpdater.scheduleUpdate('medium-priority', '.medium { color: green; }', 5)
108+
109+
vi.advanceTimersByTime(16)
110+
vi.runAllTimers()
111+
112+
expect(mockInsertFunction).toHaveBeenCalledTimes(3)
113+
// 应该按优先级从高到低执行
114+
expect(mockInsertFunction).toHaveBeenNthCalledWith(1, 'high-priority', '.high { color: blue; }')
115+
expect(mockInsertFunction).toHaveBeenNthCalledWith(2, 'medium-priority', '.medium { color: green; }')
116+
expect(mockInsertFunction).toHaveBeenNthCalledWith(3, 'low-priority', '.low { color: red; }')
117+
})
118+
})
119+
120+
describe('async mode', () => {
121+
beforeEach(() => {
122+
vi.useFakeTimers()
123+
configureStyleProcessing({
124+
enableBatchUpdates: true,
125+
enableAsync: true,
126+
batchDelay: 16,
127+
})
128+
})
129+
130+
it('should use setTimeout for async processing', () => {
131+
batchUpdater.scheduleUpdate('test-class', '.test { color: red; }')
132+
133+
expect(mockInsertFunction).not.toHaveBeenCalled()
134+
135+
// 只需要 setTimeout 的延迟
136+
vi.advanceTimersByTime(16)
137+
138+
expect(mockInsertFunction).toHaveBeenCalledWith('test-class', '.test { color: red; }')
139+
})
140+
})
141+
142+
describe('manual flush', () => {
143+
beforeEach(() => {
144+
configureStyleProcessing({ enableBatchUpdates: true, batchDelay: 100 })
145+
})
146+
147+
it('should flush pending updates immediately', () => {
148+
batchUpdater.scheduleUpdate('test-class', '.test { color: red; }')
149+
batchUpdater.scheduleUpdate('other-class', '.other { color: blue; }')
150+
151+
expect(batchUpdater.getPendingCount()).toBe(2)
152+
expect(mockInsertFunction).not.toHaveBeenCalled()
153+
154+
batchUpdater.flushSync()
155+
156+
expect(mockInsertFunction).toHaveBeenCalledTimes(2)
157+
expect(batchUpdater.getPendingCount()).toBe(0)
158+
})
159+
160+
it('should handle flush when no updates are pending', () => {
161+
batchUpdater.flushSync()
162+
163+
expect(mockInsertFunction).not.toHaveBeenCalled()
164+
expect(batchUpdater.getPendingCount()).toBe(0)
165+
})
166+
167+
it('should not interfere with scheduled flush', () => {
168+
vi.useFakeTimers()
169+
170+
batchUpdater.scheduleUpdate('test-class', '.test { color: red; }')
171+
172+
// 手动刷新
173+
batchUpdater.flushSync()
174+
expect(mockInsertFunction).toHaveBeenCalledTimes(1)
175+
176+
// 添加新的更新
177+
batchUpdater.scheduleUpdate('new-class', '.new { color: blue; }')
178+
179+
// 等待自动刷新
180+
vi.advanceTimersByTime(100)
181+
vi.runAllTimers()
182+
183+
expect(mockInsertFunction).toHaveBeenCalledTimes(2)
184+
})
185+
})
186+
187+
describe('performance monitoring integration', () => {
188+
beforeEach(() => {
189+
vi.useFakeTimers()
190+
configureStyleProcessing({
191+
enableBatchUpdates: true,
192+
enablePerformanceMonitoring: true,
193+
batchDelay: 16,
194+
})
195+
})
196+
197+
it('should log performance information when monitoring is enabled', () => {
198+
const consoleSpy = vi.spyOn(console, 'debug').mockImplementation(() => {})
199+
200+
batchUpdater.scheduleUpdate('class1', '.class1 { color: red; }')
201+
batchUpdater.scheduleUpdate('class2', '.class2 { color: blue; }')
202+
203+
vi.advanceTimersByTime(16)
204+
vi.runAllTimers()
205+
206+
expect(consoleSpy).toHaveBeenCalledWith(
207+
expect.stringContaining('[vue-styled-components] Batch update completed: 2 updates'),
208+
)
209+
210+
consoleSpy.mockRestore()
211+
})
212+
213+
it('should not log when performance monitoring is disabled', () => {
214+
configureStyleProcessing({ enablePerformanceMonitoring: false })
215+
216+
const consoleSpy = vi.spyOn(console, 'debug').mockImplementation(() => {})
217+
218+
batchUpdater.scheduleUpdate('test-class', '.test { color: red; }')
219+
220+
vi.advanceTimersByTime(16)
221+
vi.runAllTimers()
222+
223+
expect(consoleSpy).not.toHaveBeenCalled()
224+
225+
consoleSpy.mockRestore()
226+
})
227+
})
228+
229+
describe('insert function management', () => {
230+
it('should handle missing insert function gracefully', () => {
231+
batchUpdater.setInsertFunction(null as any)
232+
233+
expect(() => {
234+
batchUpdater.scheduleUpdate('test-class', '.test { color: red; }')
235+
batchUpdater.flushSync()
236+
}).not.toThrow()
237+
})
238+
239+
it('should allow changing insert function', () => {
240+
const newMockFunction = vi.fn()
241+
242+
batchUpdater.scheduleUpdate('test-class', '.test { color: red; }')
243+
batchUpdater.setInsertFunction(newMockFunction)
244+
batchUpdater.flushSync()
245+
246+
expect(mockInsertFunction).not.toHaveBeenCalled()
247+
expect(newMockFunction).toHaveBeenCalledWith('test-class', '.test { color: red; }')
248+
})
249+
})
250+
251+
describe('edge cases', () => {
252+
it('should handle empty className', () => {
253+
configureStyleProcessing({ enableBatchUpdates: false })
254+
255+
batchUpdater.scheduleUpdate('', '.empty { color: red; }')
256+
257+
expect(mockInsertFunction).toHaveBeenCalledWith('', '.empty { color: red; }')
258+
})
259+
260+
it('should handle empty CSS string', () => {
261+
configureStyleProcessing({ enableBatchUpdates: false })
262+
263+
batchUpdater.scheduleUpdate('test-class', '')
264+
265+
expect(mockInsertFunction).toHaveBeenCalledWith('test-class', '')
266+
})
267+
268+
it('should handle zero priority', () => {
269+
vi.useFakeTimers()
270+
configureStyleProcessing({ enableBatchUpdates: true })
271+
272+
batchUpdater.scheduleUpdate('test-class', '.test { color: red; }', 0)
273+
274+
vi.advanceTimersByTime(16)
275+
vi.runAllTimers()
276+
277+
expect(mockInsertFunction).toHaveBeenCalledWith('test-class', '.test { color: red; }')
278+
})
279+
280+
it('should handle negative priority', () => {
281+
vi.useFakeTimers()
282+
configureStyleProcessing({ enableBatchUpdates: true })
283+
284+
batchUpdater.scheduleUpdate('low', '.low { color: red; }', -1)
285+
batchUpdater.scheduleUpdate('high', '.high { color: blue; }', 1)
286+
287+
vi.advanceTimersByTime(16)
288+
vi.runAllTimers()
289+
290+
expect(mockInsertFunction).toHaveBeenNthCalledWith(1, 'high', '.high { color: blue; }')
291+
expect(mockInsertFunction).toHaveBeenNthCalledWith(2, 'low', '.low { color: red; }')
292+
})
293+
})
294+
295+
describe('clear functionality', () => {
296+
it('should clear all pending updates', () => {
297+
configureStyleProcessing({ enableBatchUpdates: true })
298+
299+
batchUpdater.scheduleUpdate('class1', '.class1 { color: red; }')
300+
batchUpdater.scheduleUpdate('class2', '.class2 { color: blue; }')
301+
302+
expect(batchUpdater.getPendingCount()).toBe(2)
303+
304+
batchUpdater.clear()
305+
306+
expect(batchUpdater.getPendingCount()).toBe(0)
307+
})
308+
309+
it('should prevent scheduled flush after clear', () => {
310+
vi.useFakeTimers()
311+
configureStyleProcessing({ enableBatchUpdates: true, batchDelay: 16 })
312+
313+
batchUpdater.scheduleUpdate('test-class', '.test { color: red; }')
314+
batchUpdater.clear()
315+
316+
vi.advanceTimersByTime(16)
317+
vi.runAllTimers()
318+
319+
expect(mockInsertFunction).not.toHaveBeenCalled()
320+
})
321+
})
322+
})

0 commit comments

Comments
 (0)