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
- Set up a plugin (or theme) skeleton that holds source files in a src/ directory and outputs to dist/ (or build/).
- Use Vite during development to serve assets from the dev server (e.g., http://localhost:5173) so the WordPress editor gets HMR-enabled modules.
- 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.
- 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}
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
- Run npm run build — Vite will create dist/ with hashed files and manifest.json.
- Ensure PHP reads the manifest.json and maps entries to URLs and CSS files.
- Register block scripts/styles with register_block_type or register_block_type_from_metadata using mapped handles.
- 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 🙂 |