Introduction
In my last article, I shown you how you could run a Node project using Oracle JET. In this post I would use the project as a basis and talk about another interesting topic, optimization. There are many aspects how HTML based client-server applications should be optimized, but mostly comes down to two key points: minimize the number of request, and compress the server output to the client. In this article I will talk about how to achieve this with Oracle JET.
Main Article
Today in Internet there are already a lot of articles talking about how to optimize web based applications. Oracle JET provides also a topic in the development guide, which you can follow here:
http://docs.oracle.com/middleware/jet112/jet/developer/GUID-3601ED20-4ECE-462C-8020-A55C86854142.htm#JETDG278
Basically all best practices you know about optimizing client HTML based application apply to Oracle JET as well. I would like to pick up my top 3, where you can get the best results:
#1 Reduce the number of HTTP Request (aka “The best HTTP request is the one you don’t have to do!”)
#2 Compress the output – gzip the content output to the client to reduce the bandwidth usage for faster load
#3 Use client/browser cache
The first two points are in our experience so far the most important. Even if you don’t use browser cache, if you reduce the number of resources required to load to render the page and compress the size of the loaded content to a minimum, your page will load fast. Best case you should have one request loading the page HTML markup, one CSS file request load and one JS request file load. You can go even further and for example if you know which resources you have to load for specific page hit, merge for example the CSS and JS code into the HTML page, so that you will have only one initial request, and then partially load the rest of the resources if required.
To make a example I will use the Oracle JET Quick Start Basic project. If you load the project into the browser you will realize following page footprint (46 Request and 1.8MB content to load):
The reason is that the Quick Start application is delivered in so called debug state. You load all modules and files separately not minified and not compressed. This is the mode you really want to have during the application development. In case of code error you can debug and see exactly the problem, in which file and line a error occurred, it will be mostly in readable and understandable format.
Let’s assume that this is my final project now and I would like to deploy this on my production system. Before I do so, I would have to make sure that I apply rule #1, minimize the number of request. You can do this of course manually, however I would like to automate the process. Since Oracle JET relays on RequireJS to load the dependencies, I would use the Require JS Optimizer for that purpose.
http://requirejs.org/docs/optimization.html
The RequireJS Optimizer is a tool which allows you to minify, uglify and merge modules into single file. It understands the RequireJS project structure and runs on Node, Java with Rhino and Nashorn or in the browser. Since I setup Node project I will run it as Node module. For how to install it follow the link I shared above.
After you installed the optimizer, you have to create a build file, which I called build.js. You can select any name you like. This file will contain the settings, which will tell the require JS optimizer how to merge the files. For the purpose of completeness, I will share here the complete build file:
({ insertRequire: false, baseUrl: 'public/js', mainConfigFile: 'public/js/main.js', out: 'public/js/main.min.js', findNestedDependencies: true, //optimize: 'none', name: "main", paths: { 'knockout': 'libs/knockout/knockout-3.3.0', 'jquery': 'libs/jquery/jquery-2.1.3.min', 'jqueryui-amd': 'libs/jquery/jqueryui-amd-1.11.4.min', 'promise': 'libs/es6-promise/promise-1.0.0.min', 'hammerjs': 'libs/hammer/hammer-2.0.4.min', 'ojdnd': 'libs/dnd-polyfill/dnd-polyfill-1.0.0.min', 'ojs': 'libs/oj/v1.1.2/min', 'ojL10n': 'libs/oj/v1.1.2/ojL10n', 'ojtranslations': 'libs/oj/v1.1.2/resources', 'signals': 'libs/js-signals/signals.min', 'text': 'libs/require/text' }, shim: { jquery: { exports: '$' } }, include: [ //'modules/app', 'modules/footer', 'modules/graphics', 'modules/header', 'modules/home', 'modules/library', 'modules/people', 'modules/performance', 'ojs/ojcore', 'knockout', 'jquery', 'ojs/ojrouter', 'ojs/ojknockout', 'ojs/ojbutton', 'ojs/ojtoolbar', 'ojs/ojmenu', 'ojs/ojmodule', 'text!templates/compContent.tmpl.html', //'text!templates/demoAppHeaderOffCanvasTemplate.tmpl.html', 'text!templates/footer.tmpl.html', 'text!templates/graphics.tmpl.html', 'text!templates/header.tmpl.html', 'text!templates/home.tmpl.html', 'text!templates/library.tmpl.html', 'text!templates/navContent.tmpl.html', 'text!templates/people.tmpl.html', 'text!templates/performance.tmpl.html' ], bundles: { "main.min": [] }, })
For complete documentation about what all this settings mean, follow the link from the Require JS Optimizer I share above. Let me explain few of the options. In our project we have one main module, which is the main.js file, which loads all dependencies at once. Because we build and know those dependencies, we don’t want to load all those modules separately on production but merge them into one file. For that purpose we use include to show the optimizer which modules content should be loaded into the main module file. What we also do, we load all templates as well. If you remember the non optimized screen above, you will see that in the way the project is developed currently, it loads all those templates separately in the initial load. Better option here would be if we only load current needed template, and then partially load the rest of the templates when required, however this will require code changes, which I could cover in another article. Our main purpose now will be to get the best out of the current application. The new merged file will be called main.min.js and will contain all dependencies required for the initial load, which you see from the non optimized load.
Notice also the usage of bundles. Bundles allows you to configure the location of specific package within the merged modules. For example now that we merge for example the header.js file into the main.min.js file, if another module later requires the same header.js we just have to specify that it is in the main.min.js so that no new load of JS file from the server will be triggered, if this modules is already loaded somewhere.
Btw the reason that bundles is empty array in the build file, is due to a bug in the RequireJS modules, which will throw error on the build process, if you use bundles into the main file.
Next step will be to change the main.js file to use bundles. As mention above, we need to tell the main module where to find his dependencies. For that purpose change in the main.js file the requirejs.config to the following:
requirejs.config({ bundles: { 'main.min': [ //'modules/app', 'modules/footer', 'modules/graphics', 'modules/header', 'modules/home', 'modules/library', 'modules/people', 'modules/performance', 'ojs/ojcore', 'knockout', 'jquery', 'ojs/ojrouter', 'ojs/ojknockout', 'ojs/ojbutton', 'ojs/ojtoolbar', 'ojs/ojmenu', 'ojs/ojmodule', 'text!templates/compContent.tmpl.html', //'text!templates/demoAppHeaderOffCanvasTemplate.tmpl.html', 'text!templates/footer.tmpl.html', 'text!templates/graphics.tmpl.html', 'text!templates/header.tmpl.html', 'text!templates/home.tmpl.html', 'text!templates/library.tmpl.html', 'text!templates/navContent.tmpl.html', 'text!templates/people.tmpl.html', 'text!templates/performance.tmpl.html' ] }, // Path mappings for the logical module names paths: { 'knockout': 'libs/knockout/knockout-3.3.0', 'jquery': 'libs/jquery/jquery-2.1.3.min', 'jqueryui-amd': 'libs/jquery/jqueryui-amd-1.11.4.min', 'promise': 'libs/es6-promise/promise-1.0.0.min', 'hammerjs': 'libs/hammer/hammer-2.0.4.min', 'ojdnd': 'libs/dnd-polyfill/dnd-polyfill-1.0.0.min', 'ojs': 'libs/oj/v1.1.2/min', 'ojL10n': 'libs/oj/v1.1.2/ojL10n', 'ojtranslations': 'libs/oj/v1.1.2/resources', 'signals': 'libs/js-signals/signals.min', 'text': 'libs/require/text' }, // Shim configurations for modules that do not expose AMD shim: { 'jquery': { exports: ['jQuery', '$'] } /*,'crossroads': { deps: ['signals'], exports: 'crossroads' }*/ }, // This section configures the i18n plugin. It is merging the Oracle JET built-in translation // resources with a custom translation file. // Any resource file added, must be placed under a directory named "nls". You can use a path mapping or you can define // a path that is relative to the location of this main.js file. config: { ojL10n: { merge: { //'ojtranslations/nls/ojtranslations': 'resources/nls/menu' } } } });
We now add the bundles configuration, which says that the specified dependencies can be found within the main.min.js file. You also have to change the path to the templates, to align with the path from the bundles configuration:
oj.ModuleBinding.defaults.viewPath = 'text!templates/';
Now that the configuration is ready we can run the optimizer and generate the new optimized file.
To run the Require JS Optimizer execute following line within the project folder: r.js -o build.js
This should produce following output:
Tracing dependencies for: main Uglifying file: /Users/lypelov/development/JET/oraclejetonnodejs/public/js/main.min.js /Users/lypelov/development/JET/oraclejetonnodejs/public/js/main.min.js ---------------- /Users/lypelov/development/JET/oraclejetonnodejs/public/js/libs/oj/v1.1.2/ojL10n.js /Users/lypelov/development/JET/oraclejetonnodejs/public/js/libs/oj/v1.1.2/resources/nls/ojtranslations.js ojL10n!ojtranslations/nls/ojtranslations /Users/lypelov/development/JET/oraclejetonnodejs/public/js/libs/oj/v1.1.2/min/ojcore.js /Users/lypelov/development/JET/oraclejetonnodejs/public/js/libs/knockout/knockout-3.3.0.js /Users/lypelov/development/JET/oraclejetonnodejs/public/js/libs/jquery/jquery-2.1.3.min.js /Users/lypelov/development/JET/oraclejetonnodejs/public/js/libs/js-signals/signals.min.js /Users/lypelov/development/JET/oraclejetonnodejs/public/js/libs/es6-promise/promise-1.0.0.min.js /Users/lypelov/development/JET/oraclejetonnodejs/public/js/libs/oj/v1.1.2/min/ojrouter.js /Users/lypelov/development/JET/oraclejetonnodejs/public/js/libs/oj/v1.1.2/min/ojknockout.js /Users/lypelov/development/JET/oraclejetonnodejs/public/js/libs/jquery/jqueryui-amd-1.11.4.min/core.js /Users/lypelov/development/JET/oraclejetonnodejs/public/js/libs/jquery/jqueryui-amd-1.11.4.min/widget.js /Users/lypelov/development/JET/oraclejetonnodejs/public/js/libs/oj/v1.1.2/min/ojmessaging.js /Users/lypelov/development/JET/oraclejetonnodejs/public/js/libs/oj/v1.1.2/min/ojcomponentcore.js /Users/lypelov/development/JET/oraclejetonnodejs/public/js/libs/oj/v1.1.2/min/ojbutton.js /Users/lypelov/development/JET/oraclejetonnodejs/public/js/libs/oj/v1.1.2/min/ojtoolbar.js /Users/lypelov/development/JET/oraclejetonnodejs/public/js/libs/jquery/jqueryui-amd-1.11.4.min/position.js /Users/lypelov/development/JET/oraclejetonnodejs/public/js/libs/oj/v1.1.2/min/ojpopupcore.js /Users/lypelov/development/JET/oraclejetonnodejs/public/js/libs/oj/v1.1.2/min/ojmenu.js /Users/lypelov/development/JET/oraclejetonnodejs/public/js/libs/oj/v1.1.2/min/ojmodule.js /Users/lypelov/development/JET/oraclejetonnodejs/public/js/main.js /Users/lypelov/development/JET/oraclejetonnodejs/public/js/modules/footer.js /Users/lypelov/development/JET/oraclejetonnodejs/public/js/modules/graphics.js /Users/lypelov/development/JET/oraclejetonnodejs/public/js/libs/oj/v1.1.2/min/ojlistview.js /Users/lypelov/development/JET/oraclejetonnodejs/public/js/libs/oj/v1.1.2/min/ojnavigationlist.js /Users/lypelov/development/JET/oraclejetonnodejs/public/js/libs/hammer/hammer-2.0.4.min.js /Users/lypelov/development/JET/oraclejetonnodejs/public/js/libs/oj/v1.1.2/min/ojjquery-hammer.js /Users/lypelov/development/JET/oraclejetonnodejs/public/js/libs/oj/v1.1.2/min/ojoffcanvas.js /Users/lypelov/development/JET/oraclejetonnodejs/public/js/libs/oj/v1.1.2/min/ojdatacollection-common.js /Users/lypelov/development/JET/oraclejetonnodejs/public/js/libs/jquery/jqueryui-amd-1.11.4.min/mouse.js /Users/lypelov/development/JET/oraclejetonnodejs/public/js/libs/jquery/jqueryui-amd-1.11.4.min/draggable.js /Users/lypelov/development/JET/oraclejetonnodejs/public/js/libs/oj/v1.1.2/min/ojdialog.js /Users/lypelov/development/JET/oraclejetonnodejs/public/js/modules/header.js /Users/lypelov/development/JET/oraclejetonnodejs/public/js/modules/home.js /Users/lypelov/development/JET/oraclejetonnodejs/public/js/modules/library.js /Users/lypelov/development/JET/oraclejetonnodejs/public/js/modules/people.js /Users/lypelov/development/JET/oraclejetonnodejs/public/js/modules/performance.js /Users/lypelov/development/JET/oraclejetonnodejs/public/js/libs/require/text.js text!templates/compContent.tmpl.html text!templates/footer.tmpl.html text!templates/graphics.tmpl.html text!templates/header.tmpl.html text!templates/home.tmpl.html text!templates/library.tmpl.html text!templates/navContent.tmpl.html text!templates/people.tmpl.html text!templates/performance.tmpl.html
The new main.min.js file is ready and we can use it in our index.html project. To do so change the line including the main module with this:
<script data-main="js/main.min" src="js/libs/require/require.js"</script>
If you run again the project in the browser you should see now completely different picture: We now have only 9 requests and 721K of content to load. If we take one simple step in our Node project and allow the compression on line 22 from the app.js file:
// setup compression app.use(compression());
…the page footprint will look even better only 190K:
If you look at the rest of the 9 request you can realize that we can reduce them even too. For example you can merge the 3 CSS files into one, and remove the pictures and use CSS Sprites instead. Also the optimizer has an option to include the require.js file into the module file as well, so all this could reduce the number of requests to 3.
Note that there is one template called demoAppHeaderOffCanvasTemplate.tmpl.html, which we did not include into the main.min.js file. This is because of a issue with the template which requires specific DIV to be loaded otherwise the left side menu won’t work on small screen devices. I would work later to see if there is a way to fix that problem.
All content listed on this page is the property of Oracle Corp. Redistribution not allowed without written permission