Learning Gutenberg: Setting up a Custom webpack Config
Publikováno: 24.5.2018
Gutenberg introduces the modern JavaScript stack into the WordPress ecosystem, which means some new tooling should be learned. Although tools like create-guten-block are incredibly useful, it’s also handy to know what’s going on under the hood.
Article Series:
- Series Introduction
- What is Gutenberg, Anyway?
- A Primer with create-guten-block
- Modern JavaScript Syntax
- React 101
- Setting up a Custom webpack (This Post)
- A Custom "Card" Block (Coming Soon!)
The files we will be configuring here should be familiar from what we covered…
The post Learning Gutenberg: Setting up a Custom webpack Config appeared first on CSS-Tricks.
Gutenberg introduces the modern JavaScript stack into the WordPress ecosystem, which means some new tooling should be learned. Although tools like create-guten-block are incredibly useful, it’s also handy to know what’s going on under the hood.
Article Series:
- Series Introduction
- What is Gutenberg, Anyway?
- A Primer with create-guten-block
- Modern JavaScript Syntax
- React 101
- Setting up a Custom webpack (This Post)
- A Custom "Card" Block (Coming Soon!)
Let’s jump in!
Getting started
Webpack takes the small, modular aspects of your front-end codebase and smooshes them down into one efficient file. It’s highly extendable and configurable and works as the beating heart of some of the most popular products and projects on the web. It’s very much a JavaScript tool, although it can be used for pretty much whatever you want. For this tutorial, it’s sole focus is JavaScript though.
What we’re going to get webpack doing is watch for our changes on some custom block files and compile them with Babel to generate JavaScript files that can be read by most browsers. It’ll also merge any dependencies that we import.
Setting up a plugin
Hopefully you still have a local WordPress instance running from our primer in Part 2, but if not, you’ll need to have one installed to continue with what we’re about to do. In that install, navigate to wp-content/plugins
and create a fresh directory called card-block
(spoiler alert: we’re going to make a card block... who doesn’t like cards?).
Then, inside card-block
, create a file called card-block.php
. This will be the equivalent to plugin.php
from create-guten-block. Next, drop in this chunk of comments to tell WordPress to acknowledge this directory as a plugin and display it in the Plugins page of the Dashboard:
<?php
/*
Plugin Name: Card Block
*/
Don’t forget the opening PHP tag, but you can leave the closing one off since we’ll be adding more to this file soon enough.
WordPress looks for these comments to register a plugin in the same way it looks for comments at the top of style.css in a theme. This is an abbreviated version of what you’ll find at the top of other plugins’ main files. If you were planning to release it on the WordPress plugin repository, you’d want to add a description and version number as well as license and author information.
Getting started with webpack
The first thing we’re going to do is initialize npm. Run the following at the root of your plugin folder (wp-content/plugins/card-block
):
npm init
This will ask you a few questions about your project and ultimately generate you a package.json
file, which lists dependencies and stores core information about your project.
Next, let’s install webpack:
npm install webpack --save-dev
You might have noticed that we’re installing webpack locally to our project. This is a good practice to get into with crucial packages that are prone to large, breaking changes. It also means you and your team are all singing off the same song sheet.
Then run this:
npm install extract-text-webpack-plugin@next --save-dev
Then these Sass and CSS dependencies:
npm install node-sass sass-loader css-loader --save-dev
Now, NPX to allow us to use our local dependencies instead of any global ones:
npm install npx -g
Lastly, run this:
npm install webpack-cli --save-dev
That will install the webpack CLI for you.
Now that we have this installed, we should create our config file. Still in the root of your plugin, create a file called webpack.config.js
and open that file so we can get coding.
With this webpack file, we’re going to be working with traditional ES5 code for maximum compatibility, because it runs with Node JS. You can use ES6 and Babel, but we’ll keep things as simple as possible for this tutorial.
Alight, let’s add some constants and imports. Add the following, right at the top of your webpack.config.js
file:
var ExtractText = require('extract-text-webpack-plugin');
var debug = process.env.NODE_ENV !== 'production';
var webpack = require('webpack');
The debug
var is declaring whether or not we’re in debug
mode. This is our default mode, but it can be overridden by prepending our webpack commands with NODE_ENV=production
. The debug
boolean flag will determine whether webpack generates sourcemaps and minifies the code.
As you can see, we’re requiring some dependencies. We know that webpack is needed, so we’ll skip that. Let’s instead focus our attention on ExtractText
. Essentially, ExtractText
enables us to pull in files other than JavaScript into the mix. We’re going to need this for our Sass files. By default, webpack assumes everything is JavaScript, so ExtractText
kind of *translates* the other types of files.
Let’s add some config now. Add the following after the webpack
definition:
var extractEditorSCSS = new ExtractText({
filename: './blocks.editor.build.css'
});
var extractBlockSCSS = new ExtractText({
filename: './blocks.style.build.css'
});
What we’ve done there is instantiate two instances of ExtractText
by passing a config object. All that we’ve set is the output of our two block stylesheets. We’ll come to these in the next series, but right now all you need to know is that this is the first step in getting our Sass compiling.
OK, after that last bit of code, add the following:
var plugins = [ extractEditorSCSS, extractBlockSCSS ];
Here we’ve got two arrays of plugins. Our ExtractText
instances live in the core plugins set and we’ve got a couple of optimization plugins that are only smooshed into the core plugins set if we’re not in debug
mode. We’re running that logic right at the end.
Next up, add this SCSS config object:
var scssConfig = {
use: [
{
loader: 'css-loader'
},
{
loader: 'sass-loader',
options: {
outputStyle: 'compressed'
}
}
]
};
This object is going to tell our webpack instance how to behave when it comes across scss
files. We’ve got it in a config object to keep things as DRY as possible.
Last up, the meat and taters of the config:
module.exports = {
context: __dirname,
devtool: debug ? 'inline-sourcemap' : null,
mode: debug ? 'development' : 'production',
entry: './blocks/src/blocks.js',
output: {
path: __dirname + '/blocks/dist/',
filename: 'blocks.build.js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader'
}
]
},
{
test: /editor\.scss$/,
exclude: /node_modules/,
use: extractEditorSCSS.extract(scssConfig)
},
{
test: /style\.scss$/,
exclude: /node_modules/,
use: extractBlockSCSS.extract(scssConfig)
}
]
},
plugins: plugins
};
That’s our entire config, so let’s break it down.
The script starts with module.exports
which is essentially saying, "when you import or require me, this is what you’re getting." We can determine what we expose to whatever imports our code, which means we could run code above module.exports
that do some heavy calculations, for example.
Next, let’s look at some of these following properties:
context
is our base where paths will resolve from. We’ve passed__dirname
, which is the current working directory.devtool
is where we define what sort of sourcemap we may or may not want. If we’re not indebug
mode, we passnull
with a ternary operator.entry
is where we tell webpack to start its journey of pack-ery. In our instance, this is the path to ourblocks.js
file.output
is what it says on the tin. We’re passing an object that defines the output path and the filename that we want to call it.
Next is module
, which we’ll got into a touch more detail. The module
section can contain multiple rules. In our instance, the only rule
we have is looking for JavaScript and SCSS files. It’s doing this by searching with a regular expression that’s defined by the test
property. The end-goal of the rule is to find the right sort of files and pass them into a loader, which is babel-loader
in our case. As we learned in a previous tutorial in this series, Babel is what converts our modern ES6 code into more supported ES5 code.
Lastly is our plugins section. Here, we pass our array of plugin instances. In our project, we have plugins that minify code, remove duplicate code and one that reduces the length of commonly used IDs. This is all to make sure our production code is optimized.
For reference, this is how your full config file should look:
var ExtractText = require('extract-text-webpack-plugin');
var debug = process.env.NODE_ENV !== 'production';
var webpack = require('webpack');
var extractEditorSCSS = new ExtractText({
filename: './blocks.editor.build.css'
});
var extractBlockSCSS = new ExtractText({
filename: './blocks.style.build.css'
});
var plugins = [extractEditorSCSS, extractBlockSCSS];
var scssConfig = {
use: [
{
loader: 'css-loader'
},
{
loader: 'sass-loader',
options: {
outputStyle: 'compressed'
}
}
]
};
module.exports = {
context: __dirname,
devtool: debug ? 'inline-sourcemap' : null,
mode: debug ? 'development' : 'production',
entry: './blocks/src/blocks.js',
output: {
path: __dirname + '/blocks/dist/',
filename: 'blocks.build.js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader'
}
]
},
{
test: /editor\.scss$/,
exclude: /node_modules/,
use: extractEditorSCSS.extract(scssConfig)
},
{
test: /style\.scss$/,
exclude: /node_modules/,
use: extractBlockSCSS.extract(scssConfig)
}
]
},
plugins: plugins
};
Registering our block
Back in card-block.php
, our main task now is to enqueue the JavaScript and CSS files we will be building with webpack. In a theme, we would do this with call wp_enqueue_script
and wp_enqueue_style
inside an action added to wp_enqueue_scripts
. We essentially do the same thing here, except instead we enqueue the scripts and styles with a function specific to blocks.
Drop this code below the opening comment in card-block.php
:
function my_register_gutenberg_card_block() {
// Register our block script with WordPress
wp_register_script(
'gutenberg-card-block',
plugins_url('/blocks/dist/blocks.build.js', __FILE__),
array('wp-blocks', 'wp-element')
);
// Register our block's base CSS
wp_register_style(
'gutenberg-card-block-style',
plugins_url( '/blocks/dist/blocks.style.build.css', __FILE__ ),
array( 'wp-blocks' )
);
// Register our block's editor-specific CSS
wp_register_style(
'gutenberg-card-block-edit-style',
plugins_url('/blocks/dist/blocks.editor.build.css', __FILE__),
array( 'wp-edit-blocks' )
);
// Enqueue the script in the editor
register_block_type('card-block/main', array(
'editor_script' => 'gutenberg-card-block',
'editor_style' => 'gutenberg-card-block-edit-style',
'style' => 'gutenberg-card-block-edit-style'
));
}
add_action('init', 'my_register_gutenberg_card_block');
As the above comments indicate, we first register our script with WordPress using the handle gutenberg-card-block
with two dependencies: wp-blocks
and wp-elements
. This function only registers a script, it does not enqueue it. We do something similar for out edit and main stylesheets.
Our final function, register_block_type
, does the enqueuing for us. It also gives the block a name, card-block/main
, which identifies this block as the main
block within the namespace card-block
, then identifies the script and styles we just registered as the main editor script, editor stylesheet, and primary stylesheet for the block.
If you are familiar with theme development, you’ve probably used get_template_directory()
to handle file paths in hooks like the ones above. For plugin development, we use the function plugins_url()
which does pretty much the same thing, except instead of concatenating a path like this: get_template_directory() . 'https://cdn.css-tricks.com/script.js'
, plugins_url()
accepts a string path as a parameter and does the concatenation for us. The second parameter, _ FILE _
, is one of PHP’s magic constants that equates to the full file path of the current file.
Getting Babel running
Babel turns our ES6 code into better-supported ES5 code, so we need to install some dependencies. In the root of your plugin (wp-content/plugins/card-block
), run the following:
npm install babel-core babel-loader babel-plugin-add-module-exports babel-plugin-transform-react-jsx babel-preset-env --save-dev
That big ol’ npm install adds all the Babel dependencies. Now we can add our .babelrc
file which stores some settings for us. It prevents us from having to repeat them over and over in the command line.
While still in your theme folder, add the following file: .babelrc
.
Now open it up and paste the following:
{
"presets": ["env"],
"plugins": [
["transform-react-jsx", {
"pragma": "wp.element.createElement"
}]
]
}
So, what we’ve got there are two things:
"presets": ["env"]
is basically magic. It automatically determines which ES features to use to generate your ES5 code. We used to have to add different presets for all the different ES versions (e.g. ES2015), but it’s been simplified.
In the plugins, you’ll notice that there’s a React JSX transformer. That’s sorting out our JSX and turning it into proper JavaScript, but what we’re doing is telling it to generate WordPress elements, rather than React elements, which JSX is more commonly associated with.
Generate stub files
The last thing we’re going to do is generate some stub files and test that our webpack and WordPress setup is all good.
Go into your plugin directory and create a folder called blocks
and, within that, create two folders: one called src
and one called dist
.
Inside the src
folder, create the following files. We’ve added the paths, too, so you put them in the right place:
blocks.js
common.scss
block/block.js
block/editor.scss
block/style.scss
OK, so now we’ve generated the minimum amount of things, let’s run webpack. Open up your terminal and move into your current plugin folder—then we can run the following, which will fire-up webpack:
npx webpack
Pretty dang straightforward, huh? If you go ahead and look in your dist
folder, you should see some compiled goodies in there!
Wrapping up
A lot of setup has been done, but all of our ducks are in a row. We’ve set up webpack, Babel and WordPress to all work as a team to build out or custom Gutenberg block (and future blocks). Hopefully now you feel more comfortable working with webpack and feel like you could dive in and make customizations to fit your projects.
Article Series:
- Series Introduction
- What is Gutenberg, Anyway?
- A Primer with create-guten-block
- Modern JavaScript Syntax
- React 101
- Setting up a Custom webpack (This Post)
- A Custom "Card" Block (Coming Soon!)
The post Learning Gutenberg: Setting up a Custom webpack Config appeared first on CSS-Tricks.