For years, web developers have deployed their web applications according to the best practices defined by Yahoo’s Exceptional Performance team. Because traditional applications culminate in a vast collection of JavaScript source files, CSS stylesheets, and image artifacts, the best practices mandate the collective compression of these assets into single file downloads. In theory, delivering a single JavaScript file and a single CSS file reduces the overall number of HTTP requests (as well as reduces total asset size) resulting in optimal transmission and improved page initialization times.
Although there are a variety of publicly available tools to accomplish these basic tasks, none offers the convenience, dependency management, and build automation capabilities of Sencha Cmd for applications built with Ext JS and Sencha Touch. By simply running sencha app build, you only deploy the subset of the framework code that you actually need. When combined with your application code, the output is an optimized production build.
Typically small to medium size applications benefit the most from this build and deployment strategy, but the enterprise developer often must consider additional assembly factors. For example:
- Enterprise desktop applications frequently involve hundreds of input forms and other complex presentation layouts. If incredibly large enterprise applications can exceed 8MB of JavaScript, CSS and images, does compressing everything into a single file really make a difference?
- Role-based applications also present interesting engineering problems. Consider a “low level” user might only have 10% of functionality available to them — why should they download the other 90% of the code? Should the enterprise even risk serving them the compressed code for which they don’t have access privileges?
Clearly, the best practices surrounding this approach to minification plateau when applications get sufficiently large and/or complicated. Page initialization begins degrading at this scale because simply loading and parsing this much JavaScript (without even executing it) can freeze the browser.
There was no immediate solution to this problem, so the Sencha Professional Services team recently developed an approach that helped one of our customers. While it doesn’t solve every problem faced when building large enterprise applications, we wanted to share this solution, which leverages Sencha Cmd to dynamically load JavaScript resources on-demand.
You can view the project files used in this article on GitHub.
Table of Contents
Wait – Isn’t Dynamic JS Loading Slow?
It can be — it depends on a few factors. Looking back at those best practices defined by Yahoo, the overall point is that reducing HTTP requests should decrease page loading times because most browsers have a default limit on the number of concurrent HTTP connections per domain (typically six). So, while in theory this means a single file would download faster than ten, it doesn’t necessarily mean a single large file will download faster than six smaller files in parallel.
First, we should consider that Ext JS and Sencha Touch applications built with Sencha Cmd have two environments: “development” and “production”. Sencha apps in “development” mode load each individual class based on their dependency chains (through the “requires” configuration), whereas apps in “production” mode would load a single “my-app-all.js” file containing only the necessary dependencies.
The approach we examined to dynamically load JavaScript code in production combines the idea of serving compressed “builds” (from “production” mode) with the flexibility of individual class loading (from “development” mode). Ultimately, we’re trying to improve the time it takes for an application to load, because application “speed” is all about the perception that the user does not wait for the UI.
Proposed Solution: Reduce JS File on Initial Load
Consider the above diagram. In a large enterprise application, only a fraction of the total code base is displayed or used on the initial screen. To achieve the best initial page load performance, it would be ideal to deliver only the code that’s needed for the initial screen. As long as the application loads quickly and the users are happy, we can selectively choose how and when to request other parts of the code base to access subsequent functionality.
Our approach to solving this problem was solely focused on improving the initial load time for a customer application — it’s important to note that this approach was specifically tailored to this specific situation and may not fit all scenarios. We did not tackle ideas surrounding selective CSS deployment, lazy-initializing controllers, or asynchronous loading mechanisms.
But, before we dive into the details of our approach, let’s first reiterate where this technique is appropriate:
- Achieving faster load times for the initial “front page” view.
- Has biggest impact in low power mobile devices (e.g. Android 2.x).
- Improves initial and subsequent “front page” view load times; both benefit from parsing less JS code, and subsequent loads typically come from the cache (if set up properly).
- Serving up JS code selectively
- Supports security-focused policies of not serving code to users who don’t have permissions to run it anyway.
- Appropriate for very large (3MB+) applications where only a small portion of the code base would get used in a given user session.
Sample App Setup
You can view the project files used in this article on GitHub.
Using Sencha Cmd, you can issue a basic “sencha generate app” command for Sencha Touch 2.3.1 apps which produces a basic project with a 2-tab tab panel. Let’s call it “ChunkCompile”. For this example, we will modify the second tab, so it contains a List and a custom component whose definitions will be downloaded only after the user navigates to the second tab.
In app/view/Main.js we can see the items array contains the following:
items: [{
xtype : ‘container’,
title : ‘Welcome’,
iconCls : ‘home’,
scrollable : true,
html : ‘Switch to next card for dynamic JS load…’,
items: {
docked : ‘top’,
xtype : ‘titlebar’,
title : ‘Welcome to Sencha Touch 2’
}
},{
xtype : ‘container’,
title : ‘NEXT CARD’,
itemId : ‘nextCard’,
iconCls : ‘action’,
layout : ‘fit’,
items: [{
docked : ‘top’,
xtype : ‘titlebar’,
title : ‘”Next Card”‘
}]
}]
Note that the second item is merely a Container with a TitleBar. This functions as a placeholder for “Page 2” in the following sequence:
- Intercept “Tab Change” event for the second tab.
- Download JavaScript content needed for Page 2.
- Add xtype “page2” into the second tab (we’ll define this view in a moment).
Loading JS & Views Dynamically
Pretend for a moment that we already have a “page2.js” file ready to go, compressed and containing only the code related to the “page2” widget. How would we load it dynamically and display the associated view?
Fortunately, we have the Ext.Loader.loadScriptFile() method. We will use this utility in app/controller/Main.js:
Ext.define(“ChunkCompile.controller.Main”, {
//…
onMainActiveCardChange: function(mainPanel, newItem, oldItem) {
// Skip if 2nd page loaded already
if (newItem.down(‘page2’)) {
return;
}
// If this is a built app, we need to load page2 JS dynamically
if (ChunkCompile.isBuilt && !ChunkCompile.view.Page2) {
// synchronously load JS
Ext.Loader.loadScriptFile(
‘page2.js’,
function() {
console.log(‘SUCCESS’);
},
function() {
console.log(‘FAIL’);
},
this,
true
);
}
// Show “Page 2” view
newItem.add({ xtype: ‘page2’ });
}
});
What’s happening here can be boiled down to the following sequence:
- We intercept the “activeitemchange” event of the “main” view as the user changes tabs.
- We use a conditional statement to determine whether we need to load the “Page 2” JS dynamically:
- “ChunkCompile.isBuilt” is a flag we inject during the build process (discussed later); we do not utilize the custom “dynamic JS load” pattern in dev mode because there are no compiled files to work with yet.
- “ChunkCompile.view.Page2” is a check for the actual presence of Page2 view definition, which would only exist if its JS definitions had been loaded.
- Note that this logic is arbitrary; the example presented is a very basic way of checking when it’s time to load Page2 JS.
- If all checks pass, we call Ext.Loader.loadScriptFile(‘page2.js’, …); note the “true” parameter at the end for synchronous execution.
- Finally, we add the view to the 2nd tab using its xtype “page2”.
Views
Now that we understand how we plan to load our packages dynamically, let’s have a quick look at the Page2 and CustomComponent classes to see how they’re defined. For this example, we deliberately made these classes simple, yet we also wanted to demonstrate this technique using multiple files.
// @tag Page2
Ext.define(‘ChunkCompile.view.Page2’, {
extend : ‘Ext.Container’,
xtype : ‘page2’,
requires : [
‘Ext.layout.VBox’,
‘Ext.List’,
‘ChunkCompile.view.CustomComponent’
],
config: {
layout : ‘vbox’,
items : [{
xtype: ‘customcomponent’
},{
xtype : ‘list’
}]
}
});
// @tag Page2
Ext.define(‘ChunkCompile.view.CustomComponent’, {
extend : ‘Ext.Component’,
xtype : ‘customcomponent’,
config : {
html: ‘Hello! This view was loaded dynamically!’
}
});
Clearly, there’s nothing special about these classes — but you should note the “// @tag Page2” comments at the top of both files (which doesn’t exist on the Main view). We will refer to this shortly when running the build.
Building page2.js vs. all-the-rest.js
One of the best kept secrets of Sencha Cmd is the powerful and flexible concatenation functionality of the “sencha compile” command. This functionality is explained in-depth in a guide: Multi-Page and Mixed Apps. In a nutshell, you can produce different code combinations to be saved into separate JavaScript files by using a powerful and flexible querying mechanism. In the following example, we will leverage source code comment searching and class namespace filtering.
The following command can be executed from our app’s directory:
sencha compile union --recursive --file=app.js and save allcode and exclude --all and include --tag Page2 and include --namespace Ext.dataview and save page2 and concat -y build/chunked/page2.js and restore allcode and exclude --set page2 and concat -y build/chunked/rest-of-app-and-touch.js
Running this command produces two files:
- build/chunked/page2.js
- build/chunked/rest-of-app-and-touch.js
This is exactly what we need for our approach to dynamic loading. Next, let’s explore how to modify sencha compile within our existing build process.
If you are curious to learn more about the options used by sencha compile, be sure to check out the Sencha Compiler Reference and Multi-Page and Mixed Apps guides in the Ext JS documentation.
Integrating Into Build Process
Note that we have used the command “sencha compile” instead of normal “sencha app build” – so some of the usual build steps (e.g. CSS generation) were not executed. Ultimately, we want to automate this as part of the normal build process, so we will need to inject our custom options for the “sencha compile” command line call whenever we run “sencha app build”. How can we accomplish that?
Take a look at the “-compile-js” Ant target in .sencha/app/js-impl.xml. Note the high-level structure:
<target name="-compile-js" depends="-detect-app-build-properties"> <if> <x-is-true value="${enable.split.mode}"/> <then> <x-compile refid="${compiler.ref.id}"> ... </x-compile> </then> <else> HERE </else> </if> </target>
The “sencha compile” command is called where I highlighted HERE, and I have modified the original parameters to fit the expected Ant format:
<x-compile refid="${compiler.ref.id}"> <![CDATA[ restore page and ${build.optimize} and save allcode and exclude -all and include -tag=Page2 and include --namespace=Ext.dataview and concat -y -out=${build.dir}/page2.js ${build.concat.options} and restore allcode and exclude -tag=Page2 and exclude --namespace=Ext.dataview and concat ${build.compression} -out=${build.classes.file} ${build.concat.options} ]]> </x-compile> <echo file="${build.classes.file}" append="true"> ChunkCompile.isBuilt = true; </echo>
Note that we echo out “ChunkCompile.isBuilt = true;” at the very bottom to the main app.js file. Recall that we used this value as a test condition in our controller to ensure we only run the dynamic JS loading in “production” mode.
Resulting File Sizes
Prior to editing js-impl.xml with our changes, we can see that calling sencha app build package on our sample application caused it to output a single JS file at 515 KB. This only included the necessary parts of the framework plus our application code.
After implementing the dynamic loading technique, we can see that the initial load of app.js drops to 477 KB — a savings of almost 10%. The subsequent load of page2.js (when the user navigates to the second tab) is a mere 48 KB and happens almost instantly.
While the code used in our sample application is trivial, the savings on load time add up very quickly. I was recently involved in building a large financial app that had over 3MB of application code running on a low powered mobile device. Using the technique presented here, we were able to reduce the original five second load time to just under one second. The best part is that the sky’s the limit — no matter how large the app gets, this technique will scale with it.
Conclusion
With more and more large HTML applications being developed via the Single Page App paradigm, it’s clear that time-tested best practices for website development fall short for large enterprise web applications. The sheer amount of JavaScript required to build these types of applications has grown exponentially in recent years, and unfortunately the tooling to control it has lagged behind. Sencha Cmd allows you to solve this problem and has many more customizable features to tweak the way your code is compressed and segmented.
While this approach may not help you to solve every problem that you face when building a large enterprise application, we hope it might resolve some scenarios. If you have dealt with similar issues in your applications, please tell us about your experiences in the comments below. We would love to hear what solutions you have created.
If you you would like to explore this approach or others for your particular situation, please contact Sencha Professional Services for a consultation.
Wow that’s awesome Ivan. This is something people have been trying to crack for quite some time, but I think you nailed with the whole integration with Sencha Cmd.
I do think that writing that massive sencha cmd query and hacking ant build files is quite complex even for the seasoned developer. If we could annotate @package page2 or something similar at the top of the files, and have sencha cmd aggregate these files and generate the compressed automatically, it would make things much simpler.
Keep rocking it! ;)
Glad you liked the article, Blake!
I’m assuming you are talking about the 2.5MB “safe” localStorage limit that would be utilized by Sencha Touch production microloader. This technique should assist you in avoiding hitting that limit, as I do not believe loadScriptFile() has any interaction with the microloader. Also you don’t have to use the microloader (i.e. “sencha app build package”), so 2.5MB limit doesn’t have to be an issue, but you’re right as far as not getting some of the nice features like delta updates.
We didn’t track metrics of which areas had what gains. Our Client gave us a requirement: this app takes 5 seconds to load and it needs to load under a second. We fulfilled the requirement with the technique presented.
As for your last question of the initial check for whether the user is logged in – there are many ways to do this and if you don’t want to “wait on a request to the server” I would suggest supplying that information alongside the initial HTML page where you start the app. However I think this question is more appropriate for the forums or you can also engage my team – Sencha Professional Services – to help you out.
This is a great blog post!
A few questions:
How do you handle the microloader’s ~2.5mb limit? Current solution is to make all scripts remote, but it jumbles load order and delta updates would be nice…
What portion of the app load time was JS parsing, cordova plugins, etc? Was JS parsing the main area of improvement for those large packaged apps? Any other areas with good roi?
How do you handle the initial check for whether the user is logged in? Current solution is waiting on a request to the server…
good article, i like this approach, but i think is very hard to improve the load speed with extjs/sencha touch because extjs is heavy only you create an app with a formpanel and build it and get a file with 500k five times bigger that the same form with jquery, so if you add a grid then 800k, so really I would like some apprach where the app load for instances 200k and then on demand get the other files, the same demo kitchen sink take 30 second to load in low speed connection, so is possible to build small app with few secods to load the page?
Regards
Frank
Hi Frank,
Glad you like the article!
As to your question of “is possible to build small app with few secods to load the page?”, I will quote the article:
“I was recently involved in building a large financial app that had over 3MB of application code running on a low powered mobile device. Using the technique presented here, we were able to reduce the original five second load time to just under one second.”
Nice technique ;) Just don’t forget that if you separate out Ext JS/Touch controllers in this fashion that you must initialize them yourself.
From my most recent experiences implementing this technique in Ext JS we’ve found that tag and namespace based filtering are not providing the most optimal division of code. Instead we are utilizing multiple entry files using several ‘union -r –file foo.js’ commands. Each file contains a listing of controllers that then chain to require many views and other classes and in turn depend on certain framework classes. Once all files are read in a common dependency tree between all the separated code is built via ‘intersect -min 2’, saved into a common file, then excluded from each other output. The result is that we can build out the page2 file with the specific classes desired & all of their unique dependencies all the way down to the framework level.
Hi Ivan, thanks for you answer, I think I should be more specific, I mean I think using your approach you improve the load time but yet extjs and sencha touch are heavy, because in slow connection take a lot time show up the aplication, if you have a good bandwith the app load in few seconds, but for instances i am using a wireless conection with 0.6 mbps and the kichen sink demo take 30 seconds in show up, using the same connection if I try http://jqueryui.com/progressbar/ it take 3 seconds and if I try http://dev.sencha.com/ext/5.0.1/examples/simple-widgets/progress-bar.html take 15 seconds, I know it is not exactly the same demo, but in load time using lighter weight solution like jquery + bootstrap + kendoui, is 5 times faster the load time, and to me extjs/sencha touch are fantastic library but the drawback is its are heavy and is hard to improve the load time in small app when you compare with lighter weight solution.
The best will be in small app a simple panel get 3 seconds in slow connection with extjs but is impossible becouse the core is heavy, using angularjs you can get better load time because in lighter, and maybe is not fair to compare but extjs allway is heavier that other ones lighter weight solution.
Regards
Frank
Hi Frank,
I am unsure if you’re asking a question there, but the general message I’m hearing is “with a slow connection this doesn’t load as fast as frameworks with a fraction of the size and functionality”. This is absolutely true, regardless of the connection speed.
In regards to the Kitchen Sink specifically, those examples are using “ext-all.js” meaning a non-optimzed “entire framework” file. You would not use that in production. In production you would leverage Sencha Cmd (https://www.sencha.com/products/sencha-cmd/) to compile a subset of framework’s JS and CSS resources and further in conjunction with the technique presented here you can get to a final JS file size the fraction of “ext-all.js”, even with a large app.
I hope this helps!
I think that should be implemented into Archtect. I have an huge app that takes about 10 secs to load.
Thanks for all the feedback by the way!
Hi Bruno, glad to see you here!
Thanks for the feedback and I couldn’t agree more – this is becoming more and more of an issue as HTML-based apps become more widespread and larger, as we watch our futuristic developer utopia vision unfold. Be on the lookout for Doug’s “DPL” that aims to solve the same (and other) problems in a slightly different fashion.
This has been around a while and there are remarkably few blogs or tutorials online.
Thank you Rich Waters. I too found the namespace and tags unwieldy.
Wish I could peek at your code ;)
The problem is largely one of organizing code so it can be chunked. What makes me crazy is not being able to keep up with the changes to Cmd.
Originally I had this down pat. I used Ant to build, and the Cmd to union/intersect. But as packages and themes became integrated into Cmd (and I started branching from ext only into some touch development), it has become too murky to remain with Ant alone.
This is reminiscent of large desktop programs in the late 90s. They became so slow to load. Splash screens forever. Borland Delphi introduced packages (runtime dynamically linked libraries) so that applications could load sections of code on demand. In practice they posed greater difficulties for debugging and code management. Eventually our team reverted to one huge executable.
Yes indeed, the latest releases of ext and touch are considerably larger than before. And they won’t get any smaller. Ditto our apps. The documentation for Cmd is constantly out of date. I’ve never found a really good overview of where sencha is going with packages (they seem like obvious candidates for dynamic loading).
Somebody needs to come up with a killer solution. Sencha almost has it, but it remains a “best kept secret”. The docs need to be scrupulously updated to reflect the current Cmd.
At one time there was a walkthough for a “multi page” app. The idea was to leverage browser handle caching and split every page into three parts: core used by all, common app stuff, and page-specifics.
With the appearance of routing we don’t need this so much any more. Sencha can handle multiple pages all by itself within one app.
I recall a forum post where a guy had put a grunt task up on github. He was chunking the code according to his own methods. Seems stupid to give up all the “framework and app awareness” that Cmd has. But getting it right with Cmd is no simple task either.
I cannot help a suspicion there is a better solution outside the box somewhere.
Appreciate this article very much, thanks.
Hi, should I hold out for Doug’s DPL or implement this now?
If I have a book and the pages are too many …and the time to load is too big …I can use this method? In Sencha touch 1?
The page are loaded in the model below using proxy :
Ext.regModel(‘Pages’, {
fields: [
{
name: ‘issue_id’,
type: ‘int’,
},
{
name: ‘text’,
type: ‘int’,
},
{
name: ‘url’,
type: ‘str’,
},
{
name: ‘filename’,
type: ‘str’,
},
{
name: ‘image_url’,
type: ‘str’,
},
{
name: ‘image_hd_url’,
type: ‘str’,
},
{
name: ‘thumb_url’,
type: ‘str’,
}
],
associations: [
{
type: ‘belongsTo’,
model: ‘Viewer’,
},{
type: ‘hasMany’,
model: ‘Links’,
name: ‘Links’
}
],
proxy: {
type: ‘ajax’,
url: ‘http://abc.net/io/lib.php?cid=’+App.client_id+’&pid;=’+(App.getParameterByName(‘pid’)?App.getParameterByName(‘pid’):App.pid)+’〈=’+((App.getParameterByName(‘lang’)) ? App.getParameterByName(‘lang’) : ‘francais’),
reader: {
type: ‘xml’,
root: ‘pagination’,
record: ‘page’
},
id: ‘pages’
}
});
Or should I change the proxy url at some point? And to try to load page by page?
Thanks in advance.
@Anon you don’t have to “hold out” for DPL; you can engage Sencha Professional Services and get access to it today
@Bia having never worked with no-longer-supported Touch 1 I don’t know the answer, but I’d suggest you try the forums:
https://www.sencha.com/forum/forumdisplay.php?56-Sencha-Touch-1.x-Forums-Unsupported
It’s actually a great and useful piece of information. I
am happy that you shared this helpful information with us.
Please stay us informed like this. Thanks for sharing.
Thank you fοr some other grea article. Where else may anyone
gett that tyypе of info in such aan ideal waʏ of writing?
I’ve a presentation subseqսent weеk, and I am oon tһe look for such information.