Asset bundling
Overview
Vite is the de-facto standard build tool for front-end development. It provides a very fast development server and bundles your assets for production without barely any configuration needed.
Tempest provides an integration with Vite that consists of a Vite plugin and a server-side package.
Quick start
To install Vite, you may run the corresponding installer command. The wizard will guide you through the installation, including adding the Vite plugin, the vite.config.ts
configuration file, the TypeScript entrypoint, and, if you chose so, Tailwind CSS.
php tempest install vite
The next step is to add the <x-vite-tags />
component to your base template. This is how the script and style tags including your entrypoints are provided to the browser.
<html lang="en"> <head> <!-- ... --> <x-vite-tags /> </head> <body> <x-slot /> </body> </html>
Running the development server
During development, the purpose of Vite is to transpile asset files on-the-fly to a format that the browser understands. This is the concept that makes Vite really fast—it doesn't need to bundle the whole application everytime some code is updated.
For Vite to be able to transpile assets, its server needs to be started. This is done by running its CLI, vite
.
npm run vite
The command above looks for the vite
script in package.json
, which in turns runs the vite
CLI. This is the equivalent of running the npx vite
command.
Entrypoints
An entrypoint is a primary script or stylesheet that serves as the starting point in an application. Any asset file ending with .entrypoint.{ts,css,js}
will automatically be discovered by Tempest, meaning you don't have to configure anything.
console.log('Hello, world! 🌊')
Manually including an entrypoint
It might happen that you only need a specific script or stylesheet in a particular view. In this situation, you may use the <x-vite-tags />
component with an entrypoint
attribute that points to the file you want to include:
<x-base> <slot name="head"> <x-vite-tags entrypoint="src/Profile/profile.css" /> </slot> <!-- ... --> </x-base>
For Vite to bundle this file in production, it still needs to be configured as an entrypoint. Otherwise, it will not be included in the production manifest, and Tempest won't be able to generate a link to it.
Manually configuring entrypoints
If you prefer, you may opt-out of the *.entrypoint.{ts,cs,js}
naming convention and manually configure entrypoints in the Vite configuration.
To do so, create a vite.config.php
file that returns a Tempest\Vite\ViteConfig
instance. You should configure the entrypoints
parameter:
return new ViteConfig( entrypoints: [ 'app/main.css', 'app/main.ts', ], );
Note that the paths to the entrypoint files must be relative to the root of the project.
If you opted in for manual entrypoint configuration, your base template should also specify which entrypoint to include by default. Otherwise, all configured entrypoints will be used.
Building for production
Running the build
script from the package.json
will bundle your application's assets, versioning them and creating a manifest.json
file.
npm run build
By default, assets are compiled in the public/build
directory. This directory should be added to .gitignore
, to avoid adding compiled assets to version control.
This directory is already in your .gitignore
if you used the php tempest install vite
command.
Using a nonce
attribute
If you application uses a Content Security Policy, you may need to include nonce
attributes to the tags generated by <x-vite-tags />
.
The value of a nonce
attribute should be used only once per request, as it is mainly used to prevent replay attacks. To generate and configure that value for each request, you may use a route middleware:
use Tempest\Support\Random; use Tempest\Vite\ViteConfig; final class ConfigureViteNonce implements HttpMiddleware { public function __construct( private readonly ViteConfig $viteConfig, ) {} public function __invoke(Request $request, HttpMiddlewareCallable $next): Response { $this->viteConfig->nonce = Random\secure_string(length: 40); return $next($request); } }
Note that middleware are not automatically registered, as their order generally matters. You may manually include this middleware to routes that need it, or apply it automatically by registering it globally:
use Tempest\Core\KernelEvent; use Tempest\EventBus\EventHandler; use Tempest\Router\HttpMiddleware; use Tempest\Router\HttpMiddlewareCallable; use Tempest\Router\Request; use Tempest\Router\Response; use Tempest\Router\Router; use Tempest\Support\Random; use Tempest\Vite\ViteConfig; final class ConfigureViteNonce implements HttpMiddleware { public function __construct( private readonly Router $router, private readonly ViteConfig $viteConfig, ) {} public function __invoke(Request $request, HttpMiddlewareCallable $next): Response { $this->viteConfig->nonce = Random\secure_string(length: 40); return $next($request); } #[EventHandler(KernelEvent::BOOTED)] public function register(): void { $this->router->addMiddleware(self::class); } }
The register
method above is an event handler that is called when Tempest boots. It registers the middleware on the injected Tempest\Router\Router
instance, effectively registering it for every route.
Alternatively, you may also set the nonce
directly in the event handler. However, keep in mind that this would be called every time the framework boots, even when only using console commands.
Subresource integrity
Tempest will detect subresource integrity hashes in your manifest.json
file and will automatically add them to the generated script and style tags.
Integrity hashes are not included in Vite manifests by default, but the vite-plugin-manifest-sri
plugin provides this functionality. You may install it through bun
or npm
and register it like any other Vite plugin in your configuration file:
import { defineConfig } from 'vite' import tailwindcss from '@tailwindcss/vite' import tempest from 'vite-plugin-tempest' import sri from 'vite-plugin-manifest-sri' export default defineConfig({ plugins: [ tailwindcss(), tempest(), sri(), ], })
Testing
By default, Tempest is intructed to not generate any tag during tests. This behavior is in place to prevent triggering ManifestNotFoundException
exceptions in your test suite.
If, for any reason, you wish to restore tag resolution in a test, you may call the allowTagResolution()
method on the ViteTester
instance:
public function setUp(): void { parent::setUp(); $this->vite->allowTagResolution(); }