Add support for webmanifest files in .htaccess and nginx configuration

This commit is contained in:
Jeppe Bundgaard
2026-06-08 13:11:21 +02:00
parent 4a83486b30
commit 0afde8ac8e
4 changed files with 39 additions and 2 deletions
+8
View File
@@ -8,6 +8,14 @@ server {
add_header Content-Security-Policy "frame-ancestors 'self'" always; add_header Content-Security-Policy "frame-ancestors 'self'" always;
add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Frame-Options "SAMEORIGIN" always;
location ~ ^/(?:.+/)?(?<webmanifest_path>(?:assets/)?manifest\.webmanifest)$ {
types { application/manifest+json webmanifest; }
default_type application/manifest+json;
add_header Content-Security-Policy "frame-ancestors 'self'" always;
add_header X-Frame-Options "SAMEORIGIN" always;
try_files /$webmanifest_path =404;
}
location ~ ^/(release-entry|release-manifest)\.json$ { location ~ ^/(release-entry|release-manifest)\.json$ {
add_header Cache-Control "no-store"; add_header Cache-Control "no-store";
add_header Content-Security-Policy "frame-ancestors 'self'" always; add_header Content-Security-Policy "frame-ancestors 'self'" always;
+4
View File
@@ -2,6 +2,10 @@
Options -MultiViews Options -MultiViews
</IfModule> </IfModule>
<IfModule mod_mime.c>
AddType application/manifest+json .webmanifest
</IfModule>
<IfModule mod_rewrite.c> <IfModule mod_rewrite.c>
RewriteEngine On RewriteEngine On
@@ -284,6 +284,7 @@ test.describe("public .htaccess static fallback", () => {
expect(source).not.toContain("/index.html"); expect(source).not.toContain("/index.html");
expect(source).toContain("index.html [L]"); expect(source).toContain("index.html [L]");
expect(source).toContain("R=404"); expect(source).toContain("R=404");
expect(source).toContain("AddType application/manifest+json .webmanifest");
for (const token of [...STATIC_DIRECTORIES, ...STATIC_FILES, "workbox-"]) { for (const token of [...STATIC_DIRECTORIES, ...STATIC_FILES, "workbox-"]) {
expect(sourceWithoutRegexEscapes).toContain(token); expect(sourceWithoutRegexEscapes).toContain(token);
@@ -297,6 +298,7 @@ test.describe("public .htaccess static fallback", () => {
expect(source).toContain("static_asset_path"); expect(source).toContain("static_asset_path");
expect(source).toContain("static_file_path"); expect(source).toContain("static_file_path");
expect(source).toContain("=404"); expect(source).toContain("=404");
expect(source).toContain("application/manifest+json webmanifest");
for (const token of [...STATIC_DIRECTORIES, ...STATIC_FILES, "workbox-"]) { for (const token of [...STATIC_DIRECTORIES, ...STATIC_FILES, "workbox-"]) {
expect(sourceWithoutRegexEscapes).toContain(token); expect(sourceWithoutRegexEscapes).toContain(token);
@@ -341,6 +343,29 @@ test.describe("public .htaccess static fallback", () => {
} }
}); });
test("does not emit conflicting Workbox precache URLs", async () => {
const serviceWorker = await fs.readFile(path.join(DIST_DIR, "sw.js"), "utf8");
const precacheEntries = Array.from(serviceWorker.matchAll(/\{url:"([^"]+)",revision:(null|"[^"]+")/g), (match) => ({
url: match[1],
revision: match[2],
}));
const revisionsByUrl = new Map<string, Set<string>>();
for (const entry of precacheEntries) {
const revisions = revisionsByUrl.get(entry.url) || new Set<string>();
revisions.add(entry.revision);
revisionsByUrl.set(entry.url, revisions);
}
const conflictingUrls = Array.from(revisionsByUrl.entries())
.filter(([, revisions]) => revisions.size > 1)
.map(([url]) => url);
expect(precacheEntries.length).toBeGreaterThan(0);
expect(conflictingUrls).toEqual([]);
expect(precacheEntries.filter((entry) => entry.url === "assets/manifest.webmanifest")).toHaveLength(1);
});
test("serves the app shell from root, deep links, and release-prefixed paths", async ({ request }) => { test("serves the app shell from root, deep links, and release-prefixed paths", async ({ request }) => {
for (const appPath of [ for (const appPath of [
"/", "/",
+2 -2
View File
@@ -554,8 +554,8 @@ export default defineConfig(({ mode }) => {
cleanupOutdatedCaches: true, cleanupOutdatedCaches: true,
skipWaiting: true, skipWaiting: true,
clientsClaim: true, clientsClaim: true,
globPatterns: ['**/*.{js,css,html,ico,svg,png,webmanifest,woff2}'], globPatterns: ['**/*.{js,css,html,ico,svg,png,woff2}'],
globIgnores: ['**/registerSW.js', '**/sw.js'], globIgnores: ['**/registerSW.js', '**/sw.js', '**/*.webmanifest'],
runtimeCaching: [ runtimeCaching: [
{ {
// cache API responses // cache API responses