DEV Community

Cover image for Using Vite with Inertia — Laravel, Vue & Tailwind
Bertug Korucu for Kodeas

Posted on

Using Vite with Inertia — Laravel, Vue & Tailwind

TLDR; 5800ms (Laravel Mix) to 224ms (Vite⚡️) on hot-reload!

We’ve been using Laravel-Mix for years and it’s been doing pretty good. However, recently we decided to build a project using Inertia.js (which was an awesome decision).

As the project started to grow, the development became a pain to wait for the webpack compiling.

Then, we decided to give Vite (from Vue’s creator Evan You) and the results were… ASTONISHING! I’ve been seeing Vite around in Twitter, but to be honest with you, I was not expecting THIS MUCH OF SPEED! 🚀

Laravel-Mix was getting too so slow. 🐢

Benchmark Test on Hot Reloading (with 16" MBP 64gb ram, 2.4 GHz 8-Core Intel Core i9)

|    Compilation     | Laravel Mix | Vite  |
|--------------------|-------------|-------|
| Initial Compile    | 13257ms     | 636ms |
| Change Reflection  | 5848ms      | 224ms |
Enter fullscreen mode Exit fullscreen mode

That’s like ‘20x for initial compilation’ and ‘25x when code change’ 😲

We are fascinated by results, so let me tell you how to set it up, so you can try it out too.

Migrating To Vite⚡️

  • First, you’ll need to install Vite:
npm install vite
Enter fullscreen mode Exit fullscreen mode
  • Then, install Tailwind
npm i tailwindcss postcss autoprefixer -D
Enter fullscreen mode Exit fullscreen mode
  • Create “vite.config.js” and “postcss.config.js” in your project base
const { resolve } = require('path');
import vue from '@vitejs/plugin-vue';

export default ({ command }) => ({
  base: command === 'serve' ? '' : '/dist/',
  publicDir: 'fake_dir_so_nothing_gets_copied',
  build: {
    manifest: true,
    outDir: resolve(__dirname, 'public/dist'),
    rollupOptions: {
      input: 'resources/js/app.js',
    },
  },

  plugins: [vue()],

  resolve: {
    alias: {
      '@': resolve('./resources/js'),
    },
  },
});
Enter fullscreen mode Exit fullscreen mode
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  }, 
}
Enter fullscreen mode Exit fullscreen mode

For the sake of completeness, here is Tailwind config (JIT is amazing as well!)

module.exports = {
  mode: "jit",
  purge: ['./resources/**/*.{js,jsx,ts,tsx,vue,blade.php}'],
  theme: {},
  variants: {},
  plugins: [],
}
Enter fullscreen mode Exit fullscreen mode
  • And finally, you need to configure your app.js for Vite to work with Inertia.js. (The production compiling part kept me in the dark for a few hours)
import 'vite/dynamic-import-polyfill';
import '../css/app.css';
import { createApp, h } from 'vue'
import { App, plugin } from '@inertiajs/inertia-vue3'

let asyncViews = () => {
  return import.meta.glob('./Pages/**/*.vue');
}

const app = createApp({
  render: () => h(App, {
    initialPage: JSON.parse(el.dataset.page),
        resolveComponent: async name => {
            if (import.meta.env.DEV) {
                return (await import(`./Pages/${name}.vue`)).default;
            } else {
                let pages = asyncViews();
                const importPage = pages[`./Pages/${name}.vue`];
                return importPage().then(module => module.default);
            }
        }
    })
})
Enter fullscreen mode Exit fullscreen mode

Few things to keep in mind are:

  • You can’t use require("file") syntax, so you always need to use import * from file.js

  • You need to specify file extensions when importing Vue components, like import FormInput from "@/Shared/Form/FormInput.vue"

  • "app.js" is the only point of entry for your app, so you need to import your app.css file in your app.js.

…and your front-end is ready 🎉


Setting up Laravel and package.json scripts

I wanted to run “hot reloading” as well as “production” environment interchangeably in my local, so I came up with the below solution. (If you figure out a better way, I’d be happy to hear)

Vite in 'dev mode' creates a local server in https://localhost:3000 (which can be configured in vite.config.js) whereas in ‘production mode’, it creates files in 'public/dist'.

  • Edit your ‘package.json’ file accordingly:
"scripts": {
    "predev": "printf \"dev\" > public/hot",
    "dev": "vite",
    "preprod": "printf \"prod\" > public/hot",
    "prod": "vite build"
},
Enter fullscreen mode Exit fullscreen mode

npm run vite is hot reloading itself and npm run dev is just for alias. The “pre” hooks are used to create a file in public directory so that the backend can figure out which mode is running.

Finally, you need to create a helper to resolve the path in your blade
— just like Laravel Mix’s {{ mix('/js/app.js') }} helper.

You can create this php file in 'app/Helpers/vite.php' (or anywhere you like)

<?php

use Illuminate\Support\HtmlString;

if (! function_exists('vite_assets')) {
    /**
     * @return HtmlString
     * @throws Exception
     */
    function vite_assets(): HtmlString
    {
        $devServerIsRunning = false;

        if (app()->environment('local')) {
            try {
                $devServerIsRunning = file_get_contents(public_path('hot')) == 'dev';
            } catch (Exception) {}
        }

        if ($devServerIsRunning) {
            return new HtmlString(<<<HTML
            <script type="module" src="http://localhost:3000/@vite/client"></script>
            <script type="module" src="http://localhost:3000/resources/js/app.js"></script>
        HTML);
        }
        $manifest = json_decode(file_get_contents(
            public_path('dist/manifest.json')
        ), true);
        return new HtmlString(<<<HTML
        <script type="module" src="/dist/{$manifest['resources/js/app.js']['file']}"></script>
        <link rel="stylesheet" href="/dist/{$manifest['resources/js/app.js']['css'][0]}">
    HTML);
    }
}
Enter fullscreen mode Exit fullscreen mode
  • And include it to your composer.json
"autoload": {
    "psr-4": {...},
    "files": [
        "app/Helpers/vite.php"
    ]
},
Enter fullscreen mode Exit fullscreen mode

[make sure to run:composer dump-autoload]

And finally add it to your master.blade.php

<!DOCTYPE html>
<html>
<head>
    <!-- Stuff -->
    {{ vite_assets() }}
    <!-- More Stuff -->
</head>
Enter fullscreen mode Exit fullscreen mode

🏁 You are all set. Enjoy the super-speed compiling times ⚡️


I believe this will change your development experience as drastic as it did to me! 🚀

I’m really curious about your compiling speeds, please leave a comment. 💬

Top comments (11)

Collapse
 
simcha profile image
Simcha

Greetings

I created a new project based on this guide sebastiandedeyne.com/vite-with-lar...

Unfortunately there is no detailed explanation in the manual (last part) about inertia with react,

And I can in no way arrange between them, it already seems that everything works but this error returns:

Uncaught (in promise) Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
Enter fullscreen mode Exit fullscreen mode

You can check out the project here: github.com/sbc640964/laravel-vite-...

Collapse
 
nehadhiman6 profile image
Neha Dhiman • Edited

unable to add flowbite in my project

added this in tailwind.config.js

module.exports = {
    content: [
        './vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php',
        './vendor/laravel/jetstream/**/*.blade.php',
        './storage/framework/views/*.php',
        './resources/views/**/*.blade.php',
        './resources/js/**/*.vue',
    ],

    theme: {
        extend: {
            fontFamily: {
                sans: ['Nunito', ...defaultTheme.fontFamily.sans],
            },
            colors: {
                "primary-color": "var(--primary-color)",
                "secondary-color": "var(--secondary-color)"
            },
        },
    },

    plugins: [require('@tailwindcss/forms'), require('@tailwindcss/typography'),  require('flowbite/plugin')],
};

Enter fullscreen mode Exit fullscreen mode

in app.js

import 'flowbite';
Enter fullscreen mode Exit fullscreen mode

this is not working!

Also i am adding Vue plugins like this:


createInertiaApp({
    title: (title) => `${title} - ${appName}`,
    resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')),
    setup({ el, app, props, plugin }) {
        return createApp({ render: () => h(app, props) })
            .use(plugin)
            .use(ZiggyVue, Ziggy)
           .mixin({ components: { FilePond } })            // THIS
            .mount(el);
    },
});
Enter fullscreen mode Exit fullscreen mode

it is working fine but is it right way to add plugins. In previous version it was Vue.use(FilePond); // not working in vue 3

Collapse
 
rrrrando profile image
Rando Õispuu

Great that more and more people are using Vite. If you want to get rid of some extra code then have look at my blade file github.com/kristjanjansen/newstack...

Collapse
 
bertugkorucu profile image
Bertug Korucu • Edited

Good idea, but I wanted to have flexibility for running npm run prod and npm run hot simultaneously in my local. And I didn't want to have that logic in my blade. If you don't need much logic code, it's ideal to place it in blade indeed.

Collapse
 
fkranenburg profile image
Ferry Kranenburg

I got this error while following this article, any ideas? I started the app with 'npm run dev' and got this:

'printf' is not recognized as an internal or external command,

Collapse
 
fkranenburg profile image
Ferry Kranenburg

If anyone is running npm in command prompt, change script in package.json to this:

"scripts": {
    "predev": "echo dev > public/hot",
    "dev": "vite",
    "preprod": "echo prod > public/hot",
    "prod": "vite build"
}
Enter fullscreen mode Exit fullscreen mode

Because 'echo' adds a newline at the end of the file you also need to change vite.php this line:

$devServerIsRunning = str_contains(file_get_contents(public_path('hot')), 'dev');

Collapse
 
bertugkorucu profile image
Bertug Korucu

Yeah, that was the reason I used printf instead of echo

Collapse
 
mwangaben profile image
Benedict Mwanga • Edited

after this inertia-link stops working as the are not render as a tag on the browser

Collapse
 
bertugkorucu profile image
Bertug Korucu

It shouldn't be happening - we built a whole app with inertia and vite and it works like charm

Collapse
 
kevmul0929 profile image
kevmul0929

Does anyone get the error

Failed to resolve import "vite/dynamic-import-polyfill" from "resources\js\app.js"
Enter fullscreen mode Exit fullscreen mode

I can't seem to find the fix for this.

Collapse
 
mitchazj profile image
Mitchell Johnson

Probably too late for this answer to be directly useful but answering in case anyone else is googling: unless you have to support older browsers (IE11 for example) then it's safe to just delete the import from your app.js file.

The import is only there to add polyfill support for es6 modules and I suspect vite removed it.
If you need it back, take a look at the legacy plugin: github.com/vitejs/vite/tree/main/p...

You'll have to reconfigure the import, and change some settings depending on your specific support needs.
But if you don't need to support legacy browsers, easiest fix is just delete the line and don't worry about it :)