Migrating to the wp-scripts: analysing two cases

The wp-scripts (@wordpress/scripts1) package is a collection of scripts to standardize and simplify the development of WordPress projects that have to transform and optimize the JavaScript code and other assets (images, fonts, CSS files, etc) to be compatible with the majority of browsers.

That way, instead of configuring tools like webpack2, Babel3 and ESLint4, you can use the wp-scripts as the only dependency to handle it and concentrate on other aspects of your WordPress project.

Reasons to migrate

  • You don’t need to learn and configure other tools (webpack, Babel, ESLint, etc)
  • Decrease the number of dependencies of your project
  • Make the maintenance and updating easier especially if you work with other projects at the same time
  • It helps maintain the quality of your code
  • It helps follow the WordPress coding standards

Reasons to not migrate

Depending on the structure of your project, you have to customize the wp-scripts. This customization can be adding a parameter to run a script and/or using a custom webpack configuration file.

Preparing the environment

We will use node v20.11.0 and npm v10.2.4. Let’s initiate a npm project and install the wp-scripts package as a dev dependency.

# Init a project with the default values
npm init -y

# Install wp-scripts 
npm install @wordpress/scripts --save-dev

In the package.json file, let’s add some basic scripts using the wp-scripts. There are others, but we will concentrate only on these that I think are the most important if you want to migrate to the wp-scripts.

{
	[...]
	"scripts": {
		"build": "wp-scripts build",
		"format": "wp-scripts format",
		"lint:css": "wp-scripts lint-style",
		"lint:js": "wp-scripts lint-js"
	},
	[...]
}

Now, let’s add three files in the src folder: index.js, style.scss and image.png.

// index.js

console.log('Hello world!');
// style.scss

.my-class {
	background-color: #9ca3af;
}

The structure of the project will look like this way:

wp-scripts-post/
├── src/
│   ├── index.js
│   ├── style.scss
│   └── image.png
├── package.json
├── node_modules/ (generated automatically)
└── package-lock.json (generated automatically)

Cases

#1

The most ideal case is when the JavaScript files import the assets via import. In this scenario, you just have to run the build command and a new folder will be created.

Using the initial project files, we need to update the index.js file to import the style.css and image.png files.

// index.js

import './style.scss';
import 'image.png';


console.log('Hello world!');

After running the command npm run build, we will have the following structure:

wp-scripts-post/
├── build/
│   ├── index.asset.php
│   ├── index.js
│   ├── style.scss
│   └── image.png
├── src/
│   ├── index.js
│   ├── style.scss
│   └── image.png
├── package.json
├── node_modules/ (generated automatically)
└── package-lock.json (generated automatically)

Notice that the index.asset.php file is created as well. This file is useful when you insert the script on a page using the function wp_enqueue_script5 because it has the script dependencies and version number.

We can have a variation of this ideal scenario when your source files are not in the src folder and/or the result of the build script shouldn’t be outputted to the build folder. For example, your files are in the folder source and the result should be outputted to the folder dist.

In this scenario, we can follow two paths. The first one is using two additional parameters in the build command to specify the input and output folders. The parameters are --webpack-src-dir and --output-path.

{
	[...]
	"scripts": {
		"build": "wp-scripts build --webpack-src-dir=source --output-path=dist",
		[...]
	},
	[...]
}

For the format, lint-css and lint-js scripts it’s necessary to add the directory path in front of the command.

{
	[...]
	"scripts": {
		[...]
		"format": "wp-scripts format ./source",
		"lint:css": "wp-scripts lint-style ./source",
		"lint:js": "wp-scripts lint-js ./source"
	},
	[...]
}

The second way to specify the input and output folders is by using a webpack configuration file. So, we need to create the file webpack.config.js.

wp-scripts-post/
├── build/
│   ├── index.asset.php
│   ├── index.js
│   ├── style.scss
│   └── image.png
├── src/
│   ├── index.js
│   ├── style.scss
│   └── image.png
├── webpack.config.js <-- <-- <--
├── package.json
├── node_modules/ (generated automatically)
└── package-lock.json (generated automatically)

In the webpack.config.js we will change these settings: entry6 and output.path7. Notice that in the file below we change only these two settings and keep using the default wp-scripts settings. What makes it easy to use the default settings is the spread operator8 (…).

const path = require('path');
const defaultConfig = require( '@wordpress/scripts/config/webpack.config' );

module.exports = {
    ...defaultConfig,
	entry: './source',
	output: {
		...defaultConfig.output,
		/**
		 * The`output.path` setting requires an
		 * absolute path and this is why we use
		 * the function `path.resolve`.
		 */
		path: path.resolve(__dirname, 'dist')
	},
}

When running the command npm run build the webpack.config.js file is automatically used since this file is in the same directory as the package.json file. Also, it’s possible to specify the path to the webpack.config.js file using the option --config.

#2

In this second case, suppose you have more than one JavaScript, CSS and image file. Other than that, you don’t import the CSS and image files in your JavaScript file. Although it’s possible to update the JavaScript files to import the assets, at this moment you just want to incorporate the wp-scripts in your project without changing the source files.

Just to recap, we have this structure of folders and files:

wp-scripts-post/
├── src/
│   ├── index.js
│   ├── style.scss
│   └── image.png
├── package.json
├── node_modules/ (generated automatically)
└── package-lock.json (generated automatically)
// index.js

console.log('Hello world!');
// style.scss

.my-class {
	background-color: #9ca3af;
}

Without applying any change and running the wp-scripts build (npm run build), the result in the build folder has only the files index.js and index.asset.php:

wp-scripts-post/
├── build/
│   ├── index.js
│   └── index.asset.php
├── src/
│   ├── index.js
│   ├── style.scss
│   └── image.png
├── package.json
├── node_modules/ (generated automatically)
└── package-lock.json (generated automatically)

This happens because the files style.scss and image.png are not imported into the index.js. That way, it’s not possible for the build script to know about the asset files.

In this scenario, we will use the webpack.config.js file to tell which files we want to use as input in the entry setting. Instead of using a string, we will use an object where the key is the name of our entry and the value is an array of strings with the path to the JavaScript and SCSS file:

const path = require('path');
const defaultConfig = require( '@wordpress/scripts/config/webpack.config' );

module.exports = {
    ...defaultConfig,
	entry: {
		index: ['./src/index.js', './src/style.scss']
	},
}

Running the command npm run build, we will have the following result in the build folder:

wp-scripts-post/
├── build/
│   ├── index.js
│   ├── style-index.css
│   └── index.asset.php

[...]

Although both files were outputted, the name of the CSS file changed from style.css to style-index.css. It can be a problem since we would have to update the name of the file in all places where this file is referenced. So let’s adjust our settings to output the CSS file with the name style.css.

The webpack is a tool that has a lot of settings and can handle a various scenarios. Also, webpack allows your functionalities to be extended via an interface for plugins9.

If you take a look at the webpack.config.js of the wp-scripts10, you can notice that the wp-scripts uses some plugins. One of them is the MiniCssExtractPlugin11. This plugin extracts the CSS to a separate file. If this plugin is not used and we run npm run build, the CSS imported in a JavaScript file would be inserted in the JavaScript compiled.

That way, to change the name of the CSS file we need to change the filename12 setting of the plugin MiniCssExtractPlugin. Since this plugin is inserted by the wp-scripts, first we will remove it and after that insert it again with our custom setting. Our custom setting consists of removing everything after the dash in the name of the file. That way, instead of style-index, the name will be just style.

// webpack.config.js

const defaultConfig = require( '@wordpress/scripts/config/webpack.config' );
const MiniCSSExtractPlugin = require( 'mini-css-extract-plugin' );

// Remove the MiniCSSExtractPlugin from wp-scripts plugin list.
const plugins = defaultConfig.plugins.filter(
	plugin => ! (plugin instanceof MiniCSSExtractPlugin)
);

module.exports = {
    ...defaultConfig,
	entry: {
		index: ['./src/index.js', './src/style.scss'],
	},
	plugins: [
		...plugins,
		// Add the MiniCSSExtractPlugin plugin
		new MiniCSSExtractPlugin( {
			filename: ({chunk}) => {
				// Regex to remove everything after `-`.
				return `${chunk.name.replace(/-.*/, '')}.css`
			},
		} ),
	]
}

Running npm run build again we will have the expected result:

wp-scripts-post/
├── build/
│   ├── index.js
│   ├── style.css
│   └── index.asset.php

[...]

An interesting detail is that if we use the image in the style.scss file, the wp-scripts will copy the image to the build folder and adjust the style.css file to refer to the correct name of the image in the build folder.

// style.scss

.my-class {
	background-color: #9ca3af;
	background-image: url('./image.png');
}
wp-scripts-post/
├── build/
│   ├── images/
│   │   └── image.438eef35.png
│   ├── index.js
│   ├── style.css
│   └── index.asset.php

[...]

If you have other .js and .scss files, you can just add them to the entry key in the webpack.config.js and while running the build command, these files will be processed by the wp-scripts.

Conclusion

The wp-scripts is a good tool and can accelerate the development of WordPress projects that strongly rely on JavaScript. However, if your project structure is a bit different from the expected structure, setting up the wp-scripts for your case can be a challenge because you will have to learn and go a bit deeper into how webpack works. Additionally, it’s hard to find tutorials and/or examples for non-trivial cases.

In the WordPress universe, there is another alternative: 10up-toolkit. But if you would like to use a tool that will not be on your path, has a more comprehensive interface and a learning curve small, so Laravel Mix can be the most adequate tool.

To know more about the wp-scripts options and settings:

Footnotes

  1. https://developer.wordpress.org/block-editor/reference-guides/packages/packages-scripts/ ↩︎
  2. https://webpack.js.org/ ↩︎
  3. https://babeljs.io/ ↩︎
  4. https://eslint.org/ ↩︎
  5. https://developer.wordpress.org/reference/functions/wp_enqueue_script/ ↩︎
  6. https://webpack.js.org/configuration/entry-context/#entry ↩︎
  7. https://webpack.js.org/configuration/output/#outputpath ↩︎
  8. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax ↩︎
  9. https://webpack.js.org/plugins/ ↩︎
  10. https://github.com/WordPress/gutenberg/blob/trunk/packages/scripts/config/webpack.config.js#L306 ↩︎
  11. https://webpack.js.org/plugins/mini-css-extract-plugin ↩︎
  12. https://webpack.js.org/plugins/mini-css-extract-plugin#filename ↩︎