├── index.js ├── .DS_Store ├── lib ├── cli-error.js ├── templates │ ├── editor-css.mustache │ ├── style-css.mustache │ ├── gitignore.mustache │ ├── editorconfig.mustache │ ├── es5 │ │ ├── plugin-php.mustache │ │ └── index-js.mustache │ └── esnext │ │ ├── plugin-php.mustache │ │ └── index-js.mustache ├── utils.js ├── test │ └── utils.js ├── log.js ├── init-wp-scripts.js ├── prompts.js ├── index.js ├── scaffold.js └── templates.js ├── assets └── create-wordpress-block.gif ├── .eslintrc.json ├── .gitignore ├── .editorconfig ├── .github └── workflows │ └── nodejs.yml ├── CHANGELOG.md ├── LICENSE ├── package.json └── README.md /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require( './lib' ); 3 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gziolo/create-wordpress-block/HEAD/.DS_Store -------------------------------------------------------------------------------- /lib/cli-error.js: -------------------------------------------------------------------------------- 1 | class CLIError extends Error {} 2 | 3 | module.exports = CLIError; 4 | -------------------------------------------------------------------------------- /assets/create-wordpress-block.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gziolo/create-wordpress-block/HEAD/assets/create-wordpress-block.gif -------------------------------------------------------------------------------- /lib/templates/editor-css.mustache: -------------------------------------------------------------------------------- 1 | /** 2 | * The following styles get applied inside the editor only. 3 | * 4 | * Replace them with your own styles or remove the file completely. 5 | */ 6 | 7 | .wp-block-{{namespace}}-{{slug}} { 8 | border: 1px dotted #f00; 9 | } 10 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | const startCase = ( input ) => { 2 | return input.split( ' ' ).map( upperFirst ).join( ' ' ); 3 | }; 4 | 5 | const upperFirst = ( input ) => { 6 | return input.charAt( 0 ).toUpperCase() + input.slice( 1 ); 7 | }; 8 | 9 | module.exports = { 10 | startCase, 11 | upperFirst, 12 | }; 13 | -------------------------------------------------------------------------------- /lib/templates/style-css.mustache: -------------------------------------------------------------------------------- 1 | /** 2 | * The following styles get applied both on the front of your site 3 | * and in the editor. 4 | * 5 | * Replace them with your own styles or remove the file completely. 6 | */ 7 | 8 | .wp-block-{{namespace}}-{{slug}} { 9 | background-color: #000; 10 | color: #fff; 11 | padding: 2px; 12 | } 13 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "plugin:@wordpress/eslint-plugin/es5" 4 | ], 5 | "globals": { 6 | "window": true, 7 | "document": true, 8 | "wp": "readonly" 9 | }, 10 | "overrides": [ 11 | { 12 | "files": [ "*.js" ], 13 | "excludedFiles": [ "es5-example/*.js" ], 14 | "extends": [ 15 | "plugin:@wordpress/eslint-plugin/recommended" 16 | ] 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /lib/templates/gitignore.mustache: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Coverage directory used by tools like istanbul 9 | coverage 10 | 11 | # Compiled binary addons (https://nodejs.org/api/addons.html) 12 | build/Release 13 | 14 | # Dependency directories 15 | node_modules/ 16 | 17 | # Optional npm cache directory 18 | .npm 19 | 20 | # Optional eslint cache 21 | .eslintcache 22 | 23 | # Output of 'npm pack' 24 | *.tgz 25 | 26 | # dotenv environment variables file 27 | .env 28 | -------------------------------------------------------------------------------- /lib/templates/editorconfig.mustache: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | # WordPress Coding Standards 5 | # https://make.wordpress.org/core/handbook/coding-standards/ 6 | 7 | root = true 8 | 9 | [*] 10 | charset = utf-8 11 | end_of_line = lf 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | indent_style = tab 15 | indent_size = 4 16 | 17 | [{*.json,*.yml}] 18 | indent_style = space 19 | indent_size = 2 20 | 21 | [*.md] 22 | trim_trailing_whitespace = false 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Coverage directory used by tools like istanbul 9 | coverage 10 | 11 | # Compiled binary addons (https://nodejs.org/api/addons.html) 12 | build/Release 13 | 14 | # Dependency directories 15 | node_modules/ 16 | 17 | # Optional npm cache directory 18 | .npm 19 | 20 | # Optional eslint cache 21 | .eslintcache 22 | 23 | # Output of 'npm pack' 24 | *.tgz 25 | 26 | # dotenv environment variables file 27 | .env 28 | 29 | # example folders 30 | *-example/ 31 | -------------------------------------------------------------------------------- /lib/test/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Internal dependnecies 3 | */ 4 | import { 5 | startCase, 6 | upperFirst, 7 | } from '../utils'; 8 | 9 | describe( 'startCase', () => { 10 | test( 'words get converted to start with upper case', () => { 11 | expect( 12 | startCase( 'hello world' ) 13 | ).toBe( 14 | 'Hello World' 15 | ); 16 | } ); 17 | } ); 18 | 19 | describe( 'upperFirst', () => { 20 | test( 'First char gets converted to upper case', () => { 21 | expect( 22 | upperFirst( 'hello world' ) 23 | ).toBe( 24 | 'Hello world' 25 | ); 26 | } ); 27 | } ); 28 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | # WordPress Coding Standards 5 | # https://make.wordpress.org/core/handbook/coding-standards/ 6 | 7 | root = true 8 | 9 | [*] 10 | charset = utf-8 11 | end_of_line = lf 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | indent_style = tab 15 | indent_size = 4 16 | 17 | [{*.json,*.yml}] 18 | indent_style = space 19 | indent_size = 2 20 | 21 | [*.md] 22 | trim_trailing_whitespace = false 23 | 24 | [*.txt] 25 | end_of_line = crlf 26 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | strategy: 11 | matrix: 12 | node-version: [10.x, 12.x] 13 | 14 | steps: 15 | - uses: actions/checkout@v1 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | - name: npm install, build, and test 21 | run: | 22 | npm ci 23 | npm run build --if-present 24 | npm test 25 | env: 26 | CI: true 27 | -------------------------------------------------------------------------------- /lib/log.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | /** 3 | * External dependencies 4 | */ 5 | const chalk = require( 'chalk' ); 6 | 7 | const clear = () => { 8 | process.stdout.write( 9 | process.platform === 'win32' ? '\x1B[2J\x1B[0f' : '\x1B[2J\x1B[3J\x1B[H' 10 | ); 11 | }; 12 | 13 | const code = ( input ) => { 14 | console.log( chalk.cyan( input ) ); 15 | }; 16 | 17 | const error = ( input ) => { 18 | console.log( chalk.bold.red( input ) ); 19 | }; 20 | 21 | const info = ( input ) => { 22 | console.log( input ); 23 | }; 24 | const success = ( input ) => { 25 | console.log( chalk.bold.green( input ) ); 26 | }; 27 | 28 | module.exports = { 29 | code, 30 | clear, 31 | error, 32 | info, 33 | success, 34 | }; 35 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Master 2 | 3 | ## 0.5.0 (2020-01-08) 4 | 5 | ### New Features 6 | 7 | - Update templates to include WordPress plugin metadata by default. 8 | 9 | ## 0.4.3 (2020-01-08) 10 | 11 | ### Bug Fix 12 | 13 | - Print available commands only for ESNext template. 14 | 15 | ## 0.4.0 (2019-12-17) 16 | 17 | ### New Features 18 | 19 | - Add full support for ESNext template, including `wp-scripts` bootstrapping. 20 | 21 | ### Enhancements 22 | 23 | - Improve the feedback shared on the console while scaffolding a block. 24 | 25 | ## 0.3.2 (2019-12-16) 26 | 27 | ### Bug Fix 28 | 29 | - Fix the paths pointing to the JS build file listed in PHP file in the ESNext template. 30 | 31 | ## 0.3.0 (2019-12-16) 32 | 33 | ### New Features 34 | 35 | - Added support for template types. `esnext` becomes the default one. `es5` is still available as an option. 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Grzegorz (Greg) Ziółkowski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/init-wp-scripts.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | const { command } = require( 'execa' ); 5 | const { install } = require( 'pkg-install' ); 6 | const { join } = require( 'path' ); 7 | const writePkg = require( 'write-pkg' ); 8 | 9 | /** 10 | * Internal dependencies 11 | */ 12 | const { info } = require( './log' ); 13 | 14 | module.exports = async function( { author, license, slug, title, version } ) { 15 | const cwd = join( process.cwd(), slug ); 16 | 17 | info( '' ); 18 | info( 'Creating a "package.json" file.' ); 19 | await writePkg( cwd, { 20 | name: slug, 21 | version, 22 | description: title, 23 | author, 24 | license, 25 | main: 'build/index.js', 26 | scripts: { 27 | build: 'wp-scripts build', 28 | 'lint:css': 'wp-scripts lint-style', 29 | 'lint:js': 'wp-scripts lint-js', 30 | start: 'wp-scripts start', 31 | }, 32 | } ); 33 | 34 | info( '' ); 35 | info( 'Installing packages. It might take a couple of minutes.' ); 36 | await install( [ 37 | '@wordpress/scripts', 38 | ], { 39 | cwd, 40 | dev: true, 41 | prefer: 'npm', 42 | } ); 43 | 44 | info( '' ); 45 | info( 'Compiling block.' ); 46 | await command( 'npm run build', { 47 | cwd, 48 | } ); 49 | }; 50 | -------------------------------------------------------------------------------- /lib/templates/es5/plugin-php.mustache: -------------------------------------------------------------------------------- 1 | '{{namespace}}-{{slug}}-block-editor', 52 | 'editor_style' => '{{namespace}}-{{slug}}-block-editor', 53 | 'style' => '{{namespace}}-{{slug}}-block', 54 | ) ); 55 | } 56 | add_action( 'init', '{{machineName}}_block_init' ); 57 | -------------------------------------------------------------------------------- /lib/templates/esnext/plugin-php.mustache: -------------------------------------------------------------------------------- 1 | '{{namespace}}-{{slug}}-block-editor', 55 | 'editor_style' => '{{namespace}}-{{slug}}-block-editor', 56 | 'style' => '{{namespace}}-{{slug}}-block', 57 | ) ); 58 | } 59 | add_action( 'init', '{{machineName}}_block_init' ); 60 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-wordpress-block", 3 | "version": "0.5.1", 4 | "description": "Generates PHP, JS and CSS code for registering a block for a WordPress plugin.", 5 | "author": "Grzegorz (Greg)", 6 | "license": "MIT", 7 | "keywords": [ 8 | "wordpress", 9 | "block" 10 | ], 11 | "homepage": "https://github.com/gziolo/create-wordpress-block#readme", 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/gziolo/create-wordpress-block.git" 15 | }, 16 | "bugs": { 17 | "url": "https://github.com/gziolo/create-wordpress-block/issues" 18 | }, 19 | "engines": { 20 | "node": ">=10.0", 21 | "npm": ">=6.1" 22 | }, 23 | "main": "index.js", 24 | "files": [ 25 | "lib" 26 | ], 27 | "bin": { 28 | "create-wordpress-block": "index.js" 29 | }, 30 | "scripts": { 31 | "prebuild:es5": "run-s clear:es5", 32 | "build:es5": "./index.js -t es5 es5-example", 33 | "prebuild:esnext": "run-s clear:esnext", 34 | "build:esnext": "./index.js esnext-example", 35 | "prebuild": "run-s check-engines", 36 | "build": "run-s build:*", 37 | "clear:es5": "rimraf ./es5-example ./esnext-example", 38 | "clear:esnext": "rimraf ./esnext-example", 39 | "clear": "run-p clear:*", 40 | "check-engines": "wp-scripts check-engines --package", 41 | "lint:css": "wp-scripts lint-style", 42 | "lint:js": "wp-scripts lint-js", 43 | "test:unit": "wp-scripts test-unit-js", 44 | "test": "run-s lint:* test:unit" 45 | }, 46 | "dependencies": { 47 | "chalk": "^3.0.0", 48 | "commander": "^4.0.1", 49 | "execa": "^3.4.0", 50 | "inquirer": "^7.0.0", 51 | "make-dir": "^3.0.0", 52 | "mustache": "^3.1.0", 53 | "pkg-install": "^1.0.0", 54 | "write-pkg": "^4.0.0" 55 | }, 56 | "devDependencies": { 57 | "@wordpress/eslint-plugin": "^3.2.0", 58 | "@wordpress/scripts": "^6.0.0", 59 | "npm-run-all": "^4.1.5", 60 | "rimraf": "^3.0.0" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/prompts.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Internal dependencies 3 | */ 4 | const { upperFirst } = require( './utils' ); 5 | 6 | const slug = { 7 | type: 'input', 8 | name: 'slug', 9 | message: 'The block slug used for identification (also the plugin and output folder name):', 10 | validate( input ) { 11 | if ( ! /^[a-z][a-z0-9\-]*$/.test( input ) ) { 12 | return 'Invalid block slug specified. Block slug can contain only lowercase alphanumeric characters or dashes, and start with a letter.'; 13 | } 14 | 15 | return true; 16 | }, 17 | }; 18 | 19 | const namespace = { 20 | type: 'input', 21 | name: 'namespace', 22 | message: 'The internal namespace for the block name (something unique for your products):', 23 | validate( input ) { 24 | if ( ! /^[a-z][a-z0-9\-]*$/.test( input ) ) { 25 | return 'Invalid block namespace specified. Block namespace can contain only lowercase alphanumeric characters or dashes, and start with a letter.'; 26 | } 27 | 28 | return true; 29 | }, 30 | }; 31 | 32 | const title = { 33 | type: 'input', 34 | name: 'title', 35 | message: 'The display title for your block:', 36 | filter( input ) { 37 | return input && upperFirst( input ); 38 | }, 39 | }; 40 | 41 | const description = { 42 | type: 'input', 43 | name: 'description', 44 | message: 'The short description for your block (optional):', 45 | filter( input ) { 46 | return input && upperFirst( input ); 47 | }, 48 | }; 49 | 50 | const dashicon = { 51 | type: 'input', 52 | name: 'dashicon', 53 | message: 'The dashicon to make it easier to identify your block (optional):', 54 | validate( input ) { 55 | if ( ! /^[a-z][a-z0-9\-]*$/.test( input ) ) { 56 | return 'Invalid dashicon name specified. Visit https://developer.wordpress.org/resource/dashicons/ to discover available names.'; 57 | } 58 | 59 | return true; 60 | }, 61 | filter( input ) { 62 | return input && 63 | input.replace( /dashicon(s)?-/, '' ); 64 | }, 65 | }; 66 | 67 | const category = { 68 | type: 'list', 69 | name: 'category', 70 | message: 'The category name to help users browse and discover your block:', 71 | choices: [ 'common', 'embed', 'formatting', 'layout', 'widgets' ], 72 | }; 73 | 74 | module.exports = { 75 | slug, 76 | namespace, 77 | title, 78 | description, 79 | dashicon, 80 | category, 81 | }; 82 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | const program = require( 'commander' ); 5 | const inquirer = require( 'inquirer' ); 6 | 7 | /** 8 | * Internal dependencies 9 | */ 10 | const CLIError = require( './cli-error' ); 11 | const { 12 | error, 13 | info, 14 | } = require( './log' ); 15 | const { name, version } = require( '../package.json' ); 16 | const scaffold = require( './scaffold' ); 17 | const { 18 | getDefaultAnswers, 19 | getPrompts, 20 | } = require( './templates' ); 21 | const { startCase } = require( './utils' ); 22 | 23 | program 24 | .name( name ) 25 | .description( 26 | 'Generates PHP, JS and CSS code for registering a block for a WordPress plugin.\n\n' + 27 | '[slug] is optional. When provided it triggers the quick mode where it is used ' + 28 | 'as the block slug used for its identification, the output location for scaffolded files, ' + 29 | 'and the name of the WordPress plugin. The rest of the configuration is set to all default values.' 30 | ) 31 | .version( version ) 32 | .arguments( '[slug]' ) 33 | .option( '-t, --template ', 'template type name, allowed values: "es5", "esnext"', 'esnext' ) 34 | .action( ( slug, { template } ) => { 35 | try { 36 | const defaultAnswers = getDefaultAnswers( template ); 37 | if ( slug ) { 38 | const title = defaultAnswers.slug === slug ? 39 | defaultAnswers.title : 40 | startCase( slug.replace( /-/, ' ' ) ); 41 | const answers = { 42 | ...defaultAnswers, 43 | slug, 44 | title, 45 | }; 46 | Promise.resolve() 47 | .then( async () => { 48 | await scaffold( template, answers ); 49 | } ); 50 | } else { 51 | inquirer 52 | .prompt( getPrompts( template ) ) 53 | .then( async ( answers ) => { 54 | await scaffold( template, { 55 | ...defaultAnswers, 56 | ...answers, 57 | } ); 58 | } ); 59 | } 60 | } catch ( e ) { 61 | if ( e instanceof CLIError ) { 62 | info( '' ); 63 | error( e.message ); 64 | process.exit( 1 ); 65 | } else { 66 | throw e; 67 | } 68 | } 69 | } ); 70 | 71 | program.on( '--help', function() { 72 | info( '' ); 73 | info( 'Examples:' ); 74 | info( ` $ ${ name }` ); 75 | info( ` $ ${ name } todo-list` ); 76 | info( ` $ ${ name } --template es5 todo-list` ); 77 | } ); 78 | 79 | program.parse( process.argv ); 80 | -------------------------------------------------------------------------------- /lib/scaffold.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | const { dirname, join } = require( 'path' ); 5 | const makeDir = require( 'make-dir' ); 6 | const { readFile, writeFile } = require( 'fs' ).promises; 7 | const { render } = require( 'mustache' ); 8 | 9 | /** 10 | * Internal dependencies 11 | */ 12 | const initWPScripts = require( './init-wp-scripts' ); 13 | const { code, clear, info, success } = require( './log' ); 14 | const { hasWPScriptsEnabled, getOutputFiles } = require( './templates' ); 15 | 16 | module.exports = async function( templateName, { 17 | namespace, 18 | slug, 19 | title, 20 | description, 21 | dashicon, 22 | category, 23 | author, 24 | license, 25 | version, 26 | } ) { 27 | clear(); 28 | info( `Creating a new WordPress block in "${ slug }" folder.` ); 29 | 30 | const outputFiles = getOutputFiles( templateName ); 31 | const view = { 32 | namespace, 33 | slug, 34 | machineName: `${ namespace }_${ slug }`.replace( /\-/g, '_' ), 35 | title, 36 | description, 37 | dashicon, 38 | category, 39 | version, 40 | author, 41 | license, 42 | textdomain: namespace, 43 | }; 44 | 45 | await Promise.all( 46 | Object.keys( outputFiles ).map( async ( fileName ) => { 47 | const template = await readFile( 48 | join( __dirname, `templates/${ outputFiles[ fileName ] }.mustache` ), 49 | 'utf8' 50 | ); 51 | const filePath = `${ slug }/${ fileName.replace( /\$slug/g, slug ) }`; 52 | await makeDir( dirname( filePath ) ); 53 | writeFile( 54 | filePath, 55 | render( template, view ) 56 | ); 57 | } ) 58 | ); 59 | 60 | if ( hasWPScriptsEnabled( templateName ) ) { 61 | await initWPScripts( view ); 62 | } 63 | 64 | clear(); 65 | success( `Done: block '${ title }' bootstrapped in "${ slug }" folder.` ); 66 | if ( hasWPScriptsEnabled( templateName ) ) { 67 | info( '' ); 68 | info( 'Inside that directory, you can run several commands:' ); 69 | info( '' ); 70 | code( ' $ npm start' ); 71 | info( ' Starts the build for development.' ); 72 | info( '' ); 73 | code( ' $ npm run build' ); 74 | info( ' Builds the code for production.' ); 75 | info( '' ); 76 | code( ' $ npm run lint:css' ); 77 | info( ' Lints CSS files.' ); 78 | info( '' ); 79 | code( ' $ npm run lint:js' ); 80 | info( ' Lints JavaScript files.' ); 81 | info( '' ); 82 | info( 'You can start by typing:' ); 83 | info( '' ); 84 | code( ` $ cd ${ slug }` ); 85 | code( ` $ npm start` ); 86 | } 87 | info( '' ); 88 | info( 'Code is Poetry' ); 89 | }; 90 | -------------------------------------------------------------------------------- /lib/templates.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Internal dependencies 3 | */ 4 | const CliError = require( './cli-error' ); 5 | const prompts = require( './prompts' ); 6 | 7 | const namespace = 'create-wordpress-block'; 8 | const dashicon = 'smiley'; 9 | const category = 'widgets'; 10 | const author = 'The WordPress Contributors'; 11 | const license = 'GPL-2.0-or-later'; 12 | const version = '0.1.0'; 13 | 14 | const templates = { 15 | es5: { 16 | defaultAnswers: { 17 | namespace, 18 | slug: 'es5-example', 19 | title: 'ES5 Example', 20 | description: 'Example block written with ES5 standard and no JSX – no build step required.', 21 | dashicon, 22 | category, 23 | author, 24 | license, 25 | version, 26 | }, 27 | outputFiles: { 28 | '.editorconfig': 'editorconfig', 29 | 'editor.css': 'editor-css', 30 | 'index.js': 'es5/index-js', 31 | '$slug.php': 'es5/plugin-php', 32 | 'style.css': 'style-css', 33 | }, 34 | }, 35 | esnext: { 36 | defaultAnswers: { 37 | namespace, 38 | slug: 'esnext-example', 39 | title: 'ESNext Example', 40 | description: 'Example block written with ESNext standard and JSX support – build step required.', 41 | dashicon, 42 | category, 43 | author, 44 | license, 45 | version, 46 | }, 47 | outputFiles: { 48 | '.editorconfig': 'editorconfig', 49 | '.gitignore': 'gitignore', 50 | 'editor.css': 'editor-css', 51 | 'src/index.js': 'esnext/index-js', 52 | '$slug.php': 'esnext/plugin-php', 53 | 'style.css': 'style-css', 54 | }, 55 | wpScriptsEnabled: true, 56 | }, 57 | }; 58 | 59 | const getTemplate = ( templateName ) => { 60 | if ( ! templates[ templateName ] ) { 61 | throw new CliError( 62 | 'Invalid template type name.' + 63 | ` Allowed values: ${ Object.keys( templates ).join( ', ' ) }.` 64 | ); 65 | } 66 | return templates[ templateName ]; 67 | }; 68 | 69 | const getDefaultAnswers = ( templateName ) => { 70 | return getTemplate( templateName ).defaultAnswers; 71 | }; 72 | 73 | const getOutputFiles = ( templateName ) => { 74 | return getTemplate( templateName ).outputFiles; 75 | }; 76 | 77 | const getPrompts = ( templateName ) => { 78 | const defaultAnswers = getDefaultAnswers( templateName ); 79 | return Object.keys( prompts ).map( ( promptName ) => { 80 | return { 81 | ...prompts[ promptName ], 82 | default: defaultAnswers[ promptName ], 83 | }; 84 | } ); 85 | }; 86 | 87 | const hasWPScriptsEnabled = ( templateName ) => { 88 | return getTemplate( templateName ).wpScriptsEnabled || false; 89 | }; 90 | 91 | module.exports = { 92 | getDefaultAnswers, 93 | getOutputFiles, 94 | getPrompts, 95 | hasWPScriptsEnabled, 96 | }; 97 | -------------------------------------------------------------------------------- /lib/templates/esnext/index-js.mustache: -------------------------------------------------------------------------------- 1 | /** 2 | * Registers a new block provided a unique name and an object defining its behavior. 3 | * 4 | * @see https://developer.wordpress.org/block-editor/developers/block-api/#registering-a-block 5 | */ 6 | import { registerBlockType } from '@wordpress/blocks'; 7 | 8 | /** 9 | * Retrieves the translation of text. 10 | * 11 | * @see https://developer.wordpress.org/block-editor/packages/packages-i18n/ 12 | */ 13 | import { __ } from '@wordpress/i18n'; 14 | 15 | /** 16 | * Every block starts by registering a new block type definition. 17 | * 18 | * @see https://developer.wordpress.org/block-editor/developers/block-api/#registering-a-block 19 | */ 20 | registerBlockType( '{{namespace}}/{{slug}}', { 21 | /** 22 | * This is the display title for your block, which can be translated with `i18n` functions. 23 | * The block inserter will show this name. 24 | */ 25 | title: __( 26 | '{{title}}', 27 | '{{textdomain}}' 28 | ), 29 | 30 | {{#description}} 31 | /** 32 | * This is a short description for your block, can be translated with `i18n` functions. 33 | * It will be shown in the Block Tab in the Settings Sidebar. 34 | */ 35 | description: __( 36 | '{{description}}', 37 | '{{textdomain}}' 38 | ), 39 | 40 | {{/description}} 41 | /** 42 | * Blocks are grouped into categories to help users browse and discover them. 43 | * The categories provided by core are `common`, `embed`, `formatting`, `layout` and `widgets`. 44 | */ 45 | category: '{{category}}', 46 | 47 | {{#dashicon}} 48 | /** 49 | * An icon property should be specified to make it easier to identify a block. 50 | * These can be any of WordPress’ Dashicons, or a custom svg element. 51 | */ 52 | icon: '{{dashicon}}', 53 | 54 | {{/dashicon}} 55 | /** 56 | * Optional block extended support features. 57 | */ 58 | supports: { 59 | // Removes support for an HTML mode. 60 | html: false, 61 | }, 62 | 63 | /** 64 | * The edit function describes the structure of your block in the context of the editor. 65 | * This represents what the editor will render when the block is used. 66 | * 67 | * @see https://developer.wordpress.org/block-editor/developers/block-api/block-edit-save/#edit 68 | * 69 | * @param {Object} [props] Properties passed from the editor. 70 | * 71 | * @return {WPElement} Element to render. 72 | */ 73 | edit( { className } ) { 74 | return ( 75 |

76 | { __( '{{title}} – hello from the editor!', '{{textdomain}}' ) } 77 |

78 | ); 79 | }, 80 | 81 | /** 82 | * The save function defines the way in which the different attributes should be combined 83 | * into the final markup, which is then serialized by the block editor into `post_content`. 84 | * 85 | * @see https://developer.wordpress.org/block-editor/developers/block-api/block-edit-save/#save 86 | * 87 | * @return {WPElement} Element to render. 88 | */ 89 | save() { 90 | return ( 91 |

92 | { __( '{{title}} – hello from the saved content!', '{{textdomain}}' ) } 93 |

94 | ); 95 | }, 96 | } ); 97 | -------------------------------------------------------------------------------- /lib/templates/es5/index-js.mustache: -------------------------------------------------------------------------------- 1 | ( function( wp ) { 2 | /** 3 | * Registers a new block provided a unique name and an object defining its behavior. 4 | * 5 | * @see https://developer.wordpress.org/block-editor/developers/block-api/#registering-a-block 6 | */ 7 | var registerBlockType = wp.blocks.registerBlockType; 8 | 9 | /** 10 | * Returns a new element of given type. Element is an abstraction layer atop React. 11 | * 12 | * @see https://developer.wordpress.org/block-editor/packages/packages-element/ 13 | */ 14 | var el = wp.element.createElement; 15 | 16 | /** 17 | * Retrieves the translation of text. 18 | * 19 | * @see https://developer.wordpress.org/block-editor/packages/packages-i18n/ 20 | */ 21 | var __ = wp.i18n.__; 22 | 23 | /** 24 | * Every block starts by registering a new block type definition. 25 | * 26 | * @see https://developer.wordpress.org/block-editor/developers/block-api/#registering-a-block 27 | */ 28 | registerBlockType( '{{namespace}}/{{slug}}', { 29 | /** 30 | * This is the display title for your block, which can be translated with `i18n` functions. 31 | * The block inserter will show this name. 32 | */ 33 | title: __( 34 | '{{title}}', 35 | '{{textdomain}}' 36 | ), 37 | 38 | {{#description}} 39 | /** 40 | * This is a short description for your block, can be translated with `i18n` functions. 41 | * It will be shown in the Block Tab in the Settings Sidebar. 42 | */ 43 | description: __( 44 | '{{description}}', 45 | '{{textdomain}}' 46 | ), 47 | 48 | {{/description}} 49 | /** 50 | * Blocks are grouped into categories to help users browse and discover them. 51 | * The categories provided by core are `common`, `embed`, `formatting`, `layout` and `widgets`. 52 | */ 53 | category: '{{category}}', 54 | 55 | {{#dashicon}} 56 | /** 57 | * An icon property should be specified to make it easier to identify a block. 58 | * These can be any of WordPress’ Dashicons, or a custom svg element. 59 | */ 60 | icon: '{{dashicon}}', 61 | 62 | {{/dashicon}} 63 | /** 64 | * Optional block extended support features. 65 | */ 66 | supports: { 67 | // Removes support for an HTML mode. 68 | html: false, 69 | }, 70 | 71 | /** 72 | * The edit function describes the structure of your block in the context of the editor. 73 | * This represents what the editor will render when the block is used. 74 | * 75 | * @see https://developer.wordpress.org/block-editor/developers/block-api/block-edit-save/#edit 76 | * 77 | * @param {Object} [props] Properties passed from the editor. 78 | * 79 | * @return {WPElement} Element to render. 80 | */ 81 | edit: function( props ) { 82 | return el( 83 | 'p', 84 | { className: props.className }, 85 | __( '{{title}} – hello from the editor!', '{{textdomain}}' ) 86 | ); 87 | }, 88 | 89 | /** 90 | * The save function defines the way in which the different attributes should be combined 91 | * into the final markup, which is then serialized by the block editor into `post_content`. 92 | * 93 | * @see https://developer.wordpress.org/block-editor/developers/block-api/block-edit-save/#save 94 | * 95 | * @return {WPElement} Element to render. 96 | */ 97 | save: function() { 98 | return el( 99 | 'p', 100 | {}, 101 | __( '{{title}} – hello from the saved content!', '{{textdomain}}' ) 102 | ); 103 | }, 104 | } ); 105 | }( 106 | window.wp 107 | ) ); 108 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This repository has moved! 2 | 3 | It is now part of the [WordPress/gutenberg](https://github.com/wordpress/gutenberg/) repository. 4 | 5 | The published npm package is now marked as deprecated. The usage has changed to: 6 | 7 | ```bash 8 | $ npm init @wordpress/block [options] [slug] 9 | ``` 10 | 11 | # Create WordPress Block 12 | Create WordPress Block is an officially supported way to create blocks for registering 13 | a block for a WordPress plugin. It offers a modern build setup with no 14 | configuration. It generates PHP, JS, CSS code, and everything else you need to start the project. 15 | 16 | It is largely inspired by [create-react-app](https://create-react-app.dev/docs/getting-started). 17 | Major kudos to [@gaearon](https://github.com/gaearon), the whole Facebook team, 18 | and the React community. 19 | 20 | ## Description 21 | 22 | Blocks are the fundamental element of the WordPress block editor. They are the 23 | primary way in which plugins and themes can register their own functionality and 24 | extend the capabilities of the editor. 25 | 26 | Visit the [Gutenberg handbook](https://developer.wordpress.org/block-editor/developers/block-api/block-registration/) 27 | to learn more about Block API. 28 | 29 | ## Quick start 30 | 31 | You just need to provide the `slug` which is the target location for scaffolded 32 | files and the internal block name. 33 | ```bash 34 | $ npm init wordpress-block todo-list 35 | $ cd todo-list 36 | $ npm start 37 | ``` 38 | 39 | _(requires `node` version `10.0.0` or above, and `npm` version `6.1.0` or above)_ 40 | 41 | ![Create example block – demo](./assets/create-wordpress-block.gif) 42 | 43 | You don’t need to install or configure tools like [webpack](https://webpack.js.org), 44 | [Babel](https://babeljs.io) or [ESLint](https://eslint.org) yourself. They are 45 | preconfigured and hidden so that you can focus on the code. 46 | 47 | ## Usage 48 | 49 | The following command generates PHP, JS and CSS code for registering a block. 50 | 51 | ```bash 52 | $ npm init wordpress-block [options] [slug] 53 | ``` 54 | 55 | `[slug]` is optional. When provided it triggers the quick mode where it is used 56 | as the block slug used for its identification, the output location for scaffolded files, 57 | and the name of the WordPress plugin. The rest of the configuration is set to all 58 | default values. 59 | 60 | Options: 61 | ```bash 62 | -t, --template template type name, allowed values: "es5", "esnext" (default: "esnext") 63 | -V, --version output the version number 64 | -h, --help output usage information 65 | ``` 66 | 67 | More examples: 68 | 69 | 1. Interactive mode - it gives a chance to customize a few most important options 70 | before the code gets generated. 71 | ```bash 72 | $ npm init wordpress-block 73 | ``` 74 | 2. ES5 template – it is also possible to pick ES5 template when you don't want 75 | to deal with a build step (`npm start`) which enables ESNext and JSX support. 76 | ```bash 77 | $ npm init wordpress-block --template es5 78 | ``` 79 | 3. Help – you need to use `npx` to output usage information. 80 | ```bash 81 | $ npx create-wordpress-block --help 82 | ``` 83 | 84 | When you scaffold a block, you must provide at least a `slug` name, the `namespace` 85 | which usually corresponds to either the `theme` or `plugin` name, and the `category`. 86 | In most cases, we recommended pairing blocks with plugins rather than themes, 87 | because only using plugin ensures that all blocks still work when your theme changes. 88 | 89 | ## Available Commands 90 | 91 | Inside that bootstrapped directory _(it doesn't apply to `es5` template)_, you 92 | can run several commands: 93 | 94 | ```bash 95 | $ npm start 96 | ``` 97 | Starts the build for development. 98 | 99 | ```bash 100 | $ npm run build 101 | ``` 102 | Builds the code for production. 103 | 104 | ```bash 105 | $ npm run lint:css 106 | ``` 107 | Lints CSS files. 108 | 109 | ```bash 110 | $ npm run lint:js 111 | ``` 112 | Lints JavaScript files. 113 | 114 | ## WP-CLI 115 | 116 | Another way of making a developer’s life easier is to use [WP-CLI](https://wp-cli.org), 117 | which provides a command-line interface for many actions you might perform on 118 | the WordPress instance. One of the commands `wp scaffold block` was used as 119 | the baseline for this tool and ES5 template in particular. 120 | 121 |

Code is Poetry.

122 | --------------------------------------------------------------------------------