Saturday, September 15, 2012

knockout.composite: Pack Your App and Make it Fly

*UPDATE* Tribe is here! Check out http://tribejs.com/ for guides and API reference. PackScript also completely supersedes resPack – more details at http://packscript.com/.

This is part 8 of a multi-part post. It’s assumed that you have a basic knowledge of the data binding and observability features of knockout.js and have read the example presented in part 4.

Part 1: Introducing knockout.composite
Part 2: The Basics
Part 3: Getting Around
Part 4: Putting it All Together
Part 5: Unit Testing: Model Citizens 
Part 6: Integration Testing: Playing Nicely Together
Part 7: Functional Testing: Automated UI Tests, Anywhere, Any Time
Part 8: Pack Your App and Make it Fly
Part 9: Parting Thoughts and Future Directions

 

Throughout this series of posts, we have completely ignored performance. As far as rendering goes, the framework does not add significant overhead. How you use knockout and how you structure your HTML and CSS have a far bigger impact on performance.

What about loading resources? You may have noticed in the examples that we’re generating quite a large number of HTTP requests, and well… they’re pretty slow in the grand scheme of things.

The examples are also fairly trivial, what happens when we have 30 panes on a single screen? 3 requests per pane (html / js / css) gives a total of 90 HTTP requests, just to display one screen!

Enter resPack

resPack is a small .NET console application that combines and minifies JavaScript and CSS files based on simple XML configuration files. It

  • provides simple templating capabilities that allow us to inject things like the source file path (hello, sourceURL tag),
  • has a “watch” mode where it will execute immediately when files change, giving us the ability to keep our code in separate files but still have a simple save-refresh debugging experience,
  • executes recursively, so we can be working in a separate library, save, refresh our app that depends on it and all is kept up to date,
  • uses the Microsoft AjaxMin library for minification. It’s quite efficient and causes minimal side effects.

Let’s look at some example configuration files.

Basic Options
<resPack>
  <output filename="../Content/Panes.css">
    <include files="*.css" recursive="true" />
    <exclude files="Common\*.css" />
  </output>
</resPack>

I don’t think I need to explain this.

Minifying

This is currently only supported for JavaScript files, but the AjaxMin library does support CSS, so this feature will be added soon.

<output filename="../Content/Panes.js" minify="true">
  <include files="*.js" recursive="true" />
</output>

An option on the output turns minification on. This will generate an additional file with the extension .min.js, in this example, Panes.min.js.

A Template for Templates
<output filename="../Content/Panes.htm">
  <include files="*.htm" recursive="true" />
  <template>
&lt;script type="text/template" id="Panes-{2}">{0}&lt;/script>
  </template>
</output>

This combines all of our HTML files for our panes and wraps them each in a script tag. The {0} token substitutes the actual file contents and {2} substitutes a formatted version of the path suitable for HTML attributes.

A Special Case for Models

For our models, we need to tell knockout.composite the path to the model and add in the sourceURL tag to enable debugging support in Chrome.

<output filename="../Content/Panes.js" minify="true">
  <include files="*.js" recursive="true" />
  <template>
{0}
ko.composite.resources.assignModelPath('/{3}');
//@ sourceURL=/Panes/{1}
  </template>
</output>


Loading it All Up

Now we’ve got our combined and minified files, how do we load them up? For CSS, we don’t need to do anything special, just add the usual link element in your header. We can also do this for our models, but we lose debugging support. This is perfectly acceptable for production.

knockout.composite gives us a simple initialise helper function that will load up these resources and begin the rendering process.

ko.composite.initialise(null, { scripts: ['/Content/Debug.js'], templates: ['/Content/Site.htm'] });

This ensures our models are loaded up with full debugging support and our templates are loaded and rendered to the header of the page. To achieve full debugging support, the framework splits JavaScript files based on the sourceURL tag and executes each script in a separate eval statement.

At this stage, Chrome is the only browser that provides full debugging support. Firebug apparently supports the sourceURL tag, but it doesn’t work in this case and I haven’t had the time to determine why. For other browsers, it makes debugging significantly easier turning this option off.

ko.composite.options.debug.splitScripts = false;

This will leave all of our script in a single file, allowing us to use the debugger’s search function to locate specific sections of code and place breakpoints.

Being Selective

Loading up your entire app up front works great for small to medium applications, but as they start to grow, file sizes may start to cause unacceptable load times. We can selectively leave out panes from our resPack configuration files. If a resource isn’t loaded on startup, it will be dynamically loaded when required.

The next version of knockout.composite will provide a mechanism for specifying different load strategies, such as a basic subset up front, by folder when requested, and even in the background after your app has fully loaded.

Make it Fly

As all of our application resources are static text files, there are some things we can do to really boost load times.

Gzip Compression

Most web servers can be configured to compress static text files on first request and transmit the gzipped version. Since our files are plain text, this is a significant saving. IIS requires special configuration to force it to compress on first request. Stackoverflow has the answers…

Deploying to a CDN

A Content Delivery Network replicates your files on a number of servers around the world that are strategically located to minimise the number of hops required for users in various places around the world.

Be aware that this introduces a number of complexities around deployment and versioning. It also requires your service layer to support cross domain requests, though JSONP makes this relatively simple.

Conclusion…

The tool is available in the Tools folder in the knockout.composite github repository. I have not yet released the source, but will do so soon, tweet me if you’re interested. The code is in need of an overhaul and I’d like to be able to specify configuration in JSON format as well as XML.

Next up, finally, a conclusion. Of sorts. For now.

0 Comments:

Post a Comment

<< Home