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