10up / wp-scaffold

10up WordPress project scaffold.
MIT License
192 stars 46 forks source link

Enhance script loading behaviour #109

Open dainemawer opened 2 years ago

dainemawer commented 2 years ago

Is your enhancement related to a problem? Please describe.

Script loading in WordPress really sucks. We have a few options available to us:

  1. We can place scripts in the <head>
  2. We can place scripts before the </body>
  3. We can use the filter script_loader_tag to append async and defer onto scripts.

None of the options above, considering the WP ecosystem really has a huge effect on performance. I would like to suggest that we build our own function which sits on top of wp_enqueue_script, very much like NextJS handles their next/script component: https://nextjs.org/docs/api-reference/next/script

In NextJS scripts can apply "strategies":

(from web.dev) The strategy attribute can take three values.

As WP is not prerendered, beforeInteractive is null and void.

We can however leverage afterInteractive and lazyOnLoad as described above, afterInteractive is effectively the equivalent of using defer. What I would like to see is an option for lazyOnLoad which loads scripts when the browsers main thread is idle using requestIdleCallback - MDN. For browsers that do no support requestIdleCallback there is a shim available.

There is currently limited support in Safari for this functionality, but otherwise great support across the web. Id envision a function like:

tenup_add_script( $handle = 'component', $filename = 'file-name', $ver = '1.0.0', $strategy = 'lazyOnLoad' );

I think a proof of concept could be worthwhile testing out to see how far we could get but would love to see something in action!

Designs

No response

Describe alternatives you've considered

No response

Code of Conduct

fabiankaegy commented 2 years ago

@dainemawer I really like this idea. One thing that I will be very interested in is whether we can eventually make this work with assets that get registered via block.json files also.

With WordPress moving more and more towards a block first approach more of the scripts and stylesheets will get automatically enqueued by WordPress through the block.json files.

These registrations in block.json files happens through the script, viewScript, style, editorScript and, editorStyle keys. Each of these values need to be of the type WPDefinedAsset Which means they can either be a string that is a relative file path or which I think would be very interesting for this proposal an object containing properties like dependencies, version, handle and src. Maybe we can also add our own custom key-value pairs to that definition and then filter the actual registration to add the proposed strategy to it.

{
    "name": "example/block",
    "viewScript": {
        "src": "file:./view.js",
        "handle": "example-block-view-script",
        "strategy": "lazyOnLoad"
    }
}

Just wanted to call this out as more and more of the registration of scripts is happening automatically abstracted away via the blocks.

darylldoyle commented 2 years ago

@dainemawer, I think this idea is excellent, and I love the thought of being able to link block scripts in with these loading strategies @fabiankaegy!

I have a couple of thoughts.

Rather than replicating the way core enqueues files:

tenup_add_script( $handle = 'component', $filename = 'file-name', $ver = '1.0.0', $strategy = 'lazyOnLoad' );

I think we could future-proof it and have all the args after the handle and filename passed as an array. This would make it easy to extend in the future without us needing to keep adding additional arguments to the function. Something like:

tenup_add_script( 
    $handle   = 'component', 
    $filename = 'file-name', 
    $args     = array(
        version  => '1.0.0',
        strategy => 'lazyOnLoad',
    )
);

We'd also need to consider how this works with dependencies and whether we'd bypass them altogether. One of the powerful things we utilise in the scaffold currently is the @wordpress/dependency-extraction-webpack-plugin package. That package simplifies the handling of dependencies for scripts massively. If we go down this route, then we'd need to find a way to handle those dependencies.

For example, if we defer a script using the afterInteractive strategy, all those who depend on it must also be deferred. In the same thought, if we set a script to use the beforeInteractive strategy, it'll be inlined, but what if that script depends on jQuery? Would this wrapper then be in charge of inlining jQuery into the page? How would that affect other scripts that relied on jQuery and used the core enqueue functionality? Would we throw a _doing_it_wrong() error instead if there are dependencies passed to a script using the beforeInteractive strategy?

I'm super excited about this; I want to make sure we think through those edge cases as they'll surely come up in a build.

fabiankaegy commented 2 years ago

While we are taking a look at the asset handling here I also want to call out this core API that is being used in blocks to make it possible to inline small assets: https://make.wordpress.org/core/2021/07/01/block-styles-loading-enhancements-in-wordpress-5-8/#inlining-small-assets

By providing a path with the script (can be added later via wp_style_add_data( $style_handle, 'path', $file_path );) you allow WordPress to know the file size of the asset and can allow it to inline small assets to reduce the amount of HTTP request for tiny files.

May be something that is worth considering for more than just blocks.

dainemawer commented 2 years ago

This is great feedback so far, thanks @fabiankaegy and @darylldoyle - I totally agree with both of your points!

  1. We should ensure that this can be future-proofed (for Gutenberg), though considering how much is for ever changing in the Gutenberg functionality (thank goodness we have Fabian) - I'd like to make sure we are not defined by this and rather look to greater horizons to solve a much greater WP issue in general
  2. Dependencies are a challenge indeed. I don't see us ever inlining jQuery. Certain strategies would essentially setup different scenarios. For instance, we could load jquery as a dependency of moment , and defer moment and not jquery - as long as jquery loads first, the deferring is somewhat irrelevant.
  3. This is additional scope here to also tackle a prefetch / preconnect mechanism for core scripts like jQuery.

@darylldoyle I like the idea of using an array. Related to point 3 if we could do something like:

tenup_add_script( 
    $handle   = 'component', 
    $filename = 'file-name', 
    $args     = array(
        version  => '1.0.0',
        strategy => 'lazyOnLoad',
                preconnect => true
    )
);

That would also be a winner.

What are our next steps here?

szepeviktor commented 1 year ago

I think object oriented programing is alien to the WordPress ecosystem. It needs an engineer to like it.

use TenUp\Utility\Script;

(new Script('url-of-javascript-file'))
    ->setHandle('component')
    ->setVersion('1.0.0')
    ->enableLazyLoading()
    ->enablePreconnect()