Posted on under Laravel by Owen Conti.
Stop, please read this before continuing!
As of Laravel 9.19, Laravel uses Vite to compile assets with official support. If you're using a Laravel version above 9.19, please do not use this guide and instead follow the official Laravel documentation.
This article was written before Laravel had official support for Vite.
In this guide, we'll replace Laravel Mix with Vite in a Laravel Jetstream (Inertia/Vue) application.
Vite is a build tool created by Evan You (creator of Vue) which utilizes the availability of native ES modules in the browser. Read more about Vite on the Why Vite page.
NOTE: This is my first time using Vite. I do not have a full understanding of Vite at the time of writing this post. If you see anything incorrect with this setup, please let me know on Twitter, @owenconti.
If you want to get up and running right away, consider using the Laravel Vite package created by Enzo Innocenzi which is an opinionated setup that handles everything for you.
Vite recommends not omitting file extensions for custom import types, ie:
.vue
files. https://vitejs.dev/config/#resolve-extensionsThis means you should ensure all of your imports use the
.vue
extension throughout your codebase.
Install npm dependencies:
1npm install --save-dev vite @vitejs/plugin-vue dotenv @vue/compiler-sfc
Uninstall Laravel Mix dependency and remove the config file:
1npm uninstall laravel-mix2rm webpack.mix.js3rm webpack.config.js
Setup PostCSS:
1// postcss.config.js2 3module.exports = {4 plugins: [5 require('postcss-import'),6 require('tailwindcss')7 ]8}
Create
vite.config.js
file:
1import vue from '@vitejs/plugin-vue'; 2const { resolve } = require('path'); 3const Dotenv = require('dotenv'); 4 5Dotenv.config(); 6 7const ASSET_URL = process.env.ASSET_URL || ''; 8 9export default {10 plugins: [11 vue(),12 ],13 14 root: 'resources',15 base: `${ASSET_URL}/dist/`,16 17 build: {18 outDir: resolve(__dirname, 'public/dist'),19 emptyOutDir: true,20 manifest: true,21 target: 'es2018',22 rollupOptions: {23 input: '/js/app.js'24 }25 },26 27 server: {28 strictPort: true,29 port: 300030 },31 32 resolve: {33 alias: {34 '@': '/js',35 }36 },37 38 optimizeDeps: {39 include: [40 'vue',41 '@inertiajs/inertia',42 '@inertiajs/inertia-vue3',43 '@inertiajs/progress',44 'axios'45 ]46 }47}
Ensure environment variables are set:
1// .env2 3// Running locally4APP_ENV=local5ASSET_URL=http://localhost:30006 7// Running production build8APP_ENV=production9ASSET_URL=https://your-asset-domain.com
Install Laravel Vite Manifest PHP package to include Vite's output files from the generated manifest:
1composer require ohseesoftware/laravel-vite-manifest
Add the Blade directive from the package which includes the generated assets:
1// app.blade.php2 3<head>4 // ... rest of head contents here5 @vite6</head>
Add npm scripts to run Vite:
1// package.json2 3"scripts": {4 "start": "vite",5 "production": "vite build"6},
Import your
.css
file inside your entry
.js
file:
1// app.js2 3import '../css/app.css';
Make a few minor changes for Inertia:
1// Add the polyfill for dynamic imports to the top of your entry .js file 2 3import 'vite/dynamic-import-polyfill'; 4 5// Change how dynamic pages are loaded 6 7const pages = import.meta.glob('./Pages/**/*.vue'); 8 9// Update the `resolveComponent` logic10 11resolveComponent: name => {12 const importPage = pages[`./Pages/${name}.vue`];13 14 if (!importPage) {15 throw new Error(`Unknown page ${name}. Is it located under Pages with a .vue extension?`);16 }17 18 return importPage().then(module => module.default);19}
That's the quick version. If you want to understand more about each step, keep reading.
Here's what we're going to setup:
Replace a default Laravel Mix setup
Compile JS (Vue)
Compile CSS (Tailwind)
If your existing Laravel Mix is more complicated than that, your mileage may vary with this guide.
We'll setup both a Vue 2 version and a Vue 3 version.
To start off, we need to create a
vite.config.js
file in the root of the repo.
Vue 2:
1import { createVuePlugin as Vue2Plugin } from 'vite-plugin-vue2'; 2const { resolve } = require('path'); 3const Dotenv = require('dotenv'); 4 5Dotenv.config(); 6 7const ASSET_URL = process.env.ASSET_URL || ''; 8 9export default {10 plugins: [11 Vue2Plugin(),12 ],13 14 root: 'resources',15 base: `${ASSET_URL}/dist/`,16 17 build: {18 outDir: resolve(__dirname, 'public/dist'),19 emptyOutDir: true,20 manifest: true,21 target: 'es2018',22 rollupOptions: {23 input: '/js/app.js'24 }25 },26 27 server: {28 strictPort: true,29 port: 300030 },31 32 resolve: {33 alias: {34 '@': '/js',35 }36 },37 38 optimizeDeps: {39 include: [40 'vue',41 'portal-vue',42 '@inertiajs/inertia',43 '@inertiajs/inertia-vue',44 '@inertiajs/progress',45 'axios'46 ]47 }48}
Vue 3:
1import vue from '@vitejs/plugin-vue'; 2const { resolve } = require('path'); 3const Dotenv = require('dotenv'); 4 5Dotenv.config(); 6 7const ASSET_URL = process.env.ASSET_URL || ''; 8 9export default {10 plugins: [11 vue(),12 ],13 14 root: 'resources',15 base: `${ASSET_URL}/dist/`,16 17 build: {18 outDir: resolve(__dirname, 'public/dist'),19 emptyOutDir: true,20 manifest: true,21 target: 'es2018',22 rollupOptions: {23 input: '/js/app.js'24 }25 },26 27 server: {28 strictPort: true,29 port: 300030 },31 32 resolve: {33 alias: {34 '@': '/js',35 }36 },37 38 optimizeDeps: {39 include: [40 'vue',41 '@inertiajs/inertia',42 '@inertiajs/inertia-vue3',43 '@inertiajs/progress',44 'axios'45 ]46 }47}
Let's run through each section.
The
plugins
section tells Vite how to handle
.vue
files.
1// Vue 2 2import { createVuePlugin as Vue2Plugin } from 'vite-plugin-vue2'; 3 4export default { 5 plugins: [ 6 Vue2Plugin(), 7 ] 8} 9 10// Vue 311import vue from '@vitejs/plugin-vue';12 13export default {14 plugins: [15 vue(),16 ]17}
The
root
option tells Vite what directory is the root directory of our application. Assets will be output relative from this directory. For example,
resources/js/app.js
will be output as
js/app.js
.
1// ...2 3root: 'resources',
The
base
option tells Vite where the assets will be served from once deployed. This is equivalent to the
publicPath
option in Webpack. We pull the
ASSET_URL
from the environment file so that our build uses the correct path when deployed to a CDN.
Note: Make sure the
ASSET_URL
is set correctly in your
.env
file when building. If you're deploying with Vapor, Vapor will set the
ASSET_URL
for you.
1// ...2 3base: `${ASSET_URL}/dist/`,
The
build
section tells Vite how the application should be built.
outDir
- The output directory that the application should be built to.
emptyOutDir
- We set this to true to suppress a warning from Vite that says we are emptying the
outDir
when it exists outside project root (
resources
).
manifest
- Tells Vite to publish a manifest file, which we'll use in production builds to find the correct file hash names.
target
- Tells Vite which browsers should be supported, you can read more on Vite's website.
rollupOptions
- These are specific options you can provide to Rollup (which Vite uses to bundle the application). In our case, we need to provide Rollup with our main entry file.
1const { resolve } = require('path'); 2 3// ... 4 5build: { 6 outDir: resolve(__dirname, 'public/dist'), 7 emptyOutDir: true, 8 manifest: true, 9 target: 'es2018',10 rollupOptions: {11 input: '/js/app.js'12 }13}
The
server
section instructs Vite on how to start the development server.
strictPort
- Forces Vite to start on the port we specified. Vite will exit if the port is in use as opposed to incrementing the port number which is default behaviour.
port
- Which port the Vite development server should run on.
1server: {2 strictPort: true,3 port: 30004},
The
resolve
section is optional. In my case, I am using it to alias
@
to
/js
.
1resolve: {2 alias: {3 '@': '/js',4 }5},
We need to tell Vite to pre-bundle the dependencies that do not ship a ESM version. The array you pass here will vary based on the dependencies in your project.
Note:
portal-vue
is not necessary in Vue 3 projects.
1optimizeDeps: { 2 include: [ 3 'vue', 4 'portal-vue', // Vue 2 5 '@inertiajs/inertia', 6 '@inertiajs/inertia-vue', // Vue 2 7 '@inertiajs/inertia-vue3', // Vue 3 8 '@inertiajs/progress', 9 'axios'10 ]11}
You'll need to make sure you install the following JS dependencies:
1// Vue 22npm install --save-dev vite vite-plugin-vue2 dotenv @vue/compiler-sfc3 4// Vue 35npm install --save-dev vite @vitejs/plugin-vue dotenv @vue/compiler-sfc
In order to compile Tailwind, we need to move our PostCSS configuration from
webpack.mix.js
into a dedicated
postcss.config.js
file, which resides at the root of your repo:
1// postcss.config.js2 3module.exports = {4 plugins: [5 require('postcss-import'),6 require('tailwindcss')7 ]8}
Here's my full
app.js
that configures Inertia:
1import 'vite/dynamic-import-polyfill'; 2 3import { createApp, h } from 'vue'; 4import { App as InertiaApp, plugin as InertiaPlugin } from '@inertiajs/inertia-vue3'; 5import { InertiaProgress } from '@inertiajs/progress'; 6 7import axios from 'axios'; 8axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; 9 10import '../css/app.css';11 12InertiaProgress.init();13 14const app = document.getElementById('app');15 16const pages = import.meta.glob('./Pages/**/*.vue');17 18createApp({19 render: () =>20 h(InertiaApp, {21 initialPage: JSON.parse(app.dataset.page),22 resolveComponent: name => {23 const importPage = pages[`./Pages/${name}.vue`];24 if (!importPage) {25 throw new Error(`Unknown page ${name}. Is it located under Pages with a .vue extension?`);26 }27 return importPage().then(module => module.default)28 }29 }),30})31 .mixin({ methods: { route } })32 .use(InertiaPlugin)33 .mount(app);
The asset path is controlled via the standard Laravel
ASSET_URL
environment variable. However, we don't provide a default, so it must be set in order for everything to work.
We need to change a couple of environment variables based on if we're running the local development server vs if we're running a production build:
1// Running locally2APP_ENV=local3ASSET_URL=http://localhost:30004 5// Running production build6APP_ENV=production7ASSET_URL=https://your-asset-domain.com
I wrote a very simple Laravel package to pull the contents of the Vite manifest and include them in your Blade view. The main logic for the package is sourced from https://github.com/andrefelipe/vite-php-setup.
The package uses the
APP_ENV
and
ASSET_URL
environment variables to decide how to load the assets.
1composer require ohseesoftware/laravel-vite-manifest
Add the Blade directive to include Vite's compiled assets:
1// app.blade.php2 3<head>4 // ... rest of head contents here5 @vite6</head>
Don't forget to remove Laravel Mix and its configuration file:
1npm uninstall laravel-mix
Hopefully you found this article useful! If you did, share it on X!
Found an issue with the article? Submit your edits against the repository.