Working with Vite in DDEV - an introduction

November 8, 2023 12 min read

Edit this page
Working with Vite in DDEV

Using Vite with DDEV

Vite is a popular web development tool that serves your JavaScript and CSS code in a clever way: Instead of bundling everything like webpack, it uses a technique called “hot module reloading”. This enables the ability to instantly update and show your changes in the browser while you’re working on your project.

This articles sums up my current personal experience. I hope it will be a helpful introduction to get started with Vite. Happy to hear your feedback!

Table of contents

General usage

Vite is written in NodeJS. DDEV already has built-in support for NodeJS.

In order to use Vite in our DDEV projects, we generally need to do two things:

  1. Expose Vite’s development server port (default: 5173):

    # .ddev/config.yaml
    web_extra_exposed_ports:
      - name: vite
        container_port: 5173
        http_port: 5172
        https_port: 5173

    This needs a ddev restart afterwards to apply changes.

  2. Adjust the Vite config to use DDEVs project URL, e.g. https://test-vite.ddev.site:5173:

    import { defineConfig } from "vite"
    const port = 5173
    const origin = `${process.env.DDEV_PRIMARY_URL}:${port}`
    
    export default defineConfig({
      // Your settings
      // ...
    
      // Adjust Vites dev server to work with DDEV
      // https://vitejs.dev/config/server-options.html
      server: {
        // respond to all network requests:
        host: "0.0.0.0",
        port: port,
        strictPort: true,
        // Defines the origin of the generated asset URLs during development
        origin: origin,
      },
    })

    This guide assumes your project runs on https://. If you can not access the HTTPS version of your project, please see DDEV installation docs.

Some more customizations might be needed depending on your CMS / framework, see PHP CMS / framework integration below. You can also use a DDEV addon.

A plain PHP example

Let’s try it out with a simple example project. We will use Vite v4 for this.

First we create a new DDEV project called test-vite:

mkdir test-vite && cd test-vite
ddev config --project-type=php
ddev start

Now we can create a simple package.json file. A quick reminder: Every npm command needs to be executed within the DDEV web container, so we use ddev npm (or ddev yarn).

ddev npm init -y

Afterwards we install Vite as development dependency:

ddev npm i vite --save-dev

For more advanced examples, check out Vite’s guide Scaffolding Your First Vite Project.

We add these script commands to the package.json:

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

An we also need to add the type property:

"type": "module"

The final package.json is as follows:

{
  "name": "vite-starter",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "vite": "^4.5.0"
  }
}

Now Vite is almost ready to go. But there are two important steps ahead of us.

Expose the vite port

Vite is installed within DDEV, but we can’t access it from outside of the Docker container yet. We need to expose port 5173 of the DDEV project.

Why?

If you use Vite on your localhost (without DDEV), the Vite development server would be accessible at http://localhost:5173/.

Now we need it make it accessible via https://test-vite.ddev.site:5173.

Fortunately exposing the port is very simple with DDEV’s config option web_extra_exposed_ports. We add the following to .ddev/config.yaml file:

web_extra_exposed_ports:
  - name: vite
    container_port: 5173
    http_port: 5172
    https_port: 5173

This is our resulting .ddev/config.yaml file:

name: test-vite
type: php
docroot: ""
php_version: "8.1"
webserver_type: nginx-fpm
xdebug_enabled: false
additional_hostnames: []
additional_fqdns: []
database:
  type: mariadb
  version: "10.4"
use_dns_when_possible: true
composer_version: "2"
web_environment: []
nodejs_version: "18"
web_extra_exposed_ports:
  - name: vite
    container_port: 5173
    http_port: 5172
    https_port: 5173

A ddev restart is necessary after changing the config.yaml file.

You can check the exposed ports with ddev describe after the restart.

Note: In other tutorials or projects you may come across docker-compose-files in the .ddev/-folder which also take care of exposing the vite port. If you use web_extra_exposed_ports, you don’t need these files.

Adjust the Vite config

The last step is to adjust the Vite config to let it know that it will run on https://test-vite.ddev.site:5173.

This can be done easily by creating a vite.config.js file like this:

import { defineConfig } from "vite"
import path from "path"

const port = 5173
const origin = `${process.env.DDEV_PRIMARY_URL}:${port}`

// https://vitejs.dev/config/
export default defineConfig({
  // Add entrypoint
  build: {
    // our entry
    rollupOptions: {
      input: path.resolve(__dirname, "src/main.js"),
    },

    // manifest
    manifest: true,
  },

  // Adjust Vites dev server for DDEV
  // https://vitejs.dev/config/server-options.html
  server: {
    // respond to all network requests:
    host: "0.0.0.0",
    port: port,
    strictPort: true,
    // Defines the origin of the generated asset URLs during development
    origin: origin,
  },
})

Technical explanation:

  • We need to define an entry file to let vite know where to start, this is done in rollupOptions.
  • Also we need to tell vite to respond to all network request, not only the ones addressed to http://localhost:5173. This is done via host: '0.0.0.0'.
  • The strict port setting is also necessary, because we only exposed port 5173. Without strictPort: true, Vite will use other ports like 5174 or 5175 if 5173 is already occupied.
  • Another important part is to let Vite know from where to load Vite-controlled assets like images referenced in CSS. This is done via server.origin.
    (DDEV automatically provides environment variables via the regular process.env variable. The variable process.env.DDEV_PRIMARY_URL will have the value https://test-vite.ddev.site in our demo project. We use it to set the correct server.origin dynamically.)

See Vite’s Server Options for all settings.

Test it

Now we need a little test web page.

Let’s create the src/main.js as our entry file:

import "./style.css"

console.log("hello vite!")

Alongside create the file src/style.css:

body {
  font-family: sans-serif;
}

p {
  color: darkslateblue;
}

And we will need a simple PHP index file of course. We put it in the root folder as index.php:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Hello Vite!</title>

    <!-- This is just an example for local development, no full integration: -->
    <script type="module" src="<?php echo $_SERVER['DDEV_PRIMARY_URL']; ?>:5173/@vite/client"></script>
    <script type="module" src="<?php echo $_SERVER['DDEV_PRIMARY_URL']; ?>:5173/src/main.js"></script>
    <!-- see https://vitejs.dev/guide/backend-integration.html -->

</head>

<body>
    <h1>Hello, Vite!</h1>
    <p>This is a simple test for hot module reloading.</p>
</body>

</html>

Run ddev launch to open https://test-vite.ddev.site/ in the browser.

Now we need to start Vite for local development:

ddev npm run dev

Reload the browser.

If you change something in style.css or main.js now, you should see the change immediately on the website as well - without a full page reload.

You can also open https://test-vite.ddev.site:5173/@vite/client and https://test-vite.ddev.site:5173/src/main.js to see if Vites dev server is accessible.

Test with an image

Vite also optimizes images referenced in CSS. These are also loaded from Vite’s dev server for local production.

Download the DDEV Logo to src/images/ddev.png.

Add this HTML container to your index.php:

<div id="image-test"></div>

Add this to your src/style.css:

#image-test {
    width: 300px;
    height: 150px;
    background-size: contain;
    background-repeat: no-repeat;
    background-image: url('/src/images/ddev.png');
}

Reload the browser (because Vite currently handles CSS and JS changes).

The image is loaded from Vite and is accessible via https://test-vite.ddev.site:5173/src/images/ddev.png for local development. This is ensured by the server.origin setting in vite.config.js.

If you want to reload your site when a PHP file changes, you could use plugins like antfu/vite-plugin-restart. Some frameworks like Laravel have support for this in their plugin.

Demo repository

You can find the source code for this simple demo here: mandrasch/ddev-vite-simple-demo

Building for production

This demo only covered the case of using the local development server for hot module reloading.

For production you would first run ddev npm run build to generate the optimized files. These will be generated in the dist/ folder with a hash:

  • /dist/assets/main-8811a981.js
  • /dist/assets/main-d6825f81.css

To include these in PHP, you will need to know the hash values. You could set build.manifest to true in Vites config. With this option enabled a /dist/manifest.json file is generated on each build, which has reference to all JS and CSS files:

Example:

{
  "src/images/ddev.png": {
    "file": "assets/ddev-f877fcaa.png",
    "src": "src/images/ddev.png"
  },
  "src/main.css": {
    "file": "assets/main-d6825f81.css",
    "src": "src/main.css"
  },
  "src/main.js": {
    "assets": [
      "assets/ddev-f877fcaa.png"
    ],
    "css": [
      "assets/main-d6825f81.css"
    ],
    "file": "assets/main-8811a981.js",
    "isEntry": true,
    "src": "src/main.js"
  }
}

You could now parse the dist/manifest.json file dynamically in PHP and get the hashed filename via $manifest["src/main.js] and include it on your production site.

This is the point where PHP libraries and CMS integrations come into play which handle this for us. In most cases, you won’t need to write this integration yourself (see below).

PHP CMS / framework integration

You can read Vite’s official guide for backend integration here:

Vite also has a list of integrations here:

But how do we use these integrations with DDEV?

The main goal for us is the same as above - we need to use Vite from https://your-project.ddev.site:5173/, not from http://localhost:5173. So adding the server settings to vite.config.js is necessary in all cases.

# .ddev/config.yaml
web_extra_exposed_ports:
  - name: node-vite
    container_port: 5173
    http_port: 5172
    https_port: 5173

A ddev restart is necessary afterwards.

The tricky part:

CMS integrations for Vite can use different approaches.

Some have official support for Docker and DDEV, others may need a little bit of tweaking.

List of PHP CMS integrations

Here is a list of example integrations I know so far.

Did I miss an integration? Please let me know!

General PHP examples

André Felipe has published https://github.com/andrefelipe/vite-php-setup as general example for Vite in PHP projects.

For DDEV you need to change the const VITE_HOST to "".$_SERVER['DDEV_PRIMARY_URL'].":5173" in public/helpers.php.

Also you might need to change the isDev() function.

CraftCMS

The Vite plugin by nystudio107 has official DDEV support. Here is a guide to change vite.config.js and config/vite.php accordingly: Using DDEV.

Note: You don’t need to use the docker-compose-file for exposing the ports if you already used web_extra_exposed_ports.

Example repositories:

Drupal

Andrew Morton gave some information about the current state, thanks very much!

Vite module: https://www.drupal.org/project/vite

This uses Vite’s manifest.json to map enabled Drupal library files to the compiled versions in /dist, or to the vite server in dev mode.

Here is a theme I contributed, with instructions for how to set it up with DDEV in the readme files. I’m trying to detail all the configuration possibilities we might need, with defaults that should work out of the box.

https://www.drupal.org/project/unocss_starter (uses Vite)

I’m blogging about the process here: https://www.drupalarchitect.info/projects/unocss-starter-theme

There are a handful of devs working on using Vite to bundle assets multiple modules/themes in Drupal. Looks like Vite and Foxy are becoming the leading solutions.

https://www.drupal.org/project/foxy

Working POC: https://github.com/darvanen/drupal-js

I think we’ll be seeing a lot of new things in this area over the next year.

Laravel

Since June 2022 Vite is the default bundler for Laravel, replacing Laravel Mix (Webpack).

First, expose the port via .ddev/config.yaml and run ddev restart:

# .ddev/config.yaml
web_extra_exposed_ports:
  - name: node-vite
    container_port: 5173
    http_port: 5172
    https_port: 5173

Afterwards, you just need to change the vite.config.js like this:

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

const port = 5173;
const origin = `${process.env.DDEV_PRIMARY_URL}:${port}`;

export default defineConfig({
    plugins: [
        laravel({
            input: ['resources/css/app.css', 'resources/js/app.js'],
            refresh: true,
        }),
    ],
    server: {
        // respond to all network requests
        host: '0.0.0.0',
        port: port,
        strictPort: true,
        // Defines the origin of the generated asset URLs during development,
        // this will also be used for the public/hot file (Vite devserver URL)
        origin: origin
    }
});

The devserver can be started via ddev npm run dev.

Example repository:

Note: Laravel’s Vite integration is a bit special, because it has its own npm integration with a so called public/hot file.

TYPO3

Florian Geierstanger made a first demo publicly available:

This lead to the development of further tools by Simon Praetorius:

The usage of “vite-asset-collector” with DDEV is documented here. The TYPO3 Slack has a Vite channel if you have questions or need support.

WordPress

For WordPress I found these libraries:

Example repository for idleberg/php-wordpress-vite-assets, quick & dirty:

Gitpod

I experimented with Gitpod support for Vite in these demo projects, see:

Note: On Gitpod, DDEVs router is not used - therefore some adjustments are needed. Exposing the port does not work via .ddev/config.yaml, instead you can use a docker-compose-file. See docker-compose.vite-workaround.yaml in the demo repositories.

See DDEV Installation: Gitpod for more information.

GitHub Codespaces

I experimented with Codespaces support for Vite in these demo projects, see:

Note: On Codespaces, DDEVs router is not used - therefore some adjustments are needed. Exposing the port does not work via .ddev/config.yaml, instead you can use a docker-compose-file. See docker-compose.vite-workaround.yaml in the demo repositories.

See DDEV Installation: Codespaces for more information.

NodeJS / headless projects

Andy Blum wrote the awesome article Node.js Development with DDEV which explains proxying requests to the correct ports of NodeJS projects running in the web container. He is using the NodeJS CMS Keystone in combination with SvelteKit (NodeJS framework) for the frontend in his tutorial - all in one DDEV project.

This approach also enables use cases like running a classic PHP backend in combination with a NodeJS hosted frontend (on another subdomain of the same DDEV project). It’s especially great for headless CMS projects.

There is an article on velir.com: How to Run Headless Drupal and NextJS on DDEV. And here is a demo repository for Laravel Breeze (PHP) and SvelteKit (NodeJS) within one DDEV project (monorepo).

But you can also use a separate DDEV project for frontend - and another one for backend of course. See communication between DDEV projects.

DDEV addons

  • ddev-vite-sidecar is a simple addon for (almost) zero-config integration of Vite into your DDEV projects. The Vite development server is exposed as a https://vite.* subdomain to your project’s main domain, which means that no ports need to be exposed to the host system.

  • Kudos to torenware, who created the first ever DDEV addon for Vite, ddev-viteserve. It’s currently not maintained.

  • ddev-vitest adds some helper commands for projects using Vitest, a vite-native testing framework..

Advanced: Autostart Vite

There is also the possibility to automatically start Vite when you start a DDEV project.

Please beware: Autostart can complicate things a bit, it’s a technique for advanced usage.

Some developers like having it run in a background daemon, others like putting it in the post-start hook. When it is started via post-start hook, the output & errors are still visible in the terminal.

Edit your .ddev/config.yaml like this and execute a command within the DDEV web container on project start, a “ddev restart” is needed afterwards:

hooks:
  post-start:
    - exec: "npm run dev"

If you want to run Vite in the background as a daemon via web_extra_daemons, edit your config.yaml like this (“ddev restart” needed):

web_extra_daemons:
  - name: vite
    command: bash -c 'npm install && npm run dev -- --host'
    directory: /var/www/html

If you use this, the Vite dev server errors are only visible via “ddev logs”. A real-life example can be found here: github.com/ddev/ddev.com.

You can also use supervisor tools like pm2 in this article: Node.js Development with DDEV - lullabot.com.

Further resources

You wrote about DDEV and Vite or published a video? Please let us know!

Posted In