Published Sep 23, 2023

Last Updated Sep 23, 2023

Development With Laravel & Vite in Lando

Categories: Toolbox

Tags: #Lando , #Vite , #Laravel

By now, anyone who's started a new Laravel project knows Laravel has started using Vite out of the box instead of Laravel Mix, and Vite is freakin' awesome.

What's Vite?

Vite is a fast bundler and dev server package with built-in Hot Module Replacement support. Vite 3 was released mid 2020. I first saw it when the hype for v3 was being built.

What's Lando?

Lando is a Docker wrapper that helps create and orchestrate multiple containers for a project from a single tool. It also provides nice .lndo.site dev URLs out of the box.

You can configure your own services as separate containers, or you can use provided recipes that should have what's needed out of the box, though the recipes seem geared towards PHP projects mostly right now.

Laravel support for Vite

It looked so good, it became Laravel's default bundler before Vite 3 stable was released! ?

Taking a quick shortcut

I don't normally do this because it kinda goes against my writing style. But this time, I'm going to put what I did right here, explain what the config options do, and then summarize how I arrived at these.

Tada, configs! ?

First, Vite:

import {defineConfig} from 'vite'
import laravel from 'laravel-vite-plugin'
import * as fs from "fs"

export default defineConfig({
	server: {
		hmr: {
			host: '0.0.0.0',
		},
		host: '<my-project>.lndo.site',
		https: {
			cert: fs.readFileSync('/certs/cert.crt'),
			key: fs.readFileSync('/certs/cert.key'),
		},
		port: 24690,
	},

	plugins: [
		laravel({
			input: ['resources/css/app.css', 'resources/js/app.js'],
			refresh: true
		}),
	],
})

This is an overly simplified version of my config. The final config I actually built using environment vars, which made it messy to read.

Here the options I used:

server.hmr.host

This tells Vite what host the HMR client should look at. This is normally 'localhost'. Since Lando is already routing traffic for the project hostname to the right container, I used that.

server.host

This tells Vite what host the HMR server should use for its websocket. This is also normally 'localhost', but that would restrict it to only traffic originating within the within the container itself. Using '0.0.0.0' exposes the websocket to the virtual network managed by Lando.

server.https
  • This is normally false. It can also be true or an object containing the container's own certificate and key.
  • Specifying the cert and key are necessary in this case, otherwise there will be a mismatch when trying to connect over HTTPS from the client.
server.port

This is the port opened for the websocket server-side.

Here's Laravel's default Vite config for comparison:

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

export default defineConfig({
	plugins: [
		laravel({
			input: ['resources/css/app.css', 'resources/js/app.js'],
      refresh: true,
    }),
  ],
});

Lando

Lando has what it calls a Landofile, named like Docker's Dockerfile—except not literally named that in the filename for some reason... The actual file is named ".lando.yml" (or ".lando.base.yml" in my case).

Here's my Landofile with some extra project-specific stuff removed:

name: <my-project> # placeholder
recipe: laravel

config:
  php: '8.2'
  composer_version: 2
  webroot: public
  database: mysql:8.0

services:
  appserver:
    build_as_root:
      - curl -sL https://deb.nodesource.com/setup_18.x | bash -
      - apt-get update -y
      - apt-get install -y nodejs
    build:
      - yarn
    ssl: true
    ports:
      - '24690:24690'
      - '24691:24691'
      - '24692:24692'

tooling:
  node:
    service: appserver

# if you want npm instead of yarn
#	npm:
#		service: appserver

  yarn:
    service: appserver

  dev:
    service: appserver
    description: Start vite dev server
    user: root
    cmd: yarn dev

I'll include some extra links to help with the syntax. Also, you definitely don't

Here are the important parts for this article:

services.appserver.build_as_root

This is a list of commands to run to set up Node within the main project container "appserver".

  1. Install Node 18 (can be 20 if you want)
  2. Update apt's package list
  3. Install Node
services.appserver.build

I only run yarn here, but you can also run npm install. Lando is very nicely installs Yarn 1.22.x for you when you need Node.

Please do not do this step in build_as_root. You'll be unpleasantly surprised when you find out you can't run lando yarn or lando npm install anymore; your permissions will be all messed up, forcing you to rebuild your appserver container.

Some background: before doing all of this directly in "appserver", I had tried creating a separate "node" service. Creating a separate Node service is fairly easy (I'll update if/when I write a post on that).

However, after much pain, I found out Vite's dev server had to be running within the same container as the rest of the project. This is because the certificate files for HTTPS need to match up. If you create a separate container, enable HTTPS and run Vite there, it'll get completely different cert files.

services.appserver.ssl

Setting this to true is what tells Lando to enable HTTPS and generate certificate files.

services.appserver.ports

This gives Lando a list of additional ports to expose to the host machine. You can write these as individual ports, like this:

		ports:
			- 26790
			- 26791
			- 26792

... or as a list of 'external:internal' port mappings.

The reason I have a few ports is, if Vite sees a port (like 26790) is taken already, it'll try to use the next port up. And if it sees that one is taken, it'll try the next one after that, and the next one, and so on.

If that happens, you'll have to restart your container to try to free up the port(s) you need. Having a few reduces the risk that this will happen.

tooling

Each item listed here becomes a custom Lando command. For example, lando node, lando npm, lando npx, lando yarn...

In our case, we want lando dev as well. This starts the Vite dev server as root within the container. This is necessary for it to have access to the certificate files. Lando builds these files as root.

If you want, you can have a run_as_root step in your "appserver" service that does this. I did at first. But that makes it harder to stop/restart. This is what led to me having a separate command for this.

Wrapping up

There you go, now you know how to work with Laravel and Vite in a Lando development environment.

This ~~hair-ripping~~ pleasant side trip taught me a lot that I hadn't encountered before, including:

  1. Manually installing Ndoe and other services (like cron) inside of a container
  2. Explicitly allowing a Node server to accept traffic from outside localhost
  3. Needing to run a containerized Node service as root to allow it to use SSL certs
  4. Mapping separate sets of server and client hostnames and ports to play nice with a reverse proxy

One final note: early on, there was a bug in the Laravel Vite plugin preventing HMR options from getting passed down. This made me temporarily consider fully rebuilding the necessary Vite server config just to pass these options correctly. I'm very glad now that I didn't go down that rabbit hole. ?

Thanks for reading!

Please confirm whether you would like to allow tracking cookies on this website, in accordance with its privacy policy.