Getting blocks running on the front end is, or should be, mainly a matter of updating Webpack to create the script to run on the front end, then enqueuing it so it does. However, I’ve always found it requires some fixing of things that don’t work quite right together, that I’ve misnamed or otherwise mismatched so I’ll go through those changes too.
Updating Webpack to build a front end script
Let me start with a caveat here: this may not be the best way to do it. It is definitely not the only way to do it. However. It works! I don’t know the internals of wp-scripts and it’s not super well documented . (This is one of the reasons it would be useful to be able to eject from wp-scripts – I’d love to have a dummy project and be able to tweak a setting and see what happens. The default Webpack config is here and I suppose I could copy and paste.)
The short of it
The short of it: I’ve put the out of the box editor config in a variable and then added in a front end config and exported both.
...
const adminConfig = {
...
};
const frontEndConfig = {
...
};
module.exports = [ adminConfig, frontEndConfig ];
You can see the changes I’ve made to webpack.config.js
here.
The long of it
The long of it is explaining the front end config. 😊
frontEndConfig
looks like this:
const frontEndConfig = {
...defaultConfig,
entry: './src/front-index.js',
output: {
...defaultConfig.output,
path: path.resolve( process.cwd(), 'dist' ), // For some reason, npm run build removes the front bundle from /build so I'm explicitly setting the path here so it doesn't randomly change.
filename: 'front.bundle.js',
},
stats: {
maxModules: Infinity, // Way too much information but I like it.
exclude: undefined,
},
plugins: [
// including ...defaultConfig.plugins here causes it not to build the bundle.
new BrowserSyncPlugin(
{
host: 'localhost',
port: 3124,
proxy: frontEndEnv,
open: true,
files: [ "dist/front.bundle.js", "build/*.css" ],
},
{
injectCss: true,
reload: false,
}
),
new DependencyExtractionWebpackPlugin(), // This creates the assets php file.
],
};
It uses the spread operator to add the default config as a starting point. There were a few things I had to change and I’ve added in stats for debugging purposes, you can remove that bit if you don’t like it.
entry
I’ve made a new entry point for the front end, front-index.js:
/**
* The entry point for building the front end.
* This is working around the file structure already set up for the block editor blocks.
*/
import React from 'react';
import ReactDOM from 'react-dom';
import Game from './blocks/click-game/components/game.js';
const elementsToRender = document.getElementsByClassName( 'porchy' );
const blockNameToComponents = {
'porchy/clickgame': Game,
};
for ( let element of elementsToRender ) {
const attrString = element.getAttribute( 'data-attr' ); // The JSON string with the attributes.
const blockName = element.getAttribute( 'data-block' ); // The block's name.
const attr = JSON.parse( attrString ); // Putting the attributes in a JSON object.
const ComponentToUse = blockNameToComponents[ blockName ]; // Which component should this use?
ReactDOM.render( <ComponentToUse attr={ attr } />, element ); // Render the component.
}
It picks up everything with a class of “porchy” (this really should be way more specific), then iterates over the elements, going through this process for each one:
- grabs the
data-attr
of the element to get the block’s attributes as a string - grabs the the
data-block
of the element to get the blockname - parses the attributes string into JSON
- figures out which component to use by the blockname (additional blocks should be added to
blockNameToComponents
object with the full blockname as the key and the component as the value – remember to import the component) - renders the component into the element
output
For whatever reason wp-scripts helpfully cleans the build
folder of anything it thinks is not supposed to be in there. So I’m using dist
as the front end folder. Whatever. I’m bringing in the default config and then explicitly setting the folder and filename even though I found if I left "output"
blank it worked the same way. (I’ve learned not to trust default behaviour with WordPress plugins and the block editor seems particularly unstable this way.) This is what is enqueued on the front end.
stats
I like this, I love seeing things scroll down the terminal, but it’s not at all necessary.
plugins
Importing the default config here causes the front end build to fail so I’ve included what I want:
- Browsersync plugin for front end syncing. It uses an update
frontEndEnv
variable – add this tolocal.json
, mine ends up looking like the following – and you’ll also need to set the variable in your Webpack config.
{
"adminURL": "https://idontthink.test/wp-admin/post.php?post=154&action=edit",
"frontEndURL": "https://idontthink.test/test-post/"
}
DependencyExtractionWebpackPlugin
(this needs to be explicitly required at the top of the file or it fails). This is the plugin which builds the php assets file which will end up in the output folder set previously,dist
.
Including the built script on the front end
Once I had the script, I needed to include it on the front end. This is done the same way as including it in the editor, albeit on a different hook (there might be a better hook than init
😬). Here’s what that looks like:
function front_end_scripts() {
wp_enqueue_script(
'porchy-mjj-block-example-frontend',
plugins_url( '/dist/front.bundle.js', __FILE__ ),
front_end_asset_file( 'front.bundle', 'dependencies' ),
front_end_asset_file( 'front.bundle', 'version' ),
true
);
}
add_action( 'init', __NAMESPACE__ . '\front_end_scripts' );
/**
* Get asset file.
*
* @param string $handle Asset handle to reference.
* @param string $key What do we want to return: version or dependencies.
*/
function front_end_asset_file( $handle, $key ) {
$default_asset_file = array(
'dependencies' => array(),
'version' => '1.0',
);
$asset_filepath = plugin_dir_path( __FILE__ ) . "/dist/{$handle}.asset.php";
$asset_file = file_exists( $asset_filepath ) ? include $asset_filepath : $default_asset_file;
if ( 'version' === $key ) {
return $asset_file['version'];
}
if ( 'dependencies' === $key ) {
return $asset_file['dependencies'];
}
}
You may notice in the diff that there is additional bits to make it work on Gatsby, ie here where you’re reading this (most likely). I’m not going to go over that now, this is too long already. I’ll try and post on that later.
Issues fixed
I am incapable of keeping names straight the first time around and there were a number of issues I had to fix to make it all work happily together.
- In game.js I had forgotten the attributes on the front end would be coming through as
props.attr
so updated that and the way I passed through the attributes on the editor block to match. - I explicitly set the defaults for the min time and max time of a game so those would work on the front end.
- I got the className wrong of the block (sigh) and fixed that.
- I moved the admin index.js entry file from the blocks directory. This is my preference. I would have renamed it decided against it. Writing this, I noticed my
package.json
has the wrong filename, argh, will fix that later. It still builds.
Delicateness and future changes
The way I’m picking up the elements in front-index.js
isn’t robust at all and could easily break if someone else uses the same class of the elements picked up (in this case it’s “porchy”). Maybe next time I’ll add the version number to the class for something like porchy-ab7fb51d103d94d2f7f578f1674385ed
, set the version number in a localised variable and then pick it up to build the class name in front-index.js
.
Let me know what I’ve missed here or anything unclear.
Leave a Reply