Replacing create-guten-block with a custom Webpack

If you’re reading this, I expect you’ve either read my table block tutorial or built your own blocks with create-guten-block. If you haven’t dealt with JavaScript that has to be compiled – or transpiled – you may want to go through a create-guten-block tutorial as it makes things easier to set up in the beginning.

But like many premade starter kits, create-guten-block comes with a massive toolkit where you may only need a flathead and one hex key. Also, with this big of a starter kit, dependencies and security vulnerabilities creep in. I’ve always been a fan of simplifying whenever possible, so I wanted to share what I’ve learned so far about setting up a custom webpack.

WordPress Scripts

When I first set out to leave create-guten-block behind, I tried WordPress’s own “zero config” scripts. As of yesterday, in early August 2019, they were at version 3.3.0. Today I see they’ve already updated to 3.4.0. The reason I decided not to stick with them? They don’t automatically compile SASS into the two CSS files needed for a WP block. So already, “zero config” is a myth. If I have to install my own modules anyway, I’ll leave behind the bloat of Core scripts – and the security vulnerabilities, because they crept in there, too.

A truly custom Webpack

Now we’re past the preamble, and we can actually set up Webpack. Before you proceed, make sure you have Node installed. Then, fire up your command line and create a new folder. You may want to do this inside of a local WordPress install. I’m still dealing with REST API issues on my local installs, so personally, I’m setting this up in a random folder and then uploading to a staging site.

Installing npm packages

In the command prompt, inside the new folder, run

npm init

Next let’s install the bare-minimum packages we need. First, let’s install webpack:

npm install --save-dev webpack webpack-cli

Next, Babel, which will transpile everything, including JSX:

npm install --save-dev @babel/core babel-loader @babel/preset-env @babel/preset-react

Finally, PostCSS, which transpiles SASS into the two separate CSS files that blocks need:

npm install --save-dev css-loader ignore-emit-webpack-plugin mini-css-extract-plugin node-sass path postcss-loader postcss-preset-env sass-loader

Config files

Now there are a few files we need to set up.

Open up your package.json file (which the npm init command created for you) and replace the scripts section:

  "scripts": {
    "dev": "webpack --mode development",
    "build": "webpack --mode production"
  },

This allows you to run Webpack in two modes – it’ll run one set of commands in development mode, and a separate set in production mode. This is so you can do things like minify your JS and CSS in Production, but keep everything more human-readable on your Development site.

We need to create the next two files. First, create a .babelrc file in the root folder of your plugin. Its contents is fairly short.

{
    "presets": [
        "@babel/preset-env",
        "@babel/preset-react"
    ]
}

The next file, webpack.config.js, also goes in your root plugin folder. It’s a little longer. This file tells Webpack what files to look for and what to do with them – it will use babel-loader for any .js files, and the MiniCssExtractPlugin and css-loader for any .css files.

I offer my sincerest thanks to Jeffrey Carandang’s excellent tutorial, which uses @wordpress/scripts but revealed how to compile the 2 separate .css files using Webpack 4.

const isProduction = process.env.NODE_ENV === 'production';
const mode = isProduction ? 'production' : 'development';
const path = require( 'path' );
const postcssPresetEnv = require( 'postcss-preset-env' );
const MiniCssExtractPlugin = require( 'mini-css-extract-plugin' );
const IgnoreEmitPlugin = require( 'ignore-emit-webpack-plugin' );

const production = process.env.NODE_ENV === '';

module.exports = {
	watch: true,
	mode,
	entry: {
		index: path.resolve( process.cwd(), 'src', 'index.js' ),
		style: path.resolve( process.cwd(), 'src', 'style.scss' ),
		editor: path.resolve( process.cwd(), 'src', 'editor.scss' ),
	},
	output: {
		filename: '[name].js',
		path: path.resolve( process.cwd(), 'build' ),
	},
	optimization: {
		splitChunks: {
			cacheGroups: {
				editor: {
					name: 'editor',
					test: /editor\.(sc|sa|c)ss$/,
					chunks: 'all',
					enforce: true,
				},
				style: {
					name: 'style',
					test: /style\.(sc|sa|c)ss$/,
					chunks: 'all',
					enforce: true,
				},
				default: false,
			},
		},
	},
	module: {
		rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: "babel-loader"
                }
            },
			{
				test: /\.(sc|sa|c)ss$/,
				exclude: /node_modules/,
				use: [
					{
						loader: MiniCssExtractPlugin.loader,
					},
					{
						loader: 'css-loader',
						options: {
							sourceMap: false,
						},
					},
					{
						loader: 'sass-loader',
						options: {
							sourceMap: false,
						},
					},
					{
						loader: 'postcss-loader',
						options: {
							ident: 'postcss',
							plugins: () => [
								postcssPresetEnv( {
									stage: 3,
									features: {
										'custom-media-queries': {
											preserve: false,
										},
										'custom-properties': {
											preserve: true,
										},
										'nesting-rules': true,
									},
								} ),
							],
						},
					},
				],
			},
		],
	},
	plugins: [
		new MiniCssExtractPlugin( {
			filename: '[name].css',
		} ),
		new IgnoreEmitPlugin( [ 'editor.js', 'front.js', 'style.js', 'editor.deps.json', 'index.deps.json' ] ),
	],
};

Note: line 97 tells Webpack to only generate 3 files: editor.css, front.css, and index.js. You can leave this out or tweak which files are removed by editing the array. Also, these options tell Webpack not to generate any CSS sourcemaps – you can change those values to “true” if you want the added files.

Congratulations – you’re done configuring things! But before you can compile anything, you need to create a few files to be compiled.

Bare-minimum project files

For this example, we’re assuming you may want to create more than one block. Create a /src/ folder inside your plugin folder to hold all of the source code. Then we’ll add 3 files right in that folder:

Top-level source files

File 1, /src/index.js:

import './blocks/myblockname';

File 2, /src/editor.scss:

@import './blocks/myblockname/editor.scss';

File 3, /src/style.scss:

@import './blocks/myblockname/style.scss';

Note: these three files, directly inside the /src/ folder, need to import all the other block files. So if you have a second block at /src/mysecondblock, make sure to import its separate JS and SCSS files into these three files so they compile together.

Now create your actual JS file in /src/blocks/myblockname:

const registerBlockType = wp.blocks.registerBlockType;
const { Dashicon } = wp.components;
const el = wp.element.createElement;
 
registerBlockType("my/blockname", {
    title: 'Barebones Block',
    icon: 'screenoptions',
    category: 'common',
    edit: props => {
        return <span>Hi!!</span>;
    },
    save: props => {
        return el('span', null, 'Hi!');
    }
});

(This sample block will output a static span with either one or two exclamation points, depending on whether you’re in the Editor or the front end.)

Individual block files

Your individual block files should be nested inside the /src/ folder. For each block, create one folder. So, for a block called “myblockname,” create a folder called /src/myblockname with three more files:

The individual block JS file at /src/blocks/myblockname/index.js:

const registerBlockType = wp.blocks.registerBlockType;
const { Dashicon } = wp.components;
const el = wp.element.createElement;
 
registerBlockType("my/blockname", {
    title: 'Barebones Block',
    icon: 'screenoptions',
    category: 'common',
    edit: props => {
        return <span>Hi!!</span>;
    },
    save: props => {
        return el('span', null, 'Hi!');
    }
});

(This sample block will output a static span with either one or two exclamation points, depending on whether you’re in the Editor or the front end.)

The editor CSS file in /src/blocks/myblockname/editor.scss:

span.wp-block-myblockname { background:red!important; }

This will turn our block an obnoxious red in the editor, just so we can easily confirm our styles are compiling and applying.

Finally, the front-end CSS file in /src/blocks/myblockname/style.scss:

span.wp-block-myblockname { background:green!important; }

Finally, create your CSS file in /src/blocks/myblockname:

body { font-size: 70px; }

Again, we’re turning the block a striking color on the front end so you can see the front end styles differ from the editor styles.

Finally, if you haven’t already set up a PHP file that registers your code as a WordPress plugin, we’d better get that set up – otherwise your block won’t be visible anywhere. Create a plugin.php file in the root folder of your plugin:

<?php
/*
Plugin Name: My Demo
*/
add_action('init', 'my_register_block');
function my_register_block() {
    // register our JavaScript
    wp_register_script(
        'my-block',
        plugins_url('/build/index.js', __FILE__),
        array('wp-blocks', 'wp-element', 'wp-editor')
    );
    // register our front-end styles
    wp_register_style(
        'my-block-style',
        plugins_url('/build/style.css', __FILE__),
        array('wp-block-library')
    );
    // register our editor styles
    wp_register_style(
        'my-block-edit-style',
        plugins_url('/build/editor.css', __FILE__),
        array('wp-edit-blocks')
    );
    // register our block
    register_block_type('my/blockname', array(
        'editor_script' => 'my-block',
        'editor_style' => 'my-block-edit-style',
        'style' => 'my-block-style'
    ));
}
?>

Now you can actually run your scripts! Back in your root folder in the console, try

npm run dev

For the record, line 11 of webpack.config.js contains the magic line that watches your files and keeps compiling until you tell it to stop by pressing “control C”. It’s a little different than create-guten-block’s “npm start” but with the same result, continuous compiling.

You should now have a JS file and two CSS files inside your /build/ folder. If you upload everything except the /node_modules folder and activate your plugin, you’ll be able to add this static block. At last, you’re ready to build whatever custom block your heart desires.

Leave a Reply

Your email address will not be published. Required fields are marked *