@caelo-cms/shared 0.10.40 → 0.10.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/media.d.ts +2 -1
- package/dist/media.d.ts.map +1 -1
- package/dist/media.js +3 -2
- package/dist/media.js.map +1 -1
- package/dist/preview-compose.js +1 -1
- package/dist/responsive-images.d.ts +4 -0
- package/dist/responsive-images.d.ts.map +1 -1
- package/dist/responsive-images.js +56 -0
- package/dist/responsive-images.js.map +1 -1
- package/dist/static-cache-policy.d.ts +15 -7
- package/dist/static-cache-policy.d.ts.map +1 -1
- package/dist/static-cache-policy.js +15 -7
- package/dist/static-cache-policy.js.map +1 -1
- package/dist/version.d.ts +2 -2
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/media.ts +3 -2
- package/src/preview-compose.ts +1 -1
- package/src/responsive-images.test.ts +74 -0
- package/src/responsive-images.ts +67 -0
- package/src/static-cache-policy.test.ts +10 -1
- package/src/static-cache-policy.ts +16 -7
- package/src/version.ts +1 -1
package/dist/media.d.ts
CHANGED
|
@@ -41,7 +41,8 @@ export declare const MEDIA_VARIANT_WIDTHS: Record<Exclude<MediaVariantTag, "orig
|
|
|
41
41
|
* `/_caelo/media/<slug>/<variant>` for a named variant (webp/crops). The
|
|
42
42
|
* `<slug>` is `media_assets.slug` — a human-meaningful name (e.g.
|
|
43
43
|
* `searchviu-logo`); the UUID id stays internal. The static generator
|
|
44
|
-
* rewrites these to `/_assets/<slug>.<ext>` (orig) /
|
|
44
|
+
* rewrites these to content-hashed `/_assets/<slug>.<hash>.<ext>` (orig) /
|
|
45
|
+
* `/_assets/<slug>/<variant>.<hash>.<ext>` (#667).
|
|
45
46
|
*
|
|
46
47
|
* Legacy form `/_caelo/media/<uuid>/<variant>` is still PARSED (existing
|
|
47
48
|
* persisted embeds keep resolving), but never newly EMITTED.
|
package/dist/media.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../src/media.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,mMAwBtB,CAAC;AACX,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7D,wEAAwE;AACxE,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAcrD,CAAC;AAEF,wEAAwE;AACxE,eAAO,MAAM,sBAAsB,QAAmB,CAAC;AAEvD;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,qEAMrB,CAAC;AACX,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAElE,oDAAoD;AACpD,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,OAAO,CAAC,eAAe,EAAE,MAAM,CAAC,EAAE,MAAM,CAKjF,CAAC;AAEF
|
|
1
|
+
{"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../src/media.ts"],"names":[],"mappings":"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"}
|
package/dist/media.js
CHANGED
|
@@ -89,7 +89,8 @@ export const MEDIA_VARIANT_WIDTHS = {
|
|
|
89
89
|
* `/_caelo/media/<slug>/<variant>` for a named variant (webp/crops). The
|
|
90
90
|
* `<slug>` is `media_assets.slug` — a human-meaningful name (e.g.
|
|
91
91
|
* `searchviu-logo`); the UUID id stays internal. The static generator
|
|
92
|
-
* rewrites these to `/_assets/<slug>.<ext>` (orig) /
|
|
92
|
+
* rewrites these to content-hashed `/_assets/<slug>.<hash>.<ext>` (orig) /
|
|
93
|
+
* `/_assets/<slug>/<variant>.<hash>.<ext>` (#667).
|
|
93
94
|
*
|
|
94
95
|
* Legacy form `/_caelo/media/<uuid>/<variant>` is still PARSED (existing
|
|
95
96
|
* persisted embeds keep resolving), but never newly EMITTED.
|
|
@@ -173,7 +174,7 @@ export const mediaUploadInputSchema = z
|
|
|
173
174
|
/**
|
|
174
175
|
* Meaningful, human-facing label for the asset (e.g. "SearchVIU logo").
|
|
175
176
|
* The handler slugifies + uniquifies it into `media_assets.slug`, which
|
|
176
|
-
* becomes the public URL (`/_assets/<slug>.<ext>`); the id stays internal.
|
|
177
|
+
* becomes the public URL (`/_assets/<slug>.<hash>.<ext>`); the id stays internal.
|
|
177
178
|
* Optional: falls back to `alt` → `originalName` → "image".
|
|
178
179
|
*/
|
|
179
180
|
name: z.string().max(200).optional(),
|
package/dist/media.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"media.js","sourceRoot":"","sources":["../src/media.ts"],"names":[],"mappings":"AAAA,mCAAmC;AAEnC;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,YAAY;IACZ,WAAW;IACX,YAAY;IACZ,YAAY;IACZ,WAAW;IACX,eAAe;IACf,qEAAqE;IACrE,wEAAwE;IACxE,sEAAsE;IACtE,oEAAoE;IACpE,qEAAqE;IACrE,mEAAmE;IACnE,cAAc;IACd,iBAAiB;IACjB,WAAW;IACX,iEAAiE;IACjE,mEAAmE;IACnE,gEAAgE;IAChE,kDAAkD;IAClD,YAAY;IACZ,WAAW;IACX,UAAU;IACV,UAAU;CACF,CAAC;AAGX,wEAAwE;AACxE,MAAM,CAAC,MAAM,eAAe,GAA8B;IACxD,YAAY,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI;IAC9B,WAAW,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI;IAC7B,YAAY,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI;IAC9B,YAAY,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI;IAC9B,WAAW,EAAE,CAAC,GAAG,IAAI,GAAG,IAAI;IAC5B,eAAe,EAAE,CAAC,GAAG,IAAI,GAAG,IAAI;IAChC,cAAc,EAAE,CAAC,GAAG,IAAI,GAAG,IAAI;IAC/B,iBAAiB,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI;IACnC,WAAW,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI;IAC7B,YAAY,EAAE,CAAC,GAAG,IAAI,GAAG,IAAI;IAC7B,WAAW,EAAE,CAAC,GAAG,IAAI,GAAG,IAAI;IAC5B,UAAU,EAAE,CAAC,GAAG,IAAI,GAAG,IAAI;IAC3B,UAAU,EAAE,CAAC,GAAG,IAAI,GAAG,IAAI;CAC5B,CAAC;AAEF,wEAAwE;AACxE,MAAM,CAAC,MAAM,sBAAsB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,MAAM;IACN,WAAW;IACX,WAAW;IACX,UAAU;IACV,UAAU;CACF,CAAC;AAGX,oDAAoD;AACpD,MAAM,CAAC,MAAM,oBAAoB,GAAqD;IACpF,WAAW,EAAE,IAAI;IACjB,WAAW,EAAE,IAAI;IACjB,UAAU,EAAE,GAAG;IACf,UAAU,EAAE,GAAG;CAChB,CAAC;AAEF
|
|
1
|
+
{"version":3,"file":"media.js","sourceRoot":"","sources":["../src/media.ts"],"names":[],"mappings":"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"}
|
package/dist/preview-compose.js
CHANGED
|
@@ -68,7 +68,7 @@ export function fontsHeadFragment(fonts) {
|
|
|
68
68
|
* composer emits it from the theme binding instead of relying on a
|
|
69
69
|
* module to carry the tag. The href is the media URL as composed
|
|
70
70
|
* (`/_caelo/media/<slug>`); the static generator's media pass rewrites
|
|
71
|
-
* it to the published `/_assets/<slug>.<ext>` and copies the bytes, the
|
|
71
|
+
* it to the published `/_assets/<slug>.<hash>.<ext>` and copies the bytes, the
|
|
72
72
|
* same as any other media reference. Returns null when no theme is
|
|
73
73
|
* threaded or no favicon is bound (nothing to declare; browsers fall
|
|
74
74
|
* back to their own `/favicon.ico` probe exactly as before).
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
export interface ImageVariantInfo {
|
|
17
17
|
readonly variant: string;
|
|
18
18
|
readonly format: string;
|
|
19
|
+
/** Pixel width of the stored file, when known (`media_variants.width`).
|
|
20
|
+
* Needed for the `orig` entry so an orig embed's srcset can offer the
|
|
21
|
+
* original at its real width (#667). */
|
|
22
|
+
readonly width?: number | null;
|
|
19
23
|
}
|
|
20
24
|
/** Extract `webp-800` → 800; `square-400` → 400; `orig` → null. */
|
|
21
25
|
export declare function parseVariantWidth(variant: string): number | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"responsive-images.d.ts","sourceRoot":"","sources":["../src/responsive-images.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;GAcG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"responsive-images.d.ts","sourceRoot":"","sources":["../src/responsive-images.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;GAcG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;6CAEyC;IACzC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAYD,mEAAmE;AACnE,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGhE;AAED,qEAAqE;AACrE,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGrD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,kBAAkB,CAAC,gBAAgB,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAc9E;AAED,MAAM,WAAW,6BAA6B;IAC5C;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1E,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,0DAA0D;IAC1D,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;CAC/D;AAID;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,gBAAgB,EAAE,CAAC,EACjE,OAAO,EAAE,6BAA6B,GACrC,MAAM,CAwER"}
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
/** Original formats the WebP ladder can stand in for. SVG/ICO are not
|
|
3
|
+
* raster sources and GIFs may be animated (the ladder is a still frame). */
|
|
4
|
+
const LADDER_REPLACEABLE_ORIG_FORMATS = new Set([
|
|
5
|
+
"png",
|
|
6
|
+
"jpeg",
|
|
7
|
+
"jpg",
|
|
8
|
+
"webp",
|
|
9
|
+
"avif",
|
|
10
|
+
]);
|
|
2
11
|
/** Extract `webp-800` → 800; `square-400` → 400; `orig` → null. */
|
|
3
12
|
export function parseVariantWidth(variant) {
|
|
4
13
|
const m = variant.match(/-(\d+)$/);
|
|
@@ -77,6 +86,22 @@ export function enrichResponsiveImages(html, variantsByAsset, options) {
|
|
|
77
86
|
if (options.rewriteSrc) {
|
|
78
87
|
out = out.replace(srcMatch[0], `src="${options.urlFor(assetId, variant, requestedFormat)}"`);
|
|
79
88
|
}
|
|
89
|
+
// #667 — an `orig` embed (the default `/_caelo/media/<slug>` form,
|
|
90
|
+
// typical for logos) shipped the full original as PNG/JPEG even when
|
|
91
|
+
// the pipeline had a WebP ladder for it: Lighthouse's image-delivery
|
|
92
|
+
// insight flagged the format and the oversize on every such image.
|
|
93
|
+
// Only the author knows the rendered width (a `width` attribute is often
|
|
94
|
+
// just the intrinsic size that CSS stretches), so the ladder is offered
|
|
95
|
+
// only when the tag declares that contract itself with `sizes`: then
|
|
96
|
+
// the WebP ladder plus the original at its real width go into srcset
|
|
97
|
+
// and the browser picks the smallest file that covers the declared
|
|
98
|
+
// size. Without `sizes` the tag keeps shipping the original.
|
|
99
|
+
const origLadder = variant === "orig" && /\bsizes=/i.test(out) && !/\bsrcset=/i.test(out)
|
|
100
|
+
? origSrcset(variants, assetId, options)
|
|
101
|
+
: null;
|
|
102
|
+
if (origLadder) {
|
|
103
|
+
out = out.replace(/<img\b/, `<img srcset="${origLadder}"`);
|
|
104
|
+
}
|
|
80
105
|
if (familyVariants.length >= 2 && !/\bsrcset=/i.test(out)) {
|
|
81
106
|
const srcset = familyVariants
|
|
82
107
|
.map((v) => `${options.urlFor(assetId, v.variant, "webp")} ${v.width}w`)
|
|
@@ -96,4 +121,35 @@ export function enrichResponsiveImages(html, variantsByAsset, options) {
|
|
|
96
121
|
return out;
|
|
97
122
|
});
|
|
98
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* The srcset for an `orig` embed whose author declared `sizes`, or null when
|
|
126
|
+
* it would not help: no raster original, no WebP breakpoint the source could
|
|
127
|
+
* fill, or an original whose width is unknown (without it the browser could
|
|
128
|
+
* be offered only candidates narrower than the image it needs, and upscale
|
|
129
|
+
* them).
|
|
130
|
+
*/
|
|
131
|
+
function origSrcset(variants, assetId, options) {
|
|
132
|
+
const orig = variants.find((v) => v.variant === "orig");
|
|
133
|
+
if (!orig || !LADDER_REPLACEABLE_ORIG_FORMATS.has(orig.format))
|
|
134
|
+
return null;
|
|
135
|
+
const origWidth = orig.width ?? null;
|
|
136
|
+
if (origWidth === null)
|
|
137
|
+
return null;
|
|
138
|
+
const ladder = variants
|
|
139
|
+
.filter((v) => variantFamily(v.variant) === "webp" && v.format === "webp")
|
|
140
|
+
.map((v) => ({ variant: v.variant, width: parseVariantWidth(v.variant) }))
|
|
141
|
+
.filter((v) => v.width !== null && v.width <= origWidth)
|
|
142
|
+
.sort((a, b) => a.width - b.width);
|
|
143
|
+
const widest = ladder.at(-1);
|
|
144
|
+
if (!widest)
|
|
145
|
+
return null;
|
|
146
|
+
const entries = ladder.map((v) => `${options.urlFor(assetId, v.variant, "webp")} ${v.width}w`);
|
|
147
|
+
// The original is only a candidate when it is wider than every WebP
|
|
148
|
+
// (a source wider than the largest breakpoint); otherwise the WebP of
|
|
149
|
+
// the same width replaces it.
|
|
150
|
+
if (origWidth > widest.width) {
|
|
151
|
+
entries.push(`${options.urlFor(assetId, "orig", orig.format)} ${origWidth}w`);
|
|
152
|
+
}
|
|
153
|
+
return entries.join(", ");
|
|
154
|
+
}
|
|
99
155
|
//# sourceMappingURL=responsive-images.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"responsive-images.js","sourceRoot":"","sources":["../src/responsive-images.ts"],"names":[],"mappings":"AAAA,mCAAmC;
|
|
1
|
+
{"version":3,"file":"responsive-images.js","sourceRoot":"","sources":["../src/responsive-images.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,9 +6,8 @@
|
|
|
6
6
|
* The rule: only a URL whose bytes can never change may be cached
|
|
7
7
|
* forever. That holds exactly when the URL itself changes whenever the
|
|
8
8
|
* content does, i.e. the file name carries a content hash. Everything
|
|
9
|
-
* else (pages, robots.txt, sitemap.xml, manifests
|
|
10
|
-
*
|
|
11
|
-
* revalidating so a publish shows up promptly.
|
|
9
|
+
* else (pages, robots.txt, sitemap.xml, manifests) must stay short-lived
|
|
10
|
+
* or revalidating so a publish shows up promptly.
|
|
12
11
|
*
|
|
13
12
|
* Content-hashed paths the static generator emits today:
|
|
14
13
|
*
|
|
@@ -24,10 +23,17 @@
|
|
|
24
23
|
* (`plugin-host/src/client-assets.ts`).
|
|
25
24
|
* - `_app/immutable/**` — Vite's hashed-output convention, kept for
|
|
26
25
|
* builds that ship SvelteKit-style bundles.
|
|
26
|
+
* - `_assets/<slug>.<16 hex>.<ext>` and
|
|
27
|
+
* `_assets/<slug-or-id>/<variant>.<16 hex>.<ext>` — published media
|
|
28
|
+
* (#667), named by the sha256 of the published bytes
|
|
29
|
+
* (`apps/static-generator/src/media-asset-path.ts`). Before #667 media
|
|
30
|
+
* shipped under its bare slug, which the operator can re-point at new
|
|
31
|
+
* bytes, so it could only get a 1-hour lifetime and Lighthouse flagged
|
|
32
|
+
* every logo and image ("Use efficient cache lifetimes").
|
|
27
33
|
*
|
|
28
|
-
* NOT content-hashed (deliberately excluded):
|
|
29
|
-
*
|
|
30
|
-
*
|
|
34
|
+
* NOT content-hashed (deliberately excluded): pages, robots.txt,
|
|
35
|
+
* sitemap.xml, the manifests, the pinned-font `.license.txt` files (named
|
|
36
|
+
* by font id) and media under the pre-#667 bare-slug names.
|
|
31
37
|
*/
|
|
32
38
|
/** Long-lived policy for content-addressed files. */
|
|
33
39
|
export declare const IMMUTABLE_CACHE_CONTROL = "public, max-age=31536000, immutable";
|
|
@@ -40,7 +46,9 @@ export declare const HTML_CACHE_CONTROL = "public, max-age=60, stale-while-reval
|
|
|
40
46
|
* the same string can be handed to Firebase Hosting's `regex` header
|
|
41
47
|
* matcher and Caddy's `path_regexp` (both RE2) and to `RegExp` here.
|
|
42
48
|
*/
|
|
43
|
-
export declare const CONTENT_HASHED_PATH_PATTERN = "^/(?:_app/immutable/.+|_assets/fonts/[^/]+/[0-9a-f]{16,64}\\.(?:woff2|woff|ttf|otf)|_caelo/plugin/[^/]+/[^/]+\\.[0-9a-f]{12}\\.(?:js|css))$";
|
|
49
|
+
export declare const CONTENT_HASHED_PATH_PATTERN = "^/(?:_app/immutable/.+|_assets/fonts/[^/]+/[0-9a-f]{16,64}\\.(?:woff2|woff|ttf|otf)|_assets/[a-z0-9][a-z0-9-]*(?:/[a-z][a-z0-9-]*)?\\.[0-9a-f]{16}\\.[a-z0-9]{2,5}|_caelo/plugin/[^/]+/[^/]+\\.[0-9a-f]{12}\\.(?:js|css))$";
|
|
50
|
+
/** Hex length of the content hash in published media names (#667). */
|
|
51
|
+
export declare const MEDIA_CONTENT_HASH_HEX_LENGTH = 16;
|
|
44
52
|
/**
|
|
45
53
|
* True when `path` names a content-addressed build output. Accepts a
|
|
46
54
|
* build-dir-relative key (`_assets/fonts/inter/ab….woff2`, as the GCS
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"static-cache-policy.d.ts","sourceRoot":"","sources":["../src/static-cache-policy.ts"],"names":[],"mappings":"AAEA
|
|
1
|
+
{"version":3,"file":"static-cache-policy.d.ts","sourceRoot":"","sources":["../src/static-cache-policy.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,qDAAqD;AACrD,eAAO,MAAM,uBAAuB,wCAAwC,CAAC;AAE7E,uEAAuE;AACvE,eAAO,MAAM,kBAAkB,qDAAqD,CAAC;AAErF;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B,+NACsL,CAAC;AAE/N,sEAAsE;AACtE,eAAO,MAAM,6BAA6B,KAAK,CAAC;AAIhD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEzD"}
|
|
@@ -7,9 +7,8 @@
|
|
|
7
7
|
* The rule: only a URL whose bytes can never change may be cached
|
|
8
8
|
* forever. That holds exactly when the URL itself changes whenever the
|
|
9
9
|
* content does, i.e. the file name carries a content hash. Everything
|
|
10
|
-
* else (pages, robots.txt, sitemap.xml, manifests
|
|
11
|
-
*
|
|
12
|
-
* revalidating so a publish shows up promptly.
|
|
10
|
+
* else (pages, robots.txt, sitemap.xml, manifests) must stay short-lived
|
|
11
|
+
* or revalidating so a publish shows up promptly.
|
|
13
12
|
*
|
|
14
13
|
* Content-hashed paths the static generator emits today:
|
|
15
14
|
*
|
|
@@ -25,10 +24,17 @@
|
|
|
25
24
|
* (`plugin-host/src/client-assets.ts`).
|
|
26
25
|
* - `_app/immutable/**` — Vite's hashed-output convention, kept for
|
|
27
26
|
* builds that ship SvelteKit-style bundles.
|
|
27
|
+
* - `_assets/<slug>.<16 hex>.<ext>` and
|
|
28
|
+
* `_assets/<slug-or-id>/<variant>.<16 hex>.<ext>` — published media
|
|
29
|
+
* (#667), named by the sha256 of the published bytes
|
|
30
|
+
* (`apps/static-generator/src/media-asset-path.ts`). Before #667 media
|
|
31
|
+
* shipped under its bare slug, which the operator can re-point at new
|
|
32
|
+
* bytes, so it could only get a 1-hour lifetime and Lighthouse flagged
|
|
33
|
+
* every logo and image ("Use efficient cache lifetimes").
|
|
28
34
|
*
|
|
29
|
-
* NOT content-hashed (deliberately excluded):
|
|
30
|
-
*
|
|
31
|
-
*
|
|
35
|
+
* NOT content-hashed (deliberately excluded): pages, robots.txt,
|
|
36
|
+
* sitemap.xml, the manifests, the pinned-font `.license.txt` files (named
|
|
37
|
+
* by font id) and media under the pre-#667 bare-slug names.
|
|
32
38
|
*/
|
|
33
39
|
/** Long-lived policy for content-addressed files. */
|
|
34
40
|
export const IMMUTABLE_CACHE_CONTROL = "public, max-age=31536000, immutable";
|
|
@@ -41,7 +47,9 @@ export const HTML_CACHE_CONTROL = "public, max-age=60, stale-while-revalidate=86
|
|
|
41
47
|
* the same string can be handed to Firebase Hosting's `regex` header
|
|
42
48
|
* matcher and Caddy's `path_regexp` (both RE2) and to `RegExp` here.
|
|
43
49
|
*/
|
|
44
|
-
export const CONTENT_HASHED_PATH_PATTERN = "^/(?:_app/immutable/.+|_assets/fonts/[^/]+/[0-9a-f]{16,64}\\.(?:woff2|woff|ttf|otf)|_caelo/plugin/[^/]+/[^/]+\\.[0-9a-f]{12}\\.(?:js|css))$";
|
|
50
|
+
export const CONTENT_HASHED_PATH_PATTERN = "^/(?:_app/immutable/.+|_assets/fonts/[^/]+/[0-9a-f]{16,64}\\.(?:woff2|woff|ttf|otf)|_assets/[a-z0-9][a-z0-9-]*(?:/[a-z][a-z0-9-]*)?\\.[0-9a-f]{16}\\.[a-z0-9]{2,5}|_caelo/plugin/[^/]+/[^/]+\\.[0-9a-f]{12}\\.(?:js|css))$";
|
|
51
|
+
/** Hex length of the content hash in published media names (#667). */
|
|
52
|
+
export const MEDIA_CONTENT_HASH_HEX_LENGTH = 16;
|
|
45
53
|
const CONTENT_HASHED_PATH_RE = new RegExp(CONTENT_HASHED_PATH_PATTERN);
|
|
46
54
|
/**
|
|
47
55
|
* True when `path` names a content-addressed build output. Accepts a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"static-cache-policy.js","sourceRoot":"","sources":["../src/static-cache-policy.ts"],"names":[],"mappings":"AAAA,mCAAmC;AAEnC
|
|
1
|
+
{"version":3,"file":"static-cache-policy.js","sourceRoot":"","sources":["../src/static-cache-policy.ts"],"names":[],"mappings":"AAAA,mCAAmC;AAEnC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,qDAAqD;AACrD,MAAM,CAAC,MAAM,uBAAuB,GAAG,qCAAqC,CAAC;AAE7E,uEAAuE;AACvE,MAAM,CAAC,MAAM,kBAAkB,GAAG,kDAAkD,CAAC;AAErF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,2BAA2B,GACtC,4NAA4N,CAAC;AAE/N,sEAAsE;AACtE,MAAM,CAAC,MAAM,6BAA6B,GAAG,EAAE,CAAC;AAEhD,MAAM,sBAAsB,GAAG,IAAI,MAAM,CAAC,2BAA2B,CAAC,CAAC;AAEvE;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,OAAO,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;AAC/E,CAAC"}
|
package/dist/version.d.ts
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* Format: SemVer 2.0.0. Pre-1.0 minor bumps are breaking; post-1.0
|
|
20
20
|
* follow standard SemVer.
|
|
21
21
|
*/
|
|
22
|
-
export declare const CAELO_VERSION = "0.10.
|
|
22
|
+
export declare const CAELO_VERSION = "0.10.42";
|
|
23
23
|
/**
|
|
24
24
|
* Deprecated alias for back-compat — early P17 work spelled this
|
|
25
25
|
* `CALEO_VERSION` (typo of "Caelo"). New code should import
|
|
@@ -31,7 +31,7 @@ export declare const CAELO_VERSION = "0.10.40";
|
|
|
31
31
|
*
|
|
32
32
|
* @deprecated use CAELO_VERSION
|
|
33
33
|
*/
|
|
34
|
-
export declare const CALEO_VERSION = "0.10.
|
|
34
|
+
export declare const CALEO_VERSION = "0.10.42";
|
|
35
35
|
/**
|
|
36
36
|
* Parsed shape — major/minor/patch + optional pre-release tag.
|
|
37
37
|
* Stable interface for callers that need to feature-gate (rare —
|
package/dist/version.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* Format: SemVer 2.0.0. Pre-1.0 minor bumps are breaking; post-1.0
|
|
21
21
|
* follow standard SemVer.
|
|
22
22
|
*/
|
|
23
|
-
export const CAELO_VERSION = "0.10.
|
|
23
|
+
export const CAELO_VERSION = "0.10.42";
|
|
24
24
|
/**
|
|
25
25
|
* Deprecated alias for back-compat — early P17 work spelled this
|
|
26
26
|
* `CALEO_VERSION` (typo of "Caelo"). New code should import
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@caelo-cms/shared",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.42",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
6
|
"description": "Shared Zod schemas + types + the two-pass HTML composer used by every Caelo CMS workspace package. Stable enough for plugin authors and embedders to depend on directly.",
|
package/src/media.ts
CHANGED
|
@@ -99,7 +99,8 @@ export const MEDIA_VARIANT_WIDTHS: Record<Exclude<MediaVariantTag, "orig">, numb
|
|
|
99
99
|
* `/_caelo/media/<slug>/<variant>` for a named variant (webp/crops). The
|
|
100
100
|
* `<slug>` is `media_assets.slug` — a human-meaningful name (e.g.
|
|
101
101
|
* `searchviu-logo`); the UUID id stays internal. The static generator
|
|
102
|
-
* rewrites these to `/_assets/<slug>.<ext>` (orig) /
|
|
102
|
+
* rewrites these to content-hashed `/_assets/<slug>.<hash>.<ext>` (orig) /
|
|
103
|
+
* `/_assets/<slug>/<variant>.<hash>.<ext>` (#667).
|
|
103
104
|
*
|
|
104
105
|
* Legacy form `/_caelo/media/<uuid>/<variant>` is still PARSED (existing
|
|
105
106
|
* persisted embeds keep resolving), but never newly EMITTED.
|
|
@@ -207,7 +208,7 @@ export const mediaUploadInputSchema = z
|
|
|
207
208
|
/**
|
|
208
209
|
* Meaningful, human-facing label for the asset (e.g. "SearchVIU logo").
|
|
209
210
|
* The handler slugifies + uniquifies it into `media_assets.slug`, which
|
|
210
|
-
* becomes the public URL (`/_assets/<slug>.<ext>`); the id stays internal.
|
|
211
|
+
* becomes the public URL (`/_assets/<slug>.<hash>.<ext>`); the id stays internal.
|
|
211
212
|
* Optional: falls back to `alt` → `originalName` → "image".
|
|
212
213
|
*/
|
|
213
214
|
name: z.string().max(200).optional(),
|
package/src/preview-compose.ts
CHANGED
|
@@ -240,7 +240,7 @@ export function fontsHeadFragment(fonts: ComposeFonts | undefined): string | nul
|
|
|
240
240
|
* composer emits it from the theme binding instead of relying on a
|
|
241
241
|
* module to carry the tag. The href is the media URL as composed
|
|
242
242
|
* (`/_caelo/media/<slug>`); the static generator's media pass rewrites
|
|
243
|
-
* it to the published `/_assets/<slug>.<ext>` and copies the bytes, the
|
|
243
|
+
* it to the published `/_assets/<slug>.<hash>.<ext>` and copies the bytes, the
|
|
244
244
|
* same as any other media reference. Returns null when no theme is
|
|
245
245
|
* threaded or no favicon is bound (nothing to declare; browsers fall
|
|
246
246
|
* back to their own `/favicon.ico` probe exactly as before).
|
|
@@ -102,3 +102,77 @@ describe("pickAiImageVariant", () => {
|
|
|
102
102
|
expect(pickAiImageVariant(["orig", "square-800", "webp-400"])).toBe("webp-400");
|
|
103
103
|
});
|
|
104
104
|
});
|
|
105
|
+
|
|
106
|
+
describe("enrichResponsiveImages — orig embeds (#667 image delivery)", () => {
|
|
107
|
+
const SLUG = "searchviu-logo";
|
|
108
|
+
const logoVariants: ImageVariantInfo[] = [
|
|
109
|
+
{ variant: "orig", format: "png", width: 960 },
|
|
110
|
+
{ variant: "webp-400", format: "webp", width: 400 },
|
|
111
|
+
{ variant: "webp-800", format: "webp", width: 800 },
|
|
112
|
+
];
|
|
113
|
+
const map = new Map([[SLUG, logoVariants]]);
|
|
114
|
+
const urlFor = (ref: string, v: string, f: string) =>
|
|
115
|
+
v === "orig" ? `/_assets/${ref}.${f}` : `/_assets/${ref}/${v}.${f}`;
|
|
116
|
+
const opts = { rewriteSrc: false, urlFor };
|
|
117
|
+
|
|
118
|
+
it("offers the WebP ladder plus the original when the author declared sizes", () => {
|
|
119
|
+
const out = enrichResponsiveImages(
|
|
120
|
+
`<img src="/_caelo/media/${SLUG}" width="160" height="40" sizes="160px" alt="searchVIU">`,
|
|
121
|
+
map,
|
|
122
|
+
{ rewriteSrc: true, urlFor, formatFor: (_r, v) => (v === "orig" ? "png" : "webp") },
|
|
123
|
+
);
|
|
124
|
+
expect(out).toContain(`src="/_assets/${SLUG}.png"`);
|
|
125
|
+
expect(out).toContain(
|
|
126
|
+
`srcset="/_assets/${SLUG}/webp-400.webp 400w, /_assets/${SLUG}/webp-800.webp 800w, /_assets/${SLUG}.png 960w"`,
|
|
127
|
+
);
|
|
128
|
+
expect(out.match(/sizes=/g)).toHaveLength(1);
|
|
129
|
+
expect(out).toContain('sizes="160px"');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("drops the original when a WebP of its width exists", () => {
|
|
133
|
+
const exact = new Map([
|
|
134
|
+
[
|
|
135
|
+
SLUG,
|
|
136
|
+
[
|
|
137
|
+
{ variant: "orig", format: "jpeg", width: 400 },
|
|
138
|
+
{ variant: "webp-400", format: "webp", width: 400 },
|
|
139
|
+
],
|
|
140
|
+
],
|
|
141
|
+
]);
|
|
142
|
+
const out = enrichResponsiveImages(
|
|
143
|
+
`<img src="/_caelo/media/${SLUG}" sizes="120px">`,
|
|
144
|
+
exact,
|
|
145
|
+
opts,
|
|
146
|
+
);
|
|
147
|
+
expect(out).toContain(`srcset="/_assets/${SLUG}/webp-400.webp 400w"`);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("keeps the original without an author sizes: a width attribute is not the rendered size", () => {
|
|
151
|
+
// CSS may stretch a `width="400"` image to 1200px; guessing sizes from
|
|
152
|
+
// the attribute would make the browser pick (and upscale) a 400px file.
|
|
153
|
+
for (const tag of [
|
|
154
|
+
`<img src="/_caelo/media/${SLUG}" width="400" style="width:100%" alt="x">`,
|
|
155
|
+
`<img src="/_caelo/media/${SLUG}" alt="no width">`,
|
|
156
|
+
]) {
|
|
157
|
+
expect(enrichResponsiveImages(tag, map, opts)).not.toContain("srcset=");
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("leaves author srcset, SVG/GIF originals and unknown original widths alone", () => {
|
|
162
|
+
const authored = enrichResponsiveImages(
|
|
163
|
+
`<img src="/_caelo/media/${SLUG}" sizes="160px" srcset="a.png 1x">`,
|
|
164
|
+
map,
|
|
165
|
+
opts,
|
|
166
|
+
);
|
|
167
|
+
expect(authored.match(/srcset=/g)).toHaveLength(1);
|
|
168
|
+
for (const orig of [
|
|
169
|
+
{ variant: "orig", format: "gif", width: 960 },
|
|
170
|
+
{ variant: "orig", format: "png" },
|
|
171
|
+
]) {
|
|
172
|
+
const m = new Map([[SLUG, [orig, { variant: "webp-400", format: "webp", width: 400 }]]]);
|
|
173
|
+
expect(
|
|
174
|
+
enrichResponsiveImages(`<img src="/_caelo/media/${SLUG}" sizes="160px">`, m, opts),
|
|
175
|
+
).not.toContain("srcset=");
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
});
|
package/src/responsive-images.ts
CHANGED
|
@@ -19,8 +19,22 @@
|
|
|
19
19
|
export interface ImageVariantInfo {
|
|
20
20
|
readonly variant: string;
|
|
21
21
|
readonly format: string;
|
|
22
|
+
/** Pixel width of the stored file, when known (`media_variants.width`).
|
|
23
|
+
* Needed for the `orig` entry so an orig embed's srcset can offer the
|
|
24
|
+
* original at its real width (#667). */
|
|
25
|
+
readonly width?: number | null;
|
|
22
26
|
}
|
|
23
27
|
|
|
28
|
+
/** Original formats the WebP ladder can stand in for. SVG/ICO are not
|
|
29
|
+
* raster sources and GIFs may be animated (the ladder is a still frame). */
|
|
30
|
+
const LADDER_REPLACEABLE_ORIG_FORMATS: ReadonlySet<string> = new Set([
|
|
31
|
+
"png",
|
|
32
|
+
"jpeg",
|
|
33
|
+
"jpg",
|
|
34
|
+
"webp",
|
|
35
|
+
"avif",
|
|
36
|
+
]);
|
|
37
|
+
|
|
24
38
|
/** Extract `webp-800` → 800; `square-400` → 400; `orig` → null. */
|
|
25
39
|
export function parseVariantWidth(variant: string): number | null {
|
|
26
40
|
const m = variant.match(/-(\d+)$/);
|
|
@@ -126,6 +140,24 @@ export function enrichResponsiveImages(
|
|
|
126
140
|
out = out.replace(srcMatch[0], `src="${options.urlFor(assetId, variant, requestedFormat)}"`);
|
|
127
141
|
}
|
|
128
142
|
|
|
143
|
+
// #667 — an `orig` embed (the default `/_caelo/media/<slug>` form,
|
|
144
|
+
// typical for logos) shipped the full original as PNG/JPEG even when
|
|
145
|
+
// the pipeline had a WebP ladder for it: Lighthouse's image-delivery
|
|
146
|
+
// insight flagged the format and the oversize on every such image.
|
|
147
|
+
// Only the author knows the rendered width (a `width` attribute is often
|
|
148
|
+
// just the intrinsic size that CSS stretches), so the ladder is offered
|
|
149
|
+
// only when the tag declares that contract itself with `sizes`: then
|
|
150
|
+
// the WebP ladder plus the original at its real width go into srcset
|
|
151
|
+
// and the browser picks the smallest file that covers the declared
|
|
152
|
+
// size. Without `sizes` the tag keeps shipping the original.
|
|
153
|
+
const origLadder =
|
|
154
|
+
variant === "orig" && /\bsizes=/i.test(out) && !/\bsrcset=/i.test(out)
|
|
155
|
+
? origSrcset(variants, assetId, options)
|
|
156
|
+
: null;
|
|
157
|
+
if (origLadder) {
|
|
158
|
+
out = out.replace(/<img\b/, `<img srcset="${origLadder}"`);
|
|
159
|
+
}
|
|
160
|
+
|
|
129
161
|
if (familyVariants.length >= 2 && !/\bsrcset=/i.test(out)) {
|
|
130
162
|
const srcset = familyVariants
|
|
131
163
|
.map((v) => `${options.urlFor(assetId, v.variant, "webp")} ${v.width}w`)
|
|
@@ -150,3 +182,38 @@ export function enrichResponsiveImages(
|
|
|
150
182
|
return out;
|
|
151
183
|
});
|
|
152
184
|
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* The srcset for an `orig` embed whose author declared `sizes`, or null when
|
|
188
|
+
* it would not help: no raster original, no WebP breakpoint the source could
|
|
189
|
+
* fill, or an original whose width is unknown (without it the browser could
|
|
190
|
+
* be offered only candidates narrower than the image it needs, and upscale
|
|
191
|
+
* them).
|
|
192
|
+
*/
|
|
193
|
+
function origSrcset(
|
|
194
|
+
variants: readonly ImageVariantInfo[],
|
|
195
|
+
assetId: string,
|
|
196
|
+
options: EnrichResponsiveImagesOptions,
|
|
197
|
+
): string | null {
|
|
198
|
+
const orig = variants.find((v) => v.variant === "orig");
|
|
199
|
+
if (!orig || !LADDER_REPLACEABLE_ORIG_FORMATS.has(orig.format)) return null;
|
|
200
|
+
const origWidth = orig.width ?? null;
|
|
201
|
+
if (origWidth === null) return null;
|
|
202
|
+
const ladder = variants
|
|
203
|
+
.filter((v) => variantFamily(v.variant) === "webp" && v.format === "webp")
|
|
204
|
+
.map((v) => ({ variant: v.variant, width: parseVariantWidth(v.variant) }))
|
|
205
|
+
.filter(
|
|
206
|
+
(v): v is { variant: string; width: number } => v.width !== null && v.width <= origWidth,
|
|
207
|
+
)
|
|
208
|
+
.sort((a, b) => a.width - b.width);
|
|
209
|
+
const widest = ladder.at(-1);
|
|
210
|
+
if (!widest) return null;
|
|
211
|
+
const entries = ladder.map((v) => `${options.urlFor(assetId, v.variant, "webp")} ${v.width}w`);
|
|
212
|
+
// The original is only a candidate when it is wider than every WebP
|
|
213
|
+
// (a source wider than the largest breakpoint); otherwise the WebP of
|
|
214
|
+
// the same width replaces it.
|
|
215
|
+
if (origWidth > widest.width) {
|
|
216
|
+
entries.push(`${options.urlFor(assetId, "orig", orig.format)} ${origWidth}w`);
|
|
217
|
+
}
|
|
218
|
+
return entries.join(", ");
|
|
219
|
+
}
|
|
@@ -13,6 +13,11 @@ const HASHED_PATHS = [
|
|
|
13
13
|
"/_caelo/plugin/consent-manager/runtime.0123456789ab.js",
|
|
14
14
|
"/_caelo/plugin/consent-manager/banner.abcdef012345.css",
|
|
15
15
|
"/_app/immutable/chunks/abc.js",
|
|
16
|
+
// #667 — published media, named by the hash of its bytes.
|
|
17
|
+
"/_assets/searchviu-logo.0123456789abcdef.png",
|
|
18
|
+
"/_assets/hero/webp-800.0123456789abcdef.webp",
|
|
19
|
+
"/_assets/0b1f6c2e-9d7a-4c1e-8f3a-2b6d9e0c1a4f/orig.fedcba9876543210.jpg",
|
|
20
|
+
"/_assets/brand-font.0123456789abcdef.woff2",
|
|
16
21
|
];
|
|
17
22
|
|
|
18
23
|
/** Build outputs served under a stable name — must stay short-lived. */
|
|
@@ -27,7 +32,7 @@ const STABLE_PATHS = [
|
|
|
27
32
|
"/routing-manifest.json",
|
|
28
33
|
"/cdn_manifest.json",
|
|
29
34
|
"/_content-types.json",
|
|
30
|
-
//
|
|
35
|
+
// Pre-#667 media addressed by bare slug: bytes can change behind the same URL.
|
|
31
36
|
"/_assets/searchviu-logo.png",
|
|
32
37
|
"/_assets/hero/w800.webp",
|
|
33
38
|
"/_assets/0b1f6c2e-9d7a-4c1e-8f3a-2b6d9e0c1a4f/orig.jpg",
|
|
@@ -40,6 +45,10 @@ const STABLE_PATHS = [
|
|
|
40
45
|
"/_caelo/plugin/consent-manager/runtime.js",
|
|
41
46
|
"/blog/_assets/fonts/inter/29ede7bd4be32ab0.woff2",
|
|
42
47
|
"/api/forms/submit",
|
|
48
|
+
"/_assets/searchviu-logo.0123456789ABCDEF.png",
|
|
49
|
+
"/_assets/searchviu-logo.0123456789abcde.png",
|
|
50
|
+
"/_assets/a/b/c.0123456789abcdef.png",
|
|
51
|
+
"/blog/_assets/searchviu-logo.0123456789abcdef.png",
|
|
43
52
|
];
|
|
44
53
|
|
|
45
54
|
describe("isContentHashedPath", () => {
|
|
@@ -8,9 +8,8 @@
|
|
|
8
8
|
* The rule: only a URL whose bytes can never change may be cached
|
|
9
9
|
* forever. That holds exactly when the URL itself changes whenever the
|
|
10
10
|
* content does, i.e. the file name carries a content hash. Everything
|
|
11
|
-
* else (pages, robots.txt, sitemap.xml, manifests
|
|
12
|
-
*
|
|
13
|
-
* revalidating so a publish shows up promptly.
|
|
11
|
+
* else (pages, robots.txt, sitemap.xml, manifests) must stay short-lived
|
|
12
|
+
* or revalidating so a publish shows up promptly.
|
|
14
13
|
*
|
|
15
14
|
* Content-hashed paths the static generator emits today:
|
|
16
15
|
*
|
|
@@ -26,10 +25,17 @@
|
|
|
26
25
|
* (`plugin-host/src/client-assets.ts`).
|
|
27
26
|
* - `_app/immutable/**` — Vite's hashed-output convention, kept for
|
|
28
27
|
* builds that ship SvelteKit-style bundles.
|
|
28
|
+
* - `_assets/<slug>.<16 hex>.<ext>` and
|
|
29
|
+
* `_assets/<slug-or-id>/<variant>.<16 hex>.<ext>` — published media
|
|
30
|
+
* (#667), named by the sha256 of the published bytes
|
|
31
|
+
* (`apps/static-generator/src/media-asset-path.ts`). Before #667 media
|
|
32
|
+
* shipped under its bare slug, which the operator can re-point at new
|
|
33
|
+
* bytes, so it could only get a 1-hour lifetime and Lighthouse flagged
|
|
34
|
+
* every logo and image ("Use efficient cache lifetimes").
|
|
29
35
|
*
|
|
30
|
-
* NOT content-hashed (deliberately excluded):
|
|
31
|
-
*
|
|
32
|
-
*
|
|
36
|
+
* NOT content-hashed (deliberately excluded): pages, robots.txt,
|
|
37
|
+
* sitemap.xml, the manifests, the pinned-font `.license.txt` files (named
|
|
38
|
+
* by font id) and media under the pre-#667 bare-slug names.
|
|
33
39
|
*/
|
|
34
40
|
|
|
35
41
|
/** Long-lived policy for content-addressed files. */
|
|
@@ -46,7 +52,10 @@ export const HTML_CACHE_CONTROL = "public, max-age=60, stale-while-revalidate=86
|
|
|
46
52
|
* matcher and Caddy's `path_regexp` (both RE2) and to `RegExp` here.
|
|
47
53
|
*/
|
|
48
54
|
export const CONTENT_HASHED_PATH_PATTERN =
|
|
49
|
-
"^/(?:_app/immutable/.+|_assets/fonts/[^/]+/[0-9a-f]{16,64}\\.(?:woff2|woff|ttf|otf)|_caelo/plugin/[^/]+/[^/]+\\.[0-9a-f]{12}\\.(?:js|css))$";
|
|
55
|
+
"^/(?:_app/immutable/.+|_assets/fonts/[^/]+/[0-9a-f]{16,64}\\.(?:woff2|woff|ttf|otf)|_assets/[a-z0-9][a-z0-9-]*(?:/[a-z][a-z0-9-]*)?\\.[0-9a-f]{16}\\.[a-z0-9]{2,5}|_caelo/plugin/[^/]+/[^/]+\\.[0-9a-f]{12}\\.(?:js|css))$";
|
|
56
|
+
|
|
57
|
+
/** Hex length of the content hash in published media names (#667). */
|
|
58
|
+
export const MEDIA_CONTENT_HASH_HEX_LENGTH = 16;
|
|
50
59
|
|
|
51
60
|
const CONTENT_HASHED_PATH_RE = new RegExp(CONTENT_HASHED_PATH_PATTERN);
|
|
52
61
|
|