Make WordPress Core

Opened 3 years ago

Closed 8 months ago

#54018 closed enhancement (fixed)

Allow scripts registered via block.json to be enqueued in the footer

Reported by: jeremyfelt's profile jeremyfelt Owned by:
Milestone: 6.4 Priority: normal
Severity: normal Version:
Component: Script Loader Keywords: has-patch
Focuses: performance Cc:

Description

It should be possible to pass an extra argument to register_block_script_handle() via register_block_type_from_metadata() that registers a block's front-end script so that it is enqueued in the footer.

OR It should be possible for this information to be provided in block.json with the script property.

OR It should be possible to filter the wp_register_script() call in register_block_script_handle() so that $footer = true can be passed just in time based on the handle of the script being registered.

I'm probably missing a possibility, and I'm not sure what approach would be best, but I would like to output front-end scripts for blocks at the end of the page.

Change History (20)

This ticket was mentioned in PR #1639 on WordPress/wordpress-develop by donmhico.


3 years ago
#1

  • Keywords has-patch added

This PR accepts the new boolean keys enqueueEditorFooter and enqueueScriptFooter in $metadata to whether or not enqueue the script in footer.

Usage

Just add enqueueScriptFooter: true if you want to enqueue the script in the footer and/or enqueueEditorFooter: true for the editor-facing script in your block.json.

Example block.json

{{{js
{

"apiVersion": 2,
"name": "mico/block-example",
"title": "Mico Test Block",
"description": "Mico Test Block",
"attributes": {

"heading": {

"type": "string",
"default": ""

}

},
"textdomain": "mico",
"enqueueScriptFooter": true,
"script": "file:./build/script.js",
"enqueueEditorFooter": true,
"editorScript": "file:./build/index.js"

}

}}}

Trac ticket: https://core.trac.wordpress.org/ticket/54018

#2 @donmhico
3 years ago

Hello @jeremyfelt,

I've attached a PR allows which what you asked by adding support to enqueueScriptFooter and enqueueEditorFooter fields in $metadata. I've thought to add the support for the editor script as well. You can see an example usage in the PR.

Let me know your thoughts.

#3 @jeremyfelt
2 years ago

  • Component changed from General to Script Loader
  • Focuses performance added

Thanks for the patch, @donmhico! I'm not in a position to review right now, but I'm going to tag it as performance with the hope that a maintainer will take a look.

#4 @gziolo
2 years ago

It’s worth noting that @adamsilverstein has just opened a proposal to extend script loading strategies in https://github.com/WordPress/performance/issues/168. Let’s make sure that the solution proposed takes that into account.

Another consideration is support for multiple scripts per type in block.json as discussed in https://github.com/WordPress/gutenberg/issues/33542. @aristath started working on that already. We already allow passing an array for styles. The same is planned for all 3 types of scripts.

Last edited 2 years ago by gziolo (previous) (diff)

#5 @ocean90
2 years ago

I'd love to see this possible, although I think it should have been in the footer by default.

Instead of having a single property I have thought about extending the properties to allow an array of objects. An example:

{
  "editorScript": [
    {
      "path": "file:editor1.js",
      "footer": true,
      "data": {
            "async": true
      }
    },
    "file:editor2.js",
    "my-editor-script-handle"
  ],
  "editorStyle": [
    {
      "path": "file:editor1.css",
      "media": "print"
    },
    {
      "path": "file:editor2.css",
      "media": "(max-width: 640px)"
    },
    "file:editor3.css"
  ],
  "style": "file:style-blocks.css",
  "viewScript": [ "my-block-view-handle", "my-block-view-2-handle" ]
}
  • For back-compat it still allows passing only one handle or file
  • You can pass an array of handles and/or file
  • You can pass an array of objects with settings passed to wp_register_style()/wp_register_script()
  • data property can be used for wp_script_add_data()
  • Not in the example but passing a single object should probably be possible too
Last edited 2 years ago by ocean90 (previous) (diff)

#6 @gziolo
2 years ago

I've attached a PR allows which what you asked by adding support to enqueueScriptFooter and enqueueEditorFooter fields in $metadata. I've thought to add the support for the editor script as well. You can see an example usage in the PR.

@donmhico, thank you for the patch proposed. I checked the code and it might not be the best fit once we allow multiple scripts per type:

"editorScript": [ "file:./editor-1.js", "file:./editor-2", 'block-editor-script-3" ],

In theory, you could also allow array for newly proposed field in block.json:

"editorScriptFooter": [ true, false, null ],

However, the issue is that you can pass both paths to the asset file, but you can also pass the registered script handle like block-editor-script-3 where you would already have that flag configured.

@jeremyfelt, one solution that works today is to register the script separately and pass the handle to block.json file. The idea was to both cover some already registered scripts, but also to offer more control on how those scripts might get registered.

OR It should be possible to filter the wp_register_script() call in register_block_script_handle() so that $footer = true can be passed just in time based on the handle of the script being registered.

This is also an option worth considering. If we do it, we should probably add a similar filter for register_block_style.

#7 @gziolo
2 years ago

I saw a comment from @ocean90 and I like this proposal. I was about to write that we could consider magic code comments in JavaScript files that would allow the build tool to extract that extra information. However, the solution described by Dominik looks way much better.

When using file:editor1.js format you still would need a build step that generates the dependencies and the version, but now you would be able to pass extra arguments when necessary or even override some of those automatically generated if really necessary.

Not in the example but passing a single object should probably be possible too

I'm not sure if we really need it because you could make it as simple as:

"editorScript": [ {
    "path": "file:abc.js",
    "data": { "async": true }
} ]

Anyway, it's the least important part here. The string format would be mostly for backward compatibility so it would be simpler to have a string or array for the field: string|(string|object)[].

Last edited 2 years ago by gziolo (previous) (diff)

#8 @belbo
13 months ago

@gziolo is this still the best way of doing this? I tried your suggestion of passing an already registered script handle, which worked but then WP scripts no-longer compiles the script I wanted to include.

For anyone else looking for a solution this is how to do it (unless @gziolo comes back with a better way as this ticket is quite old, however it is the first to show up on Google!)

In block.json

{

"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 2,
"name": "prefix/blockname"
...
"viewScript": [ "carousel-fe", "file:./carousel.js" ] enqueue the same file twice

}

in functions.php

function enqueue_fe_scripts() {

	// Dequeue script loaded by gutenberg so script not added twice
	wp_dequeue_script( 'prefix-blockname-view-script-2' );

        // include build script so can get dependencies, build version etc
        $carousel_asset = include PATH_TO_FILE . '/index.asset.php';

        // Register but don't enqueue the script, as Gutenberg will enqueue it through viewScript
	wp_register_script(
		'carousel-fe',
		PATH_TO_FILE_URI/carousel.js,
		array(),
		$carousel_asset['version'],
		true
	);
}

add_action( 'wp_enqueue_scripts', 'enqueue_fe_scripts', 30 );

As far as I can tell this is the only way of getting WP scripts to compile the file to include on the Front End AND include it in the footer.

#9 @gziolo
13 months ago

We now track the issue to improve loading strategies for block assets in the GitHub repository at https://github.com/WordPress/gutenberg/issues/46954. There are also ongoing efforts from the WordPress Performance team to introduce more options to load scripts: https://make.wordpress.org/core/2022/12/09/enhancing-the-scripts-api-with-a-loading-strategy/.

@belbo, you are correct, I didn't think about the challenges with the default behavior of @wordpress/scripts. We definitely need a better developer experience here.

#10 @belbo
13 months ago

Thanks @gziolo! The new strategy for loading blocks looks great - can't wait until added into core :-D

#11 @westonruter
12 months ago

I just discovered that because the File block's view script is loaded in the head (as other blocks' scripts are), it is currently failing hide PDF embeds for unsupported browsers because it doesn't wait to do so until the page loads. I opened a PR to fix. Naturally this issue wouldn't have happened if the script were loaded in the footer, and maybe it used to be in the footer and this is a regression.

#12 @westonruter
9 months ago

Now that script loading strategies (async & defer) are landing finally in 6.3 (#12009), the scope of this ticket should expand to not only allow block view scripts to be printed in the footer but also to get async/defer. Ideally block view scripts would be defer by default (which I understand is also the vision of the Interactivity API via modules).

#13 @westonruter
9 months ago

  • Milestone changed from Awaiting Review to 6.4

Are there any cases where a block view script has to be executed in the head? If not, instead of moving block view scripts to execute in the footer, as has been suggested on gutenberg#52536, how about keeping them in the head but add the defer attribute? This will allow early discovery of the block scripts to start loading early, but then their execution will still be delayed until the DOM has loaded, thus not impeding LCP.

#14 @westonruter
9 months ago

As I just commented on the aforementioned Gutenberg PR, we actually _can_ go ahead and mark all view scripts as defer (as opposed to in_footer:true) because they are printed in the footer already for classic themes:

When using a classic theme, the view scripts for blocks actually get printed in the footer. This is because blocks are parsed in the middle of template rendering, which means that view scripts are enqueued too later for printing at wp_head even though they have in_footer set to false. For block themes, however, parsing is done before template rendering, which means any enqueued block view script will get printed at wp_head.

Given that classic themes all print in the footer, it seems there is absolutely no reason to not add defer. This won't really benefit classic themes at all, but it will have a big impact for block themes.

#15 follow-up: @westonruter
9 months ago

I think this can be closed once gutenberg#52536 is merged and backported to core. With it, all block view scripts are marked as defer but they remain in the head to promote early discovery. I don't think we need to provide a way to further customize how the block view scripts are printed via block.json.

Last edited 9 months ago by westonruter (previous) (diff)

#16 @gziolo
8 months ago

With [56398] from #59115, scripts registered through block.json will use the defer strategy by default.

This ticket was mentioned in Slack in #core-performance by joemcgill. View the logs.


8 months ago

#18 in reply to: ↑ 15 @joemcgill
8 months ago

  • Keywords close added

Replying to westonruter:

I think this can be closed once gutenberg#52536 is merged and backported to core. With it, all block view scripts are marked as defer but they remain in the head to promote early discovery. I don't think we need to provide a way to further customize how the block view scripts are printed via block.json.

Based on @westonruter's comment. I think this can now be closed as fixed. @gziolo can you confirm?

#19 @gziolo
8 months ago

Yes, it can be closed. Using defer is now enabled by default to all view scripts that folks will register in block.json to use on the front end.

#20 @joemcgill
8 months ago

  • Keywords close removed
  • Resolution set to fixed
  • Status changed from new to closed

Thanks for confirming, @gziolo!

Note: See TracTickets for help on using tickets.