Node.js, YUI 3 & Dom Manipulation… Oh My!

By YUI TeamApril 9th, 2010

Update from the previous article: YUI 3 no longer runs in the global scope. I have made some adjustments to my nodejs-yui3 project to allow YUI 3 to run fully as a proper non-global module.

Early this week I gave you a peek at running YUI 3 on the server with Node.js. Now I’m back to tell you what I have been up to over the last week or so. A couple of weeks ago I stumbled across a project on GitHub called jsdom by Elijah Insua (@tmpvar). jsdom describes itself as:

[A] CommonJS implementation of the DOM intended to be platform-independent and as minimal/light as possible while completely adhering to the w3c DOM specifications.

That sounded interesting to me, so I forked the repository and started playing around with it to see what I could get it to do. To my surprise, it just worked. And because it’s written in JavaScript, it was easy to start hacking and adding new features. The only issue I could see was that it’s a pure DOM layer, lacking some BOM features that are necessary for most common web application development. So I set out to add the features I would need to run YUI 3 against jsdom. After a couple of days of hacking, I had a baseline implementation of jsdom that supports almost all of YUI 3’s needs.

Getting YUI 3 running with jsdom

I started by just including jsdom and setting up the “fake” document object. At this point I could load our core YUI 3 DOM functionality, but the Event module would’t load. That’s when I noticed that some key BOM abstractions were missing — for example, Event needs some kind of a window object. So I created a “fake” window object to match my “fake” document and things started to load. As I said in the previous article, YUI 3’s module system is fantastic and makes this kind of work much easier to do.

Most scripts access document and window directly. YUI 3 doesn’t do that; instead, we have references to the active document/window in a config attached to each YUI instance. These can be accessed by Y.config.doc and All YUI 3 modules adhere to this practice (and we strongly recommend you do the same in your YUI 3 Gallery modules or your own bespoke components).

Some might think this is a little excessive, but I’ve already used this feature in my early work on Editor for YUI 3. I am able to create a YUI instance and have it bound to an iframe’s window/document. This means that I can run Selector and Event inside the iframe without having to load YUI inside that document. On the server, this makes even more sense. You may have several documents open in the same process, but your YUI 3 instances only need to know about the document they’re using.

To support this work, I’ve created a new YUI 3 module called nodejs-dom. This module will include the proper libraries, if available, and set up the YUI instance with a document and window reference. Along with the configuration, it will create a new object on the instance called Browser. Since all YUI 3 module use Node and Node uses Y.config.doc, you shouldn’t need to do anything else to make YUI 3 code work on the server. But if you’re working with older JavaScript and need to access the document, window, location or navigator objects, they’re all available on the Browser object. Here’s a quick view of what the Y.Browser object looks like:

   { userAgent: 'Node.js (darwin; U; rv:0.1.33)'
   , appVersion: '0.1.33'
   , platform: 'darwin'
, window: 
   { screenTop: 0
   , pageYOffset: 0
   , screenY: 0
   , navigator: [Circular]
   , innerHeight: 768
   , pageXOffset: 0
   , screenLeft: 0
   , screenX: 0
   , innerWidth: 1024
   , length: 1
   , scrollY: 0
   , outerHeight: 768
   , contentWindow: [Circular]
   , frames: [ [Circular], [length]: 1 ]
   , setInterval:  [Function]
   , name: 'nodejs'
   , scrollX: 0
   , document: '#DOCUMENT'
   , outerWidth: 1024
   , setTimeout: { [Function]
   , location: { href: '/Users/davglass/.node_libraries/browser.js' }
, self: [Circular]
, document: [Circular]
, location: [Circular]

innerHTML support

Since innerHTML is not in the DOM Level1 spec, it’s not in jsdom. This was a requirement for me, so I needed to find a solution. I found a project on GitHub called node-htmlparser and it claimed to be able to parse HTML, including malformed syntax. I forked it and made some code changes, cleaned up the syntax and fixed a couple of issues. I’d recommend using my fork if you’re following along at home; I know my fork will work and I’ll continue to maintain it as long as needed. Eventually someone will write a parser based on @izs‘s sax-js module.

Let’s see some code

This is a very simple hello world example:

YUI().use('nodejs-dom', 'event', 'node', function(Y) {
    var document = Y.Browser.document;
    document.title = 'This is a test';
    var i = Y.Node.create('<i>Test This</i>');

    var div = document.createElement('div'); = 'foo';
    div.innerHTML = '<em id="foo">Test</em> this <strong id="bax">awesome!</strong>';
    var foo ='#foo');

The above code will return this snippet of HTML:

    <title>This is a test</title>
    <i class="foo">Test This</i>
    <div id="foo" class="bar">
      <em id="foo">Test</em> this <strong id="bax">awesome!</strong>

Is that what I think it is?

That’s the most common question I’ve received when showing demos of this stuff. The answer: YES, this is what you think it is: a full document rendered on the server by writing standard JavaScript against standard DOM and BOM APIs. I have several examples of its use in the GitHub project. These examples include rendering YUI 3 Tabviews, Sliders and Overlays. Using the 2 in 3 project I was also able to render a YUI 2 Calendar and Layout Manager.


I tossed up a couple of the examples from my git repo so you can see them in action:

The Calendar demo is designed to show an example of progressive enhancement by using YUI to generate the Calendar on the server and provide static navigation to selected days and months. This implementation uses the same JavaScript to generate the server-side view as you would use on the client to render it in pure JavaScript. There is intentionally no client side JavaScript. Think of this as the baseline you’d use for progressive enhancement, rendering a fully-functional DOM on the server side to provide functionality to clients with no JavaScript support.

The second example shows how you can mix and match what is server data and what is client data. If I have a true MVC framework, which YUI 3 provides, I can separate my data (JSON) from my widget templates (DHTML) and from page templates (static HTML). The example shows how you can use the same data but access it from 3 different places to get only the parts you want.

I hope you see the power here that I see, including a possible future free of context switching and free of writing multiple levels of rendering code in the various levels of an application to support progressive enhancement. Enjoy! (@davglass)


  1. Totally blown away. This is some seriously cool stuff.

  2. Whoa, that’s some interesting stuff you got here :) Also, maybe libxmljs will be faster for HTML parsing as it’s written in C instead of JS.

  3. This looks fantastic! I’ll have to take a closer look at it tonight to see what I can get nodejs-dom to work with–this could open up quite a few doors for templating and such. :D

  4. Please explain to me the real-world use case of server-side generation of html from javascript.
    Why and when would this be useful?

  5. @Lauren Thanks

    @Kuroki Was looking at that, but it’s not stable at the moment.

    @Stephen Awesome, can’t wait to see what people do with this stuff.

  6. @Dmitri Snytkine

    Well, html fragments come to mind. Using a templating language that loads an external template file can be a bit much when all you want to do is, for example; generate a link.

    It’s probably safe to assume the ‘href’ attribute is present, but you may or may not want to make use of ‘rel’, ‘title’, ‘class’, ‘id’, etc.

    To make a template with options for each of those will generally require a whole bunch of if statements, when you could easily just generate an element with some like jquery in this way;

    var fragment = $(”).attr(‘href’, ‘’).attr(‘id’, ‘someid’).toString();

    It’d take at least 5 times as much code and somewhat more time to achieve the same result without using DOM interactions.

  7. @Dmitri The real world use cases for this is:

    1. Progressive enhancement. Take the calendar example, you already have a fully functional calendar with YUI, but to make it work on the server you have to write another calendar in, say PHP, then you have 2x the code to maintain.

    2. Same language == less code. If my code on the server can also run on the client, then my code base is now drastically smaller.

    3. Faster turn around. Now that code on the server and on the client are nearly identical, I can spend half the time building something because I don’t need to build anything twice. Form validation is a big one. You have a form on the client that needs validation, but it needs the same validation on the server. Why write it twice in two different languages when you can do it once?

    Does that help explain it a little better?

  8. @Stephen Nice comment ;)

  9. Dave and Stephen thanks for your explanation.
    Regarding the calendar example i wonder how this compares to server side libraries?
    In developers can use so called controls which already take care of the client-side part.
    I understand the advantage for companies who create their own solutions (like yahoo with yui) but i am not sure if there are advantages for smaler webfirms.

    May i ask you to give another example how smaller firms could benfit from this as well?

    Thanks and best regards

  10. fyi there’s also a pure js window object to go along with jsdom @

  11. @rdzah It’s not complete or functional yet. It seems a little overboard to make a full window object when it’s not really needed, yet. My “fake” window will do most anything that is needed to make existing JS work ;)

  12. SiggyTheViking said:
    April 9, 2010 at 9:23 pm


    You rock. Really.

    But I have to say, did you really just publish example code that includes two separate elements with the same ID?


  13. SiggyTheViking said:
    April 9, 2010 at 9:24 pm


    Sorry I misspelled your name, Dav.

  14. Good stuff… but… Aptana’s Jaxer has this in spades (plus seamless E4X support)…

    why not fork Jaxer on github (it’s open source now) and get it running on node.js… then ALLLLLL this stuff is just free (and then some).

    BTW: to those not sure about “why this is useful”… why is PHP useful? Same… exact… use case. The values of server-side js (SSJS), however, mean we can start seeing 100% parity between code across app boundaries… and if you can’t stretch your mind far enough to see values in that, well… sorry for’ya :)

    Good stuff here (once again).

  15. nevermind (kind of)… Jaxer’s DOM/BOM/E4X/XHTML is derived directly from Spidermonkey, whereas node.js is V8 only (therefore no native E4X yet).

    Still… someone with know-how should be able to pull a ton of valuable working stuff out of the Jaxer project, if even just from a conceptual standpoint.

    Porting Spidermonkey/Tracemonkey to a V8/node.js context sound feasible?

  16. Man, i keep not making 100% sense here.

    I suppose it’s simply a question of “node.js / this handrolling approach” VS. :Jaxer/Spidermonkey”

    Eventually the node.js handrolling guys probably win when someone publishes a collection of modules that match Jaxer’s capabilities but with the value-adds that come from the node.js/CommonJS/JSGI ecosystem.

    And ok, I’m done for now. Good stuff.

  17. @SiggyTheViking —

    Actually, that (dup id) was on purpose to show that it works as expected ;)

  18. Hey Dav,

    Besides the one bug, what other issues did you find and fix in node-htmlparser? If there are more, I’d gladly accept a pull req or patch.

    Would you mind taking a look at the latest node-htmlparser? It is now at v1.0+, supports streaming, is about 40% faster than the version you forked, and I will definitely be supporting it into the future since it is the core of my current project.

  19. How does this perform? Have you ran your basic test through a benchmark util and compared it to just spitting out strings of HTML? Just curious as to whether or not this would be viable in a high traffic site.

  20. Ben —

    I haven’t done any formal benchmarking yet, since this is a proof of concept and I spent a little under a week on it ;)

    I do, however, have that on my list..

  21. Awesome awesome awesome stuff! Forking and cloning right now!

  22. None of the examples above works. The error is:
    502 Bad Gateway

  23. @Teodor —

    They are up now, my hosting provider rebooted my slice’s host and the node processes didn’t come up with the machine..

  24. Hi Dav,

    I have gone through this but couldn’t get the ‘nodejs-dom’ package for using it in my application.

    I have the following scenario.

    I have a simple HTML page where I have added YUI 3 and in a iframe I have the src of the iframe set to some other html page. Now I want to add the event delegation to the page being displayed in the iframe, so that, I can handle a click event of the page displayed in iframe in the parent html page.

    Seems to be possible according to your post. Will you please explain how.

    Thanks and regards.

  25. Phil Thpmpson said:
    June 2, 2010 at 3:20 pm

    Thank you for the comment, “Same language == less code”. That really made it click for me. I’ve just been looking at Node.js and seen jsdom before. We don’t use YUI (jQuery at the moment). So addingWould the same principles apply? Could I add node-htmlparser and your fake Browser to get the same result? Could use some pointers.

    Not only does this solve a code duplication issue but I’m thinking about it in terms of testing JS code. Something which we as company and the world in general seem to still be grappling with.

    With regards to performance, As I understand it due to Node.js being evented wouldn’t that make it pretty fast or at least scalable?

  26. @Shankha —

    Sorry, I didn’t get an email on your post. This version doesn’t support iframes or events on the server side.

  27. @Phil —

    Theoretically jQuery should run under JSDom with my node-htmlparser packages. I have not tested it as I know little to nothing about jQuery.

    I do know that from a sandbox point of view (new document per request) that it may be difficult to run a library that is not designed for configuring a document and window to an instance of itself. This is where YUI 3 really shines. Each new YUI instance (one per request) get’s it’s own document and window bound to it and it alone. Then you don’t have to worry about mixing data on multiple requests.

    One of the reasons I started working on this was to add in server-side test suites. However, it won’t be a substitute for a real browser test.

  28. Thanks for your efforts Dav.

    I am trying this out but I am getting a “ReferenceError: “document” is not defined.” error. With debugging enabled I can see that it’s occurring just after “event-delegate-debug.js” is loaded. I’ve tried running the examples as well and they have the same error.

    Any suggestions?


  29. @Peter, are you using the latest code from GitHub or the latest release? I just tested it on the latest from GitHub and all the examples still work.

    Feel free to drop me a note (davglass [at] gmail [dot] com) off this post if you are still having issues.

  30. Alan Hollis said:
    August 8, 2010 at 6:32 am


    Just tried checking this out from git. A few of the paths have changed on the external modules meaning the readme in git doesn’t work completely.

    I’m also getting

    # WARNING: node-htmlparser could not be found.
    # Element.innerHTML setter support has been disabled
    # Element.innerHTML getter support will still function
    # Download:

    When running y-server-template.js.
    Also noticed that one of the paths was point to a directory which had in your name in it. Unfortunately I can’t remember exactly where it was now.

    Apologies if there’s a better place to post this. I’m currently just messing around on a Sunday afternoon :).

  31. @Alan — You should my forks of jsdom and node-htmlparser as they are known to work with this example.

  32. @Dav

    Cheers I didn’t realise you had a fork of jsdom too. It was the browser.js module which confused me a little. There are now two separate modules which browser.js requires htmltodom and domtohtml.

    browser.js now also lets you pass in the html parser you wish to use in the .windowAugmentation call. Once I did that everything just worked.

    This was my first experience with node.js and yui so I was a little slower at working things out than I should have been.

    I appreciate your time in creating these samples, they helped me learn a great deal.

    Thanks again,


  33. Jorge Ventura said:
    December 2, 2010 at 7:32 pm

    I read this article and have a question: May I use this stuffs as a headless browser ?

    For example the code bellow will works ?

    YUI().use(‘nodejs-dom’, ‘event’, ‘node’, function(Y) {
    var document = Y.Browser.document;
    document.location = ‘http://www.ajax_application.any';

    I have a complex application that I would like to scrape using something like this. Right now I am using Envjs but I know that I will have performance penalties in production and I would like to get a better option.


  34. Jorge Ventura said:
    December 3, 2010 at 5:19 pm

    Should be


  35. @Jorge —

    You can access the content of a remote page like this:

    However, you can not guarantee that the remote page will execute Javascript and you will not have access to the page to “click links” or interact with it. It will merely be a local modifiable copy of the requested page.

  36. Jorge Ventura said:
    December 4, 2010 at 11:36 am

    I was checking your code and I think that I can complete some parts. My purpose is to scrape always the same application, I don’t care about images, CSS and the user interface (buttons, click e etc), I need only to generate the DOM structure correctly.

    About the scripts, I can run the code, after load to complete the DOM inside the page. I have to implement the methods that make changes in the DOM as document.write and document.writeln.

    Maybe necessary to implement and the Browser must have a window list because the scripts at run time will demand for new windows.

    Do you have any suggestion to make my life easier?

    Thank you,

  37. Dav

    I’m running your express server example on node v 0.3.2 (the example that combines everything with a left nav). It fails on the first set title call as “cannot call set method of null”.

    I can run the individual calendar and tabview examples, but not the datatable example or the scrape example.

    I guess anywhere I get the YUI object as a callback param is where its failing right now

    Any ideas/gotchas? Is this node 0.3 issue?

  38. I’m sorry for the dumb observation above….all the examples have the YUI object as a callback param in use.

    Here is the trace

    info: (yui): Modules missing: nodejs-dom, 1
    info: (yui): Fetching loader: /usr/local/lib/node/.npm/yui3-core/2010.11.03/package/build/loader/loader-debug.js
    info: (get): URL: /usr/local/lib/node/.npm/yui3-core/2010.11.03/package/build/loader/loader-debug.js
    (node) process.compile should not be used. Use require(‘vm’).runInThisContext instead.
    info: (get): Loaded: /usr/local/lib/node/.npm/yui3-core/2010.11.03/package/build/loader/loader-debug.js
    info: (yui): Modules missing: dump,nodejs-dom, 2
    info: (yui): Using Loader
    info: (loader): attempting to load dump, /usr/local/lib/node/.npm/yui3-core/2010.11.03/package/build/
    info: (get): URL: /usr/local/lib/node/.npm/yui3-core/2010.11.03/package/build/dump/dump-debug.js
    info: (get): Loaded: /usr/local/lib/node/.npm/yui3-core/2010.11.03/package/build/dump/dump-debug.js
    info: (loader): attempting to load nodejs-dom, /usr/local/lib/node/.npm/yui3-core/2010.11.03/package/build/
    info: (get): URL: /usr/local/lib/node/.npm/yui3/0.5.17/package/lib/yui3-dom.js
    info: (get): Loaded: /usr/local/lib/node/.npm/yui3/0.5.17/package/lib/yui3-dom.js
    info: (loader): loader finishing: success, yui_3_2_0_1_12928339018771, yui-base,yui-log,dump,nodejs-dom
    info: (yui): Attaching available dependencies: yui-base,yui-log,dump,nodejs-dom
    info: (yui): Nested use callback: yui-base,yui-log,dump,nodejs-dom
    info: (yui): caching request: nodejs-dom
    info: JSDom testing..
    info: (loader): Undefined module: yui2-datatable, matched a pattern: yui2-
    info: (loader): Undefined module: yui2-datasource, matched a pattern: yui2-
    info: (yui): Modules missing: yui2-datatable,yui2-datasource, 2
    info: (yui): Using Loader
    info: (loader): attempting to load yui2-datatable, /usr/local/lib/node/.npm/yui3-core/2010.11.03/package/build/
    info: (get): URL:
    info: (get): Loaded:
    info: (loader): attempting to load yui2-datasource, /usr/local/lib/node/.npm/yui3-core/2010.11.03/package/build/
    info: (get): URL:
    info: (get): Loaded:
    info: (loader): loader finishing: success, yui_3_2_0_1_12928339018771, yui2-datatable,yui2-datasource
    info: (loader): Undefined module: yui2-yahoo, matched a pattern: yui2-
    info: (loader): Undefined module: yui2-dom, matched a pattern: yui2-
    info: (loader): Undefined module: yui2-event, matched a pattern: yui2-
    info: (loader): Undefined module: yui2-skin-sam-datatable, matched a pattern: yui2-
    info: (loader): Undefined module: yui2-element, matched a pattern: yui2-
    info: (yui): Modules missing: yui2-yahoo,yui2-event,yui2-dom,yui2-skin-sam-datatable,yui2-element, 5
    info: (yui): Using Loader
    info: (loader): attempting to load yui2-skin-sam-datatable, /usr/local/lib/node/.npm/yui3-core/2010.11.03/package/build/
    debug: (get): Get.css
    debug: (get): Real CSS loading
    info: (loader): attempting to load yui2-yahoo, /usr/local/lib/node/.npm/yui3-core/2010.11.03/package/build/
    info: (get): URL:
    info: (get): Loaded:
    info: (loader): attempting to load yui2-event, /usr/local/lib/node/.npm/yui3-core/2010.11.03/package/build/
    info: (get): URL:
    info: (get): Loaded:
    info: (loader): attempting to load yui2-dom, /usr/local/lib/node/.npm/yui3-core/2010.11.03/package/build/
    info: (get): URL:
    info: (get): Loaded:
    info: (loader): attempting to load yui2-element, /usr/local/lib/node/.npm/yui3-core/2010.11.03/package/build/
    info: (get): URL:
    info: (get): Loaded:
    info: (loader): loader finishing: success, yui_3_2_0_1_12928339018771, yui2-yahoo,yui2-event,yui2-datasource,yui2-dom,yui2-skin-sam-datatable,yui2-element,yui2-datatable
    info: (yui): Nested use callback: yui2-datatable,yui2-datasource
    info: (yui): caching request: yui2-datatable,yui2-datasource
    info: Creating DataSource..
    info: Creating DataTable..
    info: (get): Loaded:
    error: (nodejsyui3): ———————————————————-
    error: (nodejsyui3): TypeError: Cannot set property headers of # which has only a getter
    error: (nodejsyui3): at [object Object]._formatTdEl (
    error: (nodejsyui3): at [object Object]._getTrTemplateEl (
    error: (nodejsyui3): at [object Object]._addTrEl (
    error: (nodejsyui3): at [object Object]. (
    error: (nodejsyui3): at (
    error: (nodejsyui3): at [object Object]._runRenderChain (
    error: (nodejsyui3): at [object Object].render (
    error: (nodejsyui3): at [object Object]. (
    error: (nodejsyui3): at Function.issueCallback (
    error: (nodejsyui3): at [object Object].handleResponse (
    error: (nodejsyui3): ———————————————————-

  39. I went back and installed Nave to switch between node versions, but ran into the same errors as earlier. these are the examples that do not work for me:

    scrape.js, scrape-object.js, tnt-datatable-script.js, y-server.js.

    The errors are consistently related to setters on null or setters on objects that do not have a setter.

  40. @Sai —

    Can you email them to me instead of posting here, it’s hard to read in the comments ;)

    davglass (at) gmail (dot) com

  41. this is so friggin cool and i can’t wait to see it in operation but…

    i can’t get the project to run. the debugger tells me that server.js bombs on line 35 as express.bodyDecoder() is not defined.

    i’m a heavy user of YUI and less of RoR and wanted to see how node.js and serverside YUI could help me. i never used node.js before so don’t know if the version i have is the problem (node.js 0.5.0-pre and express 2.0.0beta).


  42. @Pete —

    You should use Node 0.4.2 and Express@1.0.7 if you want to use my examples. The Express beta is too new and I haven’t moved my examples over to it yet.