Skip to content

Commit 99f820e

Browse files
aristathgziolo
andauthored
Allow multiple view scripts per block (#36176)
* Handle arrays in viewScript * Split modal script in navigation's viewScript * Enqueue modal script when needed * Add function in the 6.0-compat folder * Indentation fix * Add webpack changes to build also view files prefixed with view * fix the schema * PHPCS fix (= alignment) * remove unnecessary line * remove in_footer arg * handle script translations * Add a wp_enqueue_block_script function * rename function * Update lib/blocks.php Co-authored-by: Greg Ziółkowski <grzegorz@gziolo.pl> * allow script translations by defining a textdomain * reverse i18n logic * typo * revert navigation block changes Co-authored-by: Grzegorz Ziolkowski <grzegorz@gziolo.pl>
1 parent 36fcdb2 commit 99f820e

File tree

5 files changed

+178
-13
lines changed

5 files changed

+178
-13
lines changed

lib/blocks.php

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,3 +593,103 @@ function gutenberg_multiple_block_styles( $metadata ) {
593593
return $metadata;
594594
}
595595
add_filter( 'block_type_metadata', 'gutenberg_multiple_block_styles' );
596+
597+
if ( ! function_exists( 'wp_enqueue_block_view_script' ) ) {
598+
/**
599+
* Enqueue a frontend script for a specific block.
600+
*
601+
* Scripts enqueued using this function will only get printed
602+
* when the block gets rendered on the frontend.
603+
*
604+
* @param string $block_name The block-name, including namespace.
605+
* @param array $args An array of arguments [handle,src,deps,ver,media].
606+
*
607+
* @return void
608+
*/
609+
function wp_enqueue_block_view_script( $block_name, $args ) {
610+
$args = wp_parse_args(
611+
$args,
612+
array(
613+
'handle' => '',
614+
'src' => '',
615+
'deps' => array(),
616+
'ver' => false,
617+
'in_footer' => false,
618+
619+
// Additional arg to allow translations for the script's textdomain.
620+
'textdomain' => '',
621+
)
622+
);
623+
624+
/**
625+
* Callback function to register and enqueue scripts.
626+
*
627+
* @param string $content When the callback is used for the render_block filter,
628+
* the content needs to be returned so the function parameter
629+
* is to ensure the content exists.
630+
* @return string Block content.
631+
*/
632+
$callback = static function( $content, $block ) use ( $args, $block_name ) {
633+
634+
// Sanity check.
635+
if ( empty( $block['blockName'] ) || $block_name !== $block['blockName'] ) {
636+
return $content;
637+
}
638+
639+
// Register the stylesheet.
640+
if ( ! empty( $args['src'] ) ) {
641+
wp_register_script( $args['handle'], $args['src'], $args['deps'], $args['ver'], $args['in_footer'] );
642+
}
643+
644+
// Enqueue the stylesheet.
645+
wp_enqueue_script( $args['handle'] );
646+
647+
// If a textdomain is defined, use it to set the script translations.
648+
if ( ! empty( $args['textdomain'] ) && in_array( 'wp-i18n', $args['deps'], true ) ) {
649+
wp_set_script_translations( $args['handle'], $args['textdomain'] );
650+
}
651+
652+
return $content;
653+
};
654+
655+
/*
656+
* The filter's callback here is an anonymous function because
657+
* using a named function in this case is not possible.
658+
*
659+
* The function cannot be unhooked, however, users are still able
660+
* to dequeue the script registered/enqueued by the callback
661+
* which is why in this case, using an anonymous function
662+
* was deemed acceptable.
663+
*/
664+
add_filter( 'render_block', $callback, 10, 2 );
665+
}
666+
}
667+
668+
/**
669+
* Allow multiple view scripts per block.
670+
*
671+
* Filters the metadata provided for registering a block type.
672+
*
673+
* @param array $metadata Metadata for registering a block type.
674+
*
675+
* @return array
676+
*/
677+
function gutenberg_block_type_metadata_multiple_view_scripts( $metadata ) {
678+
679+
// Early return if viewScript is empty, or not an array.
680+
if ( ! isset( $metadata['viewScript'] ) || ! is_array( $metadata['viewScript'] ) ) {
681+
return $metadata;
682+
}
683+
684+
// Register all viewScript items.
685+
foreach ( $metadata['viewScript'] as $view_script ) {
686+
$item_metadata = $metadata;
687+
$item_metadata['viewScript'] = $view_script;
688+
gutenberg_block_type_metadata_view_script( array(), $item_metadata );
689+
}
690+
691+
// Proceed with the default behavior.
692+
$metadata['viewScript'] = $metadata['viewScript'][0];
693+
return $metadata;
694+
}
695+
add_filter( 'block_type_metadata', 'gutenberg_block_type_metadata_multiple_view_scripts' );

lib/compat/wordpress-6.0/blocks.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,54 @@ function gutenberg_build_query_vars_from_query_block( $block, $page ) {
122122
}
123123
return $query;
124124
}
125+
126+
/**
127+
* Registers view scripts for core blocks if handling is missing in WordPress core.
128+
*
129+
* This is a temporary solution until the Gutenberg plugin sets
130+
* the required WordPress version to 6.0.
131+
*
132+
* @param array $settings Array of determined settings for registering a block type.
133+
* @param array $metadata Metadata provided for registering a block type.
134+
*
135+
* @return array Array of settings for registering a block type.
136+
*/
137+
function gutenberg_block_type_metadata_view_script( $settings, $metadata ) {
138+
if (
139+
! isset( $metadata['viewScript'] ) ||
140+
! empty( $settings['view_script'] ) ||
141+
! isset( $metadata['file'] ) ||
142+
strpos( $metadata['file'], gutenberg_dir_path() ) !== 0
143+
) {
144+
return $settings;
145+
}
146+
147+
$view_script_path = realpath( dirname( $metadata['file'] ) . '/' . remove_block_asset_path_prefix( $metadata['viewScript'] ) );
148+
149+
if ( file_exists( $view_script_path ) ) {
150+
$view_script_id = str_replace( array( '.min.js', '.js' ), '', basename( remove_block_asset_path_prefix( $metadata['viewScript'] ) ) );
151+
$view_script_handle = str_replace( 'core/', 'wp-block-', $metadata['name'] ) . '-' . $view_script_id;
152+
wp_deregister_script( $view_script_handle );
153+
154+
// Replace suffix and extension with `.asset.php` to find the generated dependencies file.
155+
$view_asset_file = substr( $view_script_path, 0, -( strlen( '.js' ) ) ) . '.asset.php';
156+
$view_asset = file_exists( $view_asset_file ) ? require( $view_asset_file ) : null;
157+
$view_script_dependencies = isset( $view_asset['dependencies'] ) ? $view_asset['dependencies'] : array();
158+
$view_script_version = isset( $view_asset['version'] ) ? $view_asset['version'] : false;
159+
$result = wp_register_script(
160+
$view_script_handle,
161+
gutenberg_url( str_replace( gutenberg_dir_path(), '', $view_script_path ) ),
162+
$view_script_dependencies,
163+
$view_script_version
164+
);
165+
if ( $result ) {
166+
$settings['view_script'] = $view_script_handle;
167+
168+
if ( ! empty( $metadata['textdomain'] ) && in_array( 'wp-i18n', $view_script_dependencies, true ) ) {
169+
wp_set_script_translations( $view_script_handle, $metadata['textdomain'] );
170+
}
171+
}
172+
}
173+
return $settings;
174+
}
175+
add_filter( 'block_type_metadata_settings', 'gutenberg_block_type_metadata_view_script', 10, 2 );

packages/block-library/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ _This package assumes that your code will run in an **ES2015+** environment. If
1414

1515
## Building JavaScript for the browser
1616

17-
If a `view.js` file is present in the block's directory, this file will be built along other assets, making it available to load from the browser.
17+
If a `view.js` file (or a file prefixed with `view`, e.g. `view-example.js`) is present in the block's directory, this file will be built along other assets, making it available to load from the browser.
1818

1919
This enables us to, for instance, load this file when the block is present on the page in two ways:
2020

schemas/json/block.json

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -421,8 +421,18 @@
421421
"description": "Block type frontend and editor script definition. It will be enqueued both in the editor and when viewing the content on the front of the site."
422422
},
423423
"viewScript": {
424-
"type": "string",
425-
"description": "Block type frontend script definition. It will be enqueued only when viewing the content on the front of the site."
424+
"description": "Block type frontend script definition. It will be enqueued only when viewing the content on the front of the site.",
425+
"oneOf": [
426+
{
427+
"type": "string"
428+
},
429+
{
430+
"type": "array",
431+
"items": {
432+
"type": "string"
433+
}
434+
}
435+
]
426436
},
427437
"editorStyle": {
428438
"description": "Block type editor style definition. It will only be enqueued in the context of the editor.",

tools/webpack/blocks.js

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@ const DependencyExtractionWebpackPlugin = require( '@wordpress/dependency-extrac
1717
const { baseConfig, plugins, stylesTransform } = require( './shared' );
1818

1919
/*
20-
* Matches a block's name in paths in the form
21-
* build-module/<blockName>/view.js
20+
* Matches a block's filepaths in the form build-module/<filename>.js
2221
*/
23-
const blockNameRegex = new RegExp( /(?<=build-module\/).*(?=(\/view))/g );
22+
const blockViewRegex = new RegExp(
23+
/build-module\/(?<filename>.*\/view.*).js$/
24+
);
2425

2526
/**
2627
* We need to automatically rename some functions when they are called inside block files,
@@ -31,26 +32,29 @@ const prefixFunctions = [ 'build_query_vars_from_query_block' ];
3132

3233
const createEntrypoints = () => {
3334
/*
34-
* Returns an array of paths to view.js files within the `@wordpress/block-library` package.
35-
* These paths can be matched by the regex `blockNameRegex` in order to extract
36-
* the block's name.
35+
* Returns an array of paths to block view files within the `@wordpress/block-library` package.
36+
* These paths can be matched by the regex `blockViewRegex` in order to extract
37+
* the block's filename.
3738
*
3839
* Returns an empty array if no files were found.
3940
*/
4041
const blockViewScriptPaths = fastGlob.sync(
41-
'./packages/block-library/build-module/**/view.js'
42+
'./packages/block-library/build-module/**/view*.js'
4243
);
4344

4445
/*
4546
* Go through the paths found above, in order to define webpack entry points for
4647
* each block's view.js file.
4748
*/
4849
return blockViewScriptPaths.reduce( ( entries, scriptPath ) => {
49-
const [ blockName ] = scriptPath.match( blockNameRegex );
50+
const result = scriptPath.match( blockViewRegex );
51+
if ( ! result?.groups?.filename ) {
52+
return entries;
53+
}
5054

5155
return {
5256
...entries,
53-
[ 'blocks/' + blockName ]: scriptPath,
57+
[ result.groups.filename ]: scriptPath,
5458
};
5559
}, {} );
5660
};
@@ -61,7 +65,7 @@ module.exports = {
6165
entry: createEntrypoints(),
6266
output: {
6367
devtoolNamespace: 'wp',
64-
filename: './build/block-library/[name]/view.min.js',
68+
filename: './build/block-library/blocks/[name].min.js',
6569
path: join( __dirname, '..', '..' ),
6670
},
6771
plugins: [

0 commit comments

Comments
 (0)