feat(ext-vite): gzip at build time, fall back to the index only for navigations

The Maven plugin now writes a maximally compressed .gz beside every text file of 1 KB
and more, so the server compresses nothing and boot only reads the files: about 50 ms
for Glossa's 500. Hashed files under assets/ skip the ETag, which nothing ever asks for.

A path that is no file gets index.html only when the request's Accept names text/html,
as a browser navigation does. Everything else, an API call to a missing route included,
gets the app's own 404 instead of the index, and a dot in a client route no longer
matters. The base path itself always serves the app.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Zakaria El Orche
2026-09-22 15:54:19 +00:00
co-authored by Claude Opus 5
parent 580417e952
commit 680bbca8c7
7 changed files with 100 additions and 70 deletions
@@ -33,7 +33,7 @@ class AssetsTest {
}
}
try (URLClassLoader loader = new URLClassLoader(new URL[]{jar.toUri().toURL()}, null)) {
assertEquals(Set.of("/index.html", "/assets/a-12345678.css"), new Assets(loader, "/").byPath.keySet());
assertEquals(Set.of("/", "/index.html", "/assets/a-12345678.css"), new Assets(loader, "/").byPath.keySet());
}
}
@@ -11,20 +11,14 @@ import static org.junit.jupiter.api.Assertions.assertNull;
class ViteExtensionTest {
private static final String JS = "/assets/app-AbCd1234.js";
private static final String NAVIGATION = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
@RegisterExtension
static final FlashTest app = FlashTest.of(flash -> flash.get("/api/before", (req, res) -> "before")
.install(new ViteExtension()).get("/api/ping", (req, res) -> "pong"));
static final FlashTest app = FlashTest.of(flash -> flash.install(new ViteExtension()));
@RegisterExtension
static final FlashTest nested = FlashTest.of(flash -> flash.install(new ViteExtension().basePath("/app/")));
@Test
void backendRoutesWin() {
app.get("/api/ping").expectStatus(200).expectBody("pong");
app.get("/api/before").expectStatus(200).expectBody("before");
}
@Test
void hashedAssetsAreCachedForeverTheRestRevalidates() {
app.get(JS).expectStatus(200)
@@ -41,17 +35,25 @@ class ViteExtensionTest {
assertNull(app.get(JS).header("Content-Encoding"));
}
/** A browser navigating gets the app, dots and all; a fetch or a script tag for nothing gets a 404. */
@Test
void clientRoutesGetTheIndexMissingFilesA404() {
app.get("/content/2").expectStatus(200).expectHeader("Cache-Control", "no-cache").expectBodyContains("spa");
app.get("/assets/missing.js").expectStatus(404);
void onlyNavigationsFallBackToTheIndex() {
for (String route : new String[]{"/content/2", "/users/ada.lovelace"}) {
app.request().header("Accept", NAVIGATION).get(route).expectStatus(200).expectHeader("Cache-Control", "no-cache").expectBodyContains("spa");
}
app.request().header("Accept", "application/json").get("/api/nope").expectStatus(404);
app.request().header("Accept", "*/*").get("/assets/missing.js").expectStatus(404);
app.get("/content/2").expectStatus(404);
app.get("/").expectStatus(200).expectBodyContains("spa");
nested.get("/app").expectStatus(200).expectBodyContains("spa");
}
@Test
void anUnchangedAssetIsNotSentAgain() {
String etag = app.get(JS).header("ETag");
app.request().header("If-None-Match", etag).get(JS).expectStatus(304)
.expectHeader("ETag", etag).expectHeader("Cache-Control", "public, max-age=31536000, immutable");
void anUnchangedFileIsNotSentAgainAHashedOneIsNeverAsked() {
String etag = app.get("/favicon.svg").header("ETag");
app.request().header("If-None-Match", etag).get("/favicon.svg").expectStatus(304)
.expectHeader("ETag", etag).expectHeader("Cache-Control", "no-cache");
assertNull(app.get(JS).header("ETag"));
}
@Test
@@ -64,7 +66,7 @@ class ViteExtensionTest {
@Test
void aBasePathPrefixesEveryRoute() {
nested.get("/app" + JS).expectStatus(200);
nested.get("/app/settings").expectStatus(200).expectBodyContains("spa");
nested.request().header("Accept", NAVIGATION).get("/app/settings").expectStatus(200).expectBodyContains("spa");
nested.get(JS).expectStatus(404);
}
}