@doswiftly/storefront-sdk 22.8.0 → 22.8.1
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 +14 -0
- package/dist/core/image.d.ts.map +1 -1
- package/dist/core/image.js +52 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 22.8.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 61a2841: Fix: images imported in code are optimized again when your build serves static assets from a CDN domain.
|
|
8
|
+
|
|
9
|
+
An image you import in code (`import hero from './hero.webp'`) becomes a content-hashed file under `/_next/static/media/`. When your build serves static assets from a CDN domain (`assetPrefix` / `getAssetPrefix()`), Next.js rewrites the `<Image>` `src` to an absolute CDN URL before the loader runs. The loader did not recognize that form, so every `srcset` entry pointed at the full-size original — no resizing or format negotiation.
|
|
10
|
+
|
|
11
|
+
The loader now recognizes code-imported images in that absolute form and routes them through the image CDN, so each `srcset` width is resized again — the same behavior as a build without `assetPrefix`. Product images and `public/` images were never affected.
|
|
12
|
+
|
|
13
|
+
No API changes — update the package and redeploy.
|
|
14
|
+
|
|
15
|
+
(`@doswiftly/storefront-operations` is version-synced with the SDK; no operation changes.)
|
|
16
|
+
|
|
3
17
|
## 22.8.0
|
|
4
18
|
|
|
5
19
|
### Minor Changes
|
package/dist/core/image.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../../src/core/image.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,gFAAgF;IAChF,GAAG,EAAE,MAAM,CAAC;IACZ,uCAAuC;IACvC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,qCAAqC;IACrC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,sCAAsC;IACtC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,eAAe;IACf,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,2GAA2G;IAC3G,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAMD;;;GAGG;AACH,eAAO,MAAM,kBAAkB,6BAA6B,CAAC;AAE7D,6EAA6E;AAC7E,MAAM,WAAW,iBAAiB;IAChC,kFAAkF;IAClF,MAAM,EAAE,MAAM,CAAC;IACf,2GAA2G;IAC3G,OAAO,EAAE,MAAM,CAAC;IAChB,+EAA+E;IAC/E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,uDAAwD,CAAC;
|
|
1
|
+
{"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../../src/core/image.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,gFAAgF;IAChF,GAAG,EAAE,MAAM,CAAC;IACZ,uCAAuC;IACvC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,qCAAqC;IACrC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,sCAAsC;IACtC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,eAAe;IACf,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,2GAA2G;IAC3G,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAMD;;;GAGG;AACH,eAAO,MAAM,kBAAkB,6BAA6B,CAAC;AAE7D,6EAA6E;AAC7E,MAAM,WAAW,iBAAiB;IAChC,kFAAkF;IAClF,MAAM,EAAE,MAAM,CAAC;IACf,2GAA2G;IAC3G,OAAO,EAAE,MAAM,CAAC;IAChB,+EAA+E;IAC/E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,uDAAwD,CAAC;AAmF9F,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,iBAAiB,EACzB,IAAI,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACnC,MAAM,CAiER;AAOD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAiD5F"}
|
package/dist/core/image.js
CHANGED
|
@@ -69,6 +69,44 @@ const NEXT_STATIC_MEDIA_PREFIX = '/_next/static/media/';
|
|
|
69
69
|
function nextStaticMediaSuffix(pathname) {
|
|
70
70
|
return pathname.startsWith(NEXT_STATIC_MEDIA_PREFIX) ? pathname.slice(NEXT_STATIC_MEDIA_PREFIX.length) : null;
|
|
71
71
|
}
|
|
72
|
+
/** Scheme + authority of an absolute http(s) URL — everything up to the first path `/`. */
|
|
73
|
+
const ABSOLUTE_URL_AUTHORITY = /^https?:\/\/[^/]+/i;
|
|
74
|
+
/**
|
|
75
|
+
* Suffix after this shop's `/s/{shopId}/_next/static/media/` when `src` is an ABSOLUTE URL — a
|
|
76
|
+
* code-imported image whose `src` carries the storefront's Next.js `assetPrefix`
|
|
77
|
+
* (`{assetPrefixBase}/s/{shopId}/_next/static/media/{suffix}`, which Next prepends before the loader
|
|
78
|
+
* runs). `null` for relative srcs, other shops' assets, and foreign hosts. Anchored on `/s/{shopId}/`
|
|
79
|
+
* so a foreign host that merely embeds the marker mid-path is rejected; matched on path structure, not
|
|
80
|
+
* host — the asset-prefix CDN host is not part of the loader config, only the resize base is.
|
|
81
|
+
*
|
|
82
|
+
* The path is taken from the RAW `src` (strip scheme/authority + query/hash), NOT via `URL.pathname`:
|
|
83
|
+
* that percent-encodes the path, which would double-encode in {@link buildNextMediaCdnUrl}. The
|
|
84
|
+
* root-relative counterpart {@link nextStaticMediaSuffix} likewise feeds a raw path, so both `src`
|
|
85
|
+
* forms of one import — relative (no `assetPrefix`) and absolute (with it) — encode identically.
|
|
86
|
+
*/
|
|
87
|
+
function absoluteNextMediaSuffix(src, shopId) {
|
|
88
|
+
// Fast reject before any allocation: must be an absolute http(s) URL that mentions the media segment.
|
|
89
|
+
if (!ABSOLUTE_URL_AUTHORITY.test(src) || !src.includes(NEXT_STATIC_MEDIA_PREFIX))
|
|
90
|
+
return null;
|
|
91
|
+
const path = src.replace(ABSOLUTE_URL_AUTHORITY, '').replace(/[?#].*$/, '');
|
|
92
|
+
const prefix = `/s/${shopId}${NEXT_STATIC_MEDIA_PREFIX}`;
|
|
93
|
+
return path.startsWith(prefix) ? path.slice(prefix.length) : null;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Image-CDN URL for a Next build-output media image (content-hashed → the hash IS the version, so the
|
|
97
|
+
* key is immutable: only the per-srcset `width`, no `?v=`). The same key shape the deploy mirror
|
|
98
|
+
* writes, so the resize CDN finds the file. Shared by both `src` forms of one import: root-relative
|
|
99
|
+
* ({@link nextStaticMediaSuffix}) and absolute `assetPrefix` ({@link absoluteNextMediaSuffix}).
|
|
100
|
+
*
|
|
101
|
+
* `encodeURIComponent` keeps `..` (unreserved) — harmless while the image CDN is unsigned + public;
|
|
102
|
+
* if HMAC signing is ever added, strip `..` segments here AND in the public/ branch.
|
|
103
|
+
*
|
|
104
|
+
* @sync-with backend deploy mirror (`_next/static/media/` mirrored 1:1 into the image-CDN key)
|
|
105
|
+
*/
|
|
106
|
+
function buildNextMediaCdnUrl(base, shopId, suffix, width) {
|
|
107
|
+
const encoded = suffix.split('/').map(encodeURIComponent).join('/');
|
|
108
|
+
return `${base}/s/${shopId}/_next/static/media/${encoded}?width=${width}`;
|
|
109
|
+
}
|
|
72
110
|
export function buildImageLoaderUrl(config, args) {
|
|
73
111
|
// `||` (not `??`): an empty injected base must still fall back to the platform default.
|
|
74
112
|
const base = (config.cdnBase || IMAGE_CDN_BASE_URL).replace(/\/+$/, '');
|
|
@@ -84,6 +122,18 @@ export function buildImageLoaderUrl(config, args) {
|
|
|
84
122
|
return src;
|
|
85
123
|
}
|
|
86
124
|
}
|
|
125
|
+
// (1b) Absolute build-output media URL — a code-imported image whose `src` carries the storefront's
|
|
126
|
+
// next.config `assetPrefix` (`{base}/s/{shopId}/_next/static/media/...`, prepended by Next
|
|
127
|
+
// before this loader). Routed to the SAME image-CDN key as the root-relative case (2a) — the two
|
|
128
|
+
// are just the assetPrefix / no-assetPrefix forms of one import. Fonts (`.woff2`) share
|
|
129
|
+
// `_next/static/media/` but are not images, so the extension gate leaves them to load raw from
|
|
130
|
+
// the asset CDN (they are not mirrored to the resize CDN).
|
|
131
|
+
if (config.shopId) {
|
|
132
|
+
const mediaSuffix = absoluteNextMediaSuffix(src, config.shopId);
|
|
133
|
+
if (mediaSuffix !== null && LOADER_IMAGE_EXTENSIONS.test(mediaSuffix)) {
|
|
134
|
+
return buildNextMediaCdnUrl(base, config.shopId, mediaSuffix, width);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
87
137
|
// (2) Local root-relative image — NOT protocol-relative (`//host`). Two sub-cases:
|
|
88
138
|
// (2a) a Next build-output media image (`/_next/static/media/*`), and (2b) a `public/` image.
|
|
89
139
|
if (src.startsWith('/') && !src.startsWith('//')) {
|
|
@@ -96,14 +146,10 @@ export function buildImageLoaderUrl(config, args) {
|
|
|
96
146
|
// extension gate is the safety that keeps framework build chunks out of the resize CDN.
|
|
97
147
|
if (!LOADER_IMAGE_EXTENSIONS.test(pathname))
|
|
98
148
|
return src;
|
|
99
|
-
// (2a) Next build-output media image
|
|
100
|
-
// the CDN key is immutable → no `?v=`. The deploy mirrors these to `s/{shopId}/_next/static/media/`.
|
|
149
|
+
// (2a) Next build-output media image — same image-CDN key as the absolute (assetPrefix) form (1b).
|
|
101
150
|
const mediaSuffix = nextStaticMediaSuffix(pathname);
|
|
102
151
|
if (mediaSuffix !== null) {
|
|
103
|
-
|
|
104
|
-
// public; if HMAC signing is ever added, strip `..` segments here AND in the public/ branch.
|
|
105
|
-
const encoded = mediaSuffix.split('/').map(encodeURIComponent).join('/');
|
|
106
|
-
return `${base}/s/${config.shopId}/_next/static/media/${encoded}?width=${width}`;
|
|
152
|
+
return buildNextMediaCdnUrl(base, config.shopId, mediaSuffix, width);
|
|
107
153
|
}
|
|
108
154
|
// Any other framework build output (hashed/immutable, NOT mirrored) → untouched.
|
|
109
155
|
const buildPrefixes = config.buildAssetPrefixes ?? FRAMEWORK_BUILD_PREFIXES;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@doswiftly/storefront-sdk",
|
|
3
|
-
"version": "22.8.
|
|
3
|
+
"version": "22.8.1",
|
|
4
4
|
"description": "Storefront runtime SDK for DoSwiftly Commerce — layered transport, middleware pipeline, React providers, Zustand stores, cache strategies. 0 runtime dependencies in core.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|