Building Wordpress Gutenberg Blocks with Vue.js
TL/DR: We can implement basic blocks in Vue without much trouble, but we quickly run into limitations if we try to use Wordpress builtins like
First off, we're going to set up the plugin with create-guten-block scaffold.
Go into the `wp-content/plugins` directory and set up a new plugin:
This creates a scaffold with a very basic initial block that lives in
src/block/block.js. Once you
activate it in
your wordpress admin, you'll be able to see it.
For the purpose of this blog post, I'm not going to change much about the functionality of this block, simply convert it to using Vue and Vue Single File Components (SFCs)
In order to use Vue.js components for our core blocks, we'll use a library called vuera that allows us to invoke Vue components within React components.
Then we'll simply replace
save with wrappers that pass along props to our Vue components.
Preparing to Customize Configuration
In order to add Vue to our component, we'll need to do some customization of our build environment. To do this with
create-guten-app we will
eject the build scripts - otherwise they are managed internal to the plugin. We do by running the following from within the plugin directory:
This populates our directory with a set of build scripts in the
scripts directory, and some webpack configuration files in the
Setting up Vue and Vuera
Our next step then is to install Vuera using npm, and set up our build configuration to allow us to use it. We will also need to install Vue, and since we want to use Vue SFCs we need
vue-loader also requires using
vue-template-compiler, so our final NPM install looks like:
To use Vue inside of React, Vuera recommends configuring a babel plugin via
.babelrc, but I was not able to get that to work within the Gutenberg environment. Instead, we'll use an alternative method of wrapping Vue components with a
VueInReact higher order component.
First, to compile our
.vue files, we'll need to configure webpack to add
vue-loader. There are two webpack configurations in
The changes we need to make are:
- Add a Vue Loader Plugin
- Add a vue-loader reference to the rules
This means that to each config file we need to add this to the plugins list:
And add this block to the
Bare Minimum Vue in Gutenberg
We're now ready for our bare minimum "Proof of concept" for putting Vue in Gutenberg. To do this I created a very simple Vue edit component that does nothing but say hello from Vue:
Then, to include this in my block, I need to import it, wrap it with a
VueInReact higher order component from
vuera, and just put it into my template.
Note: Once we wrap our component in
VueInReact, it behaves as a React component, letting us use it inside of JSX or return it anywhere that expects a component.
Props are passed exactly as you would expect, so our
Edit Vue component can reference any Gutenberg specific properties.
Using Gutenberg Builtin Components
Ok, great, so we have our Vue component rendering inside of Gutenberg just fine. But what if we want to use some of Gutenberg's builtin components, like their nice BlockControls?
This should be similarly straightforward to implement using a
ReactInVue wrapper similar to how we embedded Vue inside React.
Lets try adding some block controls to customize alignment. First we set up an attribute for alignment in our block:
Next, in our component we'll utilize the
BlockAlignmentToolbar builtin components from
In our script portion:
And then in our template:
Seems straightforward, but here we run into a bit of a challenge and drawback in the current state of Gutenberg and Vuera.
BlockControls component only is visible when the block is selected - but in our Vue based implementation it never shows up!
After some digging, I traced this to a challenge with the new React Context API.
While we can render React components just fine inside of Vue components with Vuera, many of Gutenberg's builtin components take advantage of React's Context API to change behavior based on whether an element is selected, and Context does not appear to cross the React/Vue border.
In the case of
BlockControls, this means that the element never shows up.
This is a severe limitation for building Gutenberg blocks with Vue - the builtin editor components are a huge part of making the interface consistent across all blocks.
For things like the controls -
InspectorControls, these are positioned absolutely and don't need to live inside of our core block.
We could work around this limitation by placing them outside of our Vue component using pure React, and continue to have just the meat of our component in Vue:
However, for things like
InnerBlocks, this workaround is insufficient, because by its vary nature it is embedded within the block.
At this time, I must conclude that only Gutenberg blocks that don't depend on builtins and don't nest content can be built using Vue.js
The Path Forward
The React Context API is still relatively new, and it is very possible Vuera will be able to implement a way to pass along contexts. I have opened a github issue for this exact thing, and spent a fair amount of time trying to understand how to implement it, but so far haven't been able to figure it out.
If anyone reading this understands the inner workings of the Context API and could help point me in the right direction, I'd greatly appreciate it!
Another possibility, if it turns out that passing Contexts through Vuera isn't possible, is that Gutenberg could implement an alternative way to pass down the selected state of components to subcomponents.
The main Vue component receives an
isSelected prop that is updating properly, and it could pass this down to child components. However, those components right now are not set up to receive this prop, only looking at the Context.
However we get there, I am optimistic that one day we will be able to implement complex Gutenberg Blocks using Vue.js almost as easily as we can in React. We just aren't there quite yet.