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.
Continue with Browser Cache Busting For CSS, JavaScript, And HTML for cache keys, immutable assets, HTML revalidation, and rollout ordering.
Deep Dive And Application
Start With The Requirement
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. That statement is the starting point, but a production decision needs a more precise requirement. A PHP developer responsible for both server-rendered HTML and the browser behavior that consumes it should identify who depends on the behavior, what state is allowed to change, what must remain true after success, and what the caller should observe after failure. Without those details, two implementations can both look reasonable while providing different guarantees.
For Asset Builds And Vite, write the requirement in observable terms before choosing a command, library, pattern, or provider. Name the input, the expected output, and the authority that owns the result. Then identify whether the operation is local to one process or crosses the browser, the HTTP response, the asset pipeline, and the deployment process. Every additional boundary introduces another place where data can be stale, work can be repeated, configuration can drift, or an apparently successful step can fail before the complete outcome is durable.
A useful review question is: "What fact will still be true if the process stops immediately after any individual step?" This question exposes hidden ordering assumptions. It also separates the essential guarantee from a preferred implementation. The implementation may change as the project grows, but the invariant and the evidence for it should remain understandable.
Build A Precise Mental Model
The main concepts in this lesson include Why asset builds exist, Development and production are different, The manifest connects PHP to hashed files, and Entry points, What goes into Git, Common failure modes. Do not study them as isolated vocabulary. Connect each concept to a state transition: what exists before the operation, what decision is made, what changes, and what the next observer can see.
Model each browser-visible resource as a versioned representation. The HTML selects asset URLs, the URLs select cache entries, and response headers determine when each stored representation may be reused. Use a small diagram or state table to expose ownership, transitions, and the observations available to each participant. This does not need specialist notation. Its purpose is to make the lesson-specific invariant inspectable before implementation begins.
Next, walk through one success path and at least two failure paths. One failure should happen before the authoritative change, and one should happen after that change but before the caller receives confirmation. The second case is especially important because it creates ambiguity: the caller may not know whether retrying is harmless. A robust design gives that uncertainty an explicit answer through identity, versioning, transactions, conditional operations, or documented recovery steps.
A Repeatable Implementation Workflow
Use the following workflow when applying Asset Builds And Vite:
- Describe the user or system outcome without naming a tool.
- Identify the authoritative state and the component allowed to change it.
- List every read, decision, write, message, and externally visible side effect.
- State the invariant that must survive retries, concurrency, partial failure, and deployment.
- Choose the smallest mechanism that can preserve that invariant.
- Define errors in terms the caller can act on.
- Add observability at the boundary where uncertainty remains.
- Verify the behavior with a controlled success, rejection, and recovery scenario.
This sequence prevents tool-first design. A team can replace a framework, hosting product, Git platform, data structure, or proxy while retaining the same reasoning. It also improves reviews because the reviewer can challenge one explicit assumption instead of reverse-engineering intent from configuration.
Four practical rules from this lesson deserve special attention:
- define the intended outcome for Asset Builds And Vite. Treat this as a design constraint, not a final cleanup item. Show where the rule is enforced and what happens when input or environment state violates it.
- keep ownership and boundaries explicit. Make the responsible layer visible in code or configuration. Duplicating the rule in unrelated layers creates drift and contradictory behavior.
- make failure behavior visible. Include the exceptional path in the initial implementation. An error message without a recovery or retry policy often transfers operational uncertainty to users.
- verify the real result. Verification must observe the real boundary. A helper returning the expected array or command string is not proof that the browser, database, remote repository, proxy, or provider behaves as intended.
Worked Scenario
Consider a product page deployed across two application instances while browsers and intermediaries may still hold an older representation. The team wants to apply Asset Builds And Vite, but the first design discussion should not start with a product name or one copied configuration block. Start by listing the actors, the state each actor can observe, and the point at which the result becomes authoritative.
The first pass should be deliberately simple. Create one controlled example with known input and an expected result. Record the current behavior before changing it. Apply one mechanism, then repeat the same observation. If several variables change at once, the team cannot tell which change produced the improvement or which one introduced a regression.
Now introduce pressure. Deploy new HTML while one client, one edge cache, and one application instance still hold old asset state; then inspect which URLs and directives prevent incompatible combinations. The purpose is to test the assumption that normally remains invisible and to connect the observed failure or success to the lesson-specific invariant.
Finally, inspect browser network traces, emitted headers, generated manifests, repeatable deployment checks, and visible user behavior. The evidence should let another developer explain not only that the test passed, but why the result demonstrates the intended guarantee. Save the relevant command, fixture, request, metric, or trace with the review when the decision is operationally significant.
Failure Analysis
The most valuable failures are not syntax mistakes. They are plausible designs that work in a demonstration but break when ownership, scale, or timing changes.
assuming the happy path is the only possible path. This usually happens when a developer treats one observed run as the complete specification. Reproduce the case with an explicit fixture or timeline, then move the guarantee to the layer that owns the shared state.
hiding important work behind convenient abstractions. Convenience can hide expensive or stateful work. Make that work visible through naming, logging, query inspection, graph inspection, or a dedicated boundary. The caller should know whether an operation can block, retry, mutate shared state, or contact another system.
changing several variables before measuring the result. A partial fix often replaces one failure with another. Review the complete lifecycle, including setup, normal operation, cancellation, retry, cleanup, rollback, and later maintenance. The correct solution is the one whose failure behavior remains understandable.
treating configuration as proof of behavior. Configuration and documentation describe intent, not runtime truth. Validate permissions, emitted headers, final data, process state, ordering, or output under the environment that will actually execute the work.
When a failure is discovered, resist adding an unexplained delay, broad catch block, global cache clear, forced Git update, or provider-specific switch merely because it makes the immediate symptom disappear. Record the violated invariant first. A narrow repair should restore that invariant and add a regression check that would have failed before the repair.
Verification Strategy
A strong verification plan combines fast local checks with at least one boundary-level test. Use these lesson-specific checks as starting points:
- test representative success and failure paths. Record the fixture and expected observation so the check is repeatable.
- inspect the real boundary rather than only an in-memory value. Inspect the value at the authoritative boundary rather than only the caller's optimistic interpretation.
- record enough evidence for another developer to reproduce the result. Include enough diagnostic context to distinguish invalid input, temporary dependency failure, policy rejection, and an internal defect.
- repeat the check under the environment where the behavior matters. Repeat the check after restart, retry, deployment, or changed ordering when those conditions are relevant.
Verification should also include negative evidence. Confirm that an unsafe path is rejected, that a body is absent when the protocol forbids it, that a duplicate action creates no second business effect, that an old branch cannot overwrite newer shared work, or that an algorithm does not silently accept malformed structure. Negative tests make the boundary concrete.
For performance-sensitive behavior, report a distribution and the tested input size rather than one timing. For reliability-sensitive behavior, report the final durable state and number of side effects. For security-sensitive behavior, test from an untrusted client position. For operational behavior, verify logs and metrics are useful before an incident.
Tradeoffs And Evolution
The simplest correct mechanism is usually preferable. Simplicity means fewer hidden states and clearer ownership, not fewer lines at any cost. A small application may reasonably choose a direct implementation while a larger system needs explicit coordination, queues, versioning, or managed infrastructure. The important point is to know which assumption allows the simpler design.
Record the trigger for reconsidering the choice. Useful triggers include measured latency, data volume, contention, team size, compliance needs, repeated incidents, deployment frequency, provider limitations, or review cost. This avoids premature abstraction while preventing a temporary shortcut from becoming an undocumented permanent architecture.
Compatibility also matters. Existing clients, old application instances, queued messages, cached assets, shared branches, and stored data may outlive one deployment. When changing the mechanism behind Asset Builds And Vite, plan how old and new behavior overlap. Prefer additive transitions, observable cutovers, and a rollback or roll-forward path.
Review Questions
Before considering the lesson applied, answer these questions in project-specific terms:
- What is the authoritative state, and who owns it?
- Which operation or boundary makes the result durable or shared?
- What can be repeated, reordered, cached, interrupted, or observed late?
- Which input sizes, users, environments, or providers change the tradeoff?
- What does the caller see for success, rejection, temporary failure, and ambiguous outcome?
- Which logs, metrics, traces, diffs, queries, or tests prove the guarantee?
- What is the safe recovery path?
- What future condition would justify a more complex design?
If the answers are vague, the implementation is not finished. Return to the working model, make the invariant explicit, and create a test that observes the boundary directly. The goal of Asset Builds And Vite is not merely to reproduce an example. It is to make a defensible decision, implement it with visible ownership, and leave evidence that the next developer can use.
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.