Essentials

Asset bundling

Web applications usually need to serve assets to users. Tempest provide a seamless integration with Vite, the most popular front-end development server and build tool

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.

x-base.view.php
<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.

app/main.entrypoint.ts
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:

app/Profile/show.view.php
<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:

app/vite.config.php
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:

vite.config.ts
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:

tests/SomeTest.php
public function setUp(): void
{
    parent::setUp();

    $this->vite->allowTagResolution();
}