[wp-trac] [WordPress Trac] #12009: Add support for HTML 5 "async" and "defer" attributes

WordPress Trac noreply at wordpress.org
Tue May 9 22:51:15 UTC 2023


#12009: Add support for HTML 5 "async" and "defer" attributes
-------------------------------------------------+-------------------------
 Reporter:  Otto42                               |       Owner:  10upsimon
     Type:  enhancement                          |      Status:  assigned
 Priority:  high                                 |   Milestone:  6.3
Component:  Script Loader                        |     Version:  4.6
 Severity:  normal                               |  Resolution:
 Keywords:  has-patch has-unit-tests 2nd-        |     Focuses:
  opinion                                        |  performance
-------------------------------------------------+-------------------------

Comment (by westonruter):

 Replying to [comment:97 azaozz]:
 > I still don't see why (and how) async scripts can be handled through
 script-loader. As I said earlier, they cannot be used as a dependency and
 cannot be dependent on other scripts because of the unknown execution
 order.

 In this case, the scripts themselves handle the proper execution order.
 This is elaborated on in [https://github.com/WordPress/wordpress-
 develop/pull/4391/files#r1179857620 my PR comment] and you can see an
 example for how an async library can handle the proper loading order in
 this glitch: https://async-library-script-loading-demo.glitch.me/

 In short, for async scripts, the dependencies are not so much about the
 execution order but more about a bundling mechanism. If you have an
 `async` script C that depends on B, and `async` script B depends on
 `async` script A, then doing `wp_enqueue_script('C')` should automatically
 also print the scripts for `A` and `B`.


 > > In order to maintain execution order for inline scripts attached to
 `defer` and `async` scripts, we've attempted to control the loading order
 of those inline scripts by printing them with the type `text/template` so
 they're not initially executed, and then executing them after the script
 they're attached to has loaded. @westonruter added
 [https://github.com/WordPress/wordpress-
 develop/pull/4391#issuecomment-1536869109 a great explanation] about how
 this can lead to subtle CSP issues that we need to address.
 >
 > Yea, printing inline scripts with `text/template` type seems pretty
 "hacky" imho. Lets avoid that even if it means that `defer` scripts will
 not support "after" scripts (`async` scripts shouldn't be in the script-
 loader at all imho, see above).

 I don't think we can avoid `after` scripts entirely, as then an author
 can't reliably execute some code when the script loads (without resorting
 to a [https://async-script-load-listeners.glitch.me/somewhat hacky
 `MutationObserver` solution]). I'm referring to `defer` scripts
 specifically, since the order of inline scripts for `async` should not be
 significant. To me it boils down to two options, to whether for `defer`
 scripts:

 1. We handle the deferred execution of `after` inline scripts using the
 current approach of `wpLoadAfterScripts()` with its `text/template`
 transformation.
 2. We let the inline scripts handle the deferred execution by attaching
 their own `load` event listener. For example:

 {{{#!php
 <?php
 wp_enqueue_script( 'foo', '/foo.js',array(), array( 'strategy' => 'defer'
 ) );
 wp_add_inline_script(
   'foo',
   'document.getElementById("foo-js").addEventListener("load", () => { /*
 ... */ })',
   'after'
 );
 }}}

 Which would output:

 {{{
 <script id="foo-js" src="/foo.js" defer></script>
 <script>
 document.getElementById("foo-js").addEventListener("load", () => { /* ...
 */ })
 </script>
 }}}

 The nice thing about the first option is that the deferred execution is
 automatic. The bad thing is that it forces ''all'' `after` inline script
 logic to run once the `defer` script is loaded. Perhaps you want some
 logic to run after but other logic to run before? (Then again, they could
 just use a `before` inline script for that.)

 Nevertheless, note that there is a somewhat brittle connection here
 between the `after` inline script and the `defer` script: whether there is
 a `script` element in the page with the expected `id`. If there is an
 optimization plugin that tries concatenating `defer` scripts together,
 then any such `load` event handler will fail because the original `script`
 will be gone.

 In this case, it seems perhaps the safest thing to do is to not wait for
 the `load` event on the `script[defer]` at all, but rather to just attach
 a `DOMContentLoad` event handler to the `document` which runs after all
 `defer` scripts have been evaluated. (The only gotcha here is the `defer`
 script may have failed to load.)

 > > I'd appreciate feedback on these three options:
 > >
 > > 1. WP should not support inline scripts printed after a script that is
 `async` or `defer`.
 > > 2. WP should load non-blocking scripts with a blocking strategy if an
 inline script is registered with the `after` position.
 > > 3. WP should allow inline scripts after non-blocking scripts, but
 should ensure the inline script is not executed until after the script it
 is attached to has loaded (i.e., the current approach) and address CSP
 concerns.
 >
 > Thinking 1 makes most sense. Keep in mind that "after" scripts are
 relatively rarely used. So disallowing them for `defer` scripts would
 probably not be a problem for the great majority of plugins and themes.
 >
 > Another possibility is to implement the "after" script in the `defer` or
 `async` script itself. I.e. when the script is being executed it can look
 for "external" object or function or data and use it when appropriate.
 That would be the "proper way" imho.

 If we require `defer` scripts to rely on the `DOMContentLoad` event, then
 options 1 & 2 are essentially the same, as authors should instead use
 `before` inline scripts. As I mentioned just above, I think option 3 may
 end up being unreliable due to other plugins munging `script` tags and
 losing the `id`. If the current approach is reworked to rely on
 `DOMContentLoad` instead, then this would be mitigated for option 3.

-- 
Ticket URL: <https://core.trac.wordpress.org/ticket/12009#comment:100>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform


More information about the wp-trac mailing list