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 hel