WordPress with Vite (Build/HMR)

6 minutes

read

I was just about to set up a new WordPress instance for my blog and run it on my local machine for development. I used my boilerplate theme with whom I set up all my latest WordPress themes in the last few years.

WP Boilerplate Theme (OOP)

I just open-sourced that on GitHub for people interested in checking it out, being inspired by or even using it. It’s based on Roots/Bedrock and following WordPress can be updated and managed via Composer. Absolutely basic things. Not a single website I’ve been running without that for the last 10 years.

Laravel Ecosystem

I’m always using tools from the Laravel ecosystem because they’re so simple, work out of the box, and are always based on the newest standards.

With that, I’m using Herd for all of my personal projects. If you don’t know it, check out the website. It’s a web server for your local machine with very simple configurations. It just works and is very customizable with all the good things you need for development.

Further, I’ve been using Laravel Mix in the past. Bundling based on Webpack. Super straightforward. But this time I didn’t want to use SCSS and switch to Vite. I intended to configure Vite manually within the WordPress setup and I was struggling hard with it. At some point, I got the compilation working, but not the Hot Reloading. I absolutely wanted that. So back I am in the Laravel ecosystem. Nothing bad about it. Just confirming again what great tools they’re building and helping the open-source community. So I pulled in Laravel Vite Plugin and things started to work pretty fast.

Following I explain what you have to do to get Vite running within a WordPress instance and hot module reloading working. Let’s get started.

Setup Vite

Install the Laravel Vite Plugin and configure it.

npm install laravel-vite-plugin --save-dev

After that, we create a vite.config.js file in our root directory and add the following content to it.

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import path from 'path';

export default defineConfig({
    plugins: [
        laravel({
            input: [
                'web/app/themes/mawi-theme/assets/scripts/main.js'
            ],
            publicDirectory: 'web/app/themes/mawi-theme/assets',
        }),
    ],
    resolve: {
        alias: {
            '@': path.resolve(__dirname, 'web/app/themes/mawi-theme/assets'),
        },
    },
    build: {
        assetsDir: '.',
    }
});

It already adds my assets and extends some basic things from the Laravel Vite plugin. It’s very customizable and you can do actually everything like in Vite, just with some goodies included, especially when it comes to CORS, SSL, and Hot Module reloading. That’s the reason I failed when trying to set things up with plain Vite and used the Laravel Vite Plugin in the end.

Add those scripts to your package.json to make it easy and consistent across all your projects to run certain tasks in NPM.

"scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
},

So what amazing stuff do we already have in place now? A lot 😉

Asset Bundling (Build)

The basic need is a proper build for deployment on our production server. We want all the JavaScript concatenated and minimized and also all our CSS compiled with PostCSS. Vite is supporting PostCSS out of the box. We just need to create a vite.config.js in the root directory and configure it correctly. Other preprocessors would be supported as well like SCSS or Stylus. Check out the documentation to adjust the Vite config.

We can run npm run build now and our files are compiled into a build folder within our assets folder. We get a JavaScript file, a CSS file and a manifest.json . That’s all we need at this point.

SSL working with Herd

Herd automatically supports Vite within the Laravel Vite plugin and things are just working. So activate SSL on your project within Herd and you don’t get annoying warnings from your browser saying the site is insecure.

Hot Module Reloading (Vite development server)

One thing we have to add to the ENV file is to set the APP_URL variable to our home URL, so the server is pointing to our actual homepage. After we set that, we can run npm run dev and Vite is spinning up everything we need. We have a Vite development server running at port 5173 (https://marcwieland.name:5173/). This is the base to make our hot module reloading work. That’s already working great if you get the following screen.

Everything on the side of Vite is working now.

Enqueue scripts/styles within WordPress

We now have to enqueue the proper files on your website so we can see our adjustments within the styles and scripts.

For that, we add the following file to the functions.php or the “Script” class in my boilerplate theme.

add_action('wp_enqueue_scripts', [$this, 'addScripts']);

public function addScripts()
{
    $isViteDevServer = get_theme_file_path('/assets/hot');
    if (MaWi::getDeploymentEnvironment() === 'DEVELOPMENT' && file_exists($isViteDevServer)) {
        $viteDevServer = getenv('APP_URL') . ':5173';
        wp_enqueue_script_module('vite-client', $viteDevServer . '/@vite/client', [], null);
        wp_enqueue_script_module('mawi-theme-scripts', $viteDevServer . '/web/app/themes/mawi-theme/assets/scripts/main.js', ['vite-client'], null);
    } else {
        $manifestPath = get_theme_file_path('/assets/build/manifest.json');
        if (file_exists($manifestPath)) {
            $manifest = json_decode(file_get_contents($manifestPath), true);
            $mainJsEntry = 'web/app/themes/mawi-theme/assets/scripts/main.js';
            if (isset($manifest[$mainJsEntry])) {
                $mainJs = $manifest[$mainJsEntry]['file'];
                wp_register_script('mawi-theme-scripts', get_stylesheet_directory_uri() . '/assets/build/' . $mainJs, [], false, false);
                wp_enqueue_script('mawi-theme-scripts');
  
                if (isset($manifest[$mainJsEntry]['css'])) {
                    foreach ($manifest[$mainJsEntry]['css'] as $cssFile) {
                        wp_enqueue_style('mawi-theme-style', get_stylesheet_directory_uri() . '/assets/build/' . $cssFile, [], '');
                    }
                }
            }
        }
    }
}

Change Styles/Scripts without refreshing the browser

Let’s split things up here a bit. We have two different scenarios. First, we have the development environment where we want to have our hot module reloading (HMR) working. That’s pretty straightforward actually after I figured out what to include. Laravel for example has a Vite directive within Blade which is doing all that automatically. Within WordPress, we have to read this functionality and also enqueue it within PHP.

The following two lines are generated by WordPress when npm run dev is running.

<script type="module" src="https://marcwieland.name:5173/@vite/client" id="vite-client-js-module"></script>
<script type="module" src="https://marcwieland.name:5173/web/app/themes/mawi-theme/assets/scripts/main.js" id="mawi-theme-scripts-js-module"></script>

Within our function, I check if the current environment is DEVELOPMENT and if there’s a hot file within the assets path. This means that our Vite development server is running. If this is the case we include the vite script within a type="module" attribute which is very important. That will do the magic for our hot module replacement. The second script will be our main entry.

Production asset build

For the production environment, we want an optimized build of all our assets. Those are listed within the manifest.json. In that environment, we enqueue different scripts within different hashes to bust the cache on every new build. For that to happen we read the manifest and enqueue things accordingly. That’s the code generated by WordPress using wp_enqueue_script and wp_enqueue_style. Pay attention to the file name with the hash at the end. This changes on every build fully automatically.

<link rel="stylesheet" id="mawi-theme-style-css" href="https://marcwieland.name/app/themes/mawi-theme/assets/build/main-DXmG2itA.css" type="text/css" media="all">
<script type="text/javascript" src="https://marcwieland.name/app/themes/mawi-theme/assets/build/main-DjS1c-Pc.js" id="mawi-theme-scripts-js"></script>