How do you add a simple javascript function in Sage 9?

@MWDelaney’s suggestion, which involves (ultimately) bundling all of your functions and calls into the theme’s main.js file is probably the most “Sage”-y way to handle the problem, but if having your ajax functions in a separate file and your scripts embedded in a <script> tag is necessary for your implementation, then it looks like your problem has to do with load order.

As I mentioned in your other topic, your wp_enque_script() calls are telling WordPress to print out the link to ajax.js in the footer of your page:

wp_enqueue_script(
  'sage/ajax.js', 
  asset_path('scripts/ajax.js'), 
  ['jquery'], 
  null, 
  true // Setting this to 'true' means "put this in the footer"
);

You aren’t telling WordPress where to enqueue jQuery, so it’s probably putting it in the head of your page (since that’s the default). Otherwise, you’d likely be getting an error in the console telling you the jQuery was undefined.

The <script> tag with your function call appears somewhere on your page below your head but above your footer. The browser will execute scripts as soon as it encounters them. Since it reads the page from top to bottom, it will encounter your <script> that tries to call do_function() before it encounters the link to ajax.js in the footer, where that function is actually defined.

You’ve wrapped your function call in jQuery(document).ready() with the thought (I’m assuming) that this command will prevent the call to do_function() from firing until the page is “ready,” which most people usually assume is “when the browser has all of the assets,” but this is not the case: As the documentation for ready() explains, ready() is fired “as soon as the page’s Document Object Model (DOM) becomes safe to manipulate.” The ready event does not mean all assets have been loaded, only that the browser has parsed the page’s HTML. Because ajax.js is a link to an asset (a JavaScript file), the browser does not execute its content (and become aware of do_function()) as soon as it encounters it: It must first download that file and then parse it, but it doesn’t wait to do that: It keeps loading the page, and then fires ready() when it’s finished—likely before it has finished downloading and parsing ajax.js.

Your call to do_something() is successful when you put it in ajax.js because then none of the above applies: The function is invoked in the same file where it is defined, so when the page is parsed and the file downloaded doesn’t matter.

Sage does not, directly, do anything that causes this: It’s just how browsers work. However, it looks like you copied your wp_enqueue_script() calls directly from the Sage instructions. Those calls specifically enqueue JS files in the footer for performance reasons, but this is not the WordPress default—WordPress defaults to enqueue files in the head. It’s possibly you didn’t run into this problem in the past because you loaded your scripts in the head and then the browser likely had them downloaded and parsed by the time it encountered your embedded <script>s. Webpack may have also contributed, since it does, as you observed, add some additional code to files it processes, which increases their size, which increases how long they take to download, which exacerbates the race condition that’s causing your problem.

This issue—the issue of load order and potential race conditions for JavaScript—is one of the reasons Sage prefers to compile all scripts into a single file. By doing so, it gives the developer complete control of when, where, and how those scripts are executed—it takes the unpredictability of network and browser performance out of the equation.

4 Likes