diff --git a/docs/content/2.usage/1.nuxt-img.md b/docs/content/2.usage/1.nuxt-img.md
index d1df456ed..e9440d8ef 100644
--- a/docs/content/2.usage/1.nuxt-img.md
+++ b/docs/content/2.usage/1.nuxt-img.md
@@ -259,6 +259,24 @@ export default defineNuxtConfig({
```
::
+### `providerOptions`
+
+Override the selected provider's configuration for this image only. This is useful when you have a provider with multiple tenants.
+
+The prop is typed after the selected `provider`: only options valid for that provider are accepted. Options passed here take precedence over the provider configuration in `nuxt.config`.
+
+**Example:**
+
+```vue
+
+```
+
### `preset`
Presets are predefined sets of image modifiers that can be used create unified form of images in your projects.
diff --git a/docs/content/2.usage/3.use-image.md b/docs/content/2.usage/3.use-image.md
index 7675da043..767790ff7 100644
--- a/docs/content/2.usage/3.use-image.md
+++ b/docs/content/2.usage/3.use-image.md
@@ -31,6 +31,17 @@ const backgroundStyles = computed(() => {
})
```
+The `options` object also accepts the selected provider's own options, which override the provider configuration from `nuxt.config` for this call only. This is useful for multi-tenant setups:
+
+```js
+const img = useImage()
+
+const url = img('/logo.png', { width: 100 }, {
+ provider: 'cloudinary',
+ baseURL: 'https://res.cloudinary.com/tenant-b/image/upload'
+})
+```
+
### `img.getSizes`
```js
@@ -56,6 +67,7 @@ Unstable: `getSizes` API might change or be removed.
- `options`: (object)
- `provider`: (string) Provider name other than default (see [providers](/get-started/configuration#providers))
- `preset`: Use a [preset](/get-started/configuration#presets)
+ - (any option of the selected provider, e.g. `baseURL`, overriding its configuration for this call)
**Example:** Responsive srcset with Vuetify `v-img`
diff --git a/src/runtime/image.ts b/src/runtime/image.ts
index 764d32268..786f0dc90 100644
--- a/src/runtime/image.ts
+++ b/src/runtime/image.ts
@@ -3,14 +3,22 @@ import { hasProtocol, parseURL, joinURL, withLeadingSlash } from 'ufo'
import { imageMeta } from './utils/meta'
import { checkDensities, parseDensities, parseSize, parseSizes } from './utils'
import { prerenderStaticImages } from './utils/prerender'
-import type { ImageOptions, ImageSizesOptions, CreateImageOptions, ResolvedImage, ImageCTX, $Img, ImageSizes, ImageSizesVariant, ConfiguredImageProviders } from '@nuxt/image'
+import type { ImageOptions, CreateImageOptions, ResolvedImage, ImageCTX, $Img, ImageSizes, ImageSizesVariant, ConfiguredImageProviders, DefaultProvider } from '@nuxt/image'
+
+type RuntimeImageOptions = {
+ provider?: keyof ConfiguredImageProviders
+ preset?: string
+ densities?: string
+ modifiers?: Record
+ sizes?: string | Record
+} & Record
export function createImage(globalOptions: CreateImageOptions) {
const ctx: ImageCTX = {
options: globalOptions,
}
- const getImage: $Img['getImage'] = (input: string, options = {}) => {
+ const getImage: $Img['getImage'] = (input: string, options: ImageOptions = {}) => {
const image = resolveImage(ctx, input, options)
// Prerender static images
@@ -21,24 +29,25 @@ export function createImage(globalOptions: CreateImageOptions) {
return image
}
- const $img = ((input, modifiers, options) => getImage(input, defu({ modifiers }, options)).url) as $Img
+ const $img = ((input: string, modifiers?: RuntimeImageOptions['modifiers'], options?: RuntimeImageOptions) =>
+ getImage(input, defu({ modifiers }, options)).url) as $Img
for (const presetName in globalOptions.presets) {
- $img[presetName] = ((source, modifiers, options) =>
+ $img[presetName] = ((source: string, modifiers?: RuntimeImageOptions['modifiers'], options?: RuntimeImageOptions) =>
$img(source, modifiers, { ...globalOptions.presets[presetName], ...options })) as $Img[string]
}
$img.options = globalOptions
$img.getImage = getImage
- $img.getMeta = ((input: string, options?: ImageOptions) => getMeta(ctx, input, options)) as $Img['getMeta']
- $img.getSizes = ((input: string, options: ImageSizesOptions) => getSizes(ctx, input, options)) as $Img['getSizes']
+ $img.getMeta = (input: string, options?: ImageOptions) => getMeta(ctx, input, options)
+ $img.getSizes = (input: string, options: ImageOptions = {}) => getSizes(ctx, input, options)
- ctx.$img = $img as $Img
+ ctx.$img = $img
return $img
}
-async function getMeta(ctx: ImageCTX, input: string, options?: ImageOptions) {
+async function getMeta(ctx: ImageCTX, input: string, options?: RuntimeImageOptions) {
const image = resolveImage(ctx, input, { ...options })
if (typeof image.getMeta === 'function') {
@@ -49,7 +58,7 @@ async function getMeta(ctx: ImageCTX, input: string, options?: ImageOptions) {
}
}
-function resolveImage(ctx: ImageCTX, input: string, options: ImageOptions): ResolvedImage {
+function resolveImage(ctx: ImageCTX, input: string, options: RuntimeImageOptions): ResolvedImage {
if (input && typeof input !== 'string') {
throw new TypeError(`input must be a string (received ${typeof input}: ${JSON.stringify(input)})`)
}
@@ -90,8 +99,8 @@ function resolveImage(ctx: ImageCTX, input: string, options: ImageOptions): Reso
}
}
- const _options = defu(options, preset, defaults as Partial)
- const resolvedOptions = {
+ const _options: RuntimeImageOptions = defu(options, preset, defaults as Partial)
+ const resolvedOptions: RuntimeImageOptions & { modifiers: Record } = {
..._options,
modifiers: {
..._options.modifiers,
@@ -124,7 +133,7 @@ function getPreset(ctx: ImageCTX, name?: string): ImageOptions {
return ctx.options.presets[name]
}
-function getSizes(ctx: ImageCTX, input: string, opts: ImageSizesOptions): ImageSizes {
+function getSizes(ctx: ImageCTX, input: string, opts: RuntimeImageOptions): ImageSizes {
// Merge preset options so preset-provided sizes/densities are respected
const preset = getPreset(ctx, opts.preset)
const merged = defu(opts, preset)
@@ -232,7 +241,7 @@ function getSizesVariant(key: string, size: string, height: number | undefined,
}
}
-function getVariantSrc(ctx: ImageCTX, input: string, opts: ImageSizesOptions, variant: ImageSizesVariant, density: number) {
+function getVariantSrc(ctx: ImageCTX, input: string, opts: RuntimeImageOptions, variant: ImageSizesVariant, density: number) {
return ctx.$img!(input,
{
...opts.modifiers,
diff --git a/src/runtime/utils/props.ts b/src/runtime/utils/props.ts
index dee657590..1c3360b55 100644
--- a/src/runtime/utils/props.ts
+++ b/src/runtime/utils/props.ts
@@ -1,6 +1,6 @@
import { computed } from 'vue'
-import type { ConfiguredImageProviders, ImageModifiers } from '@nuxt/image'
+import type { ConfiguredImageProviders, ImageModifiers, ProviderOptions } from '@nuxt/image'
import { parseSize } from '.'
import { useImage } from '#imports'
@@ -19,6 +19,7 @@ export interface BaseImageProps
// options
preset?: string
provider?: Provider
+ providerOptions?: ProviderOptions
sizes?: string | Record
densities?: string
@@ -37,7 +38,8 @@ export const useImageProps = (p
const $img = useImage()
const providerOptions = computed(() => ({
- provider: props.provider,
+ ...props.providerOptions as ProviderOptions,
+ provider: props.provider as keyof ConfiguredImageProviders | undefined,
preset: props.preset,
}))
diff --git a/src/types/image.ts b/src/types/image.ts
index 0ff89fb67..934802219 100644
--- a/src/types/image.ts
+++ b/src/types/image.ts
@@ -17,22 +17,30 @@ export interface ResolvedImageModifiers extends ImageModifiers {
height: number
}
-type DefaultProvider = ProviderDefaults extends Record<'provider', unknown> ? ProviderDefaults['provider'] : never
+export type DefaultProvider = ProviderDefaults extends Record<'provider', unknown> ? ProviderDefaults['provider'] : never
-export interface ImageOptions {
+export type ProviderOptions = Partial>
+
+export type ImageOptions = {
provider?: Provider
preset?: string
densities?: string
modifiers?: Partial>
& ('modifiers' extends keyof ConfiguredImageProviders[Provider] ? ConfiguredImageProviders[Provider]['modifiers'] : Record)
sizes?: string | Record
-}
+} & ProviderOptions
export interface ImageSizesOptions extends ImageOptions {
sizes: Record | string
}
-export type ProviderGetImage> = (src: string, options: Omit & { modifiers: Partial } & T, ctx: ImageCTX) => ResolvedImage
+export type ProviderGetImage> = (src: string, options: {
+ provider?: string
+ preset?: string
+ densities?: string
+ sizes?: string | Record
+ modifiers: Partial
+} & T, ctx: ImageCTX) => ResolvedImage
interface ImageModifierOptions {
modifiers?: Record
@@ -84,11 +92,11 @@ export interface ImageSizes {
}
export interface Img {
- (source: string, modifiers?: ImageOptions['modifiers'], options?: ImageOptions): ResolvedImage['url']
+ (source: string, modifiers?: ImageOptions['modifiers'], options?: ImageOptions): ResolvedImage['url']
options: CreateImageOptions
- getImage: (source: string, options?: ImageOptions) => ResolvedImage
- getSizes: (source: string, options?: ImageOptions, sizes?: string[]) => ImageSizes
- getMeta: (source: string, options?: ImageOptions) => Promise
+ getImage: (source: string, options?: ImageOptions) => ResolvedImage
+ getSizes: (source: string, options?: ImageOptions, sizes?: string[]) => ImageSizes
+ getMeta: (source: string, options?: ImageOptions) => Promise
}
export type $Img = Img & {
diff --git a/test/nuxt/use-image.test.ts b/test/nuxt/use-image.test.ts
index e0547ef1f..aea502c6f 100644
--- a/test/nuxt/use-image.test.ts
+++ b/test/nuxt/use-image.test.ts
@@ -24,7 +24,19 @@ describe('image helper', () => {
expect(() => useImage().getImage('/test.png', { preset: 'invalid' })).toThrow(Error)
})
+ it('applies per-call provider options', () => {
+ const { url } = useImage().getImage('/test.png', { provider: 'ipx', baseURL: '/custom' })
+ expect(url).toMatchInlineSnapshot('"/custom/_/test.png"')
+ })
+
it('is correctly typed for provider options', () => {
+ useImage().getImage('/test.png', {
+ provider: 'ipx',
+ baseURL: '/other',
+ // @ts-expect-error this is not a valid option for ipx
+ yolo: true,
+ })
+
useImage().getImage('/test.png', {
provider: 'ipx',
modifiers: {