Feature Request: Inject Vite Client when in dev
I've been using this script for a while to integrate my Vite assets in my WordPress theme, but noticed the other day that when I went to import CSS into my JS entryfile the PHP script failed to traverse my Vite manifest to find CSS chunks to inject.
So today I find php-wordpress-vite-assets which seems to do this just fine, but I'm noticing it doesn't handle the Vite client at all for running my Vite dev server.
I combined the two to achieve what I wanted, and seems to work fine but it would be nice if this package included Vite client injection so I could just depend on this package.
Here's what I got
Vite.lib.php
<?php
class Vite {
/**
* Flag to determine whether hot server is active.
* Calculated when Vite::initialise() is called.
*
* @var bool
*/
private static bool $isHot = false;
/**
* The URI to the hot server. Calculated when
* Vite::initialise() is called.
*
* @var string
*/
private static string $server;
/**
* The path where compiled assets will go.
*
* @var string
*/
private static string $buildPath = 'build';
/**
* Manifest file contents. Initialised
* when Vite::initialise() is called.
*
* @var array
*/
private static array $manifest = [];
public static function getHotState(): bool
{
return static::$isHot;
}
/**
* To be run in the header.php file, will check for the presence of a hot file.
*
* @param string|null $buildPath
* @param bool $output Whether to output the Vite client.
*
* @return string|null
* @throws Exception
*/
public static function init(string $buildPath = null, bool $output = true): string|null
{
static::$isHot = file_exists(static::hotFilePath());
// have we got a build path override?
if ($buildPath) {
static::$buildPath = $buildPath;
}
// are we running hot?
if (static::$isHot) {
static::$server = file_get_contents(static::hotFilePath());
$client = static::$server . '/@vite/client';
// if output
if ($output) {
printf(/** @lang text */ '<script type="module" src="%s"></script>', $client);
}
return $client;
}
// we must have a manifest file...
if (!file_exists($manifestPath = static::buildPath() . '/manifest.json')) {
throw new Exception('No Vite Manifest exists. Should hot server be running?');
}
// store our manifest contents.
static::$manifest = json_decode(file_get_contents($manifestPath), true);
return null;
}
/**
* Enqueue the module
*
* @param string|null $buildPath
*
* @return void
* @throws Exception
*/
public static function enqueue_module(string $buildPath = null): void
{
// we only want to continue if we have a client.
if (!$client = Vite::init($buildPath, false)) {
return;
}
// enqueue our client script
wp_enqueue_script('vite-client',$client,[],null);
// update html script type to module wp hack
Vite::script_type_module('vite-client');
}
/**
* Return URI path to an asset.
*
* @param $asset
*
* @return string
* @throws Exception
*/
public static function asset($asset): string
{
if (static::$isHot) {
return static::$server . '/' . ltrim($asset, '/');
}
error_log($asset);
if (!array_key_exists($asset, static::$manifest)) {
throw new Exception('Unknown Vite build asset: ' . $asset);
}
// look for chunked css
if (array_key_exists('css', static::$manifest[$asset])) {
return implode('/', [ get_stylesheet_directory_uri(), static::$buildPath, static::$manifest[$asset]['css'][0] ]);
}
return implode('/', [ get_stylesheet_directory_uri(), static::$buildPath, static::$manifest[$asset]['file'] ]);
}
/**
* Internal method to determine hotFilePath.
*
* @return string
*/
private static function hotFilePath(): string
{
return implode('/', [static::buildPath(), 'hot']);
}
/**
* Internal method to determine buildPath.
*
* @return string
*/
private static function buildPath(): string
{
return implode('/', [get_stylesheet_directory(), static::$buildPath]);
}
/**
* Return URI path to an image.
*
* @param $img
*
* @return string|null
* @throws Exception
*/
public static function img($img): ?string
{
try {
// set the asset path to the image.
$asset = 'resources/img/' . ltrim($img, '/');
// if we're not running hot, return the asset.
return static::asset($asset);
} catch (Exception $e) {
// handle the exception here or log it if needed.
// you can also return a default image or null in case of an error.
return $e->getMessage(); // optionally, you can retrieve the error message
}
}
/**
* Update html script type to module wp hack.
*
* @param $scriptHandle bool|string
* @return mixed
*/
public static function script_type_module(bool|string $scriptHandle = false): string
{
// change the script type to module
add_filter('script_loader_tag', function ($tag, $handle, $src) use ($scriptHandle) {
if ($scriptHandle !== $handle) {
return $tag;
}
// return the new script module type tag
return '<script type="module" src="' . esc_url($src) . '" id="' . $handle . '-js"></script>';
}, 10, 3);
// return false
return false;
}
}
functions.php
use Idleberg\WordPress\ViteAssets\Assets;
// enqueue the Vite module
Vite::enqueue_module();
if (Vite::getHotState()) {
$cssUri = Vite::asset('src/theme.css');
$jsUri = Vite::asset('src/theme.js');
add_action('wp_enqueue_scripts', function () use ($jsUri, $cssUri) {
wp_enqueue_style('theme-style', $cssUri, [], null, 'screen');
wp_enqueue_script('theme-script', $jsUri, [], null, false);
});
Vite::script_type_module('theme-script');
} else {
$baseUrl = get_template_directory_uri() . '/build/';
$manifest = "build/manifest.json";
$jsEntry = "src/theme.js";
$cssEntry = "src/theme.css";
$viteAssets = new Assets($manifest, $baseUrl);
$viteAssets->inject($jsEntry);
$viteAssets->inject($cssEntry);
}
Just wanted to hear thoughts on this.
Thanks!
@james0r By curiosity, are you using a JS framework (ex: Vue) or not at all? And are you using vite v5 or v4? My incomprehension (#16) seems to be related with your FR
I tried using your code aswell but i have multiple concerns:
- your hot file path
/hotdoesn't exists ?? so i get an exception (Vite doesn't generate a/hotpath, i don't know what you put in there) 😄 - you're looking for
manifest.jsonbut not in.vitefolder which is strange to me (maybe because it's not Vite 5?) - get 404 error when trying to load
@vite/client - Is there a missing example related to
initfunction? (because you say to run it insideheader.phpso i tried usingwp_headhook infunctions.php)
@DamChtlv No framework. I'm using Laravel Vite that creates the /hot file when in dev mode. Using Vite v4.5. Vite config is as follows
import { defineConfig } from 'vite'
import laravel from 'laravel-vite-plugin'
import fs from 'fs'
import { resolve } from 'path'
export default defineConfig({
base: '',
build: {
emptyOutDir: true,
manifest: true,
outDir: 'build',
assetsDir: 'assets'
},
server: {
host: "0.0.0.0",
https: false,
hmr: {
host: 'localhost',
},
},
css: {
postcss: {
plugins: [
require('postcss-import'),
require('tailwindcss/nesting'),
require('tailwindcss')(resolve(__dirname, './tailwind.config.js')),
require('autoprefixer')
]
}
},
plugins: [
laravel({
publicDirectory: 'build',
input: [
'src/theme.js',
'src/theme.css',
// 'src/scss/theme.scss'
],
refresh: [
'*/**/**.php'
]
})
],
resolve: {
alias: [
{
find: /~(.+)/,
replacement: process.cwd() + '/node_modules/$1'
},
]
}
})
Not sure what you mean about missing an init function. When in dev mode, scripts are injected here in Vite.lib.php
public static function enqueue_module(string $buildPath = null): void
{
// we only want to continue if we have a client.
if (!$client = Vite::init($buildPath, false)) {
return;
}
if (!is_admin()) {
// enqueue our client script
wp_enqueue_script('vite-client',$client,[],null);
}
// update html script type to module wp hack
Vite::script_type_module('vite-client');
}
and when in production mode they're injected via idleberg's package here via the inject() method
$viteAssets->inject($jsEntry, [
'action' => 'wp_head',
'integrity' => false
]);
$viteAssets->inject($cssEntry, [
'action' => 'wp_head',
'integrity' => false
]);
Thank you for taking time to answer!
I finally managed to make it work (after digging a lot of vite config, github repos, try & errors haha) I had few incomprehensions coming from webpack / gulp which is why i didn't understand how vite worked behind the scene. You're using laravel vite which also confused me 😅 I ended up creating my own integration in the end
Otherwise your proposal seems great :)
What's the relationship between Laravel Vite and the script linked in the initial post? Does one depend on the other? Is it the same script or a stripped down version?
I have also been looking to get this to work with WordPress and Svelte and have run into similar issues of not having a manifest file in development mode.
This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 14 days.
This issue was closed because it has been stalled for 14 days with no activity.