humanmade / wp-scripts-asset-loader
Use WP Scripts to load compile and load global assets, custom blocks, and extend block assets
Installs: 44
Dependents: 0
Suggesters: 0
Security: 0
Stars: 1
Watchers: 1
Forks: 0
Open Issues: 0
Type:wordpress-muplugin
pkg:composer/humanmade/wp-scripts-asset-loader
Requires
- composer/installers: ^1 || ^2
README
Load assets generated by WP Scripts, allows overloading block.json to extend existing blocks and generate granular theme / plugin CSS as well as register new blocks
Overview
This package relies on using @wordpress/scripts as the build tool, with a custom approach to handling block.json files that enables both:
- Generating global CSS/JS assets for a theme or plugin
- Extending existing blocks (core WordPress blocks and third-party blocks)
- Registering new custom blocks (theme-specific blocks)
This system allows you to add custom styles and scripts to core blocks without registering them as new blocks, while also supporting fully custom block registration and global CSS and JS via the same method.
The benefit is maintaining granular files that only load when needed, plus proper hash based asset versioning and compiling.
Build Tool: @wordpress/scripts
This asset loader relies on the @wordpress/scripts package, which provides:
- Webpack configuration for bundling JavaScript and CSS
- Babel transpilation for modern JavaScript
- PostCSS processing for CSS (including autoprefixer)
- Development server with hot module replacement
- Production optimization (minification, tree-shaking)
Source Directory Structure
The assets source code is organized in the src/ directory:
src/
├── blocks/
│ ├── my-namespace/ # Custom theme blocks
│ │ └── custom-block/
│ │ ├── block.json
│ │ ├── custom-block.js
│ │ ├── custom-block.scss
│ │ └── render.php
│ ├── core/ # Core block extensions
│ │ ├── button/
│ │ │ ├── block.json
│ │ │ ├── button.js
│ │ │ └── button.css
│ │ ├── paragraph/
│ │ ├── image/
│ │ └── ...
│ └── hubspot/ # Third-party block extensions
│ └── form/
│ ├── block.json
│ └── ...
└── global/
├── block.json
├── main.js
├── main.scss
├── editor.js
├── editor.scss
└── js/
Build Output Structure
After running npm run build, compiled assets are output to the build/ directory:
build/
├── blocks/
│ ├── my-namespace/
│ │ └── custom-block/
│ │ ├── block.json
│ │ ├── custom-block.js
│ │ ├── custom-block.asset.php
│ │ ├── custom-block.css
│ │ └── render.php
│ ├── core/
│ │ ├── button/
│ │ │ ├── block.json
│ │ │ ├── button.js
│ │ │ ├── button.asset.php
│ │ │ └── button.css
│ │ └── ...
│ └── hubspot/
└── global/
├── editor.js
├── editor.asset.php
├── editor.css
├── main.js
├── main.asset.php
└── main.css
The block.json System
The theme uses block.json files to define both custom blocks and block extensions. The build process automatically discovers all block.json files in the source directory.
Two Types of block.json Files
1. Custom Block Registration (with title)
Custom blocks include a title field in their block.json, which signals that this is a new block to register:
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "my-namespace/custom-block",
"title": "Today's Date",
"category": "text",
"icon": "calendar-alt",
"description": "Display today's date with customizable format.",
"attributes": {
"format": {
"type": "string",
"default": "F j, Y"
}
},
"editorScript": "file:./custom-block.js",
"style": "file:./custom-block.css",
"render": "file:./render.php"
}
Key characteristics:
- Contains
titlefield (this is the indicator) - Contains full block metadata (category, icon, description, attributes)
- Registered via
register_block_type()in PHP - Creates a new block in the block inserter
2. Block Extensions (without title)
Block extensions only contain the block name and asset references, without a title:
{
"name": "core/button",
"script": "file:./button.js",
"style": "file:./button.css"
}
Key characteristics:
- No
titlefield (this signals it's an extension) - Only contains block name and asset paths
- Does not register a new block
- Adds custom styles/scripts to existing blocks
- Styles loaded via
wp_enqueue_block_style() - Scripts added via
block_type_metadatafilter
How wp-scripts Processes block.json
The @wordpress/scripts build process:
- Discovery: Scans the
src/directory for anyblock.jsonfiles - Entry Points: For each
block.json, creates webpack entry points for:editorScript- JavaScript for the block editorscript- JavaScript for both editor and frontendviewScript- JavaScript for frontend onlystyle- CSS for both editor and frontendeditorStyle- CSS for editor only
- Compilation:
- JavaScript files are transpiled with Babel and bundled
- SCSS/CSS files are processed with PostCSS
- Asset dependency files (
.asset.php) are generated
- Output: Compiled files maintain the same directory structure in
build/
The Custom Override: extend_block_type_metadata
The theme overloads the standard WordPress block registration process using the block_type_metadata filter.
Usage
new WP_Script_Asset_Loader( 'my-asset-handle', '/path/to/build', 'https://example.com/wp-content/themes/my-asset-handle/build' );
Adding Global Assets
To add global JavaScript or CSS assets for your theme or plugin, create a block.json in the src/global/ directory and corresponding source files:
{
"name": "my-theme-or-plugin/global",
"script": "file:./main.js",
"style": "file:./main.scss",
"editorScript": "file:./editor.js",
"editorStyle": "file:./editor.scss"
}
Adding New Custom Blocks
To create a new custom block:
-
Create block directory:
mkdir -p src/blocks/my-namespace/my-block
-
Create block.json with title:
{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "my-namespace/my-block", "title": "My Block", "category": "widgets", "icon": "star-filled", "editorScript": "file:./my-block.js", "style": "file:./my-block.scss" } -
Create JavaScript file (
my-block.js):import { registerBlockType } from '@wordpress/blocks'; import { useBlockProps } from '@wordpress/block-editor'; import './my-block.scss'; const Edit = () => { const blockProps = useBlockProps(); return <div {...blockProps}>My Block Content</div>; }; registerBlockType('my-namespace/my-block', { edit: Edit, });
-
Create styles (
my-block.scss):.wp-block-my-namespace-my-block { padding: 1rem; background: var(--wp--preset--color--gold); }
-
Build:
npm run build
The block will be automatically registered.
Extending Existing Blocks
To extend a core or third-party block:
-
Create block directory:
mkdir -p src/blocks/core/heading
-
Create block.json WITHOUT title:
{ "name": "core/heading", "script": "file:./heading.js", "style": "file:./heading.scss" } -
Create JavaScript file (
heading.js):import { addFilter } from '@wordpress/hooks'; import './heading.scss'; // Modify the heading block's edit component addFilter( 'editor.BlockEdit', 'my-namespace/heading-extension', (BlockEdit) => (props) => { if (props.name !== 'core/heading') { return <BlockEdit {...props} />; } // Add custom behavior return <BlockEdit {...props} />; } );
-
Create styles (
heading.scss):.wp-block-heading { &.custom-style { color: var(--wp--preset--color--gold); } }
-
Build:
npm run build
The scripts will be automatically merged into the core/heading block registration via the block_type_metadata filter.
Build Process Internals
Entry Point Generation
@wordpress/scripts automatically generates webpack entry points from block.json files:
For this block.json:
{
"name": "core/button",
"script": "file:./button.js",
"style": "file:./button.css"
}
Webpack creates entries:
blocks/core/button/button.js→build/blocks/core/button/button.jsblocks/core/button/button.css→build/blocks/core/button/button.css
Asset Dependency Files
For each JavaScript entry, wp-scripts generates a .asset.php file:
<?php return array( 'dependencies' => array( 'wp-block-editor', 'wp-blocks', 'wp-element', 'wp-i18n' ), 'version' => '1a2b3c4d5e' );
These files are used by WordPress to:
- Properly enqueue script dependencies
- Add cache-busting version strings
- Ensure scripts load in the correct order
File Path Resolution
The file: protocol in block.json tells wp-scripts where to find source files:
"style": "file:./button.css"→ looks forbutton.cssin the same directory asblock.json- Paths are relative to the
block.jsonfile location - The
file:prefix is removed in the output
Common Patterns
Pattern 1: Style-Only Extension
Extend a block with CSS only (no JavaScript):
{
"name": "core/separator",
"style": "file:./separator.css"
}
Pattern 2: JavaScript-Only Extension
Add behavior without styles:
{
"name": "core/paragraph",
"script": "file:./paragraph.js"
}
Pattern 3: Full Extension
Add both styles and scripts:
{
"name": "core/button",
"script": "file:./button.js",
"style": "file:./button.css"
}
Pattern 4: Dynamic Custom Block
Custom block with PHP rendering:
{
"name": "my-theme/custom-block",
"title": "Today's Date",
"editorScript": "file:./custom-block.js",
"style": "file:./custom-block.css",
"render": "file:./render.php"
}
Troubleshooting
Block Not Appearing in Editor
- Check
block.jsonhastitlefield - Verify
apiVersionis set (use 3) - Check
categoryis valid - Run
npm run build - Clear WordPress object cache
Build Errors
# Clear node modules and rebuild
rm -rf node_modules package-lock.json
npm install
npm run build
Best Practices
- Use Semantic Block Names:
namespace/feature-namefor custom blocks - Keep Extensions Minimal: Only extend what's necessary
- Leverage theme.json: Use design tokens instead of hardcoded values
- Test Block Removal: Ensure styles don't load when block isn't present
- Document Extensions: Comment why you're extending a block
- Use Modern CSS: Leverage CSS custom properties from
theme.json