Wordpress - Where and how to put inline js in pages

I'm looking at the Twenty Seventeen functions.php source code right now and they left a pattern in there for inlining script (or anything for that matter) into the document with priority, shown here generalized using a named function:

function add_inline_script() {
  echo "<script>/* do awesome things */</script>\n";
}
add_action( 'wp_head', 'add_inline_script', 0 );

You might want to do this, for example, if you are loading an async script like the alternative Google Analytics async tracking snippet, enabling you to kick off non-blocking script requests as soon as the document starts getting parsed (if placed above blocking requests).

Besides async, modern browsers also allow us to manage async dependencies leveraging the Fetch API, affording one the ability to load external dependencies asynchronously in parallel without risking a race condition.

The following image and example illustrate using this technique to reduce page load time by loading scripts without blocking in the Twenty Eleven theme using Fetch Inject:

Loading Twenty Seventeen with Fetch Injection Figure 1: Twenty Seventeen theme scripts plus lazysizes loading asynchronously in parallel.

Piggybacking on a question about string interpolation. Use it while dropping arbitrary blocks of content into the page with echo using NOWDOC or HEREDOC and global assignments and string interpolation with Fetch Inject, as shown here:

/**
 * Load scripts using Fetch Inject instead of wp_enqueue_script
 * for for faster page loads and lower perceived latency.
 *
 * @since WordCamp Ubud 2017
 * @link https://wordpress.stackexchange.com/a/263733/117731
 * @link https://github.com/jhabdas/fetch-inject
 *
 */
function add_inline_script() {
  $twentyseventeen_l10n = array(
    'quote' => twentyseventeen_get_svg( array( 'icon' => 'quote-right' ) ),
    'expand' => __( 'Expand child menu', 'twentyseventeen' ),
    'collapse' => __( 'Collapse child menu', 'twentyseventeen' ),
    'icon' => twentyseventeen_get_svg( array( 'icon' => 'angle-down', 'fallback' => true ) )
  );
  $jquery_script_path = '/wp-includes/js/jquery/jquery.js?ver=1.12.4';
  $jquery_dependent_script_paths = [
    get_theme_file_uri( '/assets/js/global.js' ),
    get_theme_file_uri( '/assets/js/jquery.scrollTo.js' ),
    get_theme_file_uri( '/assets/js/skip-link-focus-fix.js' ),
    get_theme_file_uri( '/assets/js/navigation.js' )
  ];
  $lazysizes_path = get_theme_file_uri( '/assets/js/lazysizes.min.js' );
  $screen_reader_text_object_name = 'twentyseventeenScreenReaderText';
  $twentyseventeen_l10n_data_json = json_encode($twentyseventeen_l10n);
  $jquery_dependent_script_paths_json = json_encode($jquery_dependent_script_paths);
  $inline_script = <<<EOD
window.{$screen_reader_text_object_name} = $twentyseventeen_l10n_data_json;
(function () {
  'use strict';
  if (!window.fetch) return;
  /**
   * Fetch Inject v1.6.8
   * Copyright (c) 2017 Josh Habdas
   * @licence ISC
   */
  var fetchInject=function(){"use strict";const e=function(e,t,n,r,o,i,c){i=t.createElement(n),c=t.getElementsByTagName(n)[0],i.type=r.blob.type,i.appendChild(t.createTextNode(r.text)),i.onload=o(r),c?c.parentNode.insertBefore(i,c):t.head.appendChild(i)},t=function(t,n){if(!t||!Array.isArray(t))return Promise.reject(new Error("`inputs` must be an array"));if(n&&!(n instanceof Promise))return Promise.reject(new Error("`promise` must be a promise"));const r=[],o=n?[].concat(n):[],i=[];return t.forEach(e=>o.push(window.fetch(e).then(e=>{return[e.clone().text(),e.blob()]}).then(e=>{return Promise.all(e).then(e=>{r.push({text:e[0],blob:e[1]})})}))),Promise.all(o).then(()=>{return r.forEach(t=>{i.push({then:n=>{"text/css"===t.blob.type?e(window,document,"style",t,n):e(window,document,"script",t,n)}})}),Promise.all(i)})};return t}();
  fetchInject(
    $jquery_dependent_script_paths_json
  , fetchInject([
    "{$jquery_script_path}"
  ]));
    fetchInject(["{$lazysizes_path}"])
})();
EOD;
  echo "<script>{$inline_script}</script>";
}
add_action('wp_head', 'add_inline_script', 0);

Reduces perceived latency on a stock Twenty Seventeen theme by ~50 percent. Can be applied to any theme.

How does it work?

  • Replaces all of the scripts enqueued with wp_enqueue_script so they output faster by using Fetch Injection. (Note: See Fetch Inject docs for backwards browser compatibility with older browsers.)
  • Uses HEREDOC to output everything in a script tag in the head.
  • Defines a window Global the theme wants.
  • Loads all of the scripts asynchronously in parallel as shown in the above image, but waits for jQuery to finish loading before executing the dependencies (already downloaded).
  • Does all the work in an IIFE to prevent any free variables from leaking into the window scope.

HEREDOC is ideal for controlling output when you need to maintain copyright notices or other formatting via mixed templates. It also helps increase script debugging speed when you don't have the minified code handy or just want to lob something into the document for testing purposes.

If you want to ensure items are only added one time, see this answer: https://wordpress.stackexchange.com/a/131640/117731

Finally, if you wanted to add to the footer instead of the head, you can replace wp_head in the add_action call with wp_footer.

EDIT: After some more digging I found this post on David Walsh's blog, suggesting at least simple inlining has been available since at least 2012. Have fun!


wp_enqueue_script() is the only way that javascript should ever be added to your site. It allows you and other plugins to declare dependencies and even deregister scripts if needed. As mentioned on a different thread today, caching plugins can't gzip or minify your scripts if you don't use the proper WordPress technique.

If you look at the codex page, you'll see that you can control whether the script is in the header or footer.