@rafiahmedd/wp-vite
v1.0.5
Published
Vite plugin for WordPress plugin and theme development
Maintainers
Readme
WP Vite
Vite integration for WordPress plugin and theme development.
Dev/prod aware · React HMR · Vite 4 & 5 · PSR-4 · Zero config needed.
Requirements
| Tool | Version | |------|---------| | PHP | ≥ 8.0 | | WordPress | ≥ 5.8 (6.2+ for HTML Tag Processor; falls back gracefully) | | Vite | ≥ 4.0 | | Node | ≥ 18 |
Installation
PHP (Composer)
composer require rafiahmedd/wp-viteJS (npm / pnpm / yarn)
npm install -D @rafiahmedd/wp-viteQuick Start
1 — Configure Vite
Create vite.config.js in your plugin/theme root:
import { wpVite } from '@rafiahmedd/wp-vite';
export default {
plugins: [
wpVite({
input: 'src/main.ts', // entry point
outDir: 'dist', // output directory
}),
],
};Add scripts to package.json:
{
"scripts": {
"dev": "vite",
"build": "vite build"
}
}2 — Enqueue in PHP
<?php
add_action( 'wp_enqueue_scripts', function (): void {
wp_vite_enqueue(
__DIR__ . '/dist',
'src/main.ts',
[
'handle' => 'my-plugin',
'in-footer' => true,
]
);
} );3 — Develop and build
npm run dev # start HMR dev server
npm run build # build for productionThat's it. The PHP side automatically detects whether you're in dev or prod mode.
How It Works
Development mode
When npm run dev is running, the Vite plugin writes a dist/vite-dev-server.json file:
{
"origin": "http://localhost:5173",
"base": "/",
"plugins": ["vite:react-refresh", "wp-vite"]
}The PHP package finds this file and serves assets directly from the Vite dev server — giving you instant HMR.
When the dev server stops, the file is automatically removed.
Production mode
After npm run build, Vite writes either:
- Vite 5+:
dist/.vite/manifest.json - Vite 4:
dist/manifest.json
The PHP package detects whichever format is present and uses it to resolve hashed filenames for scripts and styles.
PHP API
wp_vite_enqueue( $buildDir, $entry, $options )
Register and enqueue a Vite entry point.
wp_vite_enqueue(
__DIR__ . '/dist',
'src/main.tsx',
[
'handle' => 'my-plugin', // required
'dependencies' => [ 'wp-element' ], // script deps
'css-dependencies' => [ 'wp-components' ], // style deps
'css-media' => 'all', // CSS media attribute
'css-only' => false, // true = no JS tag
'in-footer' => true, // load in footer
]
);wp_vite_register( $buildDir, $entry, $options )
Register only — returns handles you can enqueue yourself later.
$assets = wp_vite_register( __DIR__ . '/dist', 'src/main.ts', [
'handle' => 'my-plugin',
] );
// $assets = [ 'scripts' => [ 'my-plugin' ], 'styles' => [ 'my-plugin-main' ] ]
wp_enqueue_script( 'my-plugin' );
wp_enqueue_style( 'my-plugin-main' );OOP alternative
If you prefer namespaced calls:
use WpVite\AssetManager;
AssetManager::enqueue( __DIR__ . '/dist', 'src/main.tsx', [ 'handle' => 'my-plugin' ] );
AssetManager::register( __DIR__ . '/dist', 'src/admin.ts', [ 'handle' => 'my-admin' ] );React Support
Install the React plugin:
npm install -D @vitejs/plugin-react
npm install react react-domUpdate vite.config.js:
import { wpVite } from '@rafiahmedd/wp-vite';
import react from '@vitejs/plugin-react';
export default {
plugins: [
wpVite({ input: 'src/main.jsx', outDir: 'dist' }),
react(),
],
};React Fast Refresh (HMR) is injected automatically during development.
Externalising WordPress Packages (smaller bundles)
WordPress ships React, jQuery, and all @wordpress/* packages as global scripts. You can tell Vite to exclude them from your bundle using the wpScripts() plugin:
import { wpVite, wpScripts } from '@rafiahmedd/wp-vite';
import react from '@vitejs/plugin-react';
export default {
plugins: [
wpVite({ input: 'src/main.jsx', outDir: 'dist' }),
wpScripts(), // exclude react, react-dom, @wordpress/* from bundle
react(),
],
};Then declare the handles as dependencies on the PHP side:
wp_vite_enqueue( __DIR__ . '/dist', 'src/main.jsx', [
'handle' => 'my-plugin',
'dependencies' => [ 'react', 'react-dom', 'wp-element' ],
] );Customising the externals map
// Add extra globals
wpScripts({ lodash: '_', 'my-lib': 'MyLib' });
// Remove an entry from the defaults (include react in your bundle)
wpScripts({ react: false, 'react-dom': false });Default externals map:
| Package | Global |
|---------|--------|
| jquery | jQuery |
| react | React |
| react-dom | ReactDOM |
| react-dom/client | ReactDOM |
| @wordpress/api-fetch | wp.apiFetch |
| @wordpress/blocks | wp.blocks |
| @wordpress/block-editor | wp.blockEditor |
| @wordpress/components | wp.components |
| @wordpress/compose | wp.compose |
| @wordpress/data | wp.data |
| @wordpress/element | wp.element |
| @wordpress/hooks | wp.hooks |
| @wordpress/i18n | wp.i18n |
| @wordpress/notices | wp.notices |
| @wordpress/url | wp.url |
Multiple Entry Points
Each entry must be enqueued separately:
// vite.config.js
wpVite({
input: {
main: 'src/main.ts',
admin: 'src/admin.ts',
},
outDir: 'dist',
});// plugin.php
wp_vite_enqueue( __DIR__ . '/dist', 'src/main.ts', [ 'handle' => 'my-plugin-frontend' ] );
wp_vite_enqueue( __DIR__ . '/dist', 'src/admin.ts', [ 'handle' => 'my-plugin-admin' ] );Available Filters (WordPress)
| Filter | Description |
|--------|-------------|
| wp_vite_manifest_data | Modify raw manifest data after it's read from disk. |
| wp_vite_registered_assets | Alter the array of registered handles after any mode. |
| wp_vite_dev_assets | Alter handles registered in development mode. |
| wp_vite_prod_assets | Alter handles registered in production mode. |
Example — inject window.MyPlugin before the script runs:
add_filter( 'wp_vite_registered_assets', function ( array $assets ): array {
if ( in_array( 'my-plugin', $assets['scripts'], true ) ) {
wp_add_inline_script(
'my-plugin',
'window.MyPlugin = ' . wp_json_encode( [ 'ajax' => admin_url( 'admin-ajax.php' ) ] ) . ';',
'before'
);
}
return $assets;
} );Directory Structure
This package
wp-vite/
├── .github/workflows/ci.yml CI — PHP 8.0–8.3, Node 18–22
├── example/
│ ├── plugin-usage.php PHP usage examples (5 patterns)
│ └── vite.config.js Vite config examples (5 variants)
├── js/src/index.js Vite plugin — wpVite() + wpScripts()
├── src/ PSR-4 namespace: WpVite\
│ ├── AssetManager.php register() / enqueue() — core logic
│ ├── AssetOptions.php Typed readonly value object for options
│ ├── DevServer.php @vite/client + React Refresh preamble
│ ├── Manifest.php Manifest reader, cache, Vite 4+5 support
│ ├── ScriptModuleFilter.php type="module" injection (WP 6.2+ + fallback)
│ ├── UrlResolver.php Filesystem path → public WordPress URL
│ └── functions.php wp_vite_enqueue() / wp_vite_register()
├── tests/
│ ├── Stubs/wordpress.php WP function stubs — no WP install needed
│ ├── Unit/ One test class per src/ class (7 files)
│ ├── WpViteTestCase.php Base case with manifest fixture helpers
│ └── bootstrap.php
├── types/index.d.ts TypeScript declarations for the JS plugin
├── .editorconfig
├── .gitignore
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── Makefile make test / lint / fix / check / clean
├── README.md
├── SECURITY.md
├── composer.json
├── package.json
├── phpcs.xml.dist
└── phpunit.xml.distYour plugin/theme (consuming this package)
your-plugin/
├── dist/ ← build output (git-ignored)
│ ├── .vite/
│ │ └── manifest.json ← Vite 5 production manifest
│ ├── manifest.json ← Vite 4 production manifest (fallback)
│ └── vite-dev-server.json ← written on `npm run dev`, auto-deleted
├── src/
│ └── main.tsx
├── composer.json
├── package.json
├── plugin.php
└── vite.config.jsDeveloper Commands
make install # composer install + npm ci
make test # PHPUnit (no coverage)
make test-coverage # PHPUnit + HTML coverage report (needs Xdebug)
make lint # PHPCS check
make fix # PHPCBF auto-fix
make check # lint + test (same as CI)
make clean # remove vendor/, node_modules/, coverage/
make js-check # verify JS plugin syntax with NodeOr via Composer:
composer test
composer lint
composer fix
composer checkLicense
MIT
