[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