I have the following:
Modernizr.load([
{
load : '//ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js',
complete : function () {
if ( !window.jQuery ){
Modernizr.load('/js/jquery-1.6.2.min.js');
}
}
},
{
load : ["/js/someplugin.js", "/js/anotherplugin.js"],
complete : function()
{
// do some stuff
}
},
{
load: ('https:' == location.protocol ? '//ssl' : '//www') + '.google-analytics.com/ga.js'
}
]};
I read that Modernizr loads scripts Asyncronously. But in the above example, which ones are loaded async?
Do all of the following get loaded asyncronously?
jquery.min.js
someplugin.js
anotherplugin.js
ga.js
Or is it a combination of async and ordered loading like this:
jquery.min.js is loaded first
Then someplugin.js and anotherplugin.js is loaded async
finally, ga.js is loaded
I'm having a hard time testing which case it is.
You've selected a fairly complex example to dissect. So lets do it in steps.
The three parameter sets {...},{...},{...} will execute sequentially.
Inside the first parameter set you will load jQuery from google's CDN, then when complete you test if jQuery actually loaded. If not (perhaps you are developing offline and the google CDN is not reachable), you load a local copy of jQuery. So these one is "sequential", but really only one of them will load.
In the second parameter set you load someplugin.js and 'anotherplugin.js` simultaneously and asynchronously. So they will be downloaded in parallel. Its great when you can parallel 2 items at a time as that is the "weakest link" for browsers today (yup only IE, Every other browser will parallel 6-8 files at a time).
In the third parameter set you load the google analytics script.
Remember modernizr is a collection of tools. The included loader is actually just a repackaged yepnope. So you can google for more about yepnope.
The idea with the sequential loads is to be able to ensure that dependencies load in order (example your jQuery plugins must load after jQuery framework). The purpose of the parallel downloads syntax in parameter set two is to speed up performance for multiple files that are not co-dependent (example you could load multiple jQuery plugins in parallel once jQuery is loaded as long as they don't depend on eachother).
So to answer your question, your hypothesis #2 is correct. If you'd like to explore more using firebug, add some console.log statements in each parameter set's complete function. You should see the 3 groups complete sequentially every time. Now switch firebug onto the "Net" tab to watch the file requests go out. The files someplugin.js and 'anotherplugin.js` won't necessarily load in the same order everytime. The requests will go out in order, but there timing bars should overlap (showing them as parallel). Testing this locally will be hard because it'll be so fast. Put your two test files on a slow server somewhere or bias them the opposite of what you are expecting (make the first file 1mb and the second <1kb) so you can see the overlapping downloads in the network monitor tab of firebug (this is just an artificial scenario for testing purposes, you could fill a file with repeated JS comments just to make an artificially slow file for testing).
EDIT: To clarify a little more accurately, I'd like to add a quote from the yepnopejs.com homepage. yepnopejs is the resource loader that is included (and aliased) inside modernizr:
In short, whatever order you put them in, that's the order that we
execute them in. The 'load' and 'both' sets of files are executed
after your 'yep' or 'nope' sets, but the order that you specificy
within those sets is also preserved. This doesn't mean that the files
always load in this order, but we guarantee that they execute in this
order. Since this is an asynchronous loader, we load everything all at
the same time, and we just delay running it (or injecting it) until
the time is just right.
So yes, you could put jquery, followed by some plugins in the same Modernizr.load statement and they will be downloaded in parallel and injected into the DOM in the same order as specified in the arguments. The only thing you are giving up here is the ability to test if jQuery loaded successfully and perhaps grab a backup non-CDN version of jQuery if necessary. So it's your choice how critical fallback support is for you. (I don't have any sources, but I seem to recall that the google CDN has only gone down once in its entire lifetime)
Related
This question already has answers here:
Best way to execute js only on specific page
(5 answers)
Closed 5 years ago.
I want to use Content Security Policy (CSP) across my entire site. This requires all JavaScript to be in separate files. I have shared JavaScript used by all pages but there is also page specific JavaScript that I only want to run for a specific page. What is the best way to handle page specific JavaScript for best performance?
Two ways I can think of to workaround this problem is to use page specific JavaScript bundles or a single JavaScript bundle with switch statement to execute page specific content.
there is lots of ways to execute page specific javascript
Option 1 (check via class)
Set a class to body tag
<body class="PageClass">
and then check via jQuery
$(function(){
if($('body').hasClass('PageClass')){
//your code
}
});
Option 2 (check via switch case)
var windowLoc = $(location).attr('pathname'); //jquery format to get window.location.pathname
switch (windowLoc) {
case "/info.php":
//code here
break;
case "/alert.php":
//code here
break;
}
Option 3 Checking via function
make all the page specific script in the function
function homepage() {
alert('homepage code executed');
}
and then run function on specific page
homepage();
Sorry, I know this ended up being a long read, but it'll be worth it to do it as you'll be able to make the choice that's right for your site. For a tl;dr, read the first sentence of each paragraph.
First of all, no matter which route you choose you should put all of the JS common to each page in the same file to take maximum advantage of caching. That's just common sense. Also, in all cases, I assume you're using a competent minifier since that will make a bigger difference than anything else. Packagers also exist if you need one of those -- Google is your friend if you need either of these.
For the page specific JS, you should decide whether it's most important to have your first page load (the user's first contact with your site) be 'fast', or if it's most important to have the following page loads (the user's first contact with any given page) be 'fast'. Modern browser caching is quite good now, so you can rely on the browser loading from cache whenever it can. In general, if it's most important for the first page load to be fast, then create separate JS files (this way, the user isn't stuck downloading 10 MB of data before they even get to your site). If not, then put all the JS in the same file, keeping in mind that if one page has significantly more JS than others, it will adversely affect the load time of every page on your site. Note that this extra load time can be mitigated with the use of async or defer tags, more on that later.
Consider the case where page A has 5 KB of JS and page B has 5 MB of JS. If you put both scripts in the same file, page A will load more slowly (since it needs to load ~5 MB of JS) but page B will load much faster due to the JS file being cached already. If you keep them separate, page A will load much faster than page B, but there will be an average speed decrease compared to the first case. If one page doesn't have significantly more JS than another, use separate files. You'll encounter much better average load time since the "savings" of loading the big file ahead of time will be greatly diminished (you'll also avoid the issue mentioned below).
Another consideration is whether one of the JS files will change often, as this will invalidate the cached version and require the browser to redownload it. If you put all your JS together and only one of the files is volatile (especially if it's a page not often visited, such as a registration page), the end user will face a higher average load time than if you keep them separate. Stack Overflow themselves took an interesting approach to this. It appears they have a function to invalidate the cache of JS unrelated to the page and load it (if necessary) when the JS on the page loads from the cache to save loading time later.
One more thing! Beyond all this, you should also decide whether or not you should use async or defer in your script tags since you're migrating to fully "external" JS.
async allows the page to load and display to the user before the JS is finished downloading. This is a great way to hide the download of a big JS file if you decide to go the "one file to rule them all" route. However, you might also find the JS needs to be downloaded and execute in order for the page to display properly (as is the case when not using async or defer).
As a result, it might be a good idea to use a hybrid of the two suggestions and split your js into individual files that need to be loaded per page for the page to display correctly (one per page), and put all the js that doesn't into a script that loads through an async or defer tag (this being the "one big file"). defer lets the browser load it in the background after the page is displayed to the user.
Ultimately, only you can make the decisions that are right for your app. There's no one magic option that will work in all cases, but that's the reality of software design/engineering. I hope I've made the process clearer for you so you can arrive at the right choice more easily, though.
I see some strange behavior when I check network tab on chrome tools. jQuery is initiating call to other js files of page. These other files are already loaded but jQuery is adding ?_=some_random_number at the end and calling those files again.
jQuery does not load other javascript-files by default. You must have another library that loads these resources. And by the way, the ?_={timestamp} is used to invalidate browser-caching (the url changes every second, so every second a real request is made, not just a browser-cache lookup).
You can try to enforce the ajax-caching mechanism with:
$.cache = true;
while initializing your code. Thus, the requests will be cached by your browser. But, be aware, that this might tamper with other parts of your code. A better way would be to identify the reason why these extra resources are loaded, and avoid loading.
I'm working on a large project which is extensible with modules. Every module can have it's own javascript file which may only be needed on one page, multiple pages that use this module or even all pages if it is a global extension.
Right now I'm combining all .js files into one file whenever they get updated or a new module get's installed. The client only has to load one "big" .js file but parse it for every page. Let's assume someone has installed a lot of modules and the .js file grows to 1MB-2MB. Does it make sense to continue this route or should I include every .js when it is needed.
This would result in maybe 10-15 http requests more for every page. At the same time the parsing time for the .js file would be reduced since I only need to load a small portion for every page. At the same time the browser wouldn't try to execute js code that isn't even required for the current page or even possible to execute.
Comparing both scenarios is rather difficult for me since I would have to rewrite a lot of code. Before I continue I would like to know if someone has encountered a similar problem and how he/she solved it. My biggest concern is that the parsing time of the js files grows too much. Usually network latency is the biggest concern but I've never had to deal with so many possible modules/extensions -> js files.
If these 2 conditions are true, then it doesn't really matter which path you take as long as you do the Requirement (below).
Condition 1:
The javascript files are being run inside of a standard browser, meaning they are not going to be run inside of an apple ios uiWebView app (html5 iphone/ipad app)
Condition 2:
The initial page load time does not matter so much. In other words, this is more of a web application than a web page. So users login each day, stay logged in for a long time, do lots of stuffs...logout...come back the next day...
Requirement:
Put the javascript file(s), css files and all images under a /cache directory on the web server. Tell the web server to send the max-age of 1 year in the header (only for this directory and sub-dirs). Then once the browser downloads the file, it will never again waste a round trip to the web server asking if it has the most recent version.
Then you will need to implement javascript versioning, usually this is done by adding "?jsver=1" in the js include line. Then increment the version with each change.
Use chrome inspector and make sure this is setup correctly. After the first request, the browser never sends an Etag or asks the web server for the file again for 1 year. (hard reloads will download the file again...so test using links and a standard navigation path a user would normally take. Also watch the web server log to see what requests are being severed.
Good browsers will compile the javascript to machine code and the compiled code will sit in browser's cache waiting for execution. That's why Condition #1 is important. Today, the only browser which will not JIT compile js code is Apple's Safari inside of uiWebView which only happens if you are running html/js inside of an apple app (and the app is downloaded from the app store).
Hope this makes sense. I've done these things and have reduced network round trips considerably. Read up on Etags and how the browsers make round trips to determine if is using the current version of js/css/images.
On the other hand, if you're building a web site and you want to optimize for the first time visitor, then less is better. Only have the browser download what is absolutely needed for the first page view.
You really REALLY should be using on-demand JavaScript. Only load what 90% of users will use. For things most people won't use keep them separate and load them on demand. Also you should seriously reconsider what you're doing if you've got upwards of two megabytes of JavaScript after compression.
function ondemand(url,f,exe)
{
if (eval('typeof ' + f)=='function') {eval(f+'();');}
else
{
var h = document.getElementsByTagName('head')[0];
var js = document.createElement('script');
js.setAttribute('defer','defer');
js.setAttribute('src','scripts/'+url+'.js');
js.setAttribute('type',document.getElementsByTagName('script')[0].getAttribute('type'));
h.appendChild(js);
ondemand_poll(f,0,exe);
h.appendChild(document.createTextNode('\n'));
}
}
function ondemand_poll(f,i,exe)
{
if (i<200) {setTimeout(function() {if (eval('typeof ' + f)=='function') {if (exe==1) {eval(f+'();');}} else {i++; ondemand_poll(f,i,exe);}},50);}
else {alert('Error: could not load \''+f+'\', certain features on this page may not work as intended.\n\nReloading the page may correct the problem, if it does not check your internet connection.');}
}
Example usage: load example.js (first parameter), poll for the function example_init1() (second parameter) and 1 (third parameter) means execute that function once the polling finds it...
function example() {ondemand('example','example_init1',1);}
I tried unsuccessfully to add a google map(externally loaded script) to a meteor app, and I noticed there were two kinds of problems:
If I do the simple thing and add the main API script to my <head></head>, then it gets rendered last.
When this happens, I am obliged to insert any scripts that depend on the API again in my template's <head> - after the main API script. (otherwise scripts complain they don't see the API blabla..)
Then the time for the actually function call comes - and now putting it inside <head> after the rest won't work. You need to use Template.MyTemplate.rendered.
Basically my question is:
What's the cleanest way to handle these kinds of things?
Is there some other variable/method I can use to make sure my Google main API file is called very first in my HTML?
I just released a package on atmosphere (https://atmosphere.meteor.com) that might help a bit. It's called session-extras, and it defines a couple functions that I've used to help with integrating external scripts. Code here: https://github.com/belisarius222/meteor-session-extras
The basic idea is to load a script asynchronously, and then in the callback when the script has finished loading, set a Session variable. I use the functions in the session-extras package to try to make this process a bit smoother. I have a few functions that have 3 or 4 different dependencies (scripts and subscriptions), so it was starting to get hairy...
I suppose I should add that you can then conditionally render templates based on whether all the dependencies are there. So if you have a facebook button, for example, with helpers that check the Session variables, you can give it a "disabled" css class and show "loading facebook..." until all the necessary scripts have loaded.
edit 03/14/2013
There is also an entirely different approach that is applicable in many cases: create your own package. This is currently possible with Meteorite (instructions), and the functionality should soon be available in Meteor itself. Some examples of this approach are:
jquery-rate-it: https://github.com/dandv/meteor-jquery-rateit
meteor-mixpanel: https://github.com/belisarius222/meteor-mixpanel
If you put a js file in a package, it loads before your app code, which is often a good way to include libraries. Another advantage of making a package is that packages can declare dependencies on each other, so if the script in question is, for example, a jQuery plugin, you can specify in the package's package.js file that the package depends on jQuery, and that will ensure the correct load order.
Sometimes it gets a little more interesting (in the Chinese curse sense), since many external services, including mixpanel and filepicker.io, have a 2-part loading process: 1) a JS snippet to be included at the end of the body, and 2) a bigger script loaded from a CDN asynchronously by that snippet. The js snippet generally (but not always!) makes some methods available for use before the bigger script loads, so that you can call its functions without having to set up more logic to determine its load status. Mixpanel does that, although it's important to remember that some of the JS snippets from external services expect you to set the API key at the end of the snippet, guaranteed to be before the bigger script loads; in some cases if the script loads before the API key is set, the library won't function correctly. See the meteor-mixpanel package for an example of an attempt at a workaround.
It's possible to simply download the bigger js file yourself from the CDN and stick it in your application; however, there are good reasons not to do this:
1) the hosted code might change, and unless you check it religiously, your code could get out of date and start to use an old version of the API
2) these libraries have usually been optimized to load the snippet quickly in a way that doesn't increase your page load time dramatically. If you include the bigger JS file in your application, then your server has to serve it, not a CDN, and it will serve it on initial page load.
It sounds like you're loading your Javascript files by linking it with HTML in your template. There's a more Meteor way of doing this:
From the Meteor Docs:
Meteor gathers all JavaScript files in your tree with the exception of
the server and public subdirectories for the client. It minifies this
bundle and serves it to each new client. You're free to use a single
JavaScript file for your entire application, or create a nested tree
of separate files, or anything in between.
So with that in mind, rather than link the gmaps.js into head, just download the un-minified version of gmaps and drop it in you application's tree.
Also from the Meteor Docs:
It is best to write your application in such a way that it is
insensitive to the order in which files are loaded, for example by
using Meteor.startup, or by moving load order sensitive code into
Smart Packages, which can explicitly control both the load order of
their contents and their load order with respect to other packages.
However sometimes load order dependencies in your application are
unavoidable. The JavaScript and CSS files in an application are loaded
according to these rules:
Files in the lib directory at the root of your application are loaded
first.
[emphasis added]
And if the sequence is still an issue, drop the js file into client/lib and it will load before all the Javascript you've written.
I've used meteor-external-file-loader and a bit of asynchronous looping to load some scripts, which will load javascript (or stylesheets) in the order you specify.
Make sure to have meteorite and add the package above >> mrt add external-file-loader
Here's the function I wrote to make use of this package:
var loadFiles = function(files, callback) {
if (!callback) callback = function() {};
(function step(files, timeout, callback) {
if (!files.length) return callback();
var loader;
var file = files.shift();
var extension = file.split(".").pop();
if (extension === "js")
loader = Meteor.Loader.loadJs(file, function() {}, timeout);
else if (extension === "css") {
Meteor.Loader.loadCss(file);
loader = $.Deferred().resolve();
}
else {
return step(files, timeout, callback);
}
loader.done(function() {
console.log("Loaded: " + file);
step(files, timeout, callback);
}).fail(function() {
console.error("Failed to load: " + file);
step(files, timeout, callback);
});
})(files, 5000, callback);
}
Then to use this, add to one of your created methods for a template like so:
Template.yourpage.created = function() {
var files = [
"//ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js",
"javascripts/bootstrap.min.js"
];
loadFiles(files, function() {
console.log("Scripts loaded!");
});
}
Quick edit: Found it's just a good idea to place the functionality from the .created method in the /lib folder in Meteor.startup();
Meteor.startup(function() {
if (Meteor.isClient) {
// Load files here.
}
});
Caveat: Lots of javascript files = really long load time.... Not sure how that will be affected by the normal meteor javascript files, and the load order there. I would just make sure that there are no conflicts until users take action on the website (or that if there is, they are loaded first).
In a ASP.NET Masterpage I am using YepNope to unconditionally and asynchronously load jQuery (from the Google CDN, with local fallback) and some scripts which are used on all pages in the site. In the MasterPage I have created a ContentPlaceHolder before the closing body tag (and below the YepNope script that loads those used on all pages) which is used for scripts used on individual page. As jQuery should be available on every page in the site it should not be loaded individually on those pages where there are specific scripts that use it.
The problem I have is that I can't use the callback or complete functions in the yepnope script where jQuery is loaded, as this is on the MasterPage and these are individual page scripts which are only used or added on that page, yet I need to be able to delay the execution of the individual page scripts until yepnope (which appears above the page scripts) has finished loading any dependencies (such as jQuery) used in them.
I can think of two options-
1- Make the script used on the page an external file and load that using the syntax -
yepnope('/url/to/your/script.js');
or
yepnope({ load: '/url/to/your/script.js' });
I'm not sure I like this idea as it introduces an extra HTTP request for a few lines of javascript which isn't going to be used on any other page.
2- Load jQuery again in another yepnope test object block, with the complete function wrapping up the page scripts (calling complete without a test seems to execute the function immediately, before the previous scripts are loaded) and relying on the following-
I am requesting a file twice and it's only loading once? By popular
demand, in yepnope 1.5+ we added the feature that scripts that have
already been requested not be re-executed when they are requested a
second time. This can be helpful when you are dealing with less
complex serverside templating system and all you really care about is
that all of your dependencies are available.
In the page I could presumably load the same version of jQuery from the Google CDN, which based on the above would not actually be loaded twice, and then load the page scripts in an anonymous function called from the complete function of the yepnope test object.
On the plus side this would mean that the page is no longer dependent on jQuery being loaded from the MasterPage, but a negative would be that (even assuming YepNope does not load the script twice now) we would be loading multiple versions of jQuery should the version in the MasterPage be changed without the same happening in the page in the future. From a maintenance point of view I don't feel this is a good idea, especially on the assumption (which I feel you should always make) that another developer would be the one making the changes.
It also does not seem especially elegant.
On balance I will almost certainly use the first option but I would like to know if there is a way to delay or defer scripts on a page until asynchronous loading is completed, and this cannot be done as part of the YepNope test object loading the resources.
How do other developers approach this problem?
I have come up with this as a solution I rather like.
In the MasterPage YepNope test object add the code-
complete: function() {
if (window.pageFunctions !== null && typeof (window.pageFunctions) === "function") {
window.pageFunctions();
}
}
If I want to add any JavaScript code or functions that rely on the dependencies loaded in the MasterPage I just wrap them in a function named "pageFunctions" like so-
<script type="text/javascript">
function pageFunctions() {
$(document).ready(function () {
...
});
}
</script>
I'm still interested in other (possibly better) solutions so I'm going to leave the question open for a couple of days.
I'd also appreciate comments on this as a solution.