- Chris Ruppel
- February 5, 2013
- JavaScript, Drupal, How-Tos, Performance
Everyone does it.
There’s a piece of JavaScript that will only be used on one page, perhaps to provide some unique interactivity. It’s probably attached to a View or maybe a unique node ID. It’s so easy to toss in a drupal_add_js() and move on — or worse, throw the code in your theme. Wouldn’t it be nice if you could inline all these one-use scripts and make them appear only the page they’re needed?
Inline on the Fly
Here’s an easy way to inline scripts without losing the ability to edit them easily. We don’t want our code sitting in a PHP string so we create and maintain a real JS file, and use file_get_contents() to grab it whenever the appropriate page is built.
// Ensure this JS ends up inline at the bottom of the page $options = array( 'scope' => 'footer', 'type' => 'inline', ); // JS lives in its own file but is included inline when page renders $js_code = file_get_contents(drupal_get_path('module','my_module').'/my_code.js'); // Add JS to page drupal_add_js($js_code, $options);
Optimized pages + organized code
I often find myself using hook_views_post_build() to apply this behavior when a specific Views display needs some custom JS to function properly. That way I don’t have to worry about the path, it just works anytime this View is used.
Avid Features users know it’s much more maintainable to keep the JS in its own file next to the View instead of stuffing it in a Views footer, or worse: tossing vital code for components into the theme’s “main” (read: only) JS file. Putting code in a theme file can seem swell until you copy a Feature for use in another project and just can’t figure out where that JavaScript went.
/** * Implements hook_views_post_build(). */ function my_feature_views_post_build(&$view) { $has_run = &drupal_static(__FUNCTION__); if (!$has_run) { switch ($view->name) { // Check for the relevant View(s) case 'my_view': // Check for the relevant display(s) if ($view->current_display == 'my_block') { // Ensure this JS ends up inline at the bottom of the page $options = array( 'scope' => 'footer', 'type' => 'inline', ); // JS lives in its own file but is included inline when page renders $js_code = file_get_contents(drupal_get_path('module','my_feature').'/my_code.js'); // Add JS to page drupal_add_js($js_code, $options); $has_run = TRUE; } break; } } }
Performance
Inlining a script avoids an http request and is great for frontend performance. However, if you have a page that is uncached and hit continuously, adding disk reads won’t be so great for the actual server’s performance. You can see in the second example there’s a reference to drupal_static(). This is a good way to avoid running a slow Drupal hook more than once per page request. Always make sure to cache the outcome of functions like this one in order to avoid too many disk reads.


Commenting on this Blog post is closed.
Comments
Thank you for a useful tip. Your $has_run does nothing though.
What’s the name of the module that does the code block fly-out?
@salvis, it looks like Geshi Filter module.
Glad you found it useful.
$has_runis there specifically to make sure nothing runs again after it has done its job. Since this is a Views hook that gets invoked every time a View is built, most of the time we want this function to do nothing. I did forget to set$has_run = TRUEafterdrupal_add_js()executes, so that has been fixed above. Good eye!For syntax highlighting we use a library called GeSHifilter, but the fly-out part is hand-rolled CSS.
Can you explain a bit what is the advantage of using file_get_contents over loading the file in drupal_add_js.
As far as I understand you wan’t it to be inline to avoid an additional http request? Is this correct and the only reason?
You’re correct, the main goal in this case is to use inline JavaScript to avoid the additional HTTP request. People often make a script appear inline, but use unmaintainable techniques, such as putting JS in a Views footer (which only exists as a textarea of the Views UI, not in version control), or mashing their JS into the theme when often it belongs grouped with other files.
The “one-use” phrase is key here. If you have JavaScript that affects every page of your site (e.g. your main nav) you’ll want that included in a file that is cached by your browser after the first page load.
Nice :)
What I did in the navigation timing module (it adds a bunch of inline JS on all pages) is putting the file content in a variable that gets refresh on cache rebuild: http://drupalcode.org/project/navigation_timing.git/blob/refs/heads/7.x-…
That’s a pretty good idea, too! Mentally filing this technique for later use..
Good write-up. Inline JS is indeed troublesome.
I wonder whether we could “standardize” this pattern for D8 in core?
More concretely, thinking of something along the lines of
/modules/mymodule/js/inline/mymodule.some-custom-identifier.js
In other words, we sorta standardized on putting all JS into a local/relative
./jsdirectory already. Thus, why not have./js/inlinesubdirectory in there that has some additional baked-in semantics?Then, provider a helper function along the lines of this:
$build['#attached']['inline'][] = array('mymodule', 'some-custom-identifier');
That would call
drupal_add_inline(), which in turn would have the entire logic you presented here baked-in (including the repetition protection).Non-obvious, additional benefit: Did you ever try to alter inline JS? :) With this, we’d inherently introduce IDs that are uniquely identifiable, so one can alter/swap-out inline JS snippets more effectively.
Makes sense? I’d consider that to be a very minor API addition, so a well-scoped and well-tested patch should still be able to land for D8. That said, I probably won’t have time to work on this, but let me know if you do :)
Thanks!
Heh, I’ve never tried to alter inline JS. You’re right, that’s quite the rabbit hole without a change to the API. I’m not quite up on Assetic in D8 yet, but your suggestion would be a useful addition.
Agreed. If you want to do a really early JavaScript action, this is the only good way to do it.