I believe the single-biggest barriers to entry for modern Javascript development are the build tools that are necessary to use it effectively. It’s all a black box, breaks with vague error messages, that ultimately lead you down a tunnel of “google and pray”. Unless you’ve spent a lot of time with Webpack, it’s hard to troubleshoot when it inevitably breaks. (and seriously, who freaking has time to learn all the idiosyncrasies of Webpack?)
But even with that, the benefits vastly outpace the drawbacks. When Javascript build tools work, they work beautifully – compiling your scripts down to the smallest size, ensuring that it works on all web browsers, and in the case of WordPress, even automatically enqueuing dependent scripts that are a part of WordPress core.
Thankfully, WordPress has its own Webpack configuration that is built specifically to make developing in WordPress easier. It includes all of the babel configurations, and build tools you need to compile WordPress-specific Javascript as effectively as possible. This amazing, time-saving, godsend utility is an NPM package, and it is called @wordpress/scripts. It’s not perfect, and you’ll still find yourself occasionally scratching your head wondering “what the hell is this error?” but, in the grand scheme of things I’ve found that I’m a lot less frustrated when using this package. It usually just works, and that feels pretty solid.
How to Set Up @wordpress/scripts
Regardless of if you’re working with a theme or a plugin, the process is the same – install the package using NPM. Go to the root directory of your plugin, or theme, and run:
npm install @wordpress/scripts –save-dev
Setting Up package.json
Once installed, you need to add some scripts to your package.json
file. At minimum, you’re going to need build
and start
. Your JSON would look something like this:
{
"name": "plugin_name_replace_me",
"version": "1.0.0",
"description": "Plugin Description Replace Me",
"scripts": {
"build": "wp-scripts build",
"start": "wp-scripts start"
},
"devDependencies": {
"@wordpress/scripts": "15.0"
},
"author": "",
"license": "ISC"
}
The example above is the package.json
used in the Underpin plugin boilerplate, but it would work in just about any plugin or theme. The key part is the scripts
object:
"scripts": {
"build": "wp-scripts build",
"start": "wp-scripts start"
}
Your scripts
object may have additional scripts, and that’s fine. All this does is register command line scripts that can be ran inside the directory that contains this package.json
file.
Create Your Javascript File
Now that your package.json
is all-set, it’s time to create your Javascript file. This file is going to be what @wordpress/scripts uses to create the actual Javascript file that the site will be used by your browser. By default, this script file must be placed inside ./src/index.js
, but this can be customized to be something else if-needed. More on that later.
Using Build and Start
For example, if you run npm run start
it will actually run the start
command inside the @wordpress/scripts package. Conversely, if you run npm run build
, it will run the build
command inside the @wordpress/scripts package. There are a handful of other useful commands, such as linters and translation compilation built into this command, but we’re not going to cover those in this post. You can see them in the @wordpress/scripts documentation.
Both build
and start
will compile your Javascript and your CSS/SCSS into something all web browsers can read, but each one does this a little differently.
Start
npm run start
will create an un-minified version of your script, and include a map file so that you can easily debug your scripts. Without this map file, you’d get vague errors that point to the wrong file because the browser wouldn’t otherwise know where these errors are.
When start
is ran, it will continue to run in the background, and automatically regenerate your scripts and styles when the files are changed. This is perfect for when you’re still building your scripts and styles, since it runs quietly in the background, and automatically regenerates everything for you.
Build
start
‘s priority is to help you while you’re developing, but because of this your script files will be way bigger than you’d want them to be on your live site (we’re talking megabytes people, megabytes!). This is where build
comes in.
npm run build
will create the absolute smallest file sizes it can generate by minifying scripts, and optimizing their contents. This is ideal for when you’re ready to use these themes on a live site (production). Unlike start
, this command will generate your scripts and styles one time, instead of running in the background. The scripts and styles generated by build will look garbled. With variable names minified, and everything compressed to a single line of code, so you don’t want to use this when developing. Instead, you’ll want to run this as a step in your deployment process.
Enqueuing Your Scripts
After you run either build
or watch
, a compiled version of your script will be located inside the build directory. Now you have to instruct WordPress when to add this script to your site’s head
tag. In WordPress this is done by “enqueuing” the script.
Basic WordPress Enqueue Script Example
To enqueue your script, you have to first register it, and then enqueue it at the right time. If you’re using Underpin, that would look like this:
plugin_name()->scripts()->add( 'test', [
'handle' => 'test',
'src' => plugin_name()->js_url() . 'index.js',
'name' => 'test',
'description' => 'The description',
'middlewares' => [
'Underpin_Scripts\Factories\Enqueue_Script'
]
] );
The example above instructs Underpin to:
- Register a new script called
test
, where the JS URL isbuild/index.js
. Underpin’sjs_url()
method defaults to thebuild
in your plugin or theme. - Automatically enqueue this script on the front-end. This is done inside the
Enqueue_Script
middleware. You can learn more about how script middleware works here.
If you wanted to-do this without Underpin, it would look more like this:
wp_register_script( 'test', plugin_dir_url( __FILE__ ) . 'build/index.js' );
add_action( 'wp_enqueue_scripts', function(){
wp_enqueue_script( 'test' );
} );
Automatically Setting Script Dependencies
A key feature of @wordpress/scripts is the dependency-extraction-webpack-plugin. This wonderful, glorious, amazing Webpack loader will automatically detect if your script has imported any WordPress core library, and will remove it from your compiled script. This keeps your script as small as possible, and also ensures that your script doesn’t somehow clash with another plugin’s script.
The problem, however, is that by doing this, your script won’t work unless all of the these imported scripts are loaded before your script is loaded. This means, that you would have to manually enqueue every single script you imported, as well as any script those scripts imported, too. As you can imagine, this would be a nightmare to maintain.
To work around this, dependency-extraction-webpack-plugin will automatically generate a PHP file with an array of all of the dependencies your script. This array can be passed directly to your registered script, and it will automatically enqueue all of the necessary scripts just before your script automatically.
And the best part? This happens when your script is compiled using either build
or watch
, and when it’s all set-up it works seamlessly. You won’t even notice that these scripts are not included in your file.
The generated file will be compiled inside your build directory along with your script. It’s just a matter of using that PHP file when you register your script.
With Underpin, that looks something like this:
plugin_name()->scripts()->add( 'test', [
'handle' => 'test',
'src' => plugin_name()->js_url() . 'index.js',
'name' => 'test',
'description' => 'The description',
'deps' => plugin_name()->dir() . 'build/index.asset.php', // path to dependency file generated by webpack
'middlewares' => [
'Underpin_Scripts\Factories\Enqueue_Script' // Enqueue the script on the front-end
]
] );
The key change is the deps
argument. deps
can be an array of registered script handles, or a path fo a PHP file. If the path to the asset file exists, it will automatically set the dependencies from the file.
Without Underpin, this can also be done but requires a bit of extra logic:
// Check to see if the file exists.
$deps_file = plugin_dir_path(__FILE__) . 'build/index.asset.php';
// Set default fallback to dependencies array
$deps = [];
// If the file can be found, use it to set the dependencies array.
if ( file_exists( $deps_file ) ) {
$deps_file = require( $deps_file );
$deps = $file['dependencies'];
}
// Register script
wp_register_script( 'test', plugin_dir_path( __FILE__ ) . 'build/index.js', $deps );
// Enqueue the script later-on
add_action( 'wp_enqueue_scripts', function(){
wp_enqueue_script( 'test' );
} );
How to Customize Your Webpack Configuration
@wordpress/scripts includes a default Webpack configuration, but this can be overridden with your own webpack configuration. This is done by adding a webpack.config.js
file in the root directory of your plugin or theme. When this is added, @wordpress/scripts will automatically use your Webpack config instead of the one that comes with @wordpress/scripts.
You could add your 100% custom Webpack config to this file, and completely override the configuration that comes with @wordpress/scripts, but at that point, there’s not much point in using @wordpress/scripts. Instead, I find that it makes a lot more sense to extend the config that comes with @wordpress/scripts, and modify the parts you need to modify, instead. The Underpin plugin boilerplate accomplishes this like so:
/**
* WordPress Dependencies
*/
const defaultConfig = require( '@wordpress/scripts/config/webpack.config.js' );
module.exports = {
...defaultConfig,
...{
// Add any overrides to the default here.
}
}
The above example uses Javascript’s spread operator to take the default Webpack configuration included in @wordpress/scripts, and override sections of the configuration with your own customized config. This allows you to change the parts you want, and still use @wordpress/scripts to its fullest potential.
Create Custom Entrypoints With @wordpress/scripts
By default, @wordpress/scripts will allow you to create a single entrypoint file – src/index.js
, but what happens if you want to create multiple Javascript files? Maybe you need to have one script for an admin interface, and another for the front-end of the site. Using the method above, you can override the entry
configuration of your Webpack config, and instruct @wordpress/scripts to create two files, instead.
Here is an example of the webpack.config.js file that is used in our WordPress plugin development course:
/**
* External Dependencies
*/
const path = require( 'path' );
/**
* WordPress Dependencies
*/
const defaultConfig = require( '@wordpress/scripts/config/webpack.config.js' );
module.exports = {
...defaultConfig,
...{
entry: {
admin: path.resolve( process.cwd(), 'src', 'admin.js' ),
"beer-admin": path.resolve( process.cwd(), 'src', 'beer-admin.js' ),
"beer-list": path.resolve( process.cwd(), 'src', 'beer-list.css' ),
},
}
}
Using Javascript’s spread operator, this configuration extends the default @wordpress/scripts configuration object and replace the entry
configuration. Instead of creating the default index.js file, this instructs Webpack to create three files:
- build/admin.js will be created from src/admin.js
- build/beer-admin.js will be created from src/beer-admin.js
- build/beer-list.css will be created from src/beer-list.css
From there, you would need to enqueue the styles and scripts for each item just like you did in the example above.
Conclusion
@wordpress/scripts simplifies working with Webpack, and makes it possible to create custom Gutenberg blocks, allow you to utilize core WordPress libraries like the awesome ApiFetch library. It can be extended, manipulated, and changed to suit your needs, and it can ensure that your scripts don’t conflict with other scripts. Once you get the hang of it, you’ll never want to go back to a world where you don’t have this tool at your disposal. Underpin has theme and plugin boilerplates that includes this library, and sets everything up to extend this compilation tool quickly.
Leave a Reply