@vue-storefront/nuxt 11.0.0-next.4 → 11.0.0-next.7
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/module.d.mts +7 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +27 -1
- package/dist/runtime/image-optimizer/constants.d.ts +16 -0
- package/dist/runtime/image-optimizer/constants.js +4 -0
- package/dist/runtime/image-optimizer/handler.d.ts +7 -0
- package/dist/runtime/image-optimizer/handler.js +65 -0
- package/dist/runtime/image-optimizer/loader.d.ts +20 -0
- package/dist/runtime/image-optimizer/loader.js +44 -0
- package/dist/runtime/image-optimizer/logger.d.ts +7 -0
- package/dist/runtime/image-optimizer/logger.js +2 -0
- package/dist/runtime/image-optimizer/provider.d.ts +2 -0
- package/dist/runtime/image-optimizer/provider.js +28 -0
- package/dist/runtime/image-optimizer/resolve-hosts.d.ts +19 -0
- package/dist/runtime/image-optimizer/resolve-hosts.js +24 -0
- package/dist/runtime/image-optimizer/types.d.ts +71 -0
- package/dist/runtime/image-optimizer/types.js +0 -0
- package/dist/runtime/image-optimizer/variants.d.ts +7 -0
- package/dist/runtime/image-optimizer/variants.js +29 -0
- package/dist/runtime/types.template +12 -0
- package/package.json +13 -4
package/dist/module.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
|
+
import { ImageOptimizerConfig } from '../dist/runtime/image-optimizer/types.js';
|
|
2
3
|
|
|
3
4
|
interface MiddlewareConfig {
|
|
4
5
|
/**
|
|
@@ -33,6 +34,12 @@ type LoggerOptions = Partial<{
|
|
|
33
34
|
verbosity: LogVerbosity;
|
|
34
35
|
}>;
|
|
35
36
|
interface AlokaiModuleOptions {
|
|
37
|
+
/**
|
|
38
|
+
* Opt-in Alokai Image Optimizer config. When set with at least one host, the
|
|
39
|
+
* module registers an `@nuxt/image` provider and a `/img-proxy` route that
|
|
40
|
+
* proxy matching media-host images through the optimizer CDN.
|
|
41
|
+
*/
|
|
42
|
+
imageOptimizer?: ImageOptimizerConfig;
|
|
36
43
|
logger?: LoggerOptions;
|
|
37
44
|
middleware: MiddlewareConfig;
|
|
38
45
|
multistore?: MultistoreConfig;
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { defineNuxtModule, createResolver, installModule, addTypeTemplate, addImports, addTemplate, addPluginTemplate, addImportsSources
|
|
1
|
+
import { defineNuxtModule, createResolver, installModule, addServerHandler, addTypeTemplate, addImports, addTemplate, addPluginTemplate, addImportsSources } from '@nuxt/kit';
|
|
2
2
|
import { defu } from 'defu';
|
|
3
3
|
import { genInlineTypeImport } from 'knitwork';
|
|
4
4
|
import { readFileSync } from 'node:fs';
|
|
5
|
+
import { IMG_PROXY_PREFIX } from '../dist/runtime/image-optimizer/constants.js';
|
|
6
|
+
import { resolveHosts } from '../dist/runtime/image-optimizer/resolve-hosts.js';
|
|
5
7
|
|
|
6
8
|
const RUNTIME_CONFIG = {
|
|
7
9
|
public: {}
|
|
@@ -38,6 +40,30 @@ const module$1 = defineNuxtModule({
|
|
|
38
40
|
nuxt.options.runtimeConfig.public?.alokai,
|
|
39
41
|
options
|
|
40
42
|
);
|
|
43
|
+
if (options.imageOptimizer && Object.keys(options.imageOptimizer.hosts).length > 0) {
|
|
44
|
+
const resolvedHosts = resolveHosts(options.imageOptimizer.hosts);
|
|
45
|
+
for (const host of resolvedHosts) {
|
|
46
|
+
nuxt.options.runtimeConfig.public[host.runtimeConfigKey] = nuxt.options.runtimeConfig.public[host.runtimeConfigKey] ?? "";
|
|
47
|
+
}
|
|
48
|
+
nuxt.options.runtimeConfig.public.alokaiImageOptimizer = { hosts: resolvedHosts };
|
|
49
|
+
const nuxtOptions = nuxt.options;
|
|
50
|
+
nuxtOptions.image = {
|
|
51
|
+
...nuxtOptions.image,
|
|
52
|
+
provider: "alokai",
|
|
53
|
+
providers: {
|
|
54
|
+
...nuxtOptions.image?.providers,
|
|
55
|
+
alokai: {
|
|
56
|
+
provider: localResolver.resolve(
|
|
57
|
+
"./runtime/image-optimizer/provider"
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
addServerHandler({
|
|
63
|
+
handler: localResolver.resolve("./runtime/image-optimizer/handler"),
|
|
64
|
+
route: `${IMG_PROXY_PREFIX}/:host/**:path`
|
|
65
|
+
});
|
|
66
|
+
}
|
|
41
67
|
Object.assign(
|
|
42
68
|
nuxt.options.appConfig,
|
|
43
69
|
defu(nuxt.options.appConfig, {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL prefix the provider emits and the server route is mounted on:
|
|
3
|
+
* `/img-proxy/:host/**:path`.
|
|
4
|
+
*/
|
|
5
|
+
export declare const IMG_PROXY_PREFIX = "/img-proxy";
|
|
6
|
+
/**
|
|
7
|
+
* Cache-Control applied to successful (2xx) upstream responses unless
|
|
8
|
+
* overridden per host via `cacheControl`.
|
|
9
|
+
*/
|
|
10
|
+
export declare const DEFAULT_CACHE_CONTROL = "public, max-age=3600, s-maxage=86400";
|
|
11
|
+
/**
|
|
12
|
+
* Cache-Control applied to every non-2xx or failed response so errors are
|
|
13
|
+
* never cached by the browser or the CDN.
|
|
14
|
+
*/
|
|
15
|
+
export declare const ERROR_CACHE_CONTROL = "no-store, no-cache, must-revalidate";
|
|
16
|
+
export declare const DEFAULT_QUALITY = 85;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proxy route for `/img-proxy/:host/**:path`. Reconstructs the upstream media
|
|
3
|
+
* URL the provider encoded and streams the response body back so the Alokai
|
|
4
|
+
* Image Optimizer CDN can transform it on the way to the browser.
|
|
5
|
+
*/
|
|
6
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<Response>>;
|
|
7
|
+
export default _default;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { useRuntimeConfig } from "#imports";
|
|
2
|
+
import { defineEventHandler, getRouterParam } from "h3";
|
|
3
|
+
import { ERROR_CACHE_CONTROL } from "./constants.js";
|
|
4
|
+
import { logger } from "./logger.js";
|
|
5
|
+
import { variants } from "./variants.js";
|
|
6
|
+
export default defineEventHandler(async (event) => {
|
|
7
|
+
const hostKey = getRouterParam(event, "host");
|
|
8
|
+
const rawPath = getRouterParam(event, "path") ?? "";
|
|
9
|
+
const publicConfig = useRuntimeConfig(event).public;
|
|
10
|
+
const host = (publicConfig.alokaiImageOptimizer?.hosts ?? []).find(
|
|
11
|
+
(entry) => entry.key === hostKey
|
|
12
|
+
);
|
|
13
|
+
if (!host) {
|
|
14
|
+
return errorResponse(`Unknown image proxy host "${hostKey}"`, 404);
|
|
15
|
+
}
|
|
16
|
+
const segments = decodeSegments(rawPath);
|
|
17
|
+
if (segments === void 0 || segments.length === 0 || segments.some(isUnsafeSegment)) {
|
|
18
|
+
return errorResponse("Invalid image path", 400);
|
|
19
|
+
}
|
|
20
|
+
const mediaHost = publicConfig[host.runtimeConfigKey]?.replace(/\/$/, "");
|
|
21
|
+
if (!mediaHost) {
|
|
22
|
+
return errorResponse(
|
|
23
|
+
`${host.runtimeConfigKey} runtime config is empty`,
|
|
24
|
+
500
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
let upstream;
|
|
28
|
+
try {
|
|
29
|
+
upstream = await fetch(
|
|
30
|
+
variants[host.variant].buildUpstreamUrl(mediaHost, segments),
|
|
31
|
+
{ redirect: "manual" }
|
|
32
|
+
);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
logger.error(
|
|
35
|
+
`Failed to fetch upstream image for host "${hostKey}": ${String(error)}`
|
|
36
|
+
);
|
|
37
|
+
return errorResponse("Failed to fetch upstream image", 502);
|
|
38
|
+
}
|
|
39
|
+
if (upstream.status >= 300 && upstream.status < 400) {
|
|
40
|
+
return errorResponse("Upstream redirect is not allowed", 502);
|
|
41
|
+
}
|
|
42
|
+
return new Response(upstream.body, {
|
|
43
|
+
headers: {
|
|
44
|
+
"cache-control": upstream.ok ? host.cacheControl : ERROR_CACHE_CONTROL,
|
|
45
|
+
"content-type": upstream.headers.get("content-type") ?? "application/octet-stream"
|
|
46
|
+
},
|
|
47
|
+
status: upstream.status
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
function decodeSegments(rawPath) {
|
|
51
|
+
try {
|
|
52
|
+
return rawPath.split("/").map((segment) => decodeURIComponent(segment));
|
|
53
|
+
} catch {
|
|
54
|
+
return void 0;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function isUnsafeSegment(segment) {
|
|
58
|
+
return segment === "" || segment === "." || segment === "..";
|
|
59
|
+
}
|
|
60
|
+
function errorResponse(message, status) {
|
|
61
|
+
return new Response(message, {
|
|
62
|
+
headers: { "cache-control": ERROR_CACHE_CONTROL },
|
|
63
|
+
status
|
|
64
|
+
});
|
|
65
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ResolvedImageOptimizerHost } from "./types.js";
|
|
2
|
+
export interface BuildProxyUrlParams {
|
|
3
|
+
/** Resolved hosts, matched in order; first match wins. */
|
|
4
|
+
hosts: ResolvedImageOptimizerHost[];
|
|
5
|
+
/** Invoked once per host whose media host is unset, before skipping it. */
|
|
6
|
+
onMissingHost?: (host: ResolvedImageOptimizerHost) => void;
|
|
7
|
+
quality?: number | string;
|
|
8
|
+
/** Reads the media host value for a host, or `undefined`/empty when unset. */
|
|
9
|
+
resolveMediaHost: (host: ResolvedImageOptimizerHost) => string | undefined;
|
|
10
|
+
src: string;
|
|
11
|
+
width?: number | string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Rewrites a media-host `src` to its `/img-proxy/{key}/...` proxy URL so the
|
|
15
|
+
* Alokai Image Optimizer CDN can transform it. Returns a non-matching `src`
|
|
16
|
+
* unchanged. Framework-agnostic so it can be unit-tested in isolation and
|
|
17
|
+
* shared between the `@nuxt/image` provider and any other caller.
|
|
18
|
+
*/
|
|
19
|
+
export declare function buildProxyUrl({ hosts, onMissingHost, quality, resolveMediaHost, src, width, }: BuildProxyUrlParams): string;
|
|
20
|
+
export declare function matchesMediaHost(src: string, normalizedHost: string): boolean;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { DEFAULT_QUALITY, IMG_PROXY_PREFIX } from "./constants.js";
|
|
2
|
+
import { variants } from "./variants.js";
|
|
3
|
+
export function buildProxyUrl({
|
|
4
|
+
hosts,
|
|
5
|
+
onMissingHost,
|
|
6
|
+
quality,
|
|
7
|
+
resolveMediaHost,
|
|
8
|
+
src,
|
|
9
|
+
width
|
|
10
|
+
}) {
|
|
11
|
+
for (const host of hosts) {
|
|
12
|
+
const mediaHost = resolveMediaHost(host);
|
|
13
|
+
if (!mediaHost) {
|
|
14
|
+
onMissingHost?.(host);
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
const normalizedHost = mediaHost.replace(/\/$/, "");
|
|
18
|
+
if (!matchesMediaHost(src, normalizedHost)) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const localPath = src.slice(normalizedHost.length);
|
|
22
|
+
const queryIndex = localPath.indexOf("?");
|
|
23
|
+
const pathname = queryIndex === -1 ? localPath : localPath.slice(0, queryIndex);
|
|
24
|
+
const queryString = queryIndex === -1 ? "" : localPath.slice(queryIndex + 1);
|
|
25
|
+
const proxyPath = variants[host.variant].encodePath(
|
|
26
|
+
pathname,
|
|
27
|
+
new URLSearchParams(queryString)
|
|
28
|
+
);
|
|
29
|
+
if (proxyPath === void 0) {
|
|
30
|
+
return src;
|
|
31
|
+
}
|
|
32
|
+
const params = new URLSearchParams();
|
|
33
|
+
if (width) {
|
|
34
|
+
params.set("width", String(width));
|
|
35
|
+
}
|
|
36
|
+
params.set("quality", String(quality || DEFAULT_QUALITY));
|
|
37
|
+
params.set("format", "auto");
|
|
38
|
+
return `${IMG_PROXY_PREFIX}/${host.key}${proxyPath}?${params.toString()}`;
|
|
39
|
+
}
|
|
40
|
+
return src;
|
|
41
|
+
}
|
|
42
|
+
export function matchesMediaHost(src, normalizedHost) {
|
|
43
|
+
return src === normalizedHost || src.startsWith(`${normalizedHost}/`) || src.startsWith(`${normalizedHost}?`);
|
|
44
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dedicated Alokai logger for the image optimizer. The provider runs in both
|
|
3
|
+
* the Vue and Nitro contexts, where the module's auto-imported `logger` is not
|
|
4
|
+
* uniformly available, so a self-contained instance keeps logging consistent
|
|
5
|
+
* (and avoids `console.*`) on both sides.
|
|
6
|
+
*/
|
|
7
|
+
export declare const logger: import("@alokai/connect/logger").LoggerInterface;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useRuntimeConfig } from "#imports";
|
|
2
|
+
import { defineProvider } from "@nuxt/image/runtime";
|
|
3
|
+
import { buildProxyUrl } from "./loader.js";
|
|
4
|
+
import { logger } from "./logger.js";
|
|
5
|
+
const warnedMissingHosts = /* @__PURE__ */ new Set();
|
|
6
|
+
export default defineProvider({
|
|
7
|
+
getImage(src, { modifiers = {} }) {
|
|
8
|
+
const publicConfig = useRuntimeConfig().public;
|
|
9
|
+
const hosts = publicConfig.alokaiImageOptimizer?.hosts ?? [];
|
|
10
|
+
const url = buildProxyUrl({
|
|
11
|
+
hosts,
|
|
12
|
+
onMissingHost: (host) => {
|
|
13
|
+
if (warnedMissingHosts.has(host.runtimeConfigKey)) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
warnedMissingHosts.add(host.runtimeConfigKey);
|
|
17
|
+
logger.warning(
|
|
18
|
+
`${host.runtimeConfigKey} runtime config is empty, skipping image optimization for host "${host.key}".`
|
|
19
|
+
);
|
|
20
|
+
},
|
|
21
|
+
quality: modifiers.quality,
|
|
22
|
+
resolveMediaHost: (host) => publicConfig[host.runtimeConfigKey],
|
|
23
|
+
src,
|
|
24
|
+
width: modifiers.width
|
|
25
|
+
});
|
|
26
|
+
return { url };
|
|
27
|
+
}
|
|
28
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ImageOptimizerHosts, ResolvedImageOptimizerHost } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Normalizes the `hosts` config into an ordered list of fully-resolved host
|
|
4
|
+
* configs. Throws on misconfiguration so mistakes surface at module init
|
|
5
|
+
* instead of as silently unoptimized images.
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
export declare function resolveHosts(hosts: ImageOptimizerHosts): ResolvedImageOptimizerHost[];
|
|
10
|
+
/**
|
|
11
|
+
* Derives the public runtime config key that holds the media host, e.g.
|
|
12
|
+
* `commerce` -> `commerceMediaHost`, `my-cms` -> `myCmsMediaHost`. Nuxt
|
|
13
|
+
* overrides this key at runtime from the matching `NUXT_PUBLIC_{KEY}_MEDIA_HOST`
|
|
14
|
+
* env variable, so the value is configurable per deployment without the env
|
|
15
|
+
* variable name having to be declared anywhere.
|
|
16
|
+
*
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
export declare function deriveRuntimeConfigKey(hostKey: string): string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { DEFAULT_CACHE_CONTROL } from "./constants.js";
|
|
2
|
+
export function resolveHosts(hosts) {
|
|
3
|
+
return Object.entries(hosts).map(([key, config]) => resolveHost(key, config));
|
|
4
|
+
}
|
|
5
|
+
export function deriveRuntimeConfigKey(hostKey) {
|
|
6
|
+
const camelKey = hostKey.split("-").filter(Boolean).map(
|
|
7
|
+
(word, index) => index === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1)
|
|
8
|
+
).join("");
|
|
9
|
+
return `${camelKey}MediaHost`;
|
|
10
|
+
}
|
|
11
|
+
const HOST_KEY_PATTERN = /^(?=.*[a-z])[a-z0-9-]+$/;
|
|
12
|
+
function resolveHost(key, config) {
|
|
13
|
+
if (!HOST_KEY_PATTERN.test(key)) {
|
|
14
|
+
throw new Error(
|
|
15
|
+
`Invalid image optimizer host key "${key}". Use lowercase letters, digits and hyphens only - the key becomes a URL segment and a runtime config key.`
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
cacheControl: config.cacheControl ?? DEFAULT_CACHE_CONTROL,
|
|
20
|
+
key,
|
|
21
|
+
runtimeConfigKey: deriveRuntimeConfigKey(key),
|
|
22
|
+
variant: config.variant ?? "default"
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in URL scheme. `sapcc` encodes the `?context=` query parameter as a
|
|
3
|
+
* path segment and appends a synthetic filename when the path has none.
|
|
4
|
+
*/
|
|
5
|
+
export type ImageOptimizerVariant = "default" | "sapcc";
|
|
6
|
+
export interface ImageOptimizerHostConfig {
|
|
7
|
+
/**
|
|
8
|
+
* Cache-Control for successful (2xx) upstream responses.
|
|
9
|
+
*
|
|
10
|
+
* @default "public, max-age=3600, s-maxage=86400"
|
|
11
|
+
*/
|
|
12
|
+
cacheControl?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Built-in URL scheme used to encode and reconstruct the proxied path.
|
|
15
|
+
*
|
|
16
|
+
* @default "default"
|
|
17
|
+
*/
|
|
18
|
+
variant?: ImageOptimizerVariant;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Media hosts whose images should be optimized, keyed by host key. The key
|
|
22
|
+
* becomes the `:host` URL segment and the media host value is read at runtime
|
|
23
|
+
* from the matching `NUXT_PUBLIC_{KEY}_MEDIA_HOST` env variable.
|
|
24
|
+
*/
|
|
25
|
+
export type ImageOptimizerHosts = Record<string, ImageOptimizerHostConfig>;
|
|
26
|
+
export interface ImageOptimizerConfig {
|
|
27
|
+
hosts: ImageOptimizerHosts;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Fully-resolved per-host config. Serialized into the public runtime config so
|
|
31
|
+
* both the `@nuxt/image` provider and the proxy server route can read it.
|
|
32
|
+
*
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
export interface ResolvedImageOptimizerHost {
|
|
36
|
+
cacheControl: string;
|
|
37
|
+
key: string;
|
|
38
|
+
/** Public runtime config key holding the resolved media host value. */
|
|
39
|
+
runtimeConfigKey: string;
|
|
40
|
+
variant: ImageOptimizerVariant;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Shape of `useRuntimeConfig().public` as the image optimizer reads it: the
|
|
44
|
+
* resolved hosts plus the dynamically-keyed media host values.
|
|
45
|
+
*
|
|
46
|
+
* @internal
|
|
47
|
+
*/
|
|
48
|
+
export interface ImageOptimizerPublicConfig {
|
|
49
|
+
[runtimeConfigKey: string]: unknown;
|
|
50
|
+
alokaiImageOptimizer?: {
|
|
51
|
+
hosts?: ResolvedImageOptimizerHost[];
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Builds the path emitted after `/img-proxy/{key}` on the provider (client)
|
|
56
|
+
* side. Receives the pathname relative to the media host (with leading slash,
|
|
57
|
+
* without query string) and the parsed query string of the original `src`.
|
|
58
|
+
* Returns the proxy path (starting with `/`), or `undefined` to skip proxying
|
|
59
|
+
* and return the original `src` unchanged.
|
|
60
|
+
*
|
|
61
|
+
* @internal
|
|
62
|
+
*/
|
|
63
|
+
export type EncodePathHook = (pathname: string, searchParams: URLSearchParams) => string | undefined;
|
|
64
|
+
/**
|
|
65
|
+
* Rebuilds the upstream URL on the server-route side. Receives the media host
|
|
66
|
+
* (without trailing slash) and the decoded path segments. Returns an absolute
|
|
67
|
+
* URL.
|
|
68
|
+
*
|
|
69
|
+
* @internal
|
|
70
|
+
*/
|
|
71
|
+
export type BuildUpstreamUrlHook = (mediaHost: string, segments: string[]) => string;
|
|
File without changes
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { BuildUpstreamUrlHook, EncodePathHook, ImageOptimizerVariant } from "./types.js";
|
|
2
|
+
interface VariantImplementation {
|
|
3
|
+
buildUpstreamUrl: BuildUpstreamUrlHook;
|
|
4
|
+
encodePath: EncodePathHook;
|
|
5
|
+
}
|
|
6
|
+
export declare const variants: Record<ImageOptimizerVariant, VariantImplementation>;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const defaultVariant = {
|
|
2
|
+
buildUpstreamUrl: (mediaHost, segments) => `${mediaHost}/${joinEncoded(segments)}`,
|
|
3
|
+
// An empty pathname (src equal to the bare media host) could never match
|
|
4
|
+
// the route, so pass it through instead of emitting a dead URL.
|
|
5
|
+
encodePath: (pathname) => pathname === "" ? void 0 : pathname
|
|
6
|
+
};
|
|
7
|
+
const sapccVariant = {
|
|
8
|
+
buildUpstreamUrl: (mediaHost, segments) => {
|
|
9
|
+
const [context = "", ...path] = segments;
|
|
10
|
+
return `${mediaHost}/${joinEncoded(path)}?context=${encodeURIComponent(context)}`;
|
|
11
|
+
},
|
|
12
|
+
encodePath: (pathname, searchParams) => {
|
|
13
|
+
const context = searchParams.get("context");
|
|
14
|
+
if (!context) {
|
|
15
|
+
return void 0;
|
|
16
|
+
}
|
|
17
|
+
const hasExtension = (pathname.split("/").pop() ?? "").includes(".");
|
|
18
|
+
const separator = pathname.endsWith("/") ? "" : "/";
|
|
19
|
+
const safePathname = hasExtension ? pathname : `${pathname}${separator}image.png`;
|
|
20
|
+
return `/${encodeURIComponent(context)}${safePathname}`;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
export const variants = {
|
|
24
|
+
default: defaultVariant,
|
|
25
|
+
sapcc: sapccVariant
|
|
26
|
+
};
|
|
27
|
+
function joinEncoded(segments) {
|
|
28
|
+
return segments.map((segment) => encodeURIComponent(segment)).join("/");
|
|
29
|
+
}
|
|
@@ -42,8 +42,20 @@ export type LoggerOptions = Partial<{
|
|
|
42
42
|
includeStackTrace: boolean;
|
|
43
43
|
}>;
|
|
44
44
|
|
|
45
|
+
export type ImageOptimizerVariant = "default" | "sapcc";
|
|
46
|
+
|
|
47
|
+
export interface ImageOptimizerHostConfig {
|
|
48
|
+
variant?: ImageOptimizerVariant;
|
|
49
|
+
cacheControl?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface ImageOptimizerConfig {
|
|
53
|
+
hosts: Record<string, ImageOptimizerHostConfig>;
|
|
54
|
+
}
|
|
55
|
+
|
|
45
56
|
export interface AlokaiModuleOptions {
|
|
46
57
|
middleware: MiddlewareConfig;
|
|
47
58
|
multistore?: MultistoreConfig;
|
|
48
59
|
logger?: LoggerOptions;
|
|
60
|
+
imageOptimizer?: ImageOptimizerConfig;
|
|
49
61
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vue-storefront/nuxt",
|
|
3
|
-
"version": "11.0.0-next.
|
|
3
|
+
"version": "11.0.0-next.7",
|
|
4
4
|
"description": "Alokai dedicated features for Nuxt",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"lint:fix": "eslint --fix",
|
|
25
25
|
"format": "prettier --write .",
|
|
26
26
|
"prepare": "nuxi prepare",
|
|
27
|
+
"test:unit": "vitest run",
|
|
27
28
|
"version": "cp CHANGELOG.md ../../docs/enterprise/content/storefront/6.change-log/nuxt.md"
|
|
28
29
|
},
|
|
29
30
|
"dependencies": {
|
|
@@ -36,17 +37,25 @@
|
|
|
36
37
|
"pinia": "^3.0.4"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|
|
39
|
-
"@alokai/connect": "2.3.0-next.
|
|
40
|
+
"@alokai/connect": "2.3.0-next.10",
|
|
40
41
|
"@nuxt/devtools": "^3.0.0",
|
|
42
|
+
"@nuxt/image": "^2.0.0",
|
|
41
43
|
"@nuxt/module-builder": "^1.0.2",
|
|
42
44
|
"@types/node": "^22.0.0",
|
|
43
45
|
"eslint": "9.23.0",
|
|
44
46
|
"nuxt": "4.3.0",
|
|
45
47
|
"prettier": "3.3.2",
|
|
46
|
-
"typescript": "5.6.2"
|
|
48
|
+
"typescript": "5.6.2",
|
|
49
|
+
"vitest": "4.0.18"
|
|
47
50
|
},
|
|
48
51
|
"peerDependencies": {
|
|
49
|
-
"@alokai/connect": "2.3.0-next.
|
|
52
|
+
"@alokai/connect": "2.3.0-next.10",
|
|
53
|
+
"@nuxt/image": "^2.0.0"
|
|
54
|
+
},
|
|
55
|
+
"peerDependenciesMeta": {
|
|
56
|
+
"@nuxt/image": {
|
|
57
|
+
"optional": true
|
|
58
|
+
}
|
|
50
59
|
},
|
|
51
60
|
"publishConfig": {
|
|
52
61
|
"access": "public"
|