How to use Vite to develop blocks and assets in WordPress in WordPress

Contents

Introduction

This article is a comprehensive, practical walkthrough showing how to use Vite to develop WordPress blocks and front-end assets. It covers full project setup, development-server (HMR) workflow, production builds, integration with WordPress (editor and front-end), manifest handling, CSS/Sass, images, and tips for common pitfalls. All code examples are included and ready to copy — each code snippet is wrapped in the required

 tag.

Why use Vite for WordPress block development?

  • Blazing-fast dev server with native ESM and fast HMR.
  • Simple production builds with hashed filenames and a manifest for server-side asset mapping.
  • Modern tooling: ES modules, PostCSS/Tailwind support, and tree-shaking for production.
  • Flexible: you control how assets are registered/enqueued in WordPress.

Overview of approach

  1. Set up a plugin (or theme) skeleton that holds source files in a src/ directory and outputs to dist/ (or build/).
  2. Use Vite during development to serve assets from the dev server (e.g., http://localhost:5173) so the WordPress editor gets HMR-enabled modules.
  3. Use Vite build (manifest: true) for production to generate hashed assets plus manifest.json use PHP to map entry names to final URLs and enqueue properly.
  4. Optionally register blocks via block.json or via PHP register_block_type and use the manifest mapping for scripts and styles.

Prerequisites

  • Node.js (v14 recommended v16 ideal).
  • WordPress development environment (Local, Docker, VVV, XAMPP, etc.).
  • Familiarity with block development (registering blocks) or willingness to follow provided examples.

Project structure (recommended)

Place this inside a plugin folder (e.g., wp-content/plugins/my-vite-blocks).

my-vite-plugin/
├─ src/
│  ├─ block/
│  │  ├─ index.js        (block bootstrap for editor)
│  │  ├─ frontend.js     (frontend behavior, if any)
│  │  ├─ editor.scss
│  │  └─ style.scss
│  └─ main.js            (optional generic frontend entry)
├─ dist/                 (generated by Vite build)
├─ vite.config.js
├─ package.json
├─ vite.config.js
└─ my-vite-plugin.php    (main plugin file)

Step 1 — Initialize package.json and install dependencies

Install Vite and the packages you need. A minimal example (you will likely also need @wordpress/ packages for block registration).

{
  name: my-vite-plugin,
  version: 1.0.0,
  private: true,
  scripts: {
    dev: vite,
    build: vite build
  },
  devDependencies: {
    vite: ^5.0.0,
    sass: ^1.60.0
  },
  dependencies: {
    @wordpress/blocks: ^13.0.0,
    @wordpress/element: ^4.0.0,
    @wordpress/i18n: ^3.0.0,
    @wordpress/block-editor: ^12.0.0
  }
}

Step 2 — Vite configuration

Key Vite settings:

  • Set server host/port if you need Vite server accessible by the browser from WordPress (default 5173).
  • Set build.manifest = true so Vite generates manifest.json mapping original entry names to hashed filenames (used by PHP).
  • Set build.outDir to where WordPress will serve files (e.g., dist).
  • Optionally set base to the dev server URL during dev for absolute imports, but often easier to just point PHP to dev server in dev mode.
// vite.config.js
import { defineConfig } from vite
import path from path

export default defineConfig(({ command }) =gt {
  const isDev = command === serve
  return {
    root: path.resolve(__dirname, src),
    base: isDev ? / : /wp-content/plugins/my-vite-plugin/dist/,
    build: {
      outDir: path.resolve(__dirname, dist),
      emptyOutDir: true,
      manifest: true,
      rollupOptions: {
        input: {
          block: path.resolve(__dirname, src/block/index.js),
          frontend: path.resolve(__dirname, src/main.js) // optional
        }
      }
    },
    server: {
      host: true,
      port: 5173,
      strictPort: true,
    },
    resolve: {
      alias: {
        @: path.resolve(__dirname, src)
      }
    }
  }
})

Step 3 — Source: block editor entry and block registration (JS)

Register block(s) using block APIs. In development Vite will serve the JS module directly.

// src/block/index.js
import { registerBlockType } from @wordpress/blocks
import { __ } from @wordpress/i18n
import Edit from ./edit
import save from ./save
import ./editor.scss
import ./style.scss

registerBlockType(my-vite-plugin/my-block, {
  apiVersion: 2,
  title: __(My Vite Block, my-vite-plugin),
  category: widgets,
  icon: smiley,
  edit: Edit,
  save,
})
// src/block/edit.js
import { useBlockProps, RichText } from @wordpress/block-editor

export default function Edit({ attributes, setAttributes }) {
  return (
    
) }
// src/block/save.js
import { useBlockProps, RichText } from @wordpress/block-editor

export default function save({ attributes }) {
  return (
    
) }

Step 4 — Styles (Sass/CSS)

Import editor-specific styles and front-end styles from your entry. Vite will process SCSS via sass package and generate CSS files (which show up in manifest.css for production).

/ src/block/editor.scss /
.wp-block-my-vite-plugin-my-block {
  padding: 10px
  border: 1px dashed #ccc
}

/ src/block/style.scss (front-end) /
.wp-block-my-vite-plugin-my-block p {
  color: #333
}

Step 5 — Development workflow: serving modules from Vite

During development, you need WordPress to load the Vite dev server modules. The simplest approach is to print a module script tag into the editor and front-end when a Vite dev server is running. You can either detect an environment variable or rely on a constant in wp-config.php like define(VITE_DEV, true).

Important dev details:

  • Include the @vite/client script (handles HMR websocket connection).
  • Include your module script with type=module and path to the source entry (e.g., /src/block/index.js hosted by Vite root).
  • For editor loads (Gutenberg), add those script tags in admin_enqueue_scripts or via echo when editing posts.
// my-vite-plugin.php (excerpt)
n

    // Load the block entry as a native ESM module
    echo n

    // If you need frontend scripts while previewing the site
    // echo n
}
add_action( enqueue_block_editor_assets, my_vite_plugin_enqueue_dev_assets )
add_action( wp_enqueue_scripts, my_vite_plugin_enqueue_dev_assets ) // optional for frontend preview
?>

Notes on the dev approach

  • Directly printing module tags is a common approach for dev. This bypasses wp_enqueue_script limitations and allows type=module to be used.
  • If your WP admin is served over HTTPS and Vite dev server uses HTTP or a different hostname, adjust the protocol/host/port accordingly. You may need to configure Vite server to use HTTPS for secure contexts.
  • Ensure CORS is not blocking assets — Vite dev server typically allows it for localhost.
  • When HMR doesnt work inside Gutenberg editor, make sure @vite/client is included and the client websocket can connect (client port may need configuration with server.hmr settings in vite.config.js).

Step 6 — Production build: manifest.json and asset mapping

Vite with build.manifest: true will generate a manifest.json in dist/ that maps original entry filenames to final hashed filenames and CSS lists. Use a PHP helper to read the manifest and enqueue script and style files.

// Example dist/manifest.json (simplified)
{
  block/index.js: {
    file: assets/block.123abc.js,
    src: block/index.js,
    isEntry: true,
    css: [
      assets/block.123abc.css
    ]
  },
  main.js: {
    file: assets/main.456def.js,
    src: main.js,
    isEntry: true
  }
}

 my-vite-plugin-block,
            editor_style  => my-vite-plugin- . md5( css_files[0] ??  ),
            style         => my-vite-plugin- . md5( css_files[0] ??  )
        ) )
    }
}
add_action( init, my_vite_plugin_register_assets )
?>

How this works

  • During production, the manifest maps entry names to hashed assets. PHP reads manifest.json and enqueues the correct script and CSS URLs.
  • You can register a unique handle per entry and reference those handles in register_block_type.
  • Using filemtime() or a version value ensures cache-busting if you redeploy without hashed filenames (but Vite already uses hashes).

Alternative: block.json with file: references

WordPress block metadata (block.json) supports file:./build/index.js references that tools like @wordpress/scripts fill before packaging. If you prefer block.json, you can still use Vite — just ensure your build output path and manifest mapping are compatible with registering the block via register_block_type_from_metadata or register_block_type(__DIR__).

// src/block/block.json
{
  schema: https://schemas.wp.org/trunk/block.json,
  apiVersion: 2,
  name: my-vite-plugin/my-block,
  title: My Vite Block,
  category: widgets,
  editorScript: file:./dist/block.js,
  style: file:./dist/block.css
}

When using this approach, ensure your build outputs align with what register_block_type expects. Many projects keep a build step that copies/creates a build/ directory matching the expected structure so register_block_type( __DIR__ ) picks up the files. The manual manifest approach is more flexible and commonly used with Vite.

Images, SVGs and other static assets

Vite handles asset imports. If you import an image from your JS or CSS, the build will copy it to dist/assets and update references manifest.json entries include hashed files. For production, read the manifest or use the CSS files output which reference correct URLs. For inline SVGs and icon handling use imports or inlined components.

// Example importing an SVG inside a React-style component
import icon from ./icon.svg

export default function Icon() {
  return icon
}

HMR edge-cases in Gutenberg

  • If HMR doesnt pick up changes in the block editor frame, ensure @vite/client is loaded before your module and that the websocket is accessible from the admin iframe.
  • If using HTTPS for admin, configure server.https in vite.config.js or serve Vite over HTTPS to prevent mixed-content errors.
  • When using remote dev containers, set server.host: true and specify server.hmr.clientPort or server.hmr.host to expose websockets correctly.

Production checklist

  1. Run npm run build — Vite will create dist/ with hashed files and manifest.json.
  2. Ensure PHP reads the manifest.json and maps entries to URLs and CSS files.
  3. Register block scripts/styles with register_block_type or register_block_type_from_metadata using mapped handles.
  4. Commit/build artifacts as appropriate for your deployment strategy: some teams copy dist/ into the plugin for release others upload build artifacts to the server during CI/CD.

Complete example — Minimal plugin file (production dev support)

n
    // Editor entry
    echo n
    // Optionally load frontend entry while previewing
    // echo n
}

function my_vite_plugin_get_manifest() {
    static manifest = null
    if ( manifest !== null ) {
        return manifest
    }
    path = MY_VITE_PLUGIN_PATH . dist/manifest.json
    if ( ! file_exists( path ) ) {
        return null
    }
    manifest = json_decode( file_get_contents( path ), true )
    return manifest
}

function my_vite_plugin_register_prod() {
    manifest = my_vite_plugin_get_manifest()
    if ( ! manifest ) {
        return
    }

    entry = block/index.js // change to your vite input key
    if ( ! isset( manifest[ entry ] ) ) {
        return
    }

    file = manifest[ entry ][file]
    script_url = MY_VITE_PLUGIN_URL . dist/ . file

    wp_register_script(
        my-vite-plugin-block,
        script_url,
        array( wp-blocks, wp-element, wp-i18n, wp-block-editor ),
        filemtime( MY_VITE_PLUGIN_PATH . dist/ . basename( file ) ),
        true
    )

    // register CSS output(s)
    css_files = manifest[ entry ][css] ?? []
    foreach ( css_files as css ) {
        css_url = MY_VITE_PLUGIN_URL . dist/ . css
        wp_register_style(
            my-vite-plugin- . md5( css ),
            css_url,
            [],
            filemtime( MY_VITE_PLUGIN_PATH . dist/ . basename( css ) )
        )
    }

    register_block_type( __DIR__ . /src/block, array(
        editor_script => my-vite-plugin-block,
        editor_style  => my-vite-plugin- . md5( css_files[0] ??  ),
        style         => my-vite-plugin- . md5( css_files[0] ??  ),
    ) )
}
?>

Troubleshooting tips

  • If the editor shows an older version, ensure the dev server is running and @vite/client is included.
  • If HMR doesnt update CSS, check the CSS is imported in your JS entry (import ./style.scss) so Vite tracks it.
  • If CORS or mixed content issues occur, match protocols (https/http) and configure Vite dev server to use HTTPS if WordPress admin is HTTPS.
  • Check vite.config.js server.hmr settings when using containers or remote dev environments.
  • Ensure the path used in vite.rollupOptions.input matches what you reference as the manifest key in PHP.

Advanced topics (brief)

  • Use multiple entries for many blocks: list each entry in rollupOptions.input and map in PHP using the manifest.
  • Build a script to copy only the needed manifest files into PHP-friendly locations during CI/CD.
  • Use a WordPress-specific Vite plugin or custom Vite plugin if you need automated rewriting of block.json file: references during build — but the manifest approach is simpler and robust.
  • If you need to generate separate editor and front-end builds with different optimization, configure multiple inputs and register them appropriately.

Summary

Using Vite with WordPress gives a fast, modern dev experience and a reliable production process via the generated manifest. The main pieces to implement are (1) Vite config with manifest amp entries, (2) dev-mode injection of @vite/client and module entries, and (3) production mapping from manifest.json to enqueue scripts and styles. This article provided full examples for each part: vite.config.js, JS block entries, SCSS, and a complete PHP plugin integration that supports both dev and prod workflows.



Acepto donaciones de BAT's mediante el navegador Brave 🙂



Leave a Reply

Your email address will not be published. Required fields are marked *