Enhancing the Scripts API with a loading strategy

Enhancing the Scripts API with a loading strategy

Overview

This post outlines a proposal to add a script loading strategy enhancement to core’s existing Scripts API.  

The underlying goal of this effort is to make it easier for WordPress plugin and theme developers and core to use “modern” loading approaches for JavaScript (like defer and async), which will help WordPress sites load faster and operate more smoothly, giving users a better experience.

Why add a loading strategy?

Data from the Web Almanac project (query) indicates that render blocking JavaScript is a significant problem on the web, with 77% of mobile pages having render-blocking scripts in the document . This query shows that approximately 40% of WordPress sites stand to benefit from deferring additional scripts. Adding defer or async to script tags enables script loading without “blocking” the rest of the page load, resulting in more responsive sites overall better user experience.

Currently WordPress core as well as plugins and themes register scripts with the wp_enqueue_script and/or wp_register_script functions. Although these functions include the ability to control the placement of the script (with the in_footer parameter), they don’t include support for adding modern attributes such as defer or async to script tags. 

To add async or defer today, developers must resort to less flexible and more fragile approaches, such as directly filtering the tags at the point of output (using  the script_loader_tag filter), or handling the tag output directly using wp_print_script_tag and the wp_script_attributes filter.

Using the first approach and directly filtering the tag can easily break: for example if two plugins both try to filter a tag, or if a tag has unexpected attributes already (eg. adding defer to a tag that already has async). Using the the second approach developers must carefully handle dependencies and output manually – things that that the Scripts API usually helps take care of.

How the loading strategy works

Developers specify a loading strategy when registering or enqueueing a script. For example, a defer strategy can be specified when the script isn’t required immediately during the page load cycle. WordPress will then determine which scripts can actually use a strategy based on logic for each strategy. For example, to ensure that scripts are executed in the order they are enqueued, defer can only be used on a script if every script that depends on that script can also be deferred. Inline script tags added with wp_add_inline_script would also be considered to ensure proper execution order.

The implementation would come with several initial built-in loading strategies: defer, async, and the default blocking behavior.

Out of scope for this feature

The loading strategy does not enable direct control of script tag attributes. This idea was originally proposed 10 years ago in #22249 and several approaches were considered on that ticket including a script attribute filter. This proposal takes a step back and aims to solve the script loading strategy aspect more comprehensively and directly while avoiding exposing the potential complications of direct attribute control.

It is worth noting that it is already possible to control attributes on wp_enqueue_script tags directly using the script_loader_tag filter. However, this is a bit of a “brute force” approach which is limited and fragile because it doesn’t consider dependencies and multiple plugins can take conflicting actions on the same tag. 

What are potential concerns with this feature?

One big concern with adding this feature to the WordPress Script API is potentially introducing a breaking change. wp_enqueue_script is a fundamental API in WordPress core, and any breaking changes could have widespread implications. Possible breakage is a possible reason that adding custom attributes as proposed in #22249 was never added to core.

This new proposal aims to ensure that there is 100% backwards compatibility, resulting in zero risk of breakage. The loading strategy will ensure that all existing uses continue to function as expected; for example, passing the boolean in_footer attribute will still control script placement. In addition, it will ensure that scripts continue to be executed in the order they are enqueued – as described above in the “How the loading strategy works” section.

Conclusion and Next Steps

Giving developers the ability to specify a loading strategy will enable them to use more advanced JavaScript loading methods while still ensuring that enqueued scripts are executed in the correct order. A “strategy” approach is also forward thinking: as the web evolves, new strategies can be developed and made available to WordPress developers. After gathering feedback, we will proceed to discussing the implementation approach and, ultimately, proposing a patch.

Have you tried using defer or async with WordPress (or do you already)? How do you think this enhancement would change that? Please leave your feedback about this proposal in the comments below and if you can, join us at our weekly performance team chats, where we are likely to discuss this proposal in the future.

Thanks to @flixos90, @tweetythierry and @mxbclang for help writing and reviewing this post and for the many contributors who have added to the discussion around this enhancement already.

#core, #feature-projects, #javascript, #performance, #proposal

nnnn

Why add a loading strategy?

nnnn

Data from the Web Almanac project (query) indicates that render blocking JavaScript is a significant problem on the web, with 77% of mobile pages having render-blocking scripts in the document . This query shows that approximately 40% of WordPress sites stand to benefit from deferring additional scripts. Adding defer or async to script tags enables script loading without u201cblockingu201d the rest of the page load, resulting in more responsive sites overall better user experience.

nnnn

Currently WordPress core as well as plugins and themes register scripts with the wp_enqueue_script and/or wp_register_script functions. Although these functions include the ability to control the placement of the script (with the in_footer parameter), they donu2019t include support for adding modern attributes such as defer or async to script tags. 

nnnn

To add async or defer today, developers must resort to less flexible and more fragile approaches, such as directly filtering the tags at the point of output (using  the script_loader_tag filter), or handling the tag output directly using wp_print_script_tag and the wp_script_attributes filter.

nnnn

Using the first approach and directly filtering the tag can easily break: for example if two plugins both try to filter a tag, or if a tag has unexpected attributes already (eg. adding defer to a tag that already has async). Using the the second approach developers must carefully handle dependencies and output manually - things that that the Scripts API usually helps take care of.

nnnn

How the loading strategy works

nnnn

Developers specify a loading strategy when registering or enqueueing a script. For example, a defer strategy can be specified when the script isnu2019t required immediately during the page load cycle. WordPress will then determine which scripts can actually use a strategy based on logic for each strategy. For example, to ensure that scripts are executed in the order they are enqueued, defer can only be used on a script if every script that depends on that script can also be deferred. Inline script tags added with wp_add_inline_script would also be considered to ensure proper execution order.

nnnn

The implementation would come with several initial built-in loading strategies: defer, async, and the default blocking behavior.

nnnn

Out of scope for this feature

nnnn

The loading strategy does not enable direct control of script tag attributes. This idea was originally proposed 10 years ago in #22249 and several approaches were considered on that ticket including a script attribute filter. This proposal takes a step back and aims to solve the script loading strategy aspect more comprehensively and directly while avoiding exposing the potential complications of direct attribute control.

nnnn

It is worth noting that it is already possible to control attributes on wp_enqueue_script tags directly using the script_loader_tag filter. However, this is a bit of a u201cbrute forceu201d approach which is limited and fragile because it doesnu2019t consider dependencies and multiple plugins can take conflicting actions on the same tag.u00a0

nnnn

What are potential concerns with this feature?

nnnn

One big concern with adding this feature to the WordPress Script API is potentially introducing a breaking change. wp_enqueue_script is a fundamental API in WordPress core, and any breaking changes could have widespread implications. Possible breakage is a possible reason that adding custom attributes as proposed in #22249 was never added to core.

nnnn

This new proposal aims to ensure that there is 100% backwards compatibility, resulting in zero risk of breakage. The loading strategy will ensure that all existing uses continue to function as expected; for example, passing the boolean in_footer attribute will still control script placement. In addition, it will ensure that scripts continue to be executed in the order they are enqueued - as described above in the u201cHow the loading strategy worksu201d section.

nnnn

Conclusion and Next Steps

nnnn

Giving developers the ability to specify a loading strategy will enable them to use more advanced JavaScript loading methods while still ensuring that enqueued scripts are executed in the correct order. A u201cstrategyu201d approach is also forward thinking: as the web evolves, new strategies can be developed and made available to WordPress developers. After gathering feedback, we will proceed to discussing the implementation approach and, ultimately, proposing a patch.

nnnn

Have you tried using defer or async with WordPress (or do you already)? How do you think this enhancement would change that? Please leave your feedback about this proposal in the comments below and if you can, join us at our weekly performance team chats, where we are likely to discuss this proposal in the future.

nnnn

Thanks to @flixos90, @tweetythierry and @mxbclang for help writing and reviewing this post and for the many contributors who have added to the discussion around this enhancement already.

nnn#core, #feature-projects, #javascript, #performance, #proposal","contentFiltered":"

Overview

nnnn

This post outlines a proposal to add a script loading strategy enhancement to coreu2019s existing Scripts API.u00a0u00a0

nnnn

The underlying goal of this effort is to make it easier for WordPress plugin and theme developers and core to use u201cmodernu201d loading approaches for JavaScript (like defer and async), which will help WordPress sites load faster and operate more smoothly, giving users a better experience.

nnnn

Why add a loading strategy?

nnnn

Data from the Web Almanac project (query) indicates that render blocking JavaScript is a significant problem on the web, with 77% of mobile pages having render-blocking scripts in the document . This query shows that approximately 40% of WordPress sites stand to benefit from deferring additional scripts. Adding defer or async to script tags enables script loading without u201cblockingu201d the rest of the page load, resulting in more responsive sites overall better user experience.

nnnn

Currently WordPress core as well as plugins and themes register scripts with the wp_enqueue_script and/or wp_register_script functions. Although these functions include the ability to control the placement of the script (with the in_footer parameter), they donu2019t include support for adding modern attributes such as defer or async to script tags.u00a0

nnnn

To add async or defer today, developers must resort to less flexible and more fragile approaches, such as directly filtering the tags at the point of output (usingu00a0 the script_loader_tag filter), or handling the tag output directly using wp_print_script_tag and the wp_script_attributes filter.

nnnn

Using the first approach and directly filtering the tag can easily break: for example if two plugins both try to filter a tag, or if a tag has unexpected attributes already (eg. adding defer to a tag that already has async). Using the the second approach developers must carefully handle dependencies and output manually u2013 things that that the Scripts API usually helps take care of.

nnnn

How the loading strategy works

nnnn

Developers specify a loading strategy when registering or enqueueing a script. For example, a defer strategy can be specified when the script isnu2019t required immediately during the page load cycle. WordPress will then determine which scripts can actually use a strategy based on logic for each strategy. For example, to ensure that scripts are executed in the order they are enqueued, defer can only be used on a script if every script that depends on that script can also be deferred. Inline script tags added with wp_add_inline_script would also be considered to ensure proper execution order.

nnnn

The implementation would come with several initial built-in loading strategies: defer, async, and the default blocking behavior.

nnnn

Out of scope for this feature

nnnn

The loading strategy does not enable direct control of script tag attributes. This idea was originally proposed 10 years ago in #22249 and several approaches were considered on that ticket including a script attribute filter. This proposal takes a step back and aims to solve the script loading strategy aspect more comprehensively and directly while avoiding exposing the potential complications of direct attribute control.

nnnn

It is worth noting that it is already possible to control attributes on wp_enqueue_script tags directly using the script_loader_tag filter. However, this is a bit of a u201cbrute forceu201d approach which is limited and fragile because it doesnu2019t consider dependencies and multiple plugins can take conflicting actions on the same tag.u00a0

nnnn

What are potential concerns with this feature?

nnnn

One big concern with adding this feature to the WordPress Script API is potentially introducing a breaking change. wp_enqueue_script is a fundamental API in WordPress core, and any breaking changes could have widespread implications. Possible breakage is a possible reason that adding custom attributes as proposed in #22249 was never added to core.

nnnn

This new proposal aims to ensure that there is 100% backwards compatibility, resulting in zero risk of breakage. The loading strategy will ensure that all existing uses continue to function as expected; for example, passing the boolean in_footer attribute will still control script placement. In addition, it will ensure that scripts continue to be executed in the order they are enqueued u2013 as described above in the u201cHow the loading strategy worksu201d section.

nnnn

Conclusion and Next Steps

nnnn

Giving developers the ability to specify a loading strategy will enable them to use more advanced JavaScript loading methods while still ensuring that enqueued scripts are executed in the correct order. A u201cstrategyu201d approach is also forward thinking: as the web evolves, new strategies can be developed and made available to WordPress developers. After gathering feedback, we will proceed to discussing the implementation approach and, ultimately, proposing a patch.

nnnn

Have you tried using defer or async with WordPress (or do you already)? How do you think this enhancement would change that? Please leave your feedback about this proposal in the comments below and if you can, join us at our weekly performance team chats, where we are likely to discuss this proposal in the future.

nnnn

Thanks to @flixos90, @tweetythierry and @mxbclang for help writing and reviewing this post and for the many contributors who have added to the discussion around this enhancement already.

n

#core, #feature-projects, #javascript, #performance, #proposal

","permalink":"https://make.wordpress.org/core/2022/12/09/enhancing-the-scripts-api-with-a-loading-strategy/","unixtime":1670603609,"unixtimeModified":1670627682,"entryHeaderMeta":"","linkPages":"","footerEntryMeta":"","tagsRaw":"core, feature projects, javascript, performance, proposal","tagsArray":[{"label":"core","count":463,"link":"https://make.wordpress.org/core/tag/core/"},{"label":"feature projects","count":45,"link":"https://make.wordpress.org/core/tag/feature-projects/"},{"label":"javascript","count":125,"link":"https://make.wordpress.org/core/tag/javascript/"},{"label":"performance","count":147,"link":"https://make.wordpress.org/core/tag/performance/"},{"label":"proposal","count":33,"link":"https://make.wordpress.org/core/tag/proposal/"}],"loginRedirectURL":"https://login.wordpress.org/?redirect_to=https%3A%2F%2Fmake.wordpress.org%2Fcore%2F2022%2F12%2F09%2Fenhancing-the-scripts-api-with-a-loading-strategy%2F&locale=en_US","hasPrevPost":false,"prevPostTitle":"","prevPostURL":"","hasNextPost":false,"nextPostTitle":"","nextPostURL":"","commentsOpen":true,"is_xpost":false,"editURL":null,"postActions":"

","comments":[{"type":"comment","id":"44188","postID":"101413","postTitleRaw":"Enhancing the Scripts API with a loading strategy","cssClasses":"comment byuser comment-author-pingram3541 even thread-odd thread-alt depth-1","parentID":"0","contentRaw":"Ooh yes, gets my thumbs up!","contentFiltered":"

Ooh yes, gets my thumbs up!

n","permalink":"https://make.wordpress.org/core/2022/12/09/enhancing-the-scripts-api-with-a-loading-strategy/#comment-44188","unixtime":1670607642,"loginRedirectURL":"https://login.wordpress.org/?redirect_to=https%3A%2F%2Fmake.wordpress.org%2Fcore%2F2022%2F12%2F09%2Fenhancing-the-scripts-api-with-a-loading-strategy%2F%23comment-44188&locale=en_US","approved":true,"isTrashed":false,"prevDeleted":"","editURL":null,"depth":1,"commentDropdownActions":"","commentFooterActions":"

","commentTrashedActions":"

","mentions":[],"mentionContext":"","commentCreated":"1670607642","hasChildren":false,"userLogin":"pingram3541","userNicename":"pingram3541"},{"type":"comment","id":"44189","postID":"101413","postTitleRaw":"Enhancing the Scripts API with a loading strategy","cssClasses":"comment byuser comment-author-mat-lipe odd alt thread-even depth-1","parentID":"0","contentRaw":"WP script handling is assumed to automatically load scripts in the correct order based on dependencies. One of the problems with using the existing filters to add a `defer` tag to a script is the dependency hierarchy execution order. The filtered script gets moved in execution order and can have side effects. The biggest value I see adding the loading strategy, is we can automatically adjust lower script dependencies to fix the load order when using `defer`. `async` will still have an unpredictable order, but that is the nature of asynchronous calls. nnRemoving filter collision is also a nice bonus with using a loading strategy. nnAssuming the existing filters will remain intact (obviously) and new or existing filters will allow adjusting the values sent as "strategy", I don't see a big lift with updating 3rd party wrappers to move away from the old filters to the system.nnLets do it. :)","contentFiltered":"

WP script handling is assumed to automatically load scripts in the correct order based on dependencies. One of the problems with using the existing filters to add a `defer` tag to a script is the dependency hierarchy execution order. The filtered script gets moved in execution order and can have side effects. The biggest value I see adding the loading strategy, is we can automatically adjust lower script dependencies to fix the load order when using `defer`. `async` will still have an unpredictable order, but that is the nature of asynchronous calls.

n

Removing filter collision is also a nice bonus with using a loading strategy.

n

Assuming the existing filters will remain intact (obviously) and new or existing filters will allow adjusting the values sent as u201cstrategyu201d, I donu2019t see a big lift with updating 3rd party wrappers to move away from the old filters to the system.

n

Lets do it. ud83dude42

n","permalink":"https://make.wordpress.org/core/2022/12/09/enhancing-the-scripts-api-with-a-loading-strategy/#comment-44189","unixtime":1670609508,"loginRedirectURL":"https://login.wordpress.org/?redirect_to=https%3A%2F%2Fmake.wordpress.org%2Fcore%2F2022%2F12%2F09%2Fenhancing-the-scripts-api-with-a-loading-strategy%2F%23comment-44189&locale=en_US","approved":true,"isTrashed":false,"prevDeleted":"","editURL":null,"depth":1,"commentDropdownActions":"","commentFooterActions":"

","commentTrashedActions":"

","mentions":[],"mentionContext":"","commentCreated":"1670609508","hasChildren":false,"userLogin":"Mat Lipe","userNicename":"mat-lipe"},{"type":"comment","id":"44190","postID":"101413","postTitleRaw":"Enhancing the Scripts API with a loading strategy","cssClasses":"comment byuser comment-author-josephscott even thread-odd thread-alt depth-1","parentID":"0","contentRaw":"In general I support the concept.nn

Adding defer or async to script tags enables script loading without u201cblockingu201d the rest of the page load, resulting in more responsive sites overall better user experience.

nnThis is true, but it leaves out what can happen with script execution. I recommend the diagram at https://html.spec.whatwg.org/#the-script-element for a good visual representation of the differences.nnIn addition to the ordering issue you brought up there an issue with when the script can be executed. With defer both the script download and execution avoid blocking the HTML parser. With async only the download portion avoids blocking, and execution could happen at any point, even before the HTML parser has completed.nnBecause of this difference I generally recommend that people not use the async attribute at all. "Prefer defer" is a handy way to remember this :)nnI bring this up because I want to avoid confusing people who might think that async makes script execution avoid blocking the HTML parser. To ensure that behavior you need to use defer instead.","contentFiltered":"

In general I support the concept.

n

Adding defer or async to script tags enables script loading without u201cblockingu201d the rest of the page load, resulting in more responsive sites overall better user experience.

n

This is true, but it leaves out what can happen with script execution. I recommend the diagram at https://html.spec.whatwg.org/#the-script-element for a good visual representation of the differences.

n

In addition to the ordering issue you brought up there an issue with when the script can be executed. With defer both the script download and execution avoid blocking the HTML parser. With async only the download portion avoids blocking, and execution could happen at any point, even before the HTML parser has completed.

n

Because of this difference I generally recommend that people not use the async attribute at all. u201cPrefer deferu201d is a handy way to remember this ud83dude42

n

I bring this up because I want to avoid confusing people who might think that async makes script execution avoid blocking the HTML parser. To ensure that behavior you need to use defer instead.

n","permalink":"https://make.wordpress.org/core/2022/12/09/enhancing-the-scripts-api-with-a-loading-strategy/#comment-44190","unixtime":1670623984,"loginRedirectURL":"https://login.wordpress.org/?redirect_to=https%3A%2F%2Fmake.wordpress.org%2Fcore%2F2022%2F12%2F09%2Fenhancing-the-scripts-api-with-a-loading-strategy%2F%23comment-44190&locale=en_US","approved":true,"isTrashed":false,"prevDeleted":"","editURL":null,"depth":1,"commentDropdownActions":"","commentFooterActions":"

","commentTrashedActions":"

","mentions":[],"mentionContext":"","commentCreated":"1670623984","hasChildren":false,"userLogin":"josephscott","userNicename":"josephscott"},{"type":"comment","id":"44191","postID":"101413","postTitleRaw":"Enhancing the Scripts API with a loading strategy","cssClasses":"comment byuser comment-author-adamsilverstein bypostauthor odd alt depth-2","parentID":"44190","contentRaw":"

... Because of this difference I generally recommend that people not use the async attribute at all. u201cPrefer deferu201d is a handy way to remember this n

nThanks for the feedback. I agree, `defer` is generally preferable! Still, I have seen some legitimate use cases for `async`, especially for standalone scripts that want to execute as quickly as possible while still minimizing impact on the page load. For this reason it makes sense to support `async` out of the box while still encouraging developers "prefer defer".","contentFiltered":"

u2026 Because of this difference I generally recommend that people not use the async attribute at all. u201cPrefer deferu201d is a handy way to remember thisn

n

Thanks for the feedback. I agree, `defer` is generally preferable! Still, I have seen some legitimate use cases for `async`, especially for standalone scripts that want to execute as quickly as possible while still minimizing impact on the page load. For this reason it makes sense to support `async` out of the box while still encouraging developers u201cprefer deferu201d.

n","permalink":"https://make.wordpress.org/core/2022/12/09/enhancing-the-scripts-api-with-a-loading-strategy/#comment-44191","unixtime":1670628366,"loginRedirectURL":"https://login.wordpress.org/?redirect_to=https%3A%2F%2Fmake.wordpress.org%2Fcore%2F2022%2F12%2F09%2Fenhancing-the-scripts-api-with-a-loading-strategy%2F%23comment-44191&locale=en_US","approved":true,"isTrashed":false,"prevDeleted":"","editURL":null,"depth":2,"commentDropdownActions":"","commentFooterActions":"

","commentTrashedActions":"

","mentions":[],"mentionContext":"","commentCreated":"1670628366","hasChildren":false,"userLogin":"adamsilverstein","userNicename":"adamsilverstein"}],"postFormat":"standard","postMeta":{"isSticky":false},"postTerms":{"category":[{"label":"Proposals","count":78,"link":"https://make.wordpress.org/core/category/proposals/"}],"post_tag":[{"label":"core","count":463,"link":"https://make.wordpress.org/core/tag/core/"},{"label":"feature projects","count":45,"link":"https://make.wordpress.org/core/tag/feature-projects/"},{"label":"javascript","count":125,"link":"https://make.wordpress.org/core/tag/javascript/"},{"label":"performance","count":147,"link":"https://make.wordpress.org/core/tag/performance/"},{"label":"proposal","count":33,"link":"https://make.wordpress.org/core/tag/proposal/"}],"post_format":[]},"pluginData":[],"isPage":false,"mentions":["flixos90","tweetythierry","mxbclang"],"mentionContext":"","isTrashed":false,"userLogin":"adamsilverstein","userNicename":"adamsilverstein"}]

Leave a Reply

Your email address will not be published. Required fields are marked *