@wocart/theme 1.3.0 → 1.4.0

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/CHANGELOG.md CHANGED
@@ -5,6 +5,22 @@ All notable changes to `@wocart/theme` are recorded here. The package follows
5
5
  major version, and an export is marked `@deprecated` for at least one minor
6
6
  release before a major removes it.
7
7
 
8
+ ## 1.4.0 — 2026-10-18
9
+
10
+ ### Added
11
+
12
+ - `@wocart/theme/image-service`: `mediaImageService(() => mediaDomain)`, an
13
+ Astro image service that resizes media-domain images with Cloudflare Image
14
+ Resizing on the media domain itself, so a storefront on Cloudflare and one
15
+ on Vercel get the same URLs and edge cache. Other images are served as they
16
+ are; the domain is read per image, so one build serves every store.
17
+ `mediaImageParams` maps an Astro transform to the resizing options (Astro's
18
+ `fit` in Cloudflare's words).
19
+
20
+ ### Changed
21
+
22
+ - The `@wocart/storefront` peer range is now `^1.14.0` (`mediaImageUrl`).
23
+
8
24
  ## 1.3.0 — 2026-10-17
9
25
 
10
26
  ### Added
package/README.md CHANGED
@@ -37,6 +37,7 @@ Node 22.12 or newer.
37
37
  | `@wocart/theme` | definitions, validation, page documents, resolution, the manifest, languages and messages — framework-neutral |
38
38
  | `@wocart/theme/astro` | `createReadCache` and the `RendererIssue` type |
39
39
  | `@wocart/theme/astro/InstanceView.astro` | the reference renderer component |
40
+ | `@wocart/theme/image-service` | `mediaImageService` — an Astro image service that resizes media-domain images on the media domain (any storefront host) |
40
41
 
41
42
  ## Use
42
43
 
@@ -444,6 +445,34 @@ migration engine, by design; what follows is the whole policy:
444
445
  stick to semver so the manifest validator stays quiet and future tooling can
445
446
  reason about upgrades.
446
447
 
448
+ ## Images
449
+
450
+ `@wocart/theme/image-service` resizes every `<Image>` of a media-domain image
451
+ with Cloudflare Image Resizing, on the media domain itself
452
+ (`https://<media>/cdn-cgi/image/…`). The media domain is a Cloudflare zone
453
+ wherever the storefront runs, so a storefront on Cloudflare and one on Vercel
454
+ share the same URLs and edge cache. Any other image is served as it is; with no
455
+ media domain, every image passes through.
456
+
457
+ The domain is read on each image, not at build — one build serves every store.
458
+ Bind it once:
459
+
460
+ ```ts
461
+ // src/image-service.ts
462
+ import { MEDIA_DOMAIN } from "astro:env/server";
463
+ import { mediaImageService } from "@wocart/theme/image-service";
464
+ export default mediaImageService(() => MEDIA_DOMAIN);
465
+ ```
466
+
467
+ ```js
468
+ // astro.config.mjs
469
+ image: { service: { entrypoint: "./src/image-service.ts" } },
470
+ // @astrojs/cloudflare: imageService: "custom" · @astrojs/vercel: leave imageService off
471
+ ```
472
+
473
+ The media domain's zone needs Image Transformations on (Images →
474
+ Transformations; included in the Free plan).
475
+
447
476
  ## Testing your theme
448
477
 
449
478
  A theme proves it honors the contract with four checks in its own test suite:
@@ -0,0 +1,39 @@
1
+ /**
2
+ * An Astro image service for Wocart storefronts: every `<Image>` and
3
+ * `getImage()` of a media-domain image is resized by Cloudflare Image Resizing
4
+ * on the media domain itself (`https://<media>/cdn-cgi/image/…/<path>`).
5
+ *
6
+ * The media domain is always a Cloudflare zone, wherever the storefront runs,
7
+ * so a storefront on Cloudflare and one on Vercel get the same URLs, the same
8
+ * edge cache and no second image bill. Any other image — another host, a file
9
+ * imported into the theme — is served as it is. Without a media domain (local
10
+ * development, a store without one) every image passes through unchanged.
11
+ *
12
+ * The domain is read per call, not at build: one theme build serves every
13
+ * store. Bind it once, in the theme, to wherever the store's media domain
14
+ * lives:
15
+ *
16
+ * ```ts
17
+ * // src/image-service.ts
18
+ * import { MEDIA_DOMAIN } from "astro:env/server";
19
+ * import { mediaImageService } from "@wocart/theme/image-service";
20
+ * export default mediaImageService(() => MEDIA_DOMAIN);
21
+ * ```
22
+ *
23
+ * ```js
24
+ * // astro.config.mjs — and the adapter keeps it:
25
+ * // @astrojs/cloudflare: imageService: "custom"; @astrojs/vercel: no imageService
26
+ * image: { service: { entrypoint: "./src/image-service.ts" } },
27
+ * ```
28
+ */
29
+ import type { ExternalImageService, ImageTransform } from "astro";
30
+ import { type MediaImageParams } from "@wocart/storefront";
31
+ /** The Cloudflare resizing options an Astro image transform asks for. */
32
+ export declare function mediaImageParams(options: ImageTransform): MediaImageParams;
33
+ /**
34
+ * The image service, bound to the store's media domain.
35
+ *
36
+ * @param mediaDomain - Read on every image (e.g. `() => MEDIA_DOMAIN`): the
37
+ * store's media domain, or `undefined` to pass every image through.
38
+ */
39
+ export declare function mediaImageService(mediaDomain: () => string | undefined): ExternalImageService;
@@ -0,0 +1,73 @@
1
+ /**
2
+ * An Astro image service for Wocart storefronts: every `<Image>` and
3
+ * `getImage()` of a media-domain image is resized by Cloudflare Image Resizing
4
+ * on the media domain itself (`https://<media>/cdn-cgi/image/…/<path>`).
5
+ *
6
+ * The media domain is always a Cloudflare zone, wherever the storefront runs,
7
+ * so a storefront on Cloudflare and one on Vercel get the same URLs, the same
8
+ * edge cache and no second image bill. Any other image — another host, a file
9
+ * imported into the theme — is served as it is. Without a media domain (local
10
+ * development, a store without one) every image passes through unchanged.
11
+ *
12
+ * The domain is read per call, not at build: one theme build serves every
13
+ * store. Bind it once, in the theme, to wherever the store's media domain
14
+ * lives:
15
+ *
16
+ * ```ts
17
+ * // src/image-service.ts
18
+ * import { MEDIA_DOMAIN } from "astro:env/server";
19
+ * import { mediaImageService } from "@wocart/theme/image-service";
20
+ * export default mediaImageService(() => MEDIA_DOMAIN);
21
+ * ```
22
+ *
23
+ * ```js
24
+ * // astro.config.mjs — and the adapter keeps it:
25
+ * // @astrojs/cloudflare: imageService: "custom"; @astrojs/vercel: no imageService
26
+ * image: { service: { entrypoint: "./src/image-service.ts" } },
27
+ * ```
28
+ */
29
+ import { baseService } from "astro/assets";
30
+ import { mediaImageUrl } from "@wocart/storefront";
31
+ /** Astro's `fit` (CSS object-fit) in Cloudflare's words; a value Cloudflare also knows passes as it is. */
32
+ const CLOUDFLARE_FIT = {
33
+ cover: "cover",
34
+ contain: "contain",
35
+ fill: "squeeze",
36
+ "scale-down": "scale-down",
37
+ crop: "crop",
38
+ pad: "pad",
39
+ squeeze: "squeeze",
40
+ };
41
+ /** The Cloudflare resizing options an Astro image transform asks for. */
42
+ export function mediaImageParams(options) {
43
+ const params = {};
44
+ if (options.width)
45
+ params.width = options.width;
46
+ if (options.height)
47
+ params.height = options.height;
48
+ if (options.format)
49
+ params.format = options.format;
50
+ if (typeof options.quality === "number")
51
+ params.quality = options.quality;
52
+ const fit = options.fit ? CLOUDFLARE_FIT[options.fit] : undefined;
53
+ if (fit)
54
+ params.fit = fit;
55
+ return params;
56
+ }
57
+ /**
58
+ * The image service, bound to the store's media domain.
59
+ *
60
+ * @param mediaDomain - Read on every image (e.g. `() => MEDIA_DOMAIN`): the
61
+ * store's media domain, or `undefined` to pass every image through.
62
+ */
63
+ export function mediaImageService(mediaDomain) {
64
+ return {
65
+ ...baseService,
66
+ getURL(options) {
67
+ const src = options.src;
68
+ if (typeof src !== "string")
69
+ return src.src;
70
+ return mediaImageUrl(src, mediaImageParams(options), mediaDomain()) ?? src;
71
+ },
72
+ };
73
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wocart/theme",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "The Wocart theme contract: code-owned component definitions, JSON page documents, validation and resolution, and a reference Astro renderer.",
5
5
  "keywords": [
6
6
  "wocart",
@@ -38,6 +38,10 @@
38
38
  "default": "./dist/astro/index.js"
39
39
  },
40
40
  "./astro/InstanceView.astro": "./dist/astro/InstanceView.astro",
41
+ "./image-service": {
42
+ "types": "./dist/image-service.d.ts",
43
+ "default": "./dist/image-service.js"
44
+ },
41
45
  "./package.json": "./package.json"
42
46
  },
43
47
  "files": [
@@ -63,7 +67,7 @@
63
67
  "zod": "^4.4.3"
64
68
  },
65
69
  "peerDependencies": {
66
- "@wocart/storefront": "^1.0.0",
70
+ "@wocart/storefront": "^1.14.0",
67
71
  "astro": "^7.0.0"
68
72
  },
69
73
  "peerDependenciesMeta": {