diff --git a/packages/create-block-tutorial-template/block-templates/index.js.mustache b/packages/create-block-tutorial-template/block-templates/index.js.mustache index 05fc05d445eb9e..ba504dcf80f198 100644 --- a/packages/create-block-tutorial-template/block-templates/index.js.mustache +++ b/packages/create-block-tutorial-template/block-templates/index.js.mustache @@ -20,7 +20,9 @@ import './editor.scss'; * Internal dependencies */ import Edit from './edit'; +{{#isStaticVariant}} import save from './save'; +{{/isStaticVariant}} import metadata from './block.json'; /** @@ -41,8 +43,10 @@ registerBlockType( metadata.name, { * @see ./edit.js */ edit: Edit, + {{#isStaticVariant}} /** * @see ./save.js */ save, + {{/isStaticVariant}} } ); diff --git a/packages/create-block-tutorial-template/block-templates/save.js.mustache b/packages/create-block-tutorial-template/block-templates/save.js.mustache index 605c5165c35b0f..d390552490a579 100644 --- a/packages/create-block-tutorial-template/block-templates/save.js.mustache +++ b/packages/create-block-tutorial-template/block-templates/save.js.mustache @@ -1,3 +1,4 @@ +{{#isStaticVariant}} /** * React hook that is used to mark the block wrapper element. * It provides all the necessary props like the class name. @@ -21,3 +22,4 @@ export default function save( { attributes } ) { const blockProps = useBlockProps.save(); return
{ attributes.message }
; } +{{/isStaticVariant}} diff --git a/packages/create-block-tutorial-template/block-templates/template.php.mustache b/packages/create-block-tutorial-template/block-templates/template.php.mustache new file mode 100644 index 00000000000000..38a471b67c0e2a --- /dev/null +++ b/packages/create-block-tutorial-template/block-templates/template.php.mustache @@ -0,0 +1,5 @@ +{{#isDynamicVariant}} +

> + +

+{{/isDynamicVariant}} diff --git a/packages/create-block-tutorial-template/index.js b/packages/create-block-tutorial-template/index.js index 90f1f304ea7695..df13ae6100dc2a 100644 --- a/packages/create-block-tutorial-template/index.js +++ b/packages/create-block-tutorial-template/index.js @@ -22,6 +22,16 @@ module.exports = { html: false, }, }, + variants: { + static: {}, + dynamic: { + attributes: { + message: { + type: 'string', + }, + }, + }, + }, pluginTemplatesPath: join( __dirname, 'plugin-templates' ), blockTemplatesPath: join( __dirname, 'block-templates' ), assetsPath: join( __dirname, 'assets' ), diff --git a/packages/create-block-tutorial-template/plugin-templates/$slug.php.mustache b/packages/create-block-tutorial-template/plugin-templates/$slug.php.mustache index 676f923e7ec09b..99249aed248903 100644 --- a/packages/create-block-tutorial-template/plugin-templates/$slug.php.mustache +++ b/packages/create-block-tutorial-template/plugin-templates/$slug.php.mustache @@ -37,7 +37,35 @@ * * @see https://developer.wordpress.org/reference/functions/register_block_type/ */ +{{#isStaticVariant}} function {{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init() { register_block_type( __DIR__ . '/build' ); } add_action( 'init', '{{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init' ); +{{/isStaticVariant}} +{{#isDynamicVariant}} +function {{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init() { + register_block_type( + __DIR__ . '/build', + array( + 'render_callback' => '{{namespaceSnakeCase}}_{{slugSnakeCase}}_render_callback', + ) + ); +} +add_action( 'init', '{{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init' ); + +/** + * Render callback function + * + * @param array $attributes The block attributes. + * @param string $content The block content. + * @param WP_Block $block Block instance. + * + * @return string The rendered output. + */ +function {{namespaceSnakeCase}}_{{slugSnakeCase}}_render_callback( $atts, $content, $block) { + ob_start(); + require plugin_dir_path( __FILE__ ) . 'build/template.php'; + return ob_get_clean(); +} +{{/isDynamicVariant}} diff --git a/packages/create-block/CHANGELOG.md b/packages/create-block/CHANGELOG.md index 9dfe19437b0c1e..ee717471fc1434 100644 --- a/packages/create-block/CHANGELOG.md +++ b/packages/create-block/CHANGELOG.md @@ -9,6 +9,7 @@ ### New Feature - Add `--no-plugin` flag to allow scaffolding of a block in an existing plugin ([#41642](https://github.com/WordPress/gutenberg/pull/41642)) +- Introduce the `--variant` flag to allow selection of a variant as defined in the template ([#41289](https://github.com/WordPress/gutenberg/pull/41289)). ## 3.6.0 (2022-07-13) diff --git a/packages/create-block/README.md b/packages/create-block/README.md index acb584ed3face7..d622131b620384 100644 --- a/packages/create-block/README.md +++ b/packages/create-block/README.md @@ -50,6 +50,7 @@ Options: --no-wp-scripts disable integration with `@wordpress/scripts` package --wp-env enable integration with `@wordpress/env` package -h, --help output usage information +--variant choose a block variant as defined by the template ``` More examples: @@ -72,7 +73,13 @@ $ npx @wordpress/create-block --template my-template-package $ npx @wordpress/create-block --template ./path/to/template-directory ``` -4. Help – you need to use `npx` to output usage information. +4. Generating a dynamic block based on the built-in template. + +```bash +$ npx @wordpress/create-block --variant dynamic +``` + +5. Help – you need to use `npx` to output usage information. ```bash $ npx @wordpress/create-block --help diff --git a/packages/create-block/lib/index.js b/packages/create-block/lib/index.js index b5e41433a38bae..5df49217ef2118 100644 --- a/packages/create-block/lib/index.js +++ b/packages/create-block/lib/index.js @@ -34,8 +34,8 @@ program .arguments( '[slug]' ) .option( '-t, --template ', - 'project template type name; allowed values: "static", "es5", the name of an external npm package, or the path to a local directory', - 'static' + 'project template type name; allowed values: "standard", "es5", the name of an external npm package, or the path to a local directory', + 'standard' ) .option( '--namespace ', 'internal namespace for the block name' ) .option( @@ -58,6 +58,7 @@ program ) .option( '--wp-env', 'enable integration with `@wordpress/env` package' ) .option( '--no-plugin', 'scaffold only block files' ) + .option( '--variant ', 'the variant of the template to use' ) .action( async ( slug, @@ -70,12 +71,16 @@ program title, wpScripts, wpEnv, + variant, } ) => { await checkSystemRequirements( engines ); try { const pluginTemplate = await getPluginTemplate( templateName ); - const defaultValues = getDefaultValues( pluginTemplate ); + const defaultValues = getDefaultValues( + pluginTemplate, + variant + ); const optionsValues = Object.fromEntries( Object.entries( { plugin, @@ -85,6 +90,7 @@ program title, wpScripts, wpEnv, + variant, } ).filter( ( [ , value ] ) => value !== undefined ) ); @@ -107,14 +113,30 @@ program const filterOptionsProvided = ( { name } ) => ! Object.keys( optionsValues ).includes( name ); - const blockPrompts = getPrompts( pluginTemplate, [ - 'slug', - 'namespace', - 'title', - 'description', - 'dashicon', - 'category', - ] ).filter( filterOptionsProvided ); + + // Get the variant prompt first. This will help get the default values + const variantPrompt = + Object.keys( pluginTemplate.variants )?.length > 1 + ? getPrompts( pluginTemplate, [ 'variant' ] ) + : false; + + const variantSelection = variantPrompt + ? await inquirer.prompt( variantPrompt ) + : false; + + const blockPrompts = getPrompts( + pluginTemplate, + [ + 'slug', + 'namespace', + 'title', + 'description', + 'dashicon', + 'category', + ], + variantSelection.variant + ).filter( filterOptionsProvided ); + const blockAnswers = await inquirer.prompt( blockPrompts ); const pluginAnswers = plugin @@ -154,6 +176,7 @@ program ...defaultValues, ...optionsValues, ...blockAnswers, + ...variantSelection, ...pluginAnswers, } ); } diff --git a/packages/create-block/lib/init-package-json.js b/packages/create-block/lib/init-package-json.js index 251696c0c227e3..06bb170394d01c 100644 --- a/packages/create-block/lib/init-package-json.js +++ b/packages/create-block/lib/init-package-json.js @@ -23,6 +23,7 @@ module.exports = async ( { npmDependencies, npmDevDependencies, customScripts, + isDynamicVariant, } ) => { const cwd = join( process.cwd(), slug ); @@ -42,13 +43,17 @@ module.exports = async ( { main: wpScripts && 'build/index.js', scripts: { ...( wpScripts && { - build: 'wp-scripts build', + build: isDynamicVariant + ? 'wp-scripts build --webpack-copy-php' + : 'wp-scripts build', format: 'wp-scripts format', 'lint:css': 'wp-scripts lint-style', 'lint:js': 'wp-scripts lint-js', 'packages-update': 'wp-scripts packages-update', 'plugin-zip': 'wp-scripts plugin-zip', - start: 'wp-scripts start', + start: isDynamicVariant + ? 'wp-scripts start --webpack-copy-php' + : 'wp-scripts start', } ), ...( wpEnv && { env: 'wp-env' } ), ...customScripts, diff --git a/packages/create-block/lib/output.js b/packages/create-block/lib/output.js index 32b121b89f71da..dd442b17ae0511 100644 --- a/packages/create-block/lib/output.js +++ b/packages/create-block/lib/output.js @@ -17,7 +17,11 @@ const writeOutputTemplate = async ( inputFile, outputFile, view ) => { ? join( view.slug, outputFile.replace( /\$slug/g, view.slug ) ) : outputFile; await makeDir( dirname( outputFilePath ) ); - writeFile( outputFilePath, render( inputFile, view ) ); + // If the rendered template is empty, don't write it. This is how we can conditionally add template files. + const renderedFile = render( inputFile, view ); + if ( renderedFile.trim().length ) { + writeFile( outputFilePath, renderedFile ); + } }; module.exports = { diff --git a/packages/create-block/lib/prompts.js b/packages/create-block/lib/prompts.js index 12da9f892b80e6..9a6b9b1449537a 100644 --- a/packages/create-block/lib/prompts.js +++ b/packages/create-block/lib/prompts.js @@ -134,8 +134,16 @@ const updateURI = { message: 'A custom update URI for the plugin (optional):', }; +const variant = { + type: 'list', + name: 'variant', + message: 'The template variant to use for this block:', + choices: [], +}; + module.exports = { slug, + variant, namespace, title, description, diff --git a/packages/create-block/lib/scaffold.js b/packages/create-block/lib/scaffold.js index 495585a896233f..2636db0a3d4647 100644 --- a/packages/create-block/lib/scaffold.js +++ b/packages/create-block/lib/scaffold.js @@ -12,9 +12,10 @@ const initWPScripts = require( './init-wp-scripts' ); const initWPEnv = require( './init-wp-env' ); const { code, info, success, error } = require( './log' ); const { writeOutputAsset, writeOutputTemplate } = require( './output' ); +const { getTemplateVariantVars } = require( './templates' ); module.exports = async ( - { blockOutputTemplates, pluginOutputTemplates, outputAssets }, + { blockOutputTemplates, pluginOutputTemplates, outputAssets, variants }, { $schema, apiVersion, @@ -43,11 +44,11 @@ module.exports = async ( editorScript, editorStyle, style, + variant, } ) => { slug = slug.toLowerCase(); namespace = namespace.toLowerCase(); - /** * --no-plugin relies on the used template supporting the [blockTemplatesPath property](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-create-block/#blocktemplatespath). * If the blockOutputTemplates object has no properties, we can assume that there was a custom --template passed that @@ -99,6 +100,8 @@ module.exports = async ( editorScript, editorStyle, style, + ...getTemplateVariantVars( variants, variant ), + ...variants[ variant ], }; if ( plugin ) { diff --git a/packages/create-block/lib/templates.js b/packages/create-block/lib/templates.js index 47e05292a18bff..41933286df71ac 100644 --- a/packages/create-block/lib/templates.js +++ b/packages/create-block/lib/templates.js @@ -24,7 +24,7 @@ const predefinedPluginTemplates = { slug: 'example-static-es5', title: 'Example Static (ES5)', description: - 'Example static block scaffolded with Create Block tool – no build step required.', + 'Example block scaffolded with Create Block tool – no build step required.', dashicon: 'smiley', wpScripts: false, editorScript: 'file:./index.js', @@ -32,18 +32,31 @@ const predefinedPluginTemplates = { style: 'file:./style.css', }, templatesPath: join( __dirname, 'templates', 'es5' ), + variants: { + static: {}, + dynamic: { + slug: 'example-dynamic-es5', + title: 'Example Dynamic (ES5)', + }, + }, }, - static: { + standard: { defaultValues: { slug: 'example-static', title: 'Example Static', - description: - 'Example static block scaffolded with Create Block tool.', + description: 'Example block scaffolded with Create Block tool.', dashicon: 'smiley', supports: { html: false, }, }, + variants: { + static: {}, + dynamic: { + slug: 'example-dynamic', + title: 'Example Dynamic', + }, + }, }, }; @@ -100,6 +113,7 @@ const configToTemplate = async ( { blockTemplatesPath, defaultValues = {}, assetsPath, + variants, ...deprecated } ) => { if ( defaultValues === null || typeof defaultValues !== 'object' ) { @@ -129,6 +143,7 @@ const configToTemplate = async ( { defaultValues, outputAssets: assetsPath ? await getOutputAssets( assetsPath ) : {}, pluginOutputTemplates: await getOutputTemplates( pluginTemplatesPath ), + variants, }; }; @@ -197,7 +212,7 @@ const getPluginTemplate = async ( templateName ) => { } }; -const getDefaultValues = ( pluginTemplate ) => { +const getDefaultValues = ( pluginTemplate, variant ) => { return { $schema: 'https://schemas.wp.org/trunk/block.json', apiVersion: 2, @@ -216,12 +231,25 @@ const getDefaultValues = ( pluginTemplate ) => { editorStyle: 'file:./index.css', style: 'file:./style-index.css', ...pluginTemplate.defaultValues, + ...pluginTemplate.variants[ variant ], }; }; -const getPrompts = ( pluginTemplate, keys ) => { +const getPrompts = ( pluginTemplate, keys, variant ) => { const defaultValues = getDefaultValues( pluginTemplate ); + const variantData = pluginTemplate.variants[ variant ] ?? false; return keys.map( ( promptName ) => { + if ( promptName === 'variant' ) { + prompts[ promptName ].choices = Object.keys( + pluginTemplate.variants + ); + } + if ( variantData && variantData[ promptName ] ) { + return { + ...prompts[ promptName ], + default: variantData[ promptName ], + }; + } return { ...prompts[ promptName ], default: defaultValues[ promptName ], @@ -229,8 +257,34 @@ const getPrompts = ( pluginTemplate, keys ) => { } ); }; +const getTemplateVariantVars = ( variants, variant ) => { + const variantVars = {}; + if ( variants ) { + const variantNames = Object.keys( variants ); + const chosenVariant = variant ?? variantNames[ 0 ]; // If no variant is passed, use the first in the array as the default + + if ( variantNames.includes( chosenVariant ) ) { + for ( const variantName of variantNames ) { + const key = + variantName.charAt( 0 ).toUpperCase() + + variantName.slice( 1 ); + variantVars[ `is${ key }Variant` ] = + chosenVariant === variantName ?? false; + } + } else { + throw new CLIError( + `"${ chosenVariant }" is not a valid variant for this template. Available variants are: ${ variantNames.join( + ', ' + ) }` + ); + } + } + return variantVars; +}; + module.exports = { getPluginTemplate, getDefaultValues, getPrompts, + getTemplateVariantVars, }; diff --git a/packages/create-block/lib/templates/block/index.js.mustache b/packages/create-block/lib/templates/block/index.js.mustache index f420b693a0ceaf..7ce98bf84dff53 100644 --- a/packages/create-block/lib/templates/block/index.js.mustache +++ b/packages/create-block/lib/templates/block/index.js.mustache @@ -18,7 +18,9 @@ import './style.scss'; * Internal dependencies */ import Edit from './edit'; +{{#isStaticVariant}} import save from './save'; +{{/isStaticVariant}} import metadata from './block.json'; /** @@ -31,8 +33,10 @@ registerBlockType( metadata.name, { * @see ./edit.js */ edit: Edit, + {{#isStaticVariant}} /** * @see ./save.js */ save, + {{/isStaticVariant}} } ); diff --git a/packages/create-block/lib/templates/block/save.js.mustache b/packages/create-block/lib/templates/block/save.js.mustache index eeb81e432055cf..70452e60a6a4fa 100644 --- a/packages/create-block/lib/templates/block/save.js.mustache +++ b/packages/create-block/lib/templates/block/save.js.mustache @@ -1,3 +1,4 @@ +{{#isStaticVariant}} /** * React hook that is used to mark the block wrapper element. * It provides all the necessary props like the class name. @@ -22,3 +23,4 @@ export default function save() {

); } +{{/isStaticVariant}} diff --git a/packages/create-block/lib/templates/block/template.php.mustache b/packages/create-block/lib/templates/block/template.php.mustache new file mode 100644 index 00000000000000..349a119de8e66d --- /dev/null +++ b/packages/create-block/lib/templates/block/template.php.mustache @@ -0,0 +1,5 @@ +{{#isDynamicVariant}} +

> + +

+{{/isDynamicVariant}} diff --git a/packages/create-block/lib/templates/es5/$slug.php.mustache b/packages/create-block/lib/templates/es5/$slug.php.mustache index c8bcde2818edd3..2a9135db105fa9 100644 --- a/packages/create-block/lib/templates/es5/$slug.php.mustache +++ b/packages/create-block/lib/templates/es5/$slug.php.mustache @@ -68,7 +68,7 @@ function {{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init() { array(), filemtime( "$dir/$style_css" ) ); - + {{^isDynamicVariant}} register_block_type( '{{namespace}}/{{slug}}', array( @@ -77,5 +77,33 @@ function {{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init() { 'style' => '{{namespace}}-{{slug}}-block', ) ); + {{/isDynamicVariant}} + {{#isDynamicVariant}} + register_block_type( + '{{namespace}}/{{slug}}', + array( + 'editor_script' => '{{namespace}}-{{slug}}-block-editor', + 'editor_style' => '{{namespace}}-{{slug}}-block-editor', + 'style' => '{{namespace}}-{{slug}}-block', + 'render_callback' => '{{namespaceSnakeCase}}_{{slugSnakeCase}}_render_callback', + ) + ); + {{/isDynamicVariant}} } add_action( 'init', '{{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init' ); +{{#isDynamicVariant}} +/** + * Render callback function + * + * @param array $attributes The block attributes. + * @param string $content The block content. + * @param WP_Block $block Block instance. + * + * @return string The rendered output. + */ +function {{namespaceSnakeCase}}_{{slugSnakeCase}}_render_callback( $atts, $content, $block) { + ob_start(); + require plugin_dir_path( __FILE__ ) . '/template.php'; + return ob_get_clean(); +} +{{/isDynamicVariant}} diff --git a/packages/create-block/lib/templates/es5/index.js.mustache b/packages/create-block/lib/templates/es5/index.js.mustache index 895cd9f97aebd4..a81ad9f625fe53 100644 --- a/packages/create-block/lib/templates/es5/index.js.mustache +++ b/packages/create-block/lib/templates/es5/index.js.mustache @@ -94,8 +94,7 @@ useBlockProps(), __( '{{title}} – hello from the editor!', '{{textdomain}}' ) ); - }, - + }{{#isStaticVariant}}, /** * The save function defines the way in which the different attributes should be combined * into the final markup, which is then serialized by the block editor into `post_content`. @@ -111,6 +110,7 @@ '{{title}} – hello from the saved content!', ); }, + {{/isStaticVariant}} } ); }( window.wp diff --git a/packages/create-block/lib/templates/es5/template.php.mustache b/packages/create-block/lib/templates/es5/template.php.mustache new file mode 100644 index 00000000000000..349a119de8e66d --- /dev/null +++ b/packages/create-block/lib/templates/es5/template.php.mustache @@ -0,0 +1,5 @@ +{{#isDynamicVariant}} +

> + +

+{{/isDynamicVariant}} diff --git a/packages/create-block/lib/templates/plugin/$slug.php.mustache b/packages/create-block/lib/templates/plugin/$slug.php.mustache index 27da9860657881..89780f870bd804 100644 --- a/packages/create-block/lib/templates/plugin/$slug.php.mustache +++ b/packages/create-block/lib/templates/plugin/$slug.php.mustache @@ -37,7 +37,35 @@ * * @see https://developer.wordpress.org/reference/functions/register_block_type/ */ +{{#isStaticVariant}} function {{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init() { register_block_type( __DIR__ . '/build' ); } add_action( 'init', '{{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init' ); +{{/isStaticVariant}} +{{#isDynamicVariant}} +function {{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init() { + register_block_type( + __DIR__ . '/build', + array( + 'render_callback' => '{{namespaceSnakeCase}}_{{slugSnakeCase}}_render_callback', + ) + ); +} +add_action( 'init', '{{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init' ); + +/** + * Render callback function + * + * @param array $attributes The block attributes. + * @param string $content The block content. + * @param WP_Block $block Block instance. + * + * @return string The rendered output. + */ +function {{namespaceSnakeCase}}_{{slugSnakeCase}}_render_callback( $atts, $content, $block) { + ob_start(); + require plugin_dir_path( __FILE__ ) . 'build/template.php'; + return ob_get_clean(); +} +{{/isDynamicVariant}}