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
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
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.