3
3
*/
4
4
5
5
import type { SuperscriptConfig , PatternSet } from './types'
6
- import { processText , needsProcessing } from './processor'
6
+ import { processText , needsProcessing , clearProcessingCaches } from './processor'
7
7
import { logger } from './logger'
8
8
import {
9
9
createFragmentFromParts ,
@@ -45,6 +45,39 @@ export function processTextNode(
45
45
return false
46
46
}
47
47
48
+ /**
49
+ * Create optimized TreeWalker filter
50
+ */
51
+ function createTextNodeFilter (
52
+ config : SuperscriptConfig ,
53
+ combinedPattern : RegExp ,
54
+ ) : NodeFilter {
55
+ // Pre-compile exclude check for better performance
56
+ const excludeSelectors = config . selectors . exclude
57
+
58
+ return {
59
+ acceptNode : ( node : Node ) : number => {
60
+ const text = node . textContent
61
+
62
+ // Fast early exit for empty nodes
63
+ if ( ! text || ! text . trim ( ) ) {
64
+ return NodeFilter . FILTER_REJECT
65
+ }
66
+
67
+ // Skip if parent is excluded (check once)
68
+ const parent = node . parentElement
69
+ if ( parent && shouldExcludeElement ( parent , excludeSelectors ) ) {
70
+ return NodeFilter . FILTER_REJECT
71
+ }
72
+
73
+ // Use cached pattern check
74
+ return needsProcessing ( text , combinedPattern )
75
+ ? NodeFilter . FILTER_ACCEPT
76
+ : NodeFilter . FILTER_REJECT
77
+ } ,
78
+ }
79
+ }
80
+
48
81
/**
49
82
* Process an element and its text nodes
50
83
*/
@@ -64,33 +97,12 @@ export function processElement(
64
97
return
65
98
}
66
99
67
- // Create tree walker to find text nodes
100
+ // Create optimized tree walker
68
101
logger . trace ( 'processElement called, combinedPattern:' , combinedPattern )
69
102
const walker = document . createTreeWalker (
70
103
element ,
71
104
NodeFilter . SHOW_TEXT ,
72
- {
73
- acceptNode : ( node : Node ) : number => {
74
- // Skip empty text nodes
75
- if ( ! node . textContent ?. trim ( ) ) {
76
- return NodeFilter . FILTER_REJECT
77
- }
78
-
79
- // Skip if parent is excluded
80
- const parent = node . parentElement
81
- if ( parent && shouldExcludeElement ( parent , config . selectors . exclude ) ) {
82
- return NodeFilter . FILTER_REJECT
83
- }
84
-
85
- // Check if text contains any patterns
86
- const textContent = node . textContent || ''
87
- if ( needsProcessing ( textContent , combinedPattern ) ) {
88
- return NodeFilter . FILTER_ACCEPT
89
- }
90
-
91
- return NodeFilter . FILTER_REJECT
92
- } ,
93
- } ,
105
+ createTextNodeFilter ( config , combinedPattern ) ,
94
106
)
95
107
96
108
// Collect nodes to process (avoid modifying during iteration)
@@ -116,6 +128,38 @@ export function processElement(
116
128
}
117
129
}
118
130
131
+ /**
132
+ * Batch process elements using requestAnimationFrame
133
+ */
134
+ function batchProcessElements (
135
+ elements : Element [ ] ,
136
+ config : SuperscriptConfig ,
137
+ patterns : PatternSet ,
138
+ combinedPattern : RegExp ,
139
+ batchSize = 10 ,
140
+ ) : void {
141
+ let index = 0
142
+
143
+ function processBatch ( ) {
144
+ const endIndex = Math . min ( index + batchSize , elements . length )
145
+
146
+ for ( let i = index ; i < endIndex ; i ++ ) {
147
+ processElement ( elements [ i ] , config , patterns , combinedPattern )
148
+ }
149
+
150
+ index = endIndex
151
+
152
+ if ( index < elements . length ) {
153
+ // Process next batch in next frame
154
+ requestAnimationFrame ( processBatch )
155
+ }
156
+ }
157
+
158
+ if ( elements . length > 0 ) {
159
+ requestAnimationFrame ( processBatch )
160
+ }
161
+ }
162
+
119
163
/**
120
164
* Process all matching elements in the document
121
165
*/
@@ -126,28 +170,41 @@ export function processContent(
126
170
) : void {
127
171
logger . info ( 'processContent called with selectors:' , config . selectors . include )
128
172
129
- // Process each include selector
173
+ const allElements : Element [ ] = [ ]
174
+
175
+ // Collect all elements first
130
176
config . selectors . include . forEach ( ( selector ) => {
131
177
try {
132
178
const elements = document . querySelectorAll ( selector )
133
179
if ( elements . length > 0 ) {
134
180
logger . debug ( `Found ${ elements . length } elements for selector "${ selector } "` )
181
+ allElements . push ( ...Array . from ( elements ) )
135
182
}
136
- elements . forEach ( ( element ) => {
137
- processElement ( element , config , patterns , combinedPattern )
138
- } )
139
183
}
140
184
catch ( error ) {
141
185
logger . warn ( `Invalid selector: ${ selector } ` , error )
142
186
}
143
187
} )
188
+
189
+ // Process in batches for better performance
190
+ if ( allElements . length > 20 ) {
191
+ // Use batching for large element counts
192
+ batchProcessElements ( allElements , config , patterns , combinedPattern )
193
+ }
194
+ else {
195
+ // Process immediately for small counts
196
+ allElements . forEach ( ( element ) => {
197
+ processElement ( element , config , patterns , combinedPattern )
198
+ } )
199
+ }
144
200
}
145
201
146
202
/**
147
203
* Initialize processing for navigation
148
204
*/
149
205
export function initializeForNavigation ( ) : void {
150
206
resetProcessingFlags ( )
207
+ clearProcessingCaches ( ) // Clear caches on navigation for fresh processing
151
208
}
152
209
153
210
/**
0 commit comments