How to Integrate Vite With WordPress Development?
Discover how to integrate Vite.js with the WordPress development. This detailed guide explains the benefits of using Vite, provides practical steps and insights to build an own integration, making theme and plugin development in WordPress more efficient.
We've all been there, putting everything into a single file. It's just a universal developer experience, as well as refreshing the page manually after each change in the code, isn't it? Hovewer, what if I told you there's a better way? The way that will help you gaining more with less effort while building assets to production.
What if you could improve code readability by using multiple files and not having to worry about optimizing and loading them? What if you could build the look of your app and see the results in the browser faster than the blink of an eye, avoiding wasting time on tedious page refreshes? What if you didn't have to worry about old browser support?
What is Vite?
Vite is widely known in the frontend world, particularly with frameworks like Vue. It provides an extremely fast development environment and bundles code for production. Developers love it because it speeds up and makes development process more efficient.
It makes using modern browser features easier and organizes the pieces of your assets in the way help your website load faster without additional effort. It's like having assistant that will pack your suitcase before flight faster and more efficient, while you can focus on more friendly things, like taking a shower. It also has a cool feature called Hot Module Reload (HMR) which allows seeing the results of your work faster.
Why to use Vite with WordPress?
Since Vite has been created to build frontend apps it isn't limited to them. It's flexible enough to be used in the backend too, making it a handy tool for all sorts of web apps. Laravel for example, uses Vite as default bundler so we can do the same in WordPress. But actually why would we want to? Let's analyze the most important aspects.
Be aware, that this part is not limited just to Vue. It's more like general description why it is worth to use bundling tools in WordPress development.
#1 Bundling helps improving code readability
Let's get back to your trip. To make it easier to pack or look for things after arrival, you group them and spread them out on the bed. Sure, it takes up more space, but it's just easier for you. Finally, when you have everything, you call Vite and say "pack it for me". The Vite will do it faster and better. You'll just have to take luggage to airport. Do you see analogy? It's just like building the code and packing assets to deploy them on prod.
Breaking down the source code into smaller self-efficient modules makes the codebase more organized, user-friendly, and easier to maintain. Smaller CSS/JS files with specific functionality are not only easier to read but also better for the teamwork. Vite allows merging (bundling) them into one or more files so we don't need to enqueue them one by one manually. We can enqueue just a few production-ready assets instead.
#2 Bundling improves application performance
When we go on a trip with my wife, she usually has to take more than she can fit in the suitcase. What husband does not know this? I usually dream about a vacuum cleaner and vacuum bags in such situations to reduce the luggages' volume and pay less, or at least please my wife. And that's what the Vite does in the bundling process.
When Vite bundles multiple smaller files into one, it additionally performs step called minification. It typically includes removing comments, extra spaces, line breaks, and shortening variable and function names resulting in smaller file sizes. Futhermore, it compresses output files what speeds up the loading times and reduces bandwidth. So it just produces the code that does the same things as earlier, but with smaller size.
#3 Bundling improves old-browsers support
From time to time I go on vacation with grandparents. As we all know, they mostly don't have a sence of humors as we have and in some cases we need to explain memes that we laugh on to them. Vite can make this for us!
JS/CSS continually introduce new features, but not all are supported by older browsers. Vite allows configuring tools called polyfills which reduce compatibility problems while creating modern code. They translates modern features to the code understandable by old browsers during the build process. It results in less work to do with better results.
#4 Bundling improves development experience
Vite supports feature called Hot Module Replacement (HMR). Unlike standard refreshing the page to see the results, HMR tries to update only the parts of code that have changed, preserving the application's state and avoiding refresh resulting in instantly seeing the results in the browser as we make them in the code.
For example, making the changes in CSS files is immediately reflected in the browser window after file save without refreshing the page. It works the same for JS files, but... The code must be . Otherwise, changes to JS files will trigger a automatic refresh, a process known as Hot Reload.
How to organize assets in the codebase?
Let's start by organizing assets and setting up the directory structure for them. If you recall from my previous post I already created the resources
directory for storing our Blade
views. Now, I'll take a step further and create a few additional directories.
To keep things super simple and easy to access, I organize these assets by type. This way, finding what I need is easy whenever I require it. I've also created a few files and filled them with some to have sources that can be used for building process.
├── resources
│ ├── fonts
│ │ ├── **/*.woff2
│ ├── images
│ │ ├── **/*.*
│ ├── scripts
│ │ ├── **/*.js
│ ├── styles
│ │ ├── **/*.scss
To minimize code duplication, I set up a few constants for easier access to URIs and paths. While I could have used functions like get_template_directory
offered by WordPress, I prefer to keep the codebase flexible, not confined solely to themes. In upcoming releases, you'll see its ability to function effectively as a plugin as well.
define('FM_VERSION', '0.1.1');
define('FM_PATH', dirname(__FILE__));
define('FM_URI', home_url(str_replace(ABSPATH, '', FM_PATH)));
define('FM_HMR_HOST', 'http://localhost:5173');
define('FM_ASSETS_PATH', FM_PATH . '/dist');
define('FM_ASSETS_URI', FM_URI . '/dist');
define('FM_RESOURCES_PATH', FM_PATH . '/resources');
define('FM_RESOURCES_URI', FM_URI . '/resources');
How to build assets with Vite in WordPress?
Now, it's time to set up Vite for my project to build production release. I use Yarn as a package manager, but feel free to use what you like. Vite is flexible enough to handle various options. Oh, and because I work with Sass for styling, I install that too.
yarn add vite sass --dev
Next up is configuration. When running vite
from the command line, it automatically looks for a file named vite.config.js
inside project root. This file allows defining how Vite operates within the project so let's bundling process.
import path from 'path';
import { defineConfig } from 'vite';
const ROOT = path.resolve('../../../')
const BASE = __dirname.replace(ROOT, '');
export default defineConfig({
base: process.env.NODE_ENV === 'production' ? `${BASE}/dist/` : BASE,
build: {
manifest: true,
assetsDir: '.',
outDir: `dist`,
emptyOutDir: true,
sourcemap: true,
rollupOptions: {
input: [
'resources/scripts/scripts.js',
'resources/styles/styles.scss',
],
output: {
entryFileNames: '[hash].js',
assetFileNames: '[hash].[ext]',
},
},
},
plugins: [
{
name: 'php',
handleHotUpdate({ file, server }) {
if (file.endsWith('.php')) {
server.ws.send({ type: 'full-reload' });
}
},
},
],
});
To generate production build I need to add a new tasks to package.json
file and run yarn build
comand in the project root. The assets with manifest.json
mapping file in the dist
directory.
{
"scripts": {
"build": "vite build",
"dev": "vite"
}
}
The production bundle assumes support for modern JavaScript. By default, Vite targets browsers which support the native ES Modules, native ESM dynamic import, and import.meta, that's why we need to load scripts with type="module"
attribute.
Chrome >=87
Firefox >=78
Safari >=14
Edge >=88
We can specify custom targets via build.target
config, but keep in mind that Vite only handles syntax transforms and does not cover polyfills. If you want to support older browsers than they, you can follow the steps described in Vite documentation or
How to enqueue assets generated by Vite?
The build process is running smoothly, and the output files are generated in the dist
directory. Now, it's time to enqueue them in WordPress. I'll handle this within the newly created FM\Assets
module designed to manage assets in my project.
├── app
│ ├── Assets
│ │ ├── Assets.php
│ │ ├── Resolver.php
The output file names are hashed so I need to search for the final URL in the file. To split responsibilities and improve core readability by moving backend details to separate file I decide to use what in simple words allows to reuse code in classes.
I could skip trait usage, initialize new Resolver
instance in the class property and use it for enqueueing assets - what seems to be more correct approach - but I wanted to show how the traits can be used. I build Resolver
trait that works .
namespace FM\Assets;
trait Resolver
{
private array $manifest = [];
/**
* @action wp_enqueue_scripts 1
*/
public function load(): void
{
$path = fm()->config()->get('manifest.path');
if (empty($path) || ! file_exists($path)) {
wp_die(__('Run <code>npm run build</code> in your application root!', 'fm'));
}
$this->manifest = json_decode(file_get_contents($path), true);
}
/**
* @filter script_loader_tag 1 3
*/
public function module(string $tag, string $handle, string $url): string
{
if ((false !== strpos($url, FM_HMR_HOST)) || (false !== strpos($url, FM_ASSETS_URI))) {
$tag = str_replace('<script ', '<script type="module" ', $tag);
}
return $tag;
}
private function resolve(string $path): string
{
$url = '';
if (! empty($this->manifest["resources/{$path}"])) {
$url = FM_ASSETS_URI . "/{$this->manifest["resources/{$path}"]['file']}";
}
return apply_filters('fm/assets/resolver/url', $url, $path);
}
}
Now, I set up a main module controller, integrate the Resolver
trait there and enqueue assets using WordPress functions - wp_enqueue_style
or wp_enqueue_script
.
namespace FM\Assets;
use FM\Assets\Resolver;
class Assets
{
use Resolver;
/**
* @action wp_enqueue_scripts
*/
public function front(): void
{
wp_enqueue_style('theme', $this->resolve('styles/styles.scss'), [], fm()->config()->get('version'));
wp_enqueue_script('theme', $this->resolve('scripts/scripts.js'), [], fm()->config()->get('version'));
}
}
Here, lies a great example how I like to simplicity the code for other team members with keeping the code clean. Assets are often managed by frontent team and it's easier for them to do this in simple class like above rather than in something . I don't bother them with more complicated backend mechanisms which I hide in traits.
How to integrate Vite HMR in WordPress?
Running vite
command in the project root fires up dev server, typically available at http://localhost:5173
which needs to be integrated with WordPress. This server takes care of serving assets and handling Hot Module Replacement (HMR).
I start from extending app config with HMR setup. I set server host, client url, base path, and the value that specifies if the dev server is running. I set this by checking if development mode is enabled and if the vite server is available with wp_remote_get
.
$this->config = [
'hmr' => [
'host' => FM_HMR_HOST,
'client' => FM_HMR_HOST . '/@vite/client',
'base' => str_replace(home_url(), FM_HMR_HOST, FM_RESOURCES_URI),
'active' => wp_get_environment_type() === 'development' && ! is_wp_error(wp_remote_get(FM_HMR_HOST)),
],
];
Then, I need to include vite client and inform WordPress that it should use the assets provided by the vite server rather than the dist directory. Vite, being an external tool, should have minimal impact on my codebase, so I create a dedicated module that handles and only when dev server is running.
namespace FM\Integrations;
class Vite
{
/**
* @action wp_head 1
*/
public function client(): void
{
echo '<script type="module" src="' . fm()->config()->get('hmr.client') . '"></script>';
}
/**
* @filter fm/assets/resolver/url 1 2
*/
public function url(string $url, string $path): string
{
return fm()->config()->get('hmr.base') . "/{$path}";
}
}
This design choice adheres to the Open-Closed Principle, allowing me to easily switch to a different tool without modifying tons of core files. When I resign from using Vite, I can remove integration class, it's initialization without affecting Assets
core module at all. I just unplug this small brick from the building without modifying general structure.
And that's all! If you've stayed with me until now, please let me know what you think about this implementation! I've checked several WordPress starters that support Vite, and all of them seem unnecessarily complex. This article confirms that we can integrate Vite much more simply. In the next articles, we'll extend the integration, so stay tuned. Remember, all the code we create here is available in the public repository.
Looking for a developer who
truly cares about your business?
My team and I provide expert consultations, top-notch coding, and comprehensive audits to elevate your success.
Feedback
How satisfied you are after reading this article?