Make Webpack Configuration Easy With wordpress/scripts
Tags: wordpress, webpack, node, javascript
Published: 2021-07-09 19:27:33

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](https://www.npmjs.com/) package, and it is called [@wordpress/scripts](https//developer.wordpress.org/block-editor/reference-guides/packages/packages-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:

```json
{
  "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](https://github.com/Underpin-WP/underpin-plugin-boilerplate), but it would work in just about any plugin or theme. The key part is the `scripts` object:

```json
"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](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-scripts/#available-scripts).

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](https://www.wpdev.academy/concepts/introducing-underpin-a-modern-wordpress-framework/), that would look like this:

```php
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:

1. Register a new script called `test`, where the JS URL is `build/index.js`. Underpin’s `js_url()` method defaults to the `build` in your plugin or theme.
2. 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](https://github.com/underpin-wp/script-loader#script-middleware).

If you wanted to-do this _without_ Underpin, it would look more like this:

```php
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](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-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:

```php
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:

```php
// 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:

```javascript
/**
 * 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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) 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](https://www.wpdev.academy/course/beer-lister-plugin/):

```javascript
/**
 * 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:

1. build/admin.js will be created from src/admin.js
2. build/beer-admin.js will be created from src/beer-admin.js
3. 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](https://www.wpdev.academy/wordpress-tutorials/create-a-custom-gutenberg-block-plugin-with-underpin/), allow you to utilize core WordPress libraries like [the awesome ApiFetch library](https://www.wpdev.academy/concepts/api-fetch-the-wordpress-library-you-didnt-know-you-needed/). 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](https://github.com/Underpin-WP/underpin-theme-boilerplate) and [plugin](https://github.com/Underpin-WP/underpin-plugin-boilerplate) boilerplates that includes this library, and sets everything up to extend this compilation tool quickly.

## Looking for more WordPress Resources?

Join [WP Dev Academy’s Discord server](http://community.wpdev.academy), and become a part of a growing community of WordPress developers.