Login Register

dojo.query: A CSS Query Engine For Dojo

Ever since we began the Dojo project, it's been our intention to collect the very best, most useful JavaScript and consolidate it into a single library that makes your applications better and easier to use. I'm excited to announce a new evolution in Dojo that continues this tradition: dojo.query

Over the last year jQuery, MochiKit, Prototype, and behavior.js have been emphasizing the importance of being able to easily query arbitrary HTML DOM structures via CSS-like syntax. The importance of these APIs hasn't been lost on us at Dojo, but the lack of efficiency in most of the available libraries gave us pause. We need a generalized query system that is both powerful yet fast enough to not endanger the user experience when used heavily.

It's an important goal, and ensuring that simple-looking queries don't "hurt" disproportionately is a difficult task. After some initial work with my admittedly grotty getElementsById hack, my outlook wasn't bright. Other systems weren't looking much better. Using the behavior: expression(); hack degrades page performance something fierce and can't be made synchronous, a key requirement to meet developer ease-of-use goals. Requiring a callback for every query result is a no-go.

Luckily Jack Slockum's recent work has pointed the way to a query system that is fast enough for heavy use without waiting on the browser vendors to get their act together and give us a compact, powerful, and fast selector system. As a piece of engineering work, DomQuery is a beauty. It's fast, small, and has few dependencies. Without Jack's excellent example, I might have left the idea of a query system for Dojo on the shelf even longer. In particular, DomQuery starts to address some of the largest issues I had identified when investigating getElementsById:

  • A generic system must have enough fast-path differentiation to keep queries that are known by webdevs to be fast running at something like "native" speed.
  • It is not clear that the IE team has any plans to embrace XPath over the HTML DOM despite its clear utility and proven value. Assuming that there will continue to be bifurcation in browser-provided fast-paths for query syntaxes, it's reasonable to assume that CSS style selectors will most likely be made available natively everywhere. So what's the right query language subset for a JS library to support?
  • Current systems get choked up on queries that potentially exhibit large "node blooms" in the intermediate phases of constructing the result set. Queries of the form "div div div" give most systems a hell of a time, reducing user trust in the system. Can we level out that anomaly?

The last part is critically important because it highlights how innocent looking queries can endanger the developer and user experience. Simple interfaces that allow you to hurt yourself easily may be worse than more complicated paths into the same functionality. API ease should scale with utility, but also with expected pain or speed tradeoffs. A dangerous API should look dangerous. The gauntlet is thus laid: can we go fast enough on hard queries to provide a trustable foundation for large-scale development?

Yes. We can.

With the recent DomQuery/jQuery debate as a backdrop, I started to investigate the relative performance of some of the more esoteric options. From caching behavior hacks on IE to XPath to getElementsById, everything was on the table. The results surprised me.

In many cases, the availability of XPath isn't necessarily a win until the complexity (node-bloom size) of a query outweighs what appears to be non-trivial startup costs for the query engine. On Firefox in particular, XPath queries can yield tremendous gains for generalized queries over all nodes with a particular attribute value or over queries that look like "div div div". Surprisingly, though, simpler queries such as "div div" may actually be at a loss to regular getElementsByTagName() attacks. Confoundingly, the results are also browser specific. Not all queries that run faster in XPath vs. DOM on Firefox run faster via XPath on Opera or the WebKit nightly builds. Nevertheless, for many of the hardest queries, deferring to XPath can yield results that are 2x-4x better than other approaches.

Of course, the 80% browser doesn't support XPath, but I'm personally over losing sleep to Microsoft's bad engineering and management decisions. If the IE team decides to get with the program and give us a good query engine, we'll use it. Until then, they get best effort. Their browser only deserves to look as good (or bad) as it really is, after all.

dojo.query

With those somewhat surprising findings in hand, it was time to build a better mouse trap. The result is dojo.query, an experimental module that is currently checked in to the Dojo trunk. The system has nearly zero Dojo dependencies and can therefore be safely dropped into older Dojo environments (perhaps even pre 0.4) without difficulty.

The API is straightforward:

// include the system
  dojo.require('dojo.query');

  // run some queries
  dojo.query('#id');
  dojo.query('div:first-child');
  dojo.query('code.example');
  dojo.query('.example');

All queries return an array of unique DOM nodes. Today this is just a "raw" array as I'm investigating what extensions make sense on the return object and what should instead be deferred to a layer on top of dojo.query such as dojo.behavior.

Today dojo.query only supports CSS 1-3 queries although support may be added for xpath-ish semantics if there's sufficient demand and I can justify the engineering time based on some hope that we can make what we do work on whatever fast path browsers eventually provide. The worst possible outcome would be to provided an interface that that browsers almost make obsolete.

How fast is "fast enough"?

Using the the test suite from DomQuery (but with a Dojo-based test runner system), we can show the relative performance of DomQuery, jQuery, and dojo.query. While clearly the slowest of the bunch, jQuery 1.1 provides the richest API today and there's no telling how providing a richer return object may affect the relative timings of dojo.query on the tests where all 3 systems are relatively close. In any case, these tests show raw performance of the node query engine and not much else. You can obviously write fast apps with a slow engine by using it less and write slow apps with a fast engine by writing obscene queries or doing expensive operations on the return.

But enough jibba-jabba. Gimmie data!:

IE 7:
# Test Iterations Dojo YUI.ext JQuery 1.1
1 div span span 50 360 431 1612
2 span span div 50 290 331 1432
3 #test-data2 span span div 50 80 70 90
4 h4 10 40 120 50
5 h4.thinger 10 61 71 80
6 .thinger 10 240 200 320
7 .thinger2 10 230 200 321
8 #test-data 50 20 20 10
9 div#test-data 50 20 40 0
10 span#test-data 50 10 80 20
11 #test-data span 50 160 320 151
12 #test-data pre code 50 40 20 40
13 #test-data pre > code 10 10 10 10
14 #test-data pre code span 10 30 70 140
15 #test-data pre > code > span 10 80 50 40
16 #test-data span.hl-code 50 241 251 201
17 #test-data pre.highlighted > code 50 50 30 10
18 #test-data span:first-child 50 240 170 201
19 #test-data span:last-child 50 211 211 300
20 #test-data span:empty 50 321 100 280
21 #test-data span:first 50 40 60 161
22 #test-data span:last 50 40 70 170
23 #test-data span.hl-code, #test-data span.hl-brackets 50 411 371 571
24 #test-data span:nth-child(2) 50 280 320 400
25 #test-data span:nth-child(3) 50 221 321 401
26 #test-data span:nth-child(0n+3) 50 250 4507 6853
27 #test-data span:nth-child(even) 10 130 70 1221
28 #test-data span:nth-child(2n) 10 170 902 1402
29 #test-data span:nth-child(odd) 10 121 70 1252
30 #test-data span:nth-child(2n+1) 10 120 892 1513
31 #test-data span:contains(new) 50 541 500 1543
32 #test-data span:not(span.hl-code) 10 120 120 110
33 #test-data :first-child 50 952 911 1944
34 #test-data span.hl-default 50 221 191 230
35 #test-data span:not(:first-child) 50 541 560 451
36 #test-data2 div:last-child 50 20 20 30
37 #test-data2 code#inner1 code#inner2 50 20 10 30
38 #test-data span.hl-default:not(:first-child) 50 320 190 290
39 #test-data span[title] 50 110 180 2004
40 #test-data span[title=east] 50 250 211 3386
41 #test-data span[title="east"] 50 200 221 3064
42 #test-data span[@title="east"] 50 230 200 681
43 #test-data span[title!=east] 50 500 520 3024
44 #test-data span[title^=min] 50 290 221 2983
45 #test-data span[title$=er] 50 271 200 2983
46 #test-data span[title*=in] 50 280 200 3014
IE 6
# Test Iterations Dojo YUI.ext JQuery 1.1
1 div span span 50 1102 831 2653
2 span span div 50 681 621 2363
3 #test-data2 span span div 50 161 200 251
4 h4 10 110 421 110
5 h4.thinger 10 160 210 271
6 .thinger 10 611 651 551
7 .thinger2 10 771 641 1012
8 #test-data 50 40 61 60
9 div#test-data 50 40 150 70
10 span#test-data 50 40 371 60
11 #test-data span 50 401 871 580
12 #test-data pre code 50 120 80 160
13 #test-data pre > code 10 40 20 50
14 #test-data pre code span 10 130 211 351
15 #test-data pre > code > span 10 220 170 171
16 #test-data span.hl-code 50 761 650 802
17 #test-data pre.highlighted > code 50 161 120 250
18 #test-data span:first-child 50 750 691 1302
19 #test-data span:last-child 50 681 710 1182
20 #test-data span:empty 50 962 541 972
21 #test-data span:first 50 110 271 751
22 #test-data span:last 50 91 260 762
23 #test-data span.hl-code, #test-data span.hl-brackets 50 1052 1283 1913
24 #test-data span:nth-child(2) 50 922 1032 1683
25 #test-data span:nth-child(3) 50 790 1032 1933
26 #test-data span:nth-child(0n+3) 50 640 10265 17520
27 #test-data span:nth-child(even) 10 340 151 3044
28 #test-data span:nth-child(2n) 10 470 2213 3966
29 #test-data span:nth-child(odd) 10 310 260 2784
30 #test-data span:nth-child(2n+1) 10 390 2303 4777
31 #test-data span:contains(new) 50 1110 1002 3755
32 #test-data span:not(span.hl-code) 10 371 461 311
33 #test-data :first-child 50 2725 3015 5599
34 #test-data span.hl-default 50 640 551 681
35 #test-data span:not(:first-child) 50 1272 1473 1462
36 #test-data2 div:last-child 50 100 100 261
37 #test-data2 code#inner1 code#inner2 50 70 100 341
38 #test-data span.hl-default:not(:first-child) 50 912 580 1193
39 #test-data span[title] 50 650 721 5740
40 #test-data span[title=east] 50 971 891 7926
41 #test-data span[title="east"] 50 1021 941 8604
42 #test-data span[@title="east"] 50 1051 901 2413
43 #test-data span[title!=east] 50 1452 1742 7450
44 #test-data span[title^=min] 50 1083 841 7762
45 #test-data span[title$=er] 50 1061 921 8463
46 #test-data span[title*=in] 50 982 891 8664
Firefox 2:
# Test Iterations Dojo YUI.ext JQuery 1.1
1 div span span 50 171 350 1302
2 span span div 50 90 351 1493
3 #test-data2 span span div 50 120 80 90
4 h4 10 20 70 40
5 h4.thinger 10 70 71 80
6 .thinger 10 50 230 461
7 .thinger2 10 50 220 681
8 #test-data 50 0 0 70
9 div#test-data 50 10 150 60
10 span#test-data 50 0 80 70
11 #test-data span 50 110 180 110
12 #test-data pre code 50 160 20 30
13 #test-data pre > code 10 30 0 0
14 #test-data pre code span 10 50 40 141
15 #test-data pre > code > span 10 40 40 40
16 #test-data span.hl-code 50 221 200 190
17 #test-data pre.highlighted > code 50 160 10 50
18 #test-data span:first-child 50 260 190 451
19 #test-data span:last-child 50 240 210 340
20 #test-data span:empty 50 321 211 180
21 #test-data span:first 50 20 30 220
22 #test-data span:last 50 20 40 130
23 #test-data span.hl-code, #test-data span.hl-brackets 50 441 360 491
24 #test-data span:nth-child(2) 50 240 300 611
25 #test-data span:nth-child(3) 50 200 440 501
26 #test-data span:nth-child(0n+3) 50 201 8392 8162
27 #test-data span:nth-child(even) 10 80 70 1332
28 #test-data span:nth-child(2n) 10 90 1733 1683
29 #test-data span:nth-child(odd) 10 70 61 1332
30 #test-data span:nth-child(2n+1) 10 90 1632 1603
31 #test-data span:contains(new) 50 311 292 1623
32 #test-data span:not(span.hl-code) 10 110 100 60
33 #test-data :first-child 50 1693 1342 1983
34 #test-data span.hl-default 50 210 171 270
35 #test-data span:not(:first-child) 50 431 411 362
36 #test-data2 div:last-child 50 0 10 70
37 #test-data2 code#inner1 code#inner2 50 160 0 50
38 #test-data span.hl-default:not(:first-child) 50 331 331 411
39 #test-data span[title] 50 190 160 1941
40 #test-data span[title=east] 50 191 170 3105
41 #test-data span[title="east"] 50 190 170 3065
42 #test-data span[@title="east"] 50 190 170 465
43 #test-data span[title!=east] 50 370 340 3025
44 #test-data span[title^=min] 50 200 140 3134
45 #test-data span[title$=er] 50 210 171 3124
46 #test-data span[title*=in] 50 220 170 3133
Safari 2.0:
# Test Iterations Dojo YUI.ext JQuery 1.1
1 div span span 50 864 1401 4952
2 span span div 50 844 1258 5477
3 #test-data2 span span div 50 291 356 707
4 h4 10 204 539 351
5 h4.thinger 10 293 359 500
6 .thinger 10 1011 840 1626
7 .thinger2 10 948 851 1626
8 #test-data 50 4 40 94
9 div#test-data 50 35 110 83
10 span#test-data 50 36 360 95
11 #test-data span 50 597 1323 997
12 #test-data pre code 50 95 77 348
13 #test-data pre > code 10 40 21 50
14 #test-data pre code span 10 141 290 361
15 #test-data pre > code > span 10 233 193 311
16 #test-data span.hl-code 50 843 889 1373
17 #test-data pre.highlighted > code 50 189 101 340
18 #test-data span:first-child 50 445 575 1303
19 #test-data span:last-child 50 449 585 1321
20 #test-data span:empty 50 1103 253 969
21 #test-data span:first 50 60 65 883
22 #test-data span:last 50 93 93 865
23 #test-data span.hl-code, #test-data span.hl-brackets 50 1571 1471 2775
24 #test-data span:nth-child(2) 50 467 728 1695
25 #test-data span:nth-child(3) 50 443 704 2028
26 #test-data span:nth-child(0n+3) 50 420 2950 31899
27 #test-data span:nth-child(even) 10 504 290 5006
28 #test-data span:nth-child(2n) 10 549 627 6330
29 #test-data span:nth-child(odd) 10 498 276 4906
30 #test-data span:nth-child(2n+1) 10 519 597 6399
31 #test-data span:contains(new) 50 846 799 5628
32 #test-data span:not(span.hl-code) 10 493 644 519
33 #test-data :first-child 50 2032 2712 7656
34 #test-data span.hl-default 50 721 641 1267
35 #test-data span:not(:first-child) 50 1092 2312 1779
36 #test-data2 div:last-child 50 106 120 375
37 #test-data2 code#inner1 code#inner2 50 81 100 382
38 #test-data span.hl-default:not(:first-child) 50 1047 682 1425
39 #test-data span[title] 50 299 617 17526
40 #test-data span[title=east] 50 983 698 33624
41 #test-data span[title="east"] 50 981 709 33776
42 #test-data span[@title="east"] 50 968 697 4476
43 #test-data span[title!=east] 50 1466 1901 33950
44 #test-data span[title^=min] 50 1336 725 33928
45 #test-data span[title$=er] 50 1939 758 33977
46 #test-data span[title*=in] 50 1341 701 33643
Webkit Nightly (aka, "Leopard Safari"):
# Test Iterations Dojo YUI.ext JQuery 1.1
1 div span span 50 113 170 516
2 span span div 50 119 165 515
3 #test-data2 span span div 50 33 54 128
4 h4 10 19 40 44
5 h4.thinger 10 45 59 70
6 .thinger 10 418 428 487
7 .thinger2 10 164 164 236
8 #test-data 50 2 16 24
9 div#test-data 50 5 31 26
10 span#test-data 50 1 54 22
11 #test-data span 50 66 125 144
12 #test-data pre code 50 14 31 82
13 #test-data pre > code 10 4 7 20
14 #test-data pre code span 10 17 25 59
15 #test-data pre > code > span 10 32 27 50
16 #test-data span.hl-code 50 866 906 973
17 #test-data pre.highlighted > code 50 41 37 120
18 #test-data span:first-child 50 80 101 226
19 #test-data span:last-child 50 79 102 234
20 #test-data span:empty 50 132 74 183
21 #test-data span:first 50 20 43 145
22 #test-data span:last 50 19 37 146
23 #test-data span.hl-code, #test-data span.hl-brackets 50 1069 1113 1417
24 #test-data span:nth-child(2) 50 92 148 340
25 #test-data span:nth-child(3) 50 83 136 245
26 #test-data span:nth-child(0n+3) 50 78 1143 1772
27 #test-data span:nth-child(even) 10 42 31 306
28 #test-data span:nth-child(2n) 10 45 260 375
29 #test-data span:nth-child(odd) 10 39 30 307
30 #test-data span:nth-child(2n+1) 10 50 237 360
31 #test-data span:contains(new) 50 149 154 535
32 #test-data span:not(span.hl-code) 10 219 218 67
33 #test-data :first-child 50 383 385 867
34 #test-data span.hl-default 50 551 570 655
35 #test-data span:not(:first-child) 50 169 231 293
36 #test-data2 div:last-child 50 17 31 92
37 #test-data2 code#inner1 code#inner2 50 10 44 136
38 #test-data span.hl-default:not(:first-child) 50 611 589 680
39 #test-data span[title] 50 76 95 2600
40 #test-data span[title=east] 50 115 118 6782
41 #test-data span[title="east"] 50 110 131 6888
42 #test-data span[@title="east"] 50 118 124 362
43 #test-data span[title!=east] 50 199 203 6641
44 #test-data span[title^=min] 50 134 113 6316
45 #test-data span[title$=er] 50 157 119 6060
46 #test-data span[title*=in] 50 134 112 5734
Opera 9:
# Test Iterations Dojo YUI.ext JQuery 1.1
1 div span span 50 100 250 800
2 span span div 50 50 191 742
3 #test-data2 span span div 50 50 40 100
4 h4 10 20 40 20
5 h4.thinger 10 30 30 50
6 .thinger 10 70 110 151
7 .thinger2 10 70 101 220
8 #test-data 50 0 0 10
9 div#test-data 50 0 30 0
10 span#test-data 50 0 80 20
11 #test-data span 50 70 140 110
12 #test-data pre code 50 60 30 50
13 #test-data pre > code 10 20 10 20
14 #test-data pre code span 10 20 30 60
15 #test-data pre > code > span 10 20 20 30
16 #test-data span.hl-code 50 131 130 200
17 #test-data pre.highlighted > code 50 80 50 70
18 #test-data span:first-child 50 30 20 150
19 #test-data span:last-child 50 0 20 130
20 #test-data span:empty 50 0 20 130
21 #test-data span:first 50 10 40 90
22 #test-data span:last 50 30 40 90
23 #test-data span.hl-code, #test-data span.hl-brackets 50 260 231 381
24 #test-data span:nth-child(2) 50 40 40 110
25 #test-data span:nth-child(3) 50 50 100 140
26 #test-data span:nth-child(0n+3) 50 50 891 1171
27 #test-data span:nth-child(even) 10 10 20 210
28 #test-data span:nth-child(2n) 10 0 190 220
29 #test-data span:nth-child(odd) 10 10 20 231
30 #test-data span:nth-child(2n+1) 10 0 171 230
31 #test-data span:contains(new) 50 81 110 482
32 #test-data span:not(span.hl-code) 10 50 80 30
33 #test-data :first-child 50 180 280 510
34 #test-data span.hl-default 50 120 101 170
35 #test-data span:not(:first-child) 50 160 130 160
36 #test-data2 div:last-child 50 0 0 30
37 #test-data2 code#inner1 code#inner2 50 30 10 50
38 #test-data span.hl-default:not(:first-child) 50 200 100 220
39 #test-data span[title] 50 30 20 1944
40 #test-data span[title=east] 50 40 30 4456
41 #test-data span[title="east"] 50 40 50 4287
42 #test-data span[@title="east"] 50 20 10 250
43 #test-data span[title!=east] 50 130 130 4006
44 #test-data span[title^=min] 50 60 20 3885
45 #test-data span[title$=er] 50 40 40 3996
46 #test-data span[title*=in] 50 40 40 4136

So how'd we do? Generally, dojo.query is in the hunt with DomQuery or edges it out for raw DOM iteration. Like Jack's system dojo.query beats jQuery most of the time. dojo.query also shines on Firefox and Opera where asking questions of the DOM that would result in large "node blooms" can be sidestepped with XPath. Queries like ".thinger" and "div span span" help show how dramatic the difference can be.

Lest anyone get the wrong idea about all the "invalid" attribute tests in jQuery toward the bottom, the syntax used in the tests is generally the CSS selector variant of something that jQuery can probably support in another form. In most cases it would be possible to re-write the test to use a jQuery friendly selector to achieve the same thing.

Interestingly, it's clear that Opera and the Webkit Nightlies beat IE and Firefox for raw query performance across the board. DOM or XPath, ripping through node collections on Opera is blindingly fast. The currently shipping Safari is a dog, though. IE 6 also comes in with numbers that make IE 7's relatively pokey turnout look stellar. At somewhere between 2x and 3x faster, IE 7 is quite the improvement. It might even be something that Microsoft could crow about if IE's Open Source competition didn't make it look so feeble. Firefox isn't the fastest of the lot, but it still takes IE 7 to the woodshed on nearly every test. The gloves will really come off when the Webkit nightlies finally ship in official form.

Running all 3 libraries through the test system created by John Resig, we get results which are broadly in line. You can try them out for yourself. These larger/longer tests are here or on John's smaller test suite is here. Be warned that the test pages pull in a lot of script and do a lot of work. They can easily bog down your box for a good long while.

Getting The Code

dojo.query isn't going to be part of the 0.4.x line, and it's marked experimental for the time being. While I believe it to be correct for the cases it handles, the devil in systems like this is in the details. Furthermore, I'd like it to give more explicit guidance about when it encounters queries that it can't handle before calling it production ready.

You can get a ShrinkSafe'd copy of the library here but be sure to check back with what's in SVN frequently to get the latest, most correct version.

Also, remember that you can include a file like this with either a <script> tag somewhere after your main dojo.js file or with the usual dojo.require syntax. Either way, dropping dojo.query into your app is a snap.

It's Closures All The Way Down

I've kind of glossed over the details of how to make a system like this scream. Having a goal is the first step and being data-driven in optimization is the second. Jack Slockum's excellent DomQuery does a masterful job in reducing dominating factors. From not allocating intermediate arrays in hard cases to "compiling" down functions for future use on a particular query, DomQuery sets the bar very high. In many cases, matching or beating Jack's system proved to be very difficult and for some of the newer code in dojo.query (e.g., attribute selectors) there's still some way to go. With iterative development to tune performance a must, a couple of basic design decisions made evolving the system to meet performance targets easier.

At it's core, dojo.query uses closures as a way to keep query parsing from happening more than once. Instead of naively parsing and running the query each time or even creating a custom function from a string, dojo.query builds up a set of filtering functions in a functional programming style. The result is a system that can count on closure scope for caching intermediate variables of all sorts. It may not be a huge performance win, but the system "feels" more like idiomatic JavaScript and it's always possible to get line numbers when debugging problems. Explicit caching happens only at a coarse level. Full queries and filtering functions for query parts are the biggest recipients of direct memoization. Having the rest of the system be composed of functions that just generate functions simplifies the system greatly. Which is good, because adding another branch (XPath) adds some complexity.

As noted earlier, XPath turns out not to be a "win" on every query, even on the browsers where it can make a huge difference. To handle this, dojo.query adds a heuristic layer to the first-run parsing to figure out whether or not a query can and should be deferred to XPath. Because we can't plan for every query "shape" in the pre-processor and still be space-efficient, the heuristic is rather rough but in the future it will likely improve to handle cases where today it "punts" to regular DOM iteration. This kind of primordial "query optimizer" is likely going to be the focus of some continued development effort since anything we can improve in its heuristic is potentially a huge gain on browsers that give us a fast path. At the very least, the heuristic/optimizer layer gives us a straightforward place to plug in new back-ends when better browser fast-paths emerge or when the relative XPath/DOM performance metrics change between revs of the same browser. For instance, while the Webkit nightlies are very fast and support XPath, regular-old DOM traversal is relatively much faster on that browser than XPath. The optimizer layer lets us take that into account and do the "Right Thing" more often.

While there is some thrill in the hunt of building systems like this that are fast enough to enable fundamental changes in the way Dojo developers work, I wish it weren't necessary. The browser vendors are continuing to let us down, and every year that goes by without fast-path DOM queries, a unified behavior layer, real and portable parametric CSS, socket-like network connections, and unified 2D graphics support is opportunity lost. Sadly, while the IE team shoulders much of the blame for not shipping many of these features in any form, they're not alone in their culpability. I'm proud of dojo.query, but I wish I didn't have to be. Yet another chunk of useful javascript that will be shipped around the web ad-infinitum instead of being baked into the platform itself should make everyone sad.

Lastly, research and development for dojo.query is thanks to SitePen. If you like what Dojo and dojo.query are about, you'll love what SitePen can do (and help you do), for your applications.

Good work! I'm pleased to

Good work! I'm pleased to see people pushing the limits on selector speeds. Do you support additional combinators like "+" and "~"? One complaint, the following selectors are not valid in any language: #test-data span:first #test-data span:last #test-data span[@title="east"] #test-data span[title!=east] Although I like the last one a lot. :-)

This is great stuff. The

This is great stuff. The "coop-etition" environment of today's javascript development is providing us with quality tools, earlier. Alex, I wish I'd mentioned you by name in my recent post ( http://blog.wioota.com/2007/01/21/javascript-the-quickening/ ) for your incredible contributions to the js community and the effect it has on accelerating development overall. We should also remember John Resig's initial benchmark; I wouldn't be surprised to see a response from him! :)

Hey Dean, The "+" and "~"

Hey Dean, The "+" and "~" selectors totally slipped my mind. I'll add them tonight. As for the other ones, I'm fine with implementing them since I think we can always transform them into something fast-path-ish. All the other query systems support them, but I'm willing to entertain a solid case for removing them if you think they're dangerous. Regards

[...] I finally blogged the

[...] I finally blogged the work I’ve been doing on dojo.query. As you can guess, it’s a CSS query engine for Dojo, and as you can probably also guess, it’s fast. Or at least as fast as you can make a system that needs to hoist itself by its own petard thanks to the network latency of sending the darned thing down the wire every time. As web developers continue to expect ever more out of their tools, it’s becoming clear to me that the dynamics of the “Ajax platform” are a deck that’s heavily stacked against novices to the advantage of experts. For a long time we’ve been trying with Dojo to help tilt that balance back in favor of those who haven’t co-evolved their skills to suit the peccadilloes and vagaries of the web. But I don’t know that we (or anyone else) is really succeeding at it. The “Other Language to JavaScript” compiler crowd is building a very leaky abstraction that is going to take years to get right. Meanwhile, folks trying to do “something interesting” tend to look at the current state of play in pure-browser apps and run headlong into the arms of Macrodobe or Microsoft. Flex is a surprisingly easy sell for organizations that don’t have the health of the web in mind. [...]

Alex, Congrats on the great

Alex, Congrats on the great work. The results speak for themselves. I definitely know what a great feeling it is when you get it all working and the results are as fast as you hoped they would be. Now, I have a few comments. I hope this is taken the right way, it is in no way meant to criticize your code. I think that to be fair when publishing benchmarks that the playing field be level. This is one reason in my DomHelper benchmarks I didn't use buffering (supported by DomHelper, but not by the other libs) and with DomQuery I made the effort to implement the key missing things mentioned by John that gave DomQuery an unfair advantage. I hope you will do the same. 1. dojo.query uses temporary expandos/attributes to do node filtering as does DomQuery. This nets a big performance boost. However, unlike DomQuery dojo.query doesn't do cleanup. It leaves the temp attributes on the nodes. This means one less pass on all nodesets. Is this a big deal? I guess it depends on the developer. Personally, I felt that if I was going to set temp attributes, doing a clean up run was required. 2. dojo.query always uses expandos, as DomQuery used to. Switching from using expandos to conditional functions did net a slower time (about 10%) but allowed DomQuery to support XML documents in IE (which jQuery also supports). This is something I'm sure you will want to support anyway. 3. The closure idea used in dojo.query is actually how I initially created DomQuery. However, I found two big problems with it. The first was chaining selectors slows down because it adds a lot of loops and conditionals in the actual processing code (vs once at compilation time). The second it it doesn't allow for developer defined pseudos, matchers or operators. Speaking of that, I noticed that dojo.query does not support chaining of filters at all. This was also the case in the first pass of DomQuery. As John said when I made my initial post, to be fair in benchmarks I think it should support it. This also slowed down the initial DomQuery code about 10%. On a parting note, since some of your logic looks eerily similar to it's DomQuery counterpart, I am wondering if we can make this a true community effort and I can borrow some of your ideas as well? In particular, I'd like to use the XPath logic in DomQuery for complex selectors. ;) Warm Regards, Jack

Hey Jack, Thanks for taking

Hey Jack, Thanks for taking the time to comment. A couple of responses. 1.) as for expandos, they are on names that aren't likely to collide and I've actually made sure to not disturb the DOM structure as much as DomQuery does. I would be willing to change the code were the criticism that the name is likely to collide with something else or that there is a real-world case where it causes trouble. I wasn't able to come up with such a case when I was working on the system and so I've left the expandos there. I'd suspect that DomQuery could get something of a boost if it did the same and I see no reason for it not to. 2.) I'm pretty sure I don't want to support XML. It's a brain-dead format and every system that can give you a pure XML DOM can already give you an XPath or XSLT transform of the same. As always, I'm open to debate on this point, but similarly to #1, I haven't found a good enough reason to do it to sink the man hours. 3.) I think we'll be able to add developer-created matchers and pseudos. The guts of support for that are already in the attribute matching code and refactoring the pseudos code for it is straightforward. Instead of having a single function that accepts a node, the registered handlers just need to have functions that will return functions. We don't need more iteration to make it happen. As for filter chaining, it's a known bug that I'll be addressing shortly. Lastly, you're always welcome to use our code and ideas under the terms of our licenses, which are 100% compatible with yours. Feel free to borrow whatever you like. As you may know, all Dojo code is covered not only by our licenses (BSD and AFL) but also comes with some assurance of provenance. You can borrow Dojo code and rest assured that it's not going bite you later. Regards

[...] dojo.foo »

[...] dojo.foo » dojo.query: A CSS Query Engine For Dojo [...]

Alex, my beef with the made

Alex, my beef with the made up selectors is that they are bad from an educational perspective. New developers might believe that they are valid CSS and code them into style sheets. I'm not sure that you get any additional power from mixing XPath and CSS syntax and I can only see it leading to confusion.

[...] Alex Russell unveils

[...] Alex Russell unveils dojo.query, the latest gauntlet tossed down in the fetching-elements-by-CSS-selector wars. I’m working on a similar overhaul for Prototype which I’ll talk more about when the time is right. [...]

Thanks for your response

Thanks for your response Alex. 1. Leaving the expandos behind doesn't seem like a big deal. I added an option to DomQuery for doing clean up on expandos/attributes, this way the developer can decide. The speed boost was more noticable in FF than IE. 2. XML is a pretty common data format, but that's your decision. When creating generic models and data sources, I find the power of some xpath or selectors great. Allowing a developer to say "my data comes from the XML document at this url and it matches xyz format (selectors)" it pretty powerful. Since grids and forms in Ext allow XML loading, I must support it. I did want to find a way to make this optional as well so it doesn't affect the benchmarks (and overall performance), so I've add some detection and branching for MSXML documents. I am a big fan of benchmarking as you've probably guessed. I've taken the tests you have here and put them on my site with the modifications to level the field. The tests are pretty close in most spots, with dojo.query leading queries in FF that it uses xpath (e.g. div span div). Getting the new Ext code is easy (it's only including yui + ext-core.js) so if you would update your copy here I would appreciate it. I can send you a copy too. When you do update dojo.query to support chained selectors (e.g. input[type=checkbox][value=foo]), I will update my copy as well. Your Test: http://www.yui-ext.com/playpen/selectors/dojo/ jQuery test with dojo added: http://www.yui-ext.com/playpen/selectors/jquery/ If I do use your XPath idea in DomQuery, I will definitely add a comment at the top of it to note where the idea came from. It would be nice if dojo.query did the same, since it uses some ideas originally found in DomQuery.

Would be nice to see a

Would be nice to see a comment from John Resig (jQuery)

Alex/Jack - you can avoid

Alex/Jack - you can avoid expandos on IE by using either the sourceIndex or uniqueID DOM properties.

Another thing to think

Another thing to think about. You are not parsing out strings. Using the following markup: A bookmark the query a[href^="#"] fails for all three libraries. Parsing CSS selectors *accurately* is hard. You will have to sacrifice some speed if you want to match all selectors.

The sample markup above

The sample markup above should read: <a href="#bookmark">A bookmark</a>

Dean, Thanks for the tip on

Dean, Thanks for the tip on the expandos. I knew about uniqueId (but didn't use it because I wasn't sure what the result would be putting duplicate values in it). sourceIndex sounds interesting though. About the selector parsing, DomQuery has no problem parsing that selector. However, you won't get the results you expect on that query because browsers do different things with the href attribute of links (e.g. FF expands them to a fully qualified url so it will never match anything). If you want to test what I am saying, try # in a different attribute and it will match just fine.

Sorry Jack, I tested on IE

Sorry Jack, I tested on IE not FF. You're right, you seem to be parsing the selector correctly. If you want to fix the HREF bug in IE take a look at the code for cssQuery which handles DOM properties correctly. The specific code is something like this: element.getAttribute('href', 2); Reference: http://www.glennjones.net/Post/809/getAttributehrefbug.htm

I think the comments show

I think the comments show how important collaboration is on something like this, and how the innovation on CSS selector queries accelerates as the number of implementations increases. To elaborate on my pingback: here's the message I posted to the Prototype Core mailing list a few weeks ago: It shows a very early version of the overhauled $$ function I've been working on. Like you, Alex, I took Jack's approach and added an XPath layer, but in several instances traded performance for lines of code. This evening I'll look more closely at dojo.query to see where my code can be improved.

Whoops. Here's the link:

It seems that each developer

It seems that each developer can prove his results are the best. I've just compared the following sites: http://john.jquery.com/speed/ (comparing jQold and jQuery as delivered by JResig - obviously new jQuery won, second column) http://john.jquery.com/speed/yui.html (comparing jQuery and DomQuery as delivered by JResig - obviously jQuery won, first column) http://www.yui-ext.com/playpen/selectors/jquery/ (comparing jQuery, DomQuery and Dojo as delivered by JSlocum - obviously DomQuery won, second column) http://dojotoolkit.org/~alex/query/bench/query_lib/jquery_test_page.html (comparing jQuery, DomQuery and Dojo as delivered by ARussel - obviously Dojo won, third column) Remarks: 1. It seems that JSlocum and ARussel uses somehow outdated version of jQuery for comparison (is it unfair?) 2. It seems that JResig has improved his timings in the meantime (is it surprise?) 3. From unknown reasons, ARussel has redefined meaning and succession of some tests (is it intentional?) 4. It seems that actual JResig results are in most cases best of all. 5. I've performed all tests on Windows XP with IE6.

Krzystof: I used the latest

Krzystof: I used the latest public versions of each toolkit that I could find at the time. I even spent some time reworking the test pages in the middle of my development to use newer versions of each. If jQuery or DomQuery have been updated, I'll update my tests with them. That said, test bias is a huge problem with these systems. The DOM structures used in the other toolkits test pages don't have anything like a reasonable number of chaff nodes to indicate real-world performance, so the selector_test.php file we're using generates several hundred more chaff nodes in the tested element to ensure some semblance of real-worldness about the structures in question. Regards

Alex, I agree with you. Your

Alex, I agree with you. Your tests seem more meaningful in that you are processing a large number of nodes, equivalent to what you get on a normal page. The other tests I've seen operate on tiny DOMs and are not reflective of the real world.

Hi Alex. Congats on these

Hi Alex. Congats on these stellar results. You guys are certainly some of the most talented developers out there. I know John has contacted you off-list about standardizing the tests in some way. Its great that you're all for that as it will help give a better representation of performance and also help each project work on any bottlenecks. We're hoping Dean, Jack & Andrew will join in and help the jQuery & Dojo teams define the tests guidelines. Rey Bango jQuery Project Team

One thing I like about

One thing I like about cssQuery over all other DOM Query engines, is the ability to add a second argument in the form of an DOM node that allows any querying to start from then and there. I try to use IDs sparingly and especially when looping through a bunch of list items or whatever, it's nice to be able to do something like (a very simple example): var nodeIWant = cssQuery( 'a span', liNode )[0]; It would be great if other DOM Querying engines added this functionality as well!

Christos: Good news!

Christos: Good news! dojo.query already supports exactly that style of call. You can scope any dojo.query() call by passing a document or node element as the second param. IIRC, DomQuery might also include something similar. Regards

Christos, Unless I'm not

Christos, Unless I'm not understanding a subtle distinction, you can do the same thing in jQuery like this: var NodeIWant = $('a span', 'li')[0] You're selecting for the span, right?

I think the same but..

I think the same, please let me know if there is something i'm missing.

Liz poemas

Rey: I'm hopeful that we

Rey: I'm hopeful that we can get some good, meaningful tests in place instead of the highly artificial things we're all running our libraries against right now. It's great that you guys are organizing this. A credible test suite is going to help library authors and browser vendors alike. Regards

It's not that hard to parse

It's not that hard to parse CSS selectors accurately and quickly. You just need a three or four simple regular expressions. http://www.unfortunately.themoon.co.uk/javascript/selector/selector.js By the way, I make no claims for the speed or correctness of my little effort. But the parsing technique might be of interest.

Christos, I think everyone

Christos, I think everyone supports passing in a context node. DomQuery does as well.

[...] Kurze Zusammenfassung:

[...] Kurze Zusammenfassung: Dojo kann jetzt auch Queries. Der Rest: die üblichen Anfeindungen mit der mangelhaften Dojo-Dokumentation. [...]

[...] dojo

[...] dojo �바스�립트 툴킷� dojo.query�는 dojo를 위한 CSS Query Engine� 공개했다. CSS Query Engine�란 HTML DOM structures를 CSS 선��로 접근할 수 있�� 하여, 보다 쉽고 빠르며 효율�으로 DOM� �용할 수 있는 방법� 제공한다. [...]

Thanks for that

Thanks for that peoples! Should probably look at the actual code next time instead of just the tests and examples. :P

dojo.query: un moteur de

dojo.query: un moteur de requête CSS pour dojo Alex Russel a publié sur le blog dojo.foo un article au sujet du nouveau moteur de requête CSS qu'il vient de développer pour dojo. L"idée est de mettre à disposition du développeur utilisant le toolkit une facon simple et performante de...

[...] dojo.foo »

[...] dojo.foo » dojo.query: A CSS Query Engine For Dojo (tags: dojo javascript optimization query library) [...]

> All the other query

> All the other query systems support them, but > I’m willing to entertain a solid case for removing > them if you think they’re dangerous. They are indeed dangerous. Like Dean said any unexperienced developer might regard them as valid selectors and will therefore be badly educated. But even worse, the w3c web-api is working on the Selectors API http://www.w3.org/TR/selectors-api/ which will support some method like document.getElementBySelector or whatever, the name is being debated. Future versions of dojo.query might first check for this method for performance, and use the current implementation as fallback. This will obviously cause big problems when applications use invalid selectors which dojo.query interprets as valid, and some browser vendor first implements the Selectors API. The application will start to misbehave ! If Mozilla or FF misbehaved, most likely the webmaster you happily fix the problem, but for minority browser like Opera and Safari the problem would persist and the webmaster would use the same old excuse that those browsers have little access. It all boils down to prevent problems and keep interoperability high. Last but not least, good work :)

Alex, I looked at the

Alex, I looked at the "#test-data :first-child" test in Gecko, and it seems that almost all the time is spent performing security checks. Now the way security checks are handled could use improving, but I suspect the issue is the use of eval() -- it's pretty significant in terms of the security-check costs it imposes.

Hey Boris, We're not using

Hey Boris, We're not using eval(), that I know of, in Dojo's query code. Other ideas? Regards

[...] Alex Russell (Dojo and

[...] Alex Russell (Dojo and SitePen) has written a fascinating piece on the performance of CSS query engines as he introduces us all to dojo.query the latest in the pack, and one that looks to start well out of the gate. [...]

[...] dojo.query: A CSS

[...] dojo.query: A CSS Query Engine For Dojo For hard-core JavaScripters, the research (and associated benchmarks) that Dojo’s Alex Russell did to create a performant CSS DOM query system is fascinating. Alex is right, though: this shouldn’t be necessary, and browser vendors continue to let us down. (tags: javascript) [...]

you are right Christos

when i started with DOJO i had same issues width the examples. I think that the best way to lear DOJO is to put "hands on". Step by Step you will see that you are going to understand the DOJO logic.
Congratulations

Ramon
-------------------------------------------
Letras

first time I saw DOJO, I

first time I saw DOJO, I thought "oh my god..." ;) but now i love it...
great work...

nitro

jQuery 1.1.3: 800%+ Faster, still 20KB

misleading title...

800%+ faster than its previous version in DOM traversal...
Probably should be more of a topic in itself, not a comment to a blog entry but whatever.

-Karl

No need to require dojo.query

In 0.9 and SVN, the query is built-in already.

404

Hello RG,

Your url is returning 404:
http://jquery.com/blog/2007/07/01/jquery-113-800-faster-still-20kb/

Could u please take a look!

Update: it works now, and i solve my new Yellow Pages website. I had javascript error in my code... Fixed now.. thanks

Thanks