web php

Asset Builds And Vite

Modern PHP applications often have frontend assets that need a build step. CSS may be bundled, JavaScript may be compiled, files may be minified, and production filenames may include hashes for caching.

Vite is a common tool for this job. Laravel uses Vite by default, and other PHP projects can use it too.

Why asset builds exist

In development, you want fast rebuilds and browser refreshes. In production, you want stable static files that can be cached aggressively.

An asset build usually turns source files like this:

resources/css/app.css
resources/js/app.js

into public files like this:

public/build/assets/app-C8d9f3.css
public/build/assets/app-B2a41e.js

The hash changes when the content changes. That lets browsers cache assets for a long time without serving stale files after a deploy.

Development and production are different

In development, Vite often runs a dev server:

npm run dev

PHP templates may load assets from the Vite dev server so changes appear quickly.

In production, assets are built once:

npm run build

PHP templates then load the built files from public/build.

The manifest connects PHP to hashed files

Vite writes a manifest that maps source entry points to built filenames. A simplified manifest might look like this:

{
  "resources/js/app.js": {
    "file": "assets/app-B2a41e.js",
    "css": ["assets/app-C8d9f3.css"]
  }
}

PHP can read that manifest to print the right tags.

PHP example
<?php

declare(strict_types=1);

/**
 * @param array<string, array{file: string, css?: list<string>}> $manifest
 */
function viteTags(array $manifest, string $entry): string
{
    if (!isset($manifest[$entry])) {
        throw new InvalidArgumentException('Unknown asset entry.');
    }

    $asset = $manifest[$entry];
    $tags = [];

    foreach ($asset['css'] ?? [] as $cssFile) {
        $tags[] = '<link rel="stylesheet" href="/build/' . htmlspecialchars($cssFile, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '">';
    }

    $tags[] = '<script type="module" src="/build/' . htmlspecialchars($asset['file'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '"></script>';

    return implode(PHP_EOL, $tags);
}

$manifest = [
    'resources/js/app.js' => [
        'file' => 'assets/app-B2a41e.js',
        'css' => ['assets/app-C8d9f3.css'],
    ],
];

echo viteTags($manifest, 'resources/js/app.js') . PHP_EOL;

// Prints:
// <link rel="stylesheet" href="/build/assets/app-C8d9f3.css">
// <script type="module" src="/build/assets/app-B2a41e.js"></script>

Framework helpers do this for you. In Laravel, @vite(['resources/css/app.css', 'resources/js/app.js']) handles development and production output.

Entry points

An entry point is a file Vite starts from. A small app might have one entry, resources/js/app.js. A larger app may have separate entries for an admin area, public site, and checkout flow.

Do not load every JavaScript file on every page by habit. Entry points should match what the page actually needs.

What goes into Git

Source assets belong in Git: CSS, JavaScript, images, and configuration.

Built assets may or may not belong in Git depending on the deployment process. Many teams build during CI and deploy the generated public/build directory. Other teams commit built assets for simpler hosting. Follow the project convention.

node_modules should not be committed.

Common failure modes

If CSS or JavaScript disappears after deploy, check whether npm run build ran and whether the built files were deployed.

If the page references old filenames, check whether the manifest file matches the deployed assets.

If development assets do not load, check whether the Vite dev server is running and whether PHP is configured to use it.

If users report stale CSS, check caching headers and whether filenames are content-hashed.

What you should be able to do

After this lesson, you should be able to explain why asset builds exist, distinguish Vite development mode from production builds, understand what the manifest does, and recognise how PHP templates reference built CSS and JavaScript.

Practice

Task: Render Vite Asset Tags

Write a small PHP script that reads a simplified Vite manifest array and renders the HTML tags for one entry point.

Requirements

  • Use declare(strict_types=1);.
  • Create a function that accepts a manifest array and an entry name.
  • Render CSS files before the JavaScript module tag.
  • Escape asset paths before printing them into HTML.
  • Include one valid entry case.
  • Include one missing entry case that returns or throws a clear error.
  • Print the valid output.

Check Your Work

Run the script and confirm that hashed filenames from the manifest appear in the rendered tags.

Show solution

This solution uses an in-memory manifest so the core idea is clear without needing a Vite project installed.

PHP example
<?php

declare(strict_types=1);

/**
 * @param array<string, array{file: string, css?: list<string>}> $manifest
 */
function renderViteTags(array $manifest, string $entry): string
{
    if (!isset($manifest[$entry])) {
        throw new InvalidArgumentException('Unknown Vite entry: ' . $entry);
    }

    $asset = $manifest[$entry];
    $tags = [];

    foreach ($asset['css'] ?? [] as $cssFile) {
        $safePath = htmlspecialchars($cssFile, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
        $tags[] = '<link rel="stylesheet" href="/build/' . $safePath . '">';
    }

    $safeScript = htmlspecialchars($asset['file'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
    $tags[] = '<script type="module" src="/build/' . $safeScript . '"></script>';

    return implode(PHP_EOL, $tags);
}

$manifest = [
    'resources/js/app.js' => [
        'file' => 'assets/app-B2a41e.js',
        'css' => ['assets/app-C8d9f3.css'],
    ],
];

echo renderViteTags($manifest, 'resources/js/app.js') . PHP_EOL;

try {
    renderViteTags($manifest, 'resources/js/missing.js');
} catch (InvalidArgumentException $exception) {
    echo $exception->getMessage() . PHP_EOL;
}

// Prints:
// <link rel="stylesheet" href="/build/assets/app-C8d9f3.css">
// <script type="module" src="/build/assets/app-B2a41e.js"></script>
// Unknown Vite entry: resources/js/missing.js

In a real project, the manifest would be read from public/build/manifest.json and cached by the application or framework.

Why This Works

The valid case proves PHP can map source entries to hashed production files. The missing case fails clearly instead of silently rendering broken asset links.