Skip to content

Commit f6349e7

Browse files
authored
perf: make is_vendor_prefixed() lazy evaluation (#57)
1 parent 9990be8 commit f6349e7

File tree

4 files changed

+33
-23
lines changed

4 files changed

+33
-23
lines changed

src/css-node.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,11 @@ import {
4040
FLAG_IMPORTANT,
4141
FLAG_HAS_ERROR,
4242
FLAG_HAS_BLOCK,
43-
FLAG_VENDOR_PREFIXED,
4443
FLAG_HAS_DECLARATIONS,
4544
FLAG_HAS_PARENS,
4645
} from './arena'
4746

48-
import { CHAR_MINUS_HYPHEN, CHAR_PLUS, is_whitespace, str_starts_with } from './string-utils'
47+
import { CHAR_MINUS_HYPHEN, CHAR_PLUS, is_whitespace, is_vendor_prefixed, str_starts_with } from './string-utils'
4948
import { parse_dimension } from './parse-utils'
5049

5150
// Type name lookup table - maps numeric type to CSSTree-compatible strings
@@ -312,9 +311,28 @@ export class CSSNode {
312311
return this.arena.has_flag(this.index, FLAG_IMPORTANT)
313312
}
314313

315-
// Check if this has a vendor prefix (flag-based for performance)
314+
// Check if this has a vendor prefix (computed on-demand)
316315
get is_vendor_prefixed(): boolean {
317-
return this.arena.has_flag(this.index, FLAG_VENDOR_PREFIXED)
316+
switch (this.type) {
317+
case DECLARATION:
318+
// Check property name (e.g., -webkit-transform)
319+
return is_vendor_prefixed(this.name)
320+
case PSEUDO_CLASS_SELECTOR:
321+
case PSEUDO_ELEMENT_SELECTOR:
322+
// Check pseudo-class/element name without colons (e.g., -webkit-autofill, -webkit-scrollbar)
323+
return is_vendor_prefixed(this.name)
324+
case AT_RULE:
325+
// Check at-rule name (e.g., -webkit-keyframes from @-webkit-keyframes)
326+
return is_vendor_prefixed(this.name)
327+
case FUNCTION:
328+
// Check function name (e.g., -webkit-gradient from -webkit-gradient())
329+
return is_vendor_prefixed(this.name)
330+
case IDENTIFIER:
331+
// Check identifier value (e.g., -webkit-sticky)
332+
return is_vendor_prefixed(this.text)
333+
default:
334+
return false
335+
}
318336
}
319337

320338
// Check if this node has an error

src/parse-selector.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import {
1515
NESTING_SELECTOR,
1616
NTH_OF_SELECTOR,
1717
LANG_SELECTOR,
18-
FLAG_VENDOR_PREFIXED,
1918
FLAG_HAS_PARENS,
2019
ATTR_OPERATOR_NONE,
2120
ATTR_OPERATOR_EQUAL,
@@ -46,7 +45,6 @@ import {
4645
import { skip_whitespace_forward, skip_whitespace_and_comments_forward, skip_whitespace_and_comments_backward } from './parse-utils'
4746
import {
4847
is_whitespace,
49-
is_vendor_prefixed,
5048
str_equals,
5149
CHAR_PLUS,
5250
CHAR_TILDE,
@@ -686,10 +684,6 @@ export class SelectorParser {
686684
// Content is the pseudo name (without colons)
687685
this.arena.set_content_start_delta(node, this.lexer.token_start - start)
688686
this.arena.set_content_length(node, this.lexer.token_end - this.lexer.token_start)
689-
// Check for vendor prefix and set flag if detected
690-
if (is_vendor_prefixed(this.source, this.lexer.token_start, this.lexer.token_end)) {
691-
this.arena.set_flag(node, FLAG_VENDOR_PREFIXED)
692-
}
693687
return node
694688
} else if (token_type === TOKEN_FUNCTION) {
695689
// Pseudo-class function like :nth-child()
@@ -744,11 +738,6 @@ export class SelectorParser {
744738
// This allows formatters to distinguish :lang() from :hover
745739
this.arena.set_flag(node, FLAG_HAS_PARENS)
746740

747-
// Check for vendor prefix and set flag if detected
748-
if (is_vendor_prefixed(this.source, func_name_start, func_name_end)) {
749-
this.arena.set_flag(node, FLAG_VENDOR_PREFIXED)
750-
}
751-
752741
// Parse the content inside the parentheses
753742
if (content_end > content_start) {
754743
// Check if this is an nth-* pseudo-class

src/parse.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
BLOCK,
1111
FLAG_IMPORTANT,
1212
FLAG_HAS_BLOCK,
13-
FLAG_VENDOR_PREFIXED,
1413
FLAG_HAS_DECLARATIONS,
1514
} from './arena'
1615
import { CSSNode } from './css-node'
@@ -27,7 +26,6 @@ import {
2726
TOKEN_DELIM,
2827
TOKEN_AT_KEYWORD,
2928
} from './token-types'
30-
import { is_vendor_prefixed } from './string-utils'
3129
import { trim_boundaries } from './parse-utils'
3230

3331
export interface ParserOptions {
@@ -322,11 +320,6 @@ export class Parser {
322320
this.arena.set_content_start_delta(declaration, 0)
323321
this.arena.set_content_length(declaration, prop_end - prop_start)
324322

325-
// Check for vendor prefix and set flag if detected
326-
if (is_vendor_prefixed(this.source, prop_start, prop_end)) {
327-
this.arena.set_flag(declaration, FLAG_VENDOR_PREFIXED)
328-
}
329-
330323
// Track value start (after colon, skipping whitespace)
331324
let value_start = this.lexer.token_start
332325
let value_end = value_start

src/string-utils.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,17 @@ export function str_index_of(str: string, searchChar: string): number {
162162
* - `--custom-property` → false (CSS custom property)
163163
* - `border-radius` → false (doesn't start with hyphen)
164164
*/
165-
export function is_vendor_prefixed(source: string, start: number, end: number): boolean {
165+
// Overload signatures
166+
export function is_vendor_prefixed(text: string): boolean
167+
export function is_vendor_prefixed(source: string, start: number, end: number): boolean
168+
// Implementation
169+
export function is_vendor_prefixed(source: string, start?: number, end?: number): boolean {
170+
// Handle string-only overload
171+
if (start === undefined || end === undefined) {
172+
start = 0
173+
end = source.length
174+
}
175+
166176
// Must start with a hyphen
167177
if (source.charCodeAt(start) !== CHAR_MINUS_HYPHEN) {
168178
return false

0 commit comments

Comments
 (0)