Skip to content

Commit

Permalink
Allow multiple view scripts per block (#36176)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>

* allow script translations by defining a textdomain

* reverse i18n logic

* typo

* revert navigation block changes

Co-authored-by: Grzegorz Ziolkowski <[email protected]>
  • Loading branch information
aristath and gziolo authored Apr 7, 2022
1 parent 36fcdb2 commit 99f820e
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 13 deletions.
100 changes: 100 additions & 0 deletions lib/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -593,3 +593,103 @@ function gutenberg_multiple_block_styles( $metadata ) {
return $metadata;
}
add_filter( 'block_type_metadata', 'gutenberg_multiple_block_styles' );

if ( ! function_exists( 'wp_enqueue_block_view_script' ) ) {
/**
* Enqueue a frontend script for a specific block.
*
* Scripts enqueued using this function will only get printed
* when the block gets rendered on the frontend.
*
* @param string $block_name The block-name, including namespace.
* @param array $args An array of arguments [handle,src,deps,ver,media].
*
* @return void
*/
function wp_enqueue_block_view_script( $block_name, $args ) {
$args = wp_parse_args(
$args,
array(
'handle' => '',
'src' => '',
'deps' => array(),
'ver' => false,
'in_footer' => false,

// Additional arg to allow translations for the script's textdomain.
'textdomain' => '',
)
);

/**
* Callback function to register and enqueue scripts.
*
* @param string $content When the callback is used for the render_block filter,
* the content needs to be returned so the function parameter
* is to ensure the content exists.
* @return string Block content.
*/
$callback = static function( $content, $block ) use ( $args, $block_name ) {

// Sanity check.
if ( empty( $block['blockName'] ) || $block_name !== $block['blockName'] ) {
return $content;
}

// Register the stylesheet.
if ( ! empty( $args['src'] ) ) {
wp_register_script( $args['handle'], $args['src'], $args['deps'], $args['ver'], $args['in_footer'] );
}

// Enqueue the stylesheet.
wp_enqueue_script( $args['handle'] );

// If a textdomain is defined, use it to set the script translations.
if ( ! empty( $args['textdomain'] ) && in_array( 'wp-i18n', $args['deps'], true ) ) {
wp_set_script_translations( $args['handle'], $args['textdomain'] );
}

return $content;
};

/*
* The filter's callback here is an anonymous function because
* using a named function in this case is not possible.
*
* The function cannot be unhooked, however, users are still able
* to dequeue the script registered/enqueued by the callback
* which is why in this case, using an anonymous function
* was deemed acceptable.
*/
add_filter( 'render_block', $callback, 10, 2 );
}
}

/**
* Allow multiple view scripts per block.
*
* Filters the metadata provided for registering a block type.
*
* @param array $metadata Metadata for registering a block type.
*
* @return array
*/
function gutenberg_block_type_metadata_multiple_view_scripts( $metadata ) {

// Early return if viewScript is empty, or not an array.
if ( ! isset( $metadata['viewScript'] ) || ! is_array( $metadata['viewScript'] ) ) {
return $metadata;
}

// Register all viewScript items.
foreach ( $metadata['viewScript'] as $view_script ) {
$item_metadata = $metadata;
$item_metadata['viewScript'] = $view_script;
gutenberg_block_type_metadata_view_script( array(), $item_metadata );
}

// Proceed with the default behavior.
$metadata['viewScript'] = $metadata['viewScript'][0];
return $metadata;
}
add_filter( 'block_type_metadata', 'gutenberg_block_type_metadata_multiple_view_scripts' );
51 changes: 51 additions & 0 deletions lib/compat/wordpress-6.0/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,54 @@ function gutenberg_build_query_vars_from_query_block( $block, $page ) {
}
return $query;
}

/**
* Registers view scripts for core blocks if handling is missing in WordPress core.
*
* This is a temporary solution until the Gutenberg plugin sets
* the required WordPress version to 6.0.
*
* @param array $settings Array of determined settings for registering a block type.
* @param array $metadata Metadata provided for registering a block type.
*
* @return array Array of settings for registering a block type.
*/
function gutenberg_block_type_metadata_view_script( $settings, $metadata ) {
if (
! isset( $metadata['viewScript'] ) ||
! empty( $settings['view_script'] ) ||
! isset( $metadata['file'] ) ||
strpos( $metadata['file'], gutenberg_dir_path() ) !== 0
) {
return $settings;
}

$view_script_path = realpath( dirname( $metadata['file'] ) . '/' . remove_block_asset_path_prefix( $metadata['viewScript'] ) );

if ( file_exists( $view_script_path ) ) {
$view_script_id = str_replace( array( '.min.js', '.js' ), '', basename( remove_block_asset_path_prefix( $metadata['viewScript'] ) ) );
$view_script_handle = str_replace( 'core/', 'wp-block-', $metadata['name'] ) . '-' . $view_script_id;
wp_deregister_script( $view_script_handle );

// Replace suffix and extension with `.asset.php` to find the generated dependencies file.
$view_asset_file = substr( $view_script_path, 0, -( strlen( '.js' ) ) ) . '.asset.php';
$view_asset = file_exists( $view_asset_file ) ? require( $view_asset_file ) : null;
$view_script_dependencies = isset( $view_asset['dependencies'] ) ? $view_asset['dependencies'] : array();
$view_script_version = isset( $view_asset['version'] ) ? $view_asset['version'] : false;
$result = wp_register_script(
$view_script_handle,
gutenberg_url( str_replace( gutenberg_dir_path(), '', $view_script_path ) ),
$view_script_dependencies,
$view_script_version
);
if ( $result ) {
$settings['view_script'] = $view_script_handle;

if ( ! empty( $metadata['textdomain'] ) && in_array( 'wp-i18n', $view_script_dependencies, true ) ) {
wp_set_script_translations( $view_script_handle, $metadata['textdomain'] );
}
}
}
return $settings;
}
add_filter( 'block_type_metadata_settings', 'gutenberg_block_type_metadata_view_script', 10, 2 );
2 changes: 1 addition & 1 deletion packages/block-library/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ _This package assumes that your code will run in an **ES2015+** environment. If

## Building JavaScript for the browser

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.
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.

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

Expand Down
14 changes: 12 additions & 2 deletions schemas/json/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -421,8 +421,18 @@
"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."
},
"viewScript": {
"type": "string",
"description": "Block type frontend script definition. It will be enqueued only when viewing the content on the front of the site."
"description": "Block type frontend script definition. It will be enqueued only when viewing the content on the front of the site.",
"oneOf": [
{
"type": "string"
},
{
"type": "array",
"items": {
"type": "string"
}
}
]
},
"editorStyle": {
"description": "Block type editor style definition. It will only be enqueued in the context of the editor.",
Expand Down
24 changes: 14 additions & 10 deletions tools/webpack/blocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ const DependencyExtractionWebpackPlugin = require( '@wordpress/dependency-extrac
const { baseConfig, plugins, stylesTransform } = require( './shared' );

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

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

const createEntrypoints = () => {
/*
* Returns an array of paths to view.js files within the `@wordpress/block-library` package.
* These paths can be matched by the regex `blockNameRegex` in order to extract
* the block's name.
* Returns an array of paths to block view files within the `@wordpress/block-library` package.
* These paths can be matched by the regex `blockViewRegex` in order to extract
* the block's filename.
*
* Returns an empty array if no files were found.
*/
const blockViewScriptPaths = fastGlob.sync(
'./packages/block-library/build-module/**/view.js'
'./packages/block-library/build-module/**/view*.js'
);

/*
* Go through the paths found above, in order to define webpack entry points for
* each block's view.js file.
*/
return blockViewScriptPaths.reduce( ( entries, scriptPath ) => {
const [ blockName ] = scriptPath.match( blockNameRegex );
const result = scriptPath.match( blockViewRegex );
if ( ! result?.groups?.filename ) {
return entries;
}

return {
...entries,
[ 'blocks/' + blockName ]: scriptPath,
[ result.groups.filename ]: scriptPath,
};
}, {} );
};
Expand All @@ -61,7 +65,7 @@ module.exports = {
entry: createEntrypoints(),
output: {
devtoolNamespace: 'wp',
filename: './build/block-library/[name]/view.min.js',
filename: './build/block-library/blocks/[name].min.js',
path: join( __dirname, '..', '..' ),
},
plugins: [
Expand Down

0 comments on commit 99f820e

Please sign in to comment.