Introducing Tready.js

Tready.js is a JavaScript framework, that allows running components in Node.js and the browser.

We had a problem at HolidayCheck, we loved all of the possibilities that JavaScript had, but we weren’t quite able to make use of it as much as we would have liked to. This was due to the fact that nearly everything needed to be visible in Google’s search results. And I am not telling news when I say Google’s search engine doesn’t do JavaScript as well as the browser. Which is funny considering that Google is pushing JavaScript at the same time.

There are several solutions to overcome this limitation. However, at the time all of them involved either implementations that are exclusive to one environment or redundant implementations of the same thing – one for the server, the other for the browser.

So we asked ourselves, “Wouldn’t it be nice to implement something once and use it in both environments?”. Luckily Node.js provides a way to run JavaScript on the server. Who knows, otherwise we might have ended up trying to run PHP in the browser.

We like to call this approach “Shared Code”, but it might be better known as “Isomorphic JavaScript”. We certainly weren’t the only ones thinking like this. Nodejitsu wrote about this in 2011 and Airbnb picked it up for their Rendr framework.

But we had more ideas. We wanted to build a modular thing similar to an app store, where you’d be able to choose from a pool of apps to put together a website.

Herewith, a few words on the general requirements.

First it is necessary to understand the main problems we wanted to solve with a new architecture. We wanted to be able to launch different web sites, which we call products, for different markets. For example our Swiss product is different to our German product. The two products do however share a lot of functionality, that we didn’t want to implement twice. To put it into a simple sentence, we wanted our web site (product) to consist of several separate re-usable components, which we call apps.

So we had two main requirements (aside from being super fast):

  1. Build a modular system enabling us to put together a web page out of a pool of apps
  2. Enable free use of JavaScript while providing optimal SEO

With this in mind we set out to work on a new framework, which we now call Tready.js.

Let’s start with the app idea.

Apps

In order to allow apps to be re-used across products, they needed to stick to one common interface. Apps need a starting point and an finishing point. The finishing point is necessary to be able to tell when the HTML is ready to be shipped, in case apps are run in Node.js.
To achieve that, We decided that apps should implement a run method, which would be the starting point, as well as having access to events to trigger “ready” or “error” to be able to tell Tready.js when they are done.

The following is a very basic app that is doing nothing:

// Inject Tready.js sandbox
module.exports = function( sandbox ) {

    function MyApp () {}

    // extend from sandbox.mvc.App to provide
    // common functionality like events
    MyApp.prototype = Object.create( sandbox.mvc.App.prototype );

    // run method will be called by Tready.js
    MyApp.prototype.run = function ( params, mode ) {
        //do smth
        this.trigger( "ready" );
    };
};

This is hopefully easy to understand. Although the sandbox part might need some explanation. The idea was propagated by Nicholas Zakas in his talk about Scalable JavaScript Application Architecture, and aura.js is one popular framework embracing it.

In case you haven’t heard about it, the idea is to provide components with a sandbox that works as a mediator between the framework core and the component.

In the code example above the sandbox provided a prototype for the app. However, there is a lot more to it. It is supposed to provide all the APIs an app can use. Here are some examples:

  • sandbox.mvc (e.g. Backbone)
  • sandbox.dom (e.g. jQuery)
  • sandbox.templates (e.g. handlebars)
  • sandbox.modules (allows loading modules/files)

The existence of sandbox.modules suggests that there are modules. Modules are basically just files, which are part of an app.

The code example above only shows the minimal requirement for a module to be an app. Though one should understand that an app is actually more than just a file. The file containing that code is only the contact point for the framework to give an app instructions. It is probably easier to understand if you look at it as a controller. The usual app however, will almost certainly make use of views and models, which aren’t supposed to be part of one giant file. With sandbox.modules, app developers have the possibility to load code from different files.

Every module implements the same factory method. This is what module.exports = function ( sandbox ) {} is all about. Every module has its own sandbox instance, though they share a few pieces of context information with the other modules of one app.

Without going too much into detail, here is an example where some rendering happens:

// Inject Tready.js sandbox
module.exports = function( sandbox ) {

    function MyApp () {}

    // extend from sandbox.mvc.App to provide
    // common functionality like events
    MyApp.prototype = Object.create( sandbox.mvc.App.prototype );

    // run method will be called by Tready.js
    MyApp.prototype.run = function ( params, mode ) {

        // get parameter from the url
        var name = params.getValue( "name" );

        // render template "greeting"
        // with given data
        var html = sandbox.templates.render(
            "greeting",
            { name: name }
        );

        // put HTML into DOM
        sandbox.context.element.html( html );
        this.trigger( "ready" );
    };
};

As one can see, app developers don’t have to fiddle with the DOM placement, they just use the element that Tready.js provides. This element is set on a higher level, where apps are arranged on the web page, but this is something for another post.

Now that we have a rough understanding of apps we can go on with requirement #2.

Shared Code

From our experience the two main challenges with sharing code between the browser and Node.js are the difference in APIs and the server having to answer in one go, whereas the browser can add to the DOM constantly.

Fortunately, the foundation is set since apps aren’t using any APIs directly. Everything is proxied through the sandbox. Tready.js makes sure that the sandbox API is consistent in both environments and adapts to their characteristics behind the scenes.

The XHR is a good example for it. JavaScript developers commonly use a model to fetch data from a REST back-end. And the model prototype happens to be provided by the sandbox. In the default configuration app developers will find a Backbone Model at sandbox.mvc.Model. So Tready.js only needs to override Backbone.sync to either make use of Node.js’ http module or the browser’s XHR object.

That’s the way we solved the API problem.

As mentioned before, there is another big difference between the browser and the server. The server has only one chance to respond with HTML, while the browser can constantly add HTML to the DOM – thus making it necessary to know when all apps are finished.

Imagine the following scenario. A requests hits the server, the server sets out to start two apps while the request is waiting for a response. Now the server has to decide when the time has come to collect the HTML from both apps to make that response.

We solved this by introducing the convention that every app has to trigger “ready” or “error” when it’s done. When all apps triggered one of those events, it’s time to answer the request.

Sadly, this is exclusively necessary for the server and can’t really be considered shared code. However, it allows for better profiling of apps and to make use of timeouts to prevent stalling apps.

The following talk gives a little more detail about this topic:

js.everyhwere()

What’s next?

Tready.js is already being battle tested on http://www.holidaycheck.com and http://m.holidaycheck.ch and we are working hard on bringing it to more platforms.

We also plan to open source Tready.js, but we still have some things to sort out first.

Until then we will be publishing a series of posts about all the parts of Tready.js.

Also we’d love to hear some feedback. So if you like the idea of Tready.js and want to share a few thoughts, please leave a comment.

6 thoughts on “Introducing Tready.js”

    1. Hi Steffen,

      right now, we are using Stitch in our build tool. But in theory you could also use browserify, Tready.js doesn’t make that decision.

      We are using browserify in some modules though. But mostly for testing puposes.

      Regards
      Simon

    1. Yes, we did. In the very beginning.

      We went with CommonJS after all, because we liked the syntax better and also because it was already in place in Node.js.

      I personally think, that the difference is not so big eventually. For production you’ll mostly likely have a build step anyway and the AMD nature of RequireJS can be re-produced with CommonJS.

      However, I would argue, that AMD isn’t always right. For pages with a smaller JS code base, one bundled file will work pretty well.

      Though, CommonJS in the browser has one obvious disadvantage. You’ll need a build step in any case, therefore also during development.

  1. Hi Simon,

    are there any updates regarding this topic? Tready.js looks amazing and I’d like to get my hands on it 🙂

    Regards,
    Jörg

    1. Hi Jörg,

      unfortunately I can’t give you a date right now. But we use it with great success and we are still interested in publishing tready.js.

      If everything goes well, you might be able to use it early next year.

      Best
      Simon

Leave a Reply

Your email address will not be published. Required fields are marked *