Skip to content

Commit 5c6e2cd

Browse files
authored
perf: dedupe calls to arena.create_node() (#21)
Roughly 4% uncompressed JS file savings. before ``` > @projectwallace/css-parser@0.6.2 build > vite build && tsc --project tsconfig.build.json vite v7.2.2 building client environment for production... ✓ 15 modules transformed. dist/tokenize.js 0.33 kB │ gzip: 0.23 kB dist/index.js 2.70 kB │ gzip: 0.95 kB dist/parse-value.js 8.25 kB │ gzip: 1.80 kB dist/parse-anplusb.js 8.85 kB │ gzip: 1.62 kB dist/parse.js 16.02 kB │ gzip: 3.20 kB dist/lexer-CtBKgfVv.js 17.16 kB │ gzip: 2.94 kB dist/parse-atrule-prelude.js 18.80 kB │ gzip: 3.10 kB dist/css-node-aIMm9_Cb.js 23.50 kB │ gzip: 5.54 kB dist/parse-selector.js 27.00 kB │ gzip: 4.71 kB ✓ built in 123ms ~/www/css-parser main ❯ du -sh dist 204K dist ``` after ``` > @projectwallace/css-parser@0.6.2 build > vite build && tsc --project tsconfig.build.json vite v7.2.2 building client environment for production... ✓ 15 modules transformed. dist/tokenize.js 0.33 kB │ gzip: 0.23 kB dist/index.js 2.70 kB │ gzip: 0.95 kB dist/parse-value.js 8.25 kB │ gzip: 1.80 kB dist/parse-anplusb.js 8.85 kB │ gzip: 1.62 kB dist/parse-atrule-prelude.js 16.01 kB │ gzip: 2.87 kB dist/parse.js 16.02 kB │ gzip: 3.20 kB dist/lexer-CtBKgfVv.js 17.16 kB │ gzip: 2.94 kB dist/parse-selector.js 23.44 kB │ gzip: 4.53 kB dist/css-node-aIMm9_Cb.js 23.50 kB │ gzip: 5.54 kB ✓ built in 120ms ~/www/css-parser dedupe-create-node-calls ❯ du -sh dist 196K dist ```
1 parent 3d3ad96 commit 5c6e2cd

File tree

3 files changed

+62
-189
lines changed

3 files changed

+62
-189
lines changed

src/parse-atrule-prelude.ts

Lines changed: 24 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,16 @@ export class AtRulePreludeParser {
9797
return nodes
9898
}
9999

100+
private create_node(type: number, start: number, end: number): number {
101+
let node = this.arena.create_node()
102+
this.arena.set_type(node, type)
103+
this.arena.set_start_offset(node, start)
104+
this.arena.set_length(node, end - start)
105+
this.arena.set_start_line(node, this.lexer.token_line)
106+
this.arena.set_start_column(node, this.lexer.token_column)
107+
return node
108+
}
109+
100110
private is_and_or_not(str: string): boolean {
101111
if (str.length > 3 || str.length < 2) return false
102112
return str_equals('and', str) || str_equals('or', str) || str_equals('not', str)
@@ -152,20 +162,11 @@ export class AtRulePreludeParser {
152162

153163
if (this.is_and_or_not(text)) {
154164
// Logical operator
155-
let op = this.arena.create_node()
156-
this.arena.set_type(op, NODE_PRELUDE_OPERATOR)
157-
this.arena.set_start_offset(op, this.lexer.token_start)
158-
this.arena.set_length(op, this.lexer.token_end - this.lexer.token_start)
159-
this.arena.set_start_line(op, this.lexer.token_line)
165+
let op = this.create_node(NODE_PRELUDE_OPERATOR, this.lexer.token_start, this.lexer.token_end)
160166
components.push(op)
161167
} else {
162168
// Media type: screen, print, all
163-
let media_type = this.arena.create_node()
164-
this.arena.set_type(media_type, NODE_PRELUDE_MEDIA_TYPE)
165-
this.arena.set_start_offset(media_type, this.lexer.token_start)
166-
this.arena.set_length(media_type, this.lexer.token_end - this.lexer.token_start)
167-
this.arena.set_start_line(media_type, this.lexer.token_line)
168-
this.arena.set_start_column(media_type, this.lexer.token_column)
169+
let media_type = this.create_node(NODE_PRELUDE_MEDIA_TYPE, this.lexer.token_start, this.lexer.token_end)
169170
components.push(media_type)
170171
}
171172
} else {
@@ -177,11 +178,7 @@ export class AtRulePreludeParser {
177178
if (components.length === 0) return null
178179

179180
// Create media query node
180-
let query_node = this.arena.create_node()
181-
this.arena.set_type(query_node, NODE_PRELUDE_MEDIA_QUERY)
182-
this.arena.set_start_offset(query_node, query_start)
183-
this.arena.set_length(query_node, this.lexer.pos - query_start)
184-
this.arena.set_start_line(query_node, query_line)
181+
let query_node = this.create_node(NODE_PRELUDE_MEDIA_QUERY, query_start, this.lexer.pos)
185182

186183
// Append components as children
187184
for (let component of components) {
@@ -216,11 +213,7 @@ export class AtRulePreludeParser {
216213
let feature_end = this.lexer.token_end // After ')'
217214

218215
// Create media feature node
219-
let feature = this.arena.create_node()
220-
this.arena.set_type(feature, NODE_PRELUDE_MEDIA_FEATURE)
221-
this.arena.set_start_offset(feature, feature_start)
222-
this.arena.set_length(feature, feature_end - feature_start)
223-
this.arena.set_start_line(feature, feature_line)
216+
let feature = this.create_node(NODE_PRELUDE_MEDIA_FEATURE, feature_start, feature_end)
224217

225218
// Store feature content (without parentheses) in value fields, trimmed
226219
let trimmed = trim_boundaries(this.source, content_start, content_end)
@@ -261,19 +254,11 @@ export class AtRulePreludeParser {
261254

262255
if (this.is_and_or_not(text)) {
263256
// Logical operator
264-
let op = this.arena.create_node()
265-
this.arena.set_type(op, NODE_PRELUDE_OPERATOR)
266-
this.arena.set_start_offset(op, this.lexer.token_start)
267-
this.arena.set_length(op, this.lexer.token_end - this.lexer.token_start)
268-
this.arena.set_start_line(op, this.lexer.token_line)
257+
let op = this.create_node(NODE_PRELUDE_OPERATOR, this.lexer.token_start, this.lexer.token_end)
269258
components.push(op)
270259
} else {
271260
// Container name or other identifier
272-
let name = this.arena.create_node()
273-
this.arena.set_type(name, NODE_PRELUDE_IDENTIFIER)
274-
this.arena.set_start_offset(name, this.lexer.token_start)
275-
this.arena.set_length(name, this.lexer.token_end - this.lexer.token_start)
276-
this.arena.set_start_line(name, this.lexer.token_line)
261+
let name = this.create_node(NODE_PRELUDE_IDENTIFIER, this.lexer.token_start, this.lexer.token_end)
277262
components.push(name)
278263
}
279264
}
@@ -282,11 +267,7 @@ export class AtRulePreludeParser {
282267
if (components.length === 0) return []
283268

284269
// Create container query node
285-
let query_node = this.arena.create_node()
286-
this.arena.set_type(query_node, NODE_PRELUDE_CONTAINER_QUERY)
287-
this.arena.set_start_offset(query_node, query_start)
288-
this.arena.set_length(query_node, this.lexer.pos - query_start)
289-
this.arena.set_start_line(query_node, query_line)
270+
let query_node = this.create_node(NODE_PRELUDE_CONTAINER_QUERY, query_start, this.lexer.pos)
290271

291272
// Append components as children
292273
for (let component of components) {
@@ -332,11 +313,7 @@ export class AtRulePreludeParser {
332313
let feature_end = this.lexer.token_end
333314

334315
// Create supports query node
335-
let query = this.arena.create_node()
336-
this.arena.set_type(query, NODE_PRELUDE_SUPPORTS_QUERY)
337-
this.arena.set_start_offset(query, feature_start)
338-
this.arena.set_length(query, feature_end - feature_start)
339-
this.arena.set_start_line(query, feature_line)
316+
let query = this.create_node(NODE_PRELUDE_SUPPORTS_QUERY, feature_start, feature_end)
340317

341318
// Store query content in value fields, trimmed
342319
let trimmed = trim_boundaries(this.source, content_start, content_end)
@@ -353,11 +330,7 @@ export class AtRulePreludeParser {
353330
let text = this.source.substring(this.lexer.token_start, this.lexer.token_end)
354331

355332
if (this.is_and_or_not(text)) {
356-
let op = this.arena.create_node()
357-
this.arena.set_type(op, NODE_PRELUDE_OPERATOR)
358-
this.arena.set_start_offset(op, this.lexer.token_start)
359-
this.arena.set_length(op, this.lexer.token_end - this.lexer.token_start)
360-
this.arena.set_start_line(op, this.lexer.token_line)
333+
let op = this.create_node(NODE_PRELUDE_OPERATOR, this.lexer.token_start, this.lexer.token_end)
361334
nodes.push(op)
362335
}
363336
}
@@ -379,11 +352,7 @@ export class AtRulePreludeParser {
379352
let token_type = this.lexer.token_type
380353
if (token_type === TOKEN_IDENT) {
381354
// Layer name
382-
let layer = this.arena.create_node()
383-
this.arena.set_type(layer, NODE_PRELUDE_LAYER_NAME)
384-
this.arena.set_start_offset(layer, this.lexer.token_start)
385-
this.arena.set_length(layer, this.lexer.token_end - this.lexer.token_start)
386-
this.arena.set_start_line(layer, this.lexer.token_line)
355+
let layer = this.create_node(NODE_PRELUDE_LAYER_NAME, this.lexer.token_start, this.lexer.token_end)
387356
nodes.push(layer)
388357
} else if (token_type === TOKEN_COMMA) {
389358
// Skip comma separator
@@ -407,11 +376,7 @@ export class AtRulePreludeParser {
407376
if (this.lexer.token_type !== TOKEN_IDENT) return []
408377

409378
// Create identifier node
410-
let ident = this.arena.create_node()
411-
this.arena.set_type(ident, NODE_PRELUDE_IDENTIFIER)
412-
this.arena.set_start_offset(ident, this.lexer.token_start)
413-
this.arena.set_length(ident, this.lexer.token_end - this.lexer.token_start)
414-
this.arena.set_start_line(ident, this.lexer.token_line)
379+
let ident = this.create_node(NODE_PRELUDE_IDENTIFIER, this.lexer.token_start, this.lexer.token_end)
415380

416381
return [ident]
417382
}
@@ -494,12 +459,7 @@ export class AtRulePreludeParser {
494459
}
495460

496461
// Create URL node
497-
let url_node = this.arena.create_node()
498-
this.arena.set_type(url_node, NODE_PRELUDE_IMPORT_URL)
499-
this.arena.set_start_offset(url_node, url_start)
500-
this.arena.set_length(url_node, url_end - url_start)
501-
this.arena.set_start_line(url_node, url_line)
502-
462+
let url_node = this.create_node(NODE_PRELUDE_IMPORT_URL, url_start, url_end)
503463
return url_node
504464
}
505465

@@ -547,11 +507,7 @@ export class AtRulePreludeParser {
547507
}
548508

549509
// Create layer node
550-
let layer_node = this.arena.create_node()
551-
this.arena.set_type(layer_node, NODE_PRELUDE_IMPORT_LAYER)
552-
this.arena.set_start_offset(layer_node, layer_start)
553-
this.arena.set_length(layer_node, layer_end - layer_start)
554-
this.arena.set_start_line(layer_node, layer_line)
510+
let layer_node = this.create_node(NODE_PRELUDE_IMPORT_LAYER, layer_start, layer_end)
555511

556512
// Store the layer name (content inside parentheses), trimmed
557513
if (content_length > 0) {
@@ -604,11 +560,7 @@ export class AtRulePreludeParser {
604560
}
605561

606562
// Create supports node
607-
let supports_node = this.arena.create_node()
608-
this.arena.set_type(supports_node, NODE_PRELUDE_IMPORT_SUPPORTS)
609-
this.arena.set_start_offset(supports_node, supports_start)
610-
this.arena.set_length(supports_node, supports_end - supports_start)
611-
this.arena.set_start_line(supports_node, supports_line)
563+
let supports_node = this.create_node(NODE_PRELUDE_IMPORT_SUPPORTS, supports_start, supports_end)
612564

613565
return supports_node
614566
}

src/parse-selector.test.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ describe('parse_selector() function', () => {
4848
const classNode = firstSelector?.first_child
4949

5050
expect(classNode?.type).toBe(NODE_SELECTOR_CLASS)
51-
expect(classNode?.name).toBe('my-class')
51+
expect(classNode?.name).toBe('.my-class')
5252
})
5353

5454
it('should parse ID selector', () => {
@@ -57,7 +57,7 @@ describe('parse_selector() function', () => {
5757
const idNode = firstSelector?.first_child
5858

5959
expect(idNode?.type).toBe(NODE_SELECTOR_ID)
60-
expect(idNode?.name).toBe('my-id')
60+
expect(idNode?.name).toBe('#my-id')
6161
})
6262

6363
it('should parse compound selector', () => {
@@ -167,7 +167,7 @@ describe('SelectorParser', () => {
167167
const child = arena.get_first_child(selectorWrapper)
168168
expect(arena.get_type(child)).toBe(NODE_SELECTOR_CLASS)
169169
expect(getNodeText(arena, source, child)).toBe('.my-class')
170-
expect(getNodeContent(arena, source, child)).toBe('my-class')
170+
expect(getNodeContent(arena, source, child)).toBe('.my-class')
171171
})
172172

173173
it('should parse ID selector', () => {
@@ -185,7 +185,7 @@ describe('SelectorParser', () => {
185185
const child = arena.get_first_child(selectorWrapper)
186186
expect(arena.get_type(child)).toBe(NODE_SELECTOR_ID)
187187
expect(getNodeText(arena, source, child)).toBe('#my-id')
188-
expect(getNodeContent(arena, source, child)).toBe('my-id')
188+
expect(getNodeContent(arena, source, child)).toBe('#my-id')
189189
})
190190

191191
it('should parse universal selector', () => {
@@ -243,7 +243,7 @@ describe('SelectorParser', () => {
243243
expect(arena.get_type(children[0])).toBe(NODE_SELECTOR_TYPE)
244244
expect(getNodeText(arena, source, children[0])).toBe('div')
245245
expect(arena.get_type(children[1])).toBe(NODE_SELECTOR_CLASS)
246-
expect(getNodeContent(arena, source, children[1])).toBe('container')
246+
expect(getNodeContent(arena, source, children[1])).toBe('.container')
247247
})
248248

249249
it('should parse element with ID', () => {
@@ -260,7 +260,7 @@ describe('SelectorParser', () => {
260260
expect(children).toHaveLength(2)
261261
expect(arena.get_type(children[0])).toBe(NODE_SELECTOR_TYPE)
262262
expect(arena.get_type(children[1])).toBe(NODE_SELECTOR_ID)
263-
expect(getNodeContent(arena, source, children[1])).toBe('app')
263+
expect(getNodeContent(arena, source, children[1])).toBe('#app')
264264
})
265265

266266
it('should parse element with multiple classes', () => {
@@ -274,11 +274,11 @@ describe('SelectorParser', () => {
274274
expect(children).toHaveLength(4)
275275
expect(arena.get_type(children[0])).toBe(NODE_SELECTOR_TYPE)
276276
expect(arena.get_type(children[1])).toBe(NODE_SELECTOR_CLASS)
277-
expect(getNodeContent(arena, source, children[1])).toBe('foo')
277+
expect(getNodeContent(arena, source, children[1])).toBe('.foo')
278278
expect(arena.get_type(children[2])).toBe(NODE_SELECTOR_CLASS)
279-
expect(getNodeContent(arena, source, children[2])).toBe('bar')
279+
expect(getNodeContent(arena, source, children[2])).toBe('.bar')
280280
expect(arena.get_type(children[3])).toBe(NODE_SELECTOR_CLASS)
281-
expect(getNodeContent(arena, source, children[3])).toBe('baz')
281+
expect(getNodeContent(arena, source, children[3])).toBe('.baz')
282282
})
283283

284284
it('should parse complex compound selector', () => {
@@ -293,9 +293,9 @@ describe('SelectorParser', () => {
293293
expect(arena.get_type(children[0])).toBe(NODE_SELECTOR_TYPE)
294294
expect(getNodeText(arena, source, children[0])).toBe('div')
295295
expect(arena.get_type(children[1])).toBe(NODE_SELECTOR_CLASS)
296-
expect(getNodeContent(arena, source, children[1])).toBe('container')
296+
expect(getNodeContent(arena, source, children[1])).toBe('.container')
297297
expect(arena.get_type(children[2])).toBe(NODE_SELECTOR_ID)
298-
expect(getNodeContent(arena, source, children[2])).toBe('app')
298+
expect(getNodeContent(arena, source, children[2])).toBe('#app')
299299
})
300300
})
301301

@@ -634,7 +634,7 @@ describe('SelectorParser', () => {
634634
})
635635

636636
it('should parse attribute with uppercase case-insensitive flag', () => {
637-
const { arena, rootNode, source } = parseSelectorInternal('[type="text" I]')
637+
const { arena, rootNode } = parseSelectorInternal('[type="text" I]')
638638

639639
expect(rootNode).not.toBeNull()
640640
if (!rootNode) return
@@ -646,7 +646,7 @@ describe('SelectorParser', () => {
646646
})
647647

648648
it('should parse attribute with whitespace before flag', () => {
649-
const { arena, rootNode, source } = parseSelectorInternal('[type="text" i]')
649+
const { arena, rootNode } = parseSelectorInternal('[type="text" i]')
650650

651651
expect(rootNode).not.toBeNull()
652652
if (!rootNode) return
@@ -658,7 +658,7 @@ describe('SelectorParser', () => {
658658
})
659659

660660
it('should parse attribute without flag', () => {
661-
const { arena, rootNode, source } = parseSelectorInternal('[type="text"]')
661+
const { arena, rootNode } = parseSelectorInternal('[type="text"]')
662662

663663
expect(rootNode).not.toBeNull()
664664
if (!rootNode) return
@@ -1274,7 +1274,7 @@ describe('SelectorParser', () => {
12741274

12751275
const child = arena.get_first_child(selectorWrapper)
12761276
expect(arena.get_type(child)).toBe(NODE_SELECTOR_CLASS)
1277-
expect(getNodeContent(arena, source, child)).toBe('my-class-123')
1277+
expect(getNodeContent(arena, source, child)).toBe('.my-class-123')
12781278
})
12791279

12801280
it('should parse hyphenated element names', () => {
@@ -1310,7 +1310,7 @@ describe('SelectorParser', () => {
13101310

13111311
const child = arena.get_first_child(selectorWrapper)
13121312
expect(arena.get_type(child)).toBe(NODE_SELECTOR_CLASS)
1313-
expect(getNodeContent(arena, source, child)).toBe('block__element--modifier')
1313+
expect(getNodeContent(arena, source, child)).toBe('.block__element--modifier')
13141314
})
13151315

13161316
it('should parse Bootstrap-style selector', () => {

0 commit comments

Comments
 (0)