Summary
Add an optional dev-server “hot file” mechanism to vite-plugin-typo3 — mirroring Laravel’s laravel/vite-plugin — so the PHP side can reliably detect whether the Vite dev server is currently running, without relying solely on TYPO3_CONTEXT or environment variables.
Motivation
Today, vite-asset-collector decides whether to load assets from the dev server based on:
useDevServer extension setting (true / false / 'auto')
Environment::getContext()->isDevelopment() when set to 'auto'
VITE_SERVER_URI / VITE_PRIMARY_PORT env vars to know where the server lives
This works, but it has gaps:
-
'auto' is coarse.
In Development context, asset rendering switches to the dev server even when npm run dev is not actually running, which produces broken pages / 502s via the DDEV Vite sidecar.
-
No live signal of dev-server state.
Stopping or restarting Vite is invisible to PHP until something fails.
-
Each project rolls its own hot-file plugin.
We currently have one in our template, but this leads to subtle bugs around exit-handler binding, parent-directory creation, and HMR-restart cleanup.
A standardized hot file — written when Vite starts listening and removed when it stops — would let vite-asset-collector decide via a cheap file_exists() check, identical in spirit to Laravel’s public/hot pattern.
Proposal
Add a plugin, or extend the existing one, that during vite dev:
- Writes a marker file at
<projectRoot>/var/vite on httpServer listening.
- Creates the parent directory (
var/) if missing.
- Removes the file on:
httpServer close
- process
exit
SIGINT
SIGTERM
SIGHUP
- Binds the process-level handlers exactly once across multiple
configureServer invocations, using a module-scope guard similar to Laravel’s exitHandlersBound.
Configuration
hotFile?: string;
hotFileContent?: 'marker' | 'url';
hotFile
Override the hot-file location.
Default:
The default should be relative to process.cwd(), matching Environment::getVarPath() on the PHP side.
hotFileContent
Controls what gets written to the file.
Options:
'marker' — write a static marker such as VITE_SERVER_RUNNING
'url' — write the dev-server URL, similar to Laravel
For TYPO3, a static marker is sufficient if vite-asset-collector consumes it only via file_exists(). However, 'url' would enable richer PHP-side use later, for example surfacing the live URL in the backend.
Reference implementation
We currently maintain this locally and would be happy to upstream it as a PR if the API direction is acceptable:
import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
import { dirname, resolve } from 'node:path';
import type { Plugin } from 'vite';
const hotFile = resolve(process.cwd(), 'var/vite');
const cleanup = (): void => rmSync(hotFile, { force: true });
let exitHandlersBound = false;
export const hotFilePlugin: Plugin = {
name: 'typo3:hot-file',
apply: 'serve',
configureServer(server) {
server.httpServer?.once('listening', () => {
mkdirSync(dirname(hotFile), { recursive: true });
writeFileSync(hotFile, 'VITE_SERVER_RUNNING\n');
});
server.httpServer?.once('close', cleanup);
if (exitHandlersBound) {
return;
}
exitHandlersBound = true;
process.on('exit', cleanup);
for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP'] as const) {
process.on(signal, () => process.exit());
}
},
};
Differences vs. Laravel’s implementation
-
Adds httpServer.once('close', …).
Laravel only cleans up via process exit, so a Vite :restart can leave a stale file behind.
-
Uses rmSync(..., { force: true }) instead of existsSync + rmSync.
This is atomic and avoids a TOCTOU window.
PHP side
This pairs with a small change in vite-asset-collector, so that the 'auto' branch can short-circuit when the marker is absent:
$useDevServer = file_exists(Environment::getVarPath() . '/vite');
Happy to open a parallel issue / PR on [s2b/vite-asset-collector](https://github.com/s2b/vite-asset-collector) if this direction is agreeable.
Out of scope
-
Writing the live dev-server URL into the hot file, matching Laravel’s full contract.
This can be added later behind hotFileContent: 'url' once a PHP consumer needs it.
-
Replacing the env-var-based discovery via VITE_SERVER_URI.
The hot file is an additional signal, not a replacement.
Open questions
-
Should the plugin be opt-in, for example typo3({ hotFile: true }), or enabled by default for vite dev?
-
What should the default location be?
var/vite, matching Environment::getVarPath()
var/vite/hot, to keep var/ cleaner
-
Should this work in target: 'extension' mode, or only in target: 'project'?
Summary
Add an optional dev-server “hot file” mechanism to
vite-plugin-typo3— mirroring Laravel’slaravel/vite-plugin— so the PHP side can reliably detect whether the Vite dev server is currently running, without relying solely onTYPO3_CONTEXTor environment variables.Motivation
Today,
vite-asset-collectordecides whether to load assets from the dev server based on:useDevServerextension setting (true/false/'auto')Environment::getContext()->isDevelopment()when set to'auto'VITE_SERVER_URI/VITE_PRIMARY_PORTenv vars to know where the server livesThis works, but it has gaps:
'auto'is coarse.In
Developmentcontext, asset rendering switches to the dev server even whennpm run devis not actually running, which produces broken pages / 502s via the DDEV Vite sidecar.No live signal of dev-server state.
Stopping or restarting Vite is invisible to PHP until something fails.
Each project rolls its own hot-file plugin.
We currently have one in our template, but this leads to subtle bugs around exit-handler binding, parent-directory creation, and HMR-restart cleanup.
A standardized hot file — written when Vite starts listening and removed when it stops — would let
vite-asset-collectordecide via a cheapfile_exists()check, identical in spirit to Laravel’spublic/hotpattern.Proposal
Add a plugin, or extend the existing one, that during
vite dev:<projectRoot>/var/viteonhttpServerlistening.var/) if missing.httpServercloseexitSIGINTSIGTERMSIGHUPconfigureServerinvocations, using a module-scope guard similar to Laravel’sexitHandlersBound.Configuration
hotFileOverride the hot-file location.
Default:
The default should be relative to
process.cwd(), matchingEnvironment::getVarPath()on the PHP side.hotFileContentControls what gets written to the file.
Options:
'marker'— write a static marker such asVITE_SERVER_RUNNING'url'— write the dev-server URL, similar to LaravelFor TYPO3, a static marker is sufficient if
vite-asset-collectorconsumes it only viafile_exists(). However,'url'would enable richer PHP-side use later, for example surfacing the live URL in the backend.Reference implementation
We currently maintain this locally and would be happy to upstream it as a PR if the API direction is acceptable:
Differences vs. Laravel’s implementation
Adds
httpServer.once('close', …).Laravel only cleans up via process exit, so a Vite
:restartcan leave a stale file behind.Uses
rmSync(..., { force: true })instead ofexistsSync + rmSync.This is atomic and avoids a TOCTOU window.
PHP side
This pairs with a small change in
vite-asset-collector, so that the'auto'branch can short-circuit when the marker is absent:Happy to open a parallel issue / PR on
[s2b/vite-asset-collector](https://github.com/s2b/vite-asset-collector)if this direction is agreeable.Out of scope
Writing the live dev-server URL into the hot file, matching Laravel’s full contract.
This can be added later behind
hotFileContent: 'url'once a PHP consumer needs it.Replacing the env-var-based discovery via
VITE_SERVER_URI.The hot file is an additional signal, not a replacement.
Open questions
Should the plugin be opt-in, for example
typo3({ hotFile: true }), or enabled by default forvite dev?What should the default location be?
var/vite, matchingEnvironment::getVarPath()var/vite/hot, to keepvar/cleanerShould this work in
target: 'extension'mode, or only intarget: 'project'?