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

0.1.1 2025-12-17 18:11 UTC

This package is auto-updated.

Last update: 2025-12-17 18:12:34 UTC


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:

  1. Generating global CSS/JS assets for a theme or plugin
  2. Extending existing blocks (core WordPress blocks and third-party blocks)
  3. 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 title field (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 title field (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_metadata filter

How wp-scripts Processes block.json

The @wordpress/scripts build process:

  1. Discovery: Scans the src/ directory for any block.json files
  2. Entry Points: For each block.json, creates webpack entry points for:
    • editorScript - JavaScript for the block editor
    • script - JavaScript for both editor and frontend
    • viewScript - JavaScript for frontend only
    • style - CSS for both editor and frontend
    • editorStyle - CSS for editor only
  3. Compilation:
    • JavaScript files are transpiled with Babel and bundled
    • SCSS/CSS files are processed with PostCSS
    • Asset dependency files (.asset.php) are generated
  4. 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:

  1. Create block directory:

    mkdir -p src/blocks/my-namespace/my-block
  2. 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"
    }
  3. 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,
    });
  4. Create styles (my-block.scss):

    .wp-block-my-namespace-my-block {
        padding: 1rem;
        background: var(--wp--preset--color--gold);
    }
  5. Build:

    npm run build

The block will be automatically registered.

Extending Existing Blocks

To extend a core or third-party block:

  1. Create block directory:

    mkdir -p src/blocks/core/heading
  2. Create block.json WITHOUT title:

    {
      "name": "core/heading",
      "script": "file:./heading.js",
      "style": "file:./heading.scss"
    }
  3. 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} />;
        }
    );
  4. Create styles (heading.scss):

    .wp-block-heading {
        &.custom-style {
            color: var(--wp--preset--color--gold);
        }
    }
  5. 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.jsbuild/blocks/core/button/button.js
  • blocks/core/button/button.cssbuild/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 for button.css in the same directory as block.json
  • Paths are relative to the block.json file 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

  1. Check block.json has title field
  2. Verify apiVersion is set (use 3)
  3. Check category is valid
  4. Run npm run build
  5. 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

  1. Use Semantic Block Names: namespace/feature-name for custom blocks
  2. Keep Extensions Minimal: Only extend what's necessary
  3. Leverage theme.json: Use design tokens instead of hardcoded values
  4. Test Block Removal: Ensure styles don't load when block isn't present
  5. Document Extensions: Comment why you're extending a block
  6. Use Modern CSS: Leverage CSS custom properties from theme.json

Resources