Moving JavaScripts from <head> to the bottom of the page can improve performance by allowing the content to display before loading the JavaScripts.

When JavaScripts are in the <head>, the browser blocks HTML parsing and loading of anything that comes after the scripts in the HTML source until the scripts are done executing.

Comments

johnalbin’s picture

Status: Active » Needs review
StatusFileSize
new2.33 KB

And the patch.

johnalbin’s picture

Status: Needs review » Needs work
johnalbin’s picture

Status: Needs work » Needs review
StatusFileSize
new3.56 KB

Ok. Here's a much better patch that works with footer scope aggregation. And doesn't cause footer-scoped scripts to load before header-scoped scripts. That last patch was a complete hack now that I look at it again.

This uses hook_js_alter to change the scope of header scripts to be footer. It also adds a UI to whitelist the names of scripts that should remain in the header. The whitelist defaults to htlml5.js and modernizr_loader.js (the script used by the Modernizr module.)

mfer’s picture

You are right to note that some scripts don't work properly in the footer. Sometimes this is a flash when the JS is applied after the pages load (e.g., chosen for select tags). You might not see it in local machine dev but once you are on a remote server the problem can pop up. I saw this become an issue recently.

I'm not sure saying "Some JavaScripts do not function properly in the footer" is good enough. Someone might move jquery to the footer with chosen. See a problem and write a support ticket here. I know I'm not going to be good about responding to that.

What else could we put in to help developers with this. Or site builders. I really want to avoid the support requests this has the potential to bring.

mfer’s picture

Status: Needs review » Needs work

The use of basename here isn't going to cut it. For example, drupal core has an ajax.js and so does the views module. Saying to put ajax.js in the footer will move multiple files there without control over which is moved individually. I'd imagine there are other cases of this.... I just found this example fairly quickly. Thoughts?

rupl’s picture

I've implemented code very similar to pixelwhip's example that John linked to above. I use the full path instead of just the filename.

I think we could improve this whitelist by adding a hook that allows modules to tell Speedy which files should remain in the <head>. Then the onus is not on the site admin to whitelist correctly. The textarea could stay to allow for manual exceptions, but by and large I think modules reporting their own exceptions will be easier and less error-prone.

I have some code here but it's not quite right yet. Will circle back sometime today or tomorrow with functional patch.

As an aside, modernizr_loader.js probably needs to be dropped from the Modernizr module. The module defaults to directly loading Modernizr as of the 2.x release last year. But yeah, if Speedy lets modules report their own whitelist, this guessing game goes away! :)

mfer’s picture

@rupl modules can put their own JS in the footer if they want. This is an override to tell it to happen differently from what the module defined.

rupl’s picture

Yup, I understand. What I'm suggesting is that since we're redefining a default, it seems like less work overall if the few modules which truly require JS in the <head> just implement a whitelist entry to tell Speedy "hey, I really need my JS in the header, and I know what I'm doing."

It seems simpler to allow for automated exceptions than to refactor poorly-written JS from other modules, or to file 300 issues in various modules to ask that their drupal_add_js() scope be declared as footer.

In IRC we discussed Chosen as an example of JS that might need to be in the <head> due to a possible FOUC. @mfer brought up the need for jQuery to shift back up to the top if even one jQuery plugin is in the head. That is a valid use-case that I hadn't considered yet.

In my head this validates the need for a method by which modules can opt out of this default that we're redefining. The whitelist entry could even include a flag that says it depends on jQuery. Here's an imaginary implementation of hook_speedy_whitelist() that I just made up:


function modernizr_speedy_whitelist() {
  $items['modernizr'][] = array(
    'name' => 'Modernizr',
    'path' => modernizr_get_path(), // produces full path, e.g. 'sites/all/libraries/modernizr/modernizr.min.js'
    'jquery' => FALSE,
  );

  return $items;
}

mfer’s picture

In addition to a white list I'd like a documentation page or blog post explaining the potential problems with the footer. Something to link people to so they can better self troubleshoot.

andypost’s picture

Suppose a line on project page and readme.txt is enough

johnalbin’s picture

rupl’s picture

In Munich we had some interesting discussion about this. I was going to propose using JS_LIBRARY - 10 as the group value and specifically avoiding those entries in the _js_alter().

However, a more obvious solution popped up organically across multiple base themes without the maintainers even discussing. Peep both of them: Omega 4.x and Aurora 1.x (after loading the pages use your browser to search for "js_alter")

The functions differ slightly, but they both rely on the same setting within each drupal_add_js() call:

  if ($js['force header']) {
    $js['scope'] = 'header';
  }
  else {
    $js['scope'] = 'footer';
  }

By following this pattern, we could provide an obvious, intuitive method for people to specify scripts which must live in the <head>. What does everyone else think?

socialnicheguru’s picture

please be careful of this option. See http://drupal.org/node/1945222

issues include the following:
1. modules use hook_page_alter to include inline js in $page['page_bottom'] by various modules
2. hook_js_alter scope is not a region even though the api implies it. so scope footer does not equal theme footer. it is equivalent to to $closure in D6. $page['page_bottom'] is supposedly the equivalent but javascript variables are not added to $page['page_bottom']
3. there is no way to order js added by hook_js_alter relative to that added by hook_page_alter