Back to Blog

Compiling and Enqueuing Assets in WordPress with @wordpress/scripts

|
TL;DR: npm install @wordpress/scripts --save-dev, add build/watch/watch:hot scripts to package.json, then auto-register the compiled output in PHP using the .asset.php file it generates. No webpack config required.

Dealing with webpack directly is no picnic, and it only gets worse the bigger or more complicated a project gets. Add SCSS compiling, TypeScript, SVG imports in your CSS, or CSS modules, and now you're configuring all of that and managing a pile of npm dependencies to make it work. @wordpress/scripts does all of it for you, with no webpack config needed, unless you want to extend the built-in config later. Here's how.

Install @wordpress/scripts

In your plugin or theme directory:

bash
npm init -y
npm install @wordpress/scripts --save-dev

Configure package.json

Add custom scripts:

json
"scripts": {
  "build": "wp-scripts build",
  "watch": "wp-scripts start",
  "watch:hot": "wp-scripts start --hot"
}

Optionally, specify exact JavaScript files to compile, or a custom output path:

json
"scripts": {
  "build": "wp-scripts build assets/src/one.js assets/src/two.js --output-path=assets/dist/",
  "watch": "wp-scripts start assets/src/one.js assets/src/two.js --output-path=assets/dist/",
  "watch:hot": "wp-scripts start assets/src/one.js assets/src/two.js --output-path=assets/dist/ --hot"
}
text
Script       Command                     When to use it
build        wp-scripts build            Production build, one-time compile
watch        wp-scripts start            Development, recompiles on every save
watch:hot    wp-scripts start --hot      Development, hot module replacement without a full reload

Set up your project structure

text
assets/
  src/
    index.js     (or index.ts)
    style.scss
  build/
    index.js
    index.asset.php
    style.css

Inside index.js, import styles directly. This lets wp-scripts compile them without any separate configuration:

js
import './style.scss';

Compile your assets

Build for production:

bash
npm run build

For development, with automatic recompiling:

bash
npm run watch

For hot module replacement:

bash
npm run watch:hot

Any of these outputs the following into build/:

text
build/index.js
build/index.asset.php
build/style.css

Register and enqueue in PHP

That build/index.asset.php file is the key piece: it lets you auto-register your compiled scripts and styles instead of hand-maintaining version numbers and dependency arrays. Here's a generalized function that handles auto-registration. Customize the my-plugin parts to match your actual plugin or theme slug, so it doesn't collide with anyone else's:

php
function register_assets() {
	$asset_root = plugin_dir_path( __FILE__ ) . 'assets/build/';
	$asset_uri  = plugin_dir_url( __FILE__ ) . 'assets/build/';
	$asset_files = glob( $asset_root . '*.asset.php' );

	// Load runtime if present (used by webpack for chunking).
	if ( true === is_readable( $asset_root . 'runtime.js' ) ) {
		enqueue_script(
			'my-plugin/runtime',
			$asset_uri . 'runtime.js',
			array(),
			filemtime( $asset_root . 'runtime.js' )
		);
	}

	foreach ( $asset_files as $file ) {
		$script_meta = require $file;
		$slug = basename( $file, '.asset.php' );

		$handle = "my-plugin/{$slug}";
		$js_path = $asset_root . "{$slug}.js";
		$js_uri  = $asset_uri . "{$slug}.js";
		$css_path = $asset_root . "{$slug}.css";
		$css_uri  = $asset_uri . "{$slug}.css";

		if ( true === is_readable( $css_path ) ) {
			wp_register_style(
				$handle,
				$css_uri,
				array_filter( $script_meta['dependencies'], 'wp_style_is' ),
				$script_meta['version']
			);
		}

		if ( true === is_readable( $js_path ) ) {
			wp_register_script(
				$handle,
				$js_uri,
				array_filter( $script_meta['dependencies'], 'wp_script_is' ),
				$script_meta['version'],
				array( 'in_footer' => true )
			);
		}
	}
}

add_action( 'init', 'register_assets' );

Now enqueue the registered scripts wherever you actually need them:

php
function enqueue_my_scripts() {
    wp_enqueue_script( 'my-plugin/index' ); // 'index' comes from index.asset.php.
    wp_enqueue_style( 'my-plugin/index' );
}

add_action( 'wp_enqueue_scripts', 'enqueue_my_scripts' );

$script_meta['dependencies'] and $script_meta['version'] come straight out of the generated .asset.php file, which wp-scripts keeps in sync with whatever WordPress packages your code actually imports. You never hand-maintain that dependency list yourself.

Bonus: keep your build directory out of version control

Add this to .gitignore so the compiled output doesn't end up in your source repo, unless you specifically want to commit built assets for deployment:

text
assets/build/

Build before deployment, or commit the built assets as part of your deploy process, whichever fits your workflow.

See the @wordpress/scripts package on npm for the full command reference, and WordPress's developer documentation on wp_enqueue_script for the enqueueing functions used above.

Where to go next

This is one piece of bridging modern JavaScript tooling with WordPress. If you're building blocks or larger plugin features on top of this setup, WordPress's Block Editor Handbook is the next logical reference.

FAQ

Do I need to write my own webpack config to use @wordpress/scripts?

No. @wordpress/scripts ships a working webpack configuration out of the box that handles SCSS, TypeScript, and SVG imports in CSS. You only need a custom config if you want to extend or override its defaults.

What's the difference between build, watch, and watch:hot?

build compiles once for production. watch recompiles automatically every time you save a file, useful during development. watch:hot adds hot module replacement, updating the running page without a full reload.

What does the .asset.php file actually do?

It's generated automatically during compilation and contains the script's dependency list and a version string based on the file's contents. The PHP registration function reads it so you never have to hand-maintain dependencies or cache-busting versions yourself.

Should I commit the build folder to git?

Usually not. Add it to .gitignore and either build during deployment or as a separate step in your CI/CD process. Commit built assets only if your deployment process specifically expects them already compiled.

Why does my enqueued script or style not show up?

Check that the handle you're enqueuing matches exactly what was registered, that the file actually exists at the expected path, and that wp_enqueue_script/wp_enqueue_style are called on a hook that runs after registration, like wp_enqueue_scripts.

Share this article: