diff --git a/projects/packages/blocks/changelog/add-blocks-metadata-file b/projects/packages/blocks/changelog/add-blocks-metadata-file new file mode 100644 index 0000000000000..b1a7f0ea15f7c --- /dev/null +++ b/projects/packages/blocks/changelog/add-blocks-metadata-file @@ -0,0 +1,4 @@ +Significance: patch +Type: added + +Allow block registration with path to metadata file diff --git a/projects/packages/blocks/src/class-blocks.php b/projects/packages/blocks/src/class-blocks.php index 9286bd4ebc36d..e9a8a8e84101d 100644 --- a/projects/packages/blocks/src/class-blocks.php +++ b/projects/packages/blocks/src/class-blocks.php @@ -36,10 +36,11 @@ class Blocks { * @type array $version_requirements Array containing required Gutenberg version and, if known, the WordPress version that was released with this minimum version. * @type bool $plan_check Should we check for a specific plan before registering the block. * } + * @param string $metadata_dir Directory where the block's metadata is located. * * @return WP_Block_Type|false The registered block type on success, or false on failure. */ - public static function jetpack_register_block( $slug, $args = array() ) { + public static function jetpack_register_block( $slug, $args = array(), $metadata_dir = '' ) { if ( 0 !== strpos( $slug, 'jetpack/' ) && ! strpos( $slug, '/' ) ) { _doing_it_wrong( 'jetpack_register_block', 'Prefix the block with jetpack/ ', 'Jetpack 9.0.0' ); $slug = 'jetpack/' . $slug; @@ -94,12 +95,12 @@ function () use ( $feature_name, $method_name ) { // Ensure editor styles are registered so that the site editor knows about the // editor style dependency when copying styles to the editor iframe. - if ( ! isset( $args['editor_style'] ) ) { + if ( ! isset( $args['editor_style'] ) && ! $metadata_dir ) { $args['editor_style'] = 'jetpack-blocks-editor'; } } - return register_block_type( $slug, $args ); + return register_block_type( $metadata_dir ? $metadata_dir : $slug, $args ); } /** diff --git a/projects/plugins/jetpack/changelog/add-blocks-metadata-file b/projects/plugins/jetpack/changelog/add-blocks-metadata-file new file mode 100644 index 0000000000000..a68a6fc2b42d5 --- /dev/null +++ b/projects/plugins/jetpack/changelog/add-blocks-metadata-file @@ -0,0 +1,4 @@ +Significance: patch +Type: enhancement + +Add metadata file to blogging-prompt and business-hours blocks diff --git a/projects/plugins/jetpack/extensions/blocks/blogging-prompt/block.json b/projects/plugins/jetpack/extensions/blocks/blogging-prompt/block.json new file mode 100644 index 0000000000000..ffc4e7c0e6053 --- /dev/null +++ b/projects/plugins/jetpack/extensions/blocks/blogging-prompt/block.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 3, + "name": "jetpack/blogging-prompt", + "title": "Writing Prompt", + "description": "Answer a new and inspiring writing prompt each day.", + "keywords": [ "writing", "blogging" ], + "version": "12.5.0", + "textdomain": "jetpack", + "category": "text", + "supports": { + "align": false, + "alignWide": false, + "anchor": false, + "className": true, + "color": { + "background": true, + "gradients": true, + "link": true, + "text": true + }, + "customClassName": true, + "html": false, + "inserter": true, + "multiple": false, + "reusable": true, + "spacing": { + "margin": [ "top", "bottom" ], + "padding": true, + "blockGap": false + } + } +} diff --git a/projects/plugins/jetpack/extensions/blocks/blogging-prompt/blogging-prompt.php b/projects/plugins/jetpack/extensions/blocks/blogging-prompt/blogging-prompt.php index 7b0a236bbefbe..3636f7357a887 100644 --- a/projects/plugins/jetpack/extensions/blocks/blogging-prompt/blogging-prompt.php +++ b/projects/plugins/jetpack/extensions/blocks/blogging-prompt/blogging-prompt.php @@ -22,9 +22,15 @@ */ function register_block() { if ( ( defined( 'IS_WPCOM' ) && IS_WPCOM ) || \Jetpack::is_connection_ready() ) { + $dir = dirname( JETPACK__PLUGIN_FILE ); + $json_dir = $dir . '/_inc/blocks/' . FEATURE_NAME; + Blocks::jetpack_register_block( BLOCK_NAME, - array( 'render_callback' => __NAMESPACE__ . '\load_assets' ) + array( + 'render_callback' => __NAMESPACE__ . '\load_assets', + ), + $json_dir ); } } diff --git a/projects/plugins/jetpack/extensions/blocks/business-hours/block.json b/projects/plugins/jetpack/extensions/blocks/business-hours/block.json new file mode 100644 index 0000000000000..1b371bd0db7ce --- /dev/null +++ b/projects/plugins/jetpack/extensions/blocks/business-hours/block.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 3, + "name": "jetpack/business-hours", + "title": "Business Hours", + "description": "Display opening hours for your business.", + "keywords": [ "opening hours", "closing time", "schedule", "working day" ], + "version": "12.5.0", + "textdomain": "jetpack", + "category": "grow", + "supports": { + "html": true, + "color": { + "gradients": true + }, + "spacing": { + "margin": true, + "padding": true + }, + "typography": { + "fontSize": true, + "lineHeight": true + }, + "align": [ "wide", "full" ] + } +} diff --git a/projects/plugins/jetpack/extensions/blocks/business-hours/business-hours.php b/projects/plugins/jetpack/extensions/blocks/business-hours/business-hours.php index 2540e5b764d64..f82a8a71a79cc 100644 --- a/projects/plugins/jetpack/extensions/blocks/business-hours/business-hours.php +++ b/projects/plugins/jetpack/extensions/blocks/business-hours/business-hours.php @@ -21,25 +21,15 @@ * registration if we need to. */ function register_block() { + $dir = dirname( JETPACK__PLUGIN_FILE ); + $json_dir = $dir . '/_inc/blocks/' . FEATURE_NAME; + Blocks::jetpack_register_block( BLOCK_NAME, array( 'render_callback' => __NAMESPACE__ . '\render', - 'supports' => array( - 'color' => array( - 'gradients' => true, - ), - 'spacing' => array( - 'margin' => true, - 'padding' => true, - ), - 'typography' => array( - 'fontSize' => true, - 'lineHeight' => true, - ), - 'align' => array( 'wide', 'full' ), - ), - ) + ), + $json_dir ); } add_action( 'init', __NAMESPACE__ . '\register_block' ); diff --git a/projects/plugins/jetpack/extensions/bundles.json b/projects/plugins/jetpack/extensions/bundles.json new file mode 100644 index 0000000000000..4c63df899a77a --- /dev/null +++ b/projects/plugins/jetpack/extensions/bundles.json @@ -0,0 +1,10 @@ +[ + { + "name": "first-bundle", + "title": "First Bundle", + "description": "This is a proof of concept", + "blocks": [ "blogging-prompt" ], + "version": "1.0.0" + }, + "business-hours" +] diff --git a/projects/plugins/jetpack/tools/webpack.config.extensions.js b/projects/plugins/jetpack/tools/webpack.config.extensions.js index 127621bbd07b7..6420e49d8cdb8 100644 --- a/projects/plugins/jetpack/tools/webpack.config.extensions.js +++ b/projects/plugins/jetpack/tools/webpack.config.extensions.js @@ -20,27 +20,10 @@ const viewSetup = path.join( __dirname, '../extensions', 'view' ); const blockEditorDirectories = [ 'plugins', 'blocks' ]; const noop = function () {}; -/** - * Filters block editor scripts - * - * @param {string} type - script type - * @param {string} inputDir - input directory - * @param {Array} presetBlocks - preset blocks - * @returns {Array} list of block scripts - */ -function presetProductionExtensions( type, inputDir, presetBlocks ) { - return presetBlocks - .flatMap( block => - blockEditorDirectories.map( dir => path.join( inputDir, dir, block, `${ type }.js` ) ) - ) - .filter( fs.existsSync ); -} - const presetPath = path.join( __dirname, '../extensions', 'index.json' ); const presetIndex = require( presetPath ); const presetProductionBlocks = presetIndex.production || []; const presetNoPostEditorBlocks = presetIndex[ 'no-post-editor' ] || []; - const presetExperimentalBlocks = [ ...presetProductionBlocks, ...( presetIndex.experimental || [] ), @@ -48,14 +31,49 @@ const presetExperimentalBlocks = [ // Beta Blocks include all blocks: beta, experimental, and production blocks. const presetBetaBlocks = [ ...presetExperimentalBlocks, ...( presetIndex.beta || [] ) ]; +// Bundled blocks are built individually or grouped together in bundles. +const bundlesPath = path.join( __dirname, '../extensions', 'bundles.json' ); +const bundlesIndex = require( bundlesPath ); +const bundles = bundlesIndex || []; +const bundledBlocks = bundles + .reduce( + ( blocks, bundle ) => + 'string' === typeof bundle ? [ ...blocks, bundle ] : [ ...blocks, ...bundle.blocks ], + [] + ) + .filter( Boolean ); + +/** + * Filters block editor scripts + * + * @param {string} type - script type + * @param {string} inputDir - input directory + * @param {Array} presetBlocks - preset blocks + * @returns {Array} list of block scripts + */ +function presetProductionExtensions( type, inputDir, presetBlocks ) { + return ( + presetBlocks + // Exclude blocks that are bundled separately from the production/experimental/beta bundles. + .filter( block => ! bundledBlocks.includes( block ) ) + .flatMap( block => + blockEditorDirectories.map( dir => path.join( inputDir, dir, block, `${ type }.js` ) ) + ) + .filter( fs.existsSync ) + ); +} + // Helps split up each block into its own folder view script -const viewBlocksScripts = presetBetaBlocks.reduce( ( viewBlocks, block ) => { - const viewScriptPath = path.join( __dirname, '../extensions/blocks', block, 'view.js' ); - if ( fs.existsSync( viewScriptPath ) ) { - viewBlocks[ block + '/view' ] = [ viewSetup, ...[ viewScriptPath ] ]; - } - return viewBlocks; -}, {} ); +const viewBlocksScripts = presetBetaBlocks + // Exclude blocks that are bundled separately from the production/experimental/beta bundles. + .filter( block => ! bundledBlocks.includes( block ) ) + .reduce( ( viewBlocks, block ) => { + const viewScriptPath = path.join( __dirname, '../extensions/blocks', block, 'view.js' ); + if ( fs.existsSync( viewScriptPath ) ) { + viewBlocks[ block + '/view' ] = [ viewSetup, ...[ viewScriptPath ] ]; + } + return viewBlocks; + }, {} ); // Combines all the different production blocks into one editor.js script const editorScript = [ @@ -170,8 +188,78 @@ const sharedWebpackConfig = { }, }; -// We export two configuration files: One for admin.js, and one for components.jsx. -// The latter produces pre-rendered components HTML. +/** + * Bundles logic + */ + +const bundlesEntry = {}; +const bundlesPlugins = []; + +bundles.forEach( bundle => { + const name = 'string' === typeof bundle ? bundle : bundle.name; + const blocks = 'string' === typeof bundle ? [ bundle ] : bundle.blocks; + + // Bundle editor assets + bundlesEntry[ name + '/editor' ] = blocks.reduce( ( arr, block ) => { + const editorScriptPath = path.join( __dirname, '../extensions/blocks', block, 'editor.js' ); + + if ( fs.existsSync( editorScriptPath ) ) { + arr.push( editorScriptPath ); + } + + return arr; + }, [] ); + + // Bundle view assets + bundlesEntry[ name + '/view' ] = blocks.reduce( ( arr, block ) => { + const viewScriptPath = path.join( __dirname, '../extensions/blocks', block, 'view.js' ); + + if ( fs.existsSync( viewScriptPath ) ) { + arr.push( viewScriptPath ); + } + + return arr; + }, [] ); + + // Copy block.json files and set assets paths + blocks.forEach( block => { + bundlesPlugins.push( + new CopyWebpackPlugin( { + patterns: [ + { + from: `${ block }/block.json`, + to: `${ block }/[path][name][ext]`, + context: path.join( __dirname, '../extensions/blocks' ), + transform( content ) { + let metadata = {}; + + try { + metadata = JSON.parse( content.toString() ); + } catch ( e ) {} + + return JSON.stringify( { + ...metadata, + editorScript: `file:../${ name }/editor.js`, + editorStyle: `file:../${ name }/editor.css`, + viewScript: `file:../${ name }/view.js`, + style: `file:../${ name }/view.css`, + } ); + }, + noErrorOnMissing: true, + }, + ], + } ) + ); + } ); +} ); + +const bundlesConfig = { + ...sharedWebpackConfig, + entry: bundlesEntry, + plugins: [ ...sharedWebpackConfig.plugins, ...bundlesPlugins ], +}; + +// We export three configuration files: One for admin.js, one for components.jsx (produces pre-rendered components HTML), and one for bundles. module.exports = [ { ...sharedWebpackConfig, @@ -192,6 +280,20 @@ module.exports = [ }, ], } ), + // Copy the block.json files to the matching block folder in the build directory. + new CopyWebpackPlugin( { + patterns: [ + { + from: '**/block.json', + to: '[path][name][ext]', + context: path.join( __dirname, '../extensions/blocks' ), + globOptions: { + ignore: bundledBlocks.map( block => `**/${ block }/**` ), + }, + noErrorOnMissing: true, + }, + ], + } ), new CopyBlockEditorAssetsPlugin(), ], }, @@ -268,4 +370,5 @@ module.exports = [ } ), ], }, + bundlesConfig, ];