diff --git a/docs/pages/en/3.api/1.options.md b/docs/pages/en/3.api/1.options.md index f5d4dd4c2..34fed4985 100644 --- a/docs/pages/en/3.api/1.options.md +++ b/docs/pages/en/3.api/1.options.md @@ -175,3 +175,54 @@ export default { - For `static` provider, if images weren't crawled during generation (unreachable modals, pages or dynamic runtime size), changing `dir` from `static` causes 404 errors. - For `ipx` provider, make sure to deploy customized `dir` as well. - For some providers (like vercel), using a directory other than `static/` for assets is not supported since resizing happens at runtime (instead of build/generate time) and source fetched from the `static/` directory (deployment URL) + +## `alias` + +This option allows you to specify aliases for `src`. + +When using the default ipx provider, URL aliases are shortened on the server-side. +This is especially useful for optimizing external URLs and not including them in HTML. + +When using other providers, aliases are resolved in runtime and included in HTML. (only the usage is simplified) + +**Example:** + +```ts [nuxt.config.js] +export default { + image: { + domains: [ + 'images.unsplash.com' + ], + alias: { + unsplash: 'https://images.unsplash.com' + } + } +} +``` + +**Before** using alias: + +```html + +``` + +Generates: + +```html + +``` + +**After** using alias: + + +```html + +``` + +Generates: + +```html + +``` + +Both usage and output are simplified! diff --git a/playground/nuxt.config.ts b/playground/nuxt.config.ts index 17e4107ef..b3c66d9c2 100644 --- a/playground/nuxt.config.ts +++ b/playground/nuxt.config.ts @@ -16,12 +16,16 @@ export default { image: { domains: [ 'https://nuxtjs.org', - 'https://unsplash.com', + 'https://images.unsplash.com', 'https://upload.wikimedia.org' ], screens: { 750: 750 }, + alias: { + unsplash: 'https://images.unsplash.com', // ipx + blog: '/remote/nuxt-org/blog' // cloudinary + }, twicpics: { baseURL: 'https://demo.twic.pics/' }, diff --git a/playground/providers.ts b/playground/providers.ts index dc407be81..1bcd9d8be 100644 --- a/playground/providers.ts +++ b/playground/providers.ts @@ -18,21 +18,29 @@ export const providers: Provider[] = [ { src: '/images/colors.jpg', from: 'Jeremy Thomas', + width: 300, + height: 300, link: 'https://unsplash.com/@jeremythomasphoto?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText' }, { src: '/images/everest.jpg', - from: 'Mount Everest Wikipedia page', + from: 'Mount Everest Wikipedia page (alias)', + width: 300, + height: 300, link: 'https://en.wikipedia.org/wiki/Mount_Everest' }, { src: '/images/tacos.svg', from: 'Illustration from Icons8', + width: 300, + height: 300, link: 'https://icons8.com/illustrations/illustration/abstract-1419' }, { - src: 'https://images.unsplash.com/photo-1606112219348-204d7d8b94ee?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1940&q=80', + src: '/unsplash/photo-1606112219348-204d7d8b94ee', from: 'Photo by Omid Armin', + width: 300, + height: 300, link: 'https://unsplash.com/@omidarmin?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText' } ] @@ -45,7 +53,7 @@ export const providers: Provider[] = [ src: '/remote/nuxt-org/blog/going-full-static/main' }, { - src: '/remote/nuxt-org/blog/going-full-static/main', + src: '/blog/going-full-static/main', width: 200, height: 200, fit: 'cropping' diff --git a/src/ipx.ts b/src/ipx.ts index 0e62851fb..f0123e01c 100644 --- a/src/ipx.ts +++ b/src/ipx.ts @@ -3,15 +3,16 @@ import { update as updaterc } from 'rc9' import { mkdirp, readFile, writeFile } from 'fs-extra' import { lt } from 'semver' -import type { ProviderSetup } from './types' +import type { ProviderSetup, ImageProviders } from './types' export const ipxSetup: ProviderSetup = async (_providerOptions, moduleOptions, nuxt) => { const isStatic = nuxt.options.target === 'static' const runtimeDir = resolve(__dirname, 'runtime') - const ipxOptions = { + const ipxOptions: ImageProviders['ipx'] = { dir: resolve(nuxt.options.rootDir, moduleOptions.dir), domains: moduleOptions.domains, - sharp: moduleOptions.sharp + sharp: moduleOptions.sharp, + alias: moduleOptions.alias } // Add IPX middleware unless nuxtrc or user added a custom middleware diff --git a/src/module.ts b/src/module.ts index 910a4849e..6c5b458ab 100644 --- a/src/module.ts +++ b/src/module.ts @@ -1,6 +1,6 @@ import { resolve } from 'upath' import defu from 'defu' -import { parseURL } from 'ufo' +import { parseURL, withLeadingSlash } from 'ufo' import type { Module } from '@nuxt/types' import { setupStaticGeneration } from './generate' import { resolveProviders, detectProvider } from './provider' @@ -29,7 +29,8 @@ const imageModule: Module = async function imageModule (moduleOpt }, internalUrl: '', providers: {}, - static: {} + static: {}, + alias: {} } const options: ModuleOptions = defu(moduleOptions, nuxt.options.image, defaults) @@ -39,6 +40,9 @@ const imageModule: Module = async function imageModule (moduleOpt .map(domain => parseURL(domain, 'https://').host) .filter(Boolean) as string[] + // Normalize alias to start with leading slash + options.alias = Object.fromEntries(Object.entries(options.alias).map(e => [withLeadingSlash(e[0]), e[1]])) + options.provider = detectProvider(options.provider, nuxt.options.target === 'static') options[options.provider] = options[options.provider] || {} @@ -46,7 +50,8 @@ const imageModule: Module = async function imageModule (moduleOpt 'screens', 'presets', 'provider', - 'domains' + 'domains', + 'alias' ]) const providers = resolveProviders(nuxt, options) diff --git a/src/runtime/image.ts b/src/runtime/image.ts index e18c104c1..e820ad7d5 100644 --- a/src/runtime/image.ts +++ b/src/runtime/image.ts @@ -1,5 +1,5 @@ import defu from 'defu' -import { hasProtocol, parseURL } from 'ufo' +import { hasProtocol, parseURL, joinURL, withLeadingSlash } from 'ufo' import type { ImageOptions, ImageSizesOptions, CreateImageOptions, ResolvedImage, MapToStatic, ImageCTX, $Img } from '../types/image' import { imageMeta } from './utils/meta' import { parseSize } from './utils' @@ -86,7 +86,7 @@ async function getMeta (ctx: ImageCTX, input: string, options?: ImageOptions) { } function resolveImage (ctx: ImageCTX, input: string, options: ImageOptions): ResolvedImage { - if (typeof input !== 'string') { + if (typeof input !== 'string' || input === '') { throw new TypeError(`input must be a string (received ${typeof input}: ${JSON.stringify(input)})`) } @@ -99,6 +99,18 @@ function resolveImage (ctx: ImageCTX, input: string, options: ImageOptions): Res const { provider, defaults } = getProvider(ctx, options.provider || ctx.options.provider) const preset = getPreset(ctx, options.preset) + // Normalize input with leading slash + input = hasProtocol(input) ? input : withLeadingSlash(input) + + // Resolve alias if provider is not ipx + if (!provider.supportsAlias) { + for (const base in ctx.options.alias) { + if (input.startsWith(base)) { + input = joinURL(ctx.options.alias[base], input.substr(base.length)) + } + } + } + // Externalize remote images if domain does not match with `domains` if (provider.validateDomains && hasProtocol(input)) { const inputHost = parseURL(input).host diff --git a/src/runtime/providers/ipx.ts b/src/runtime/providers/ipx.ts index c2cba7811..fd86adad2 100644 --- a/src/runtime/providers/ipx.ts +++ b/src/runtime/providers/ipx.ts @@ -31,3 +31,4 @@ export const getImage: ProviderGetImage = (src, { modifiers = {}, baseURL = '/_i } export const validateDomains = true +export const supportsAlias = true diff --git a/src/runtime/providers/static.ts b/src/runtime/providers/static.ts index af4ef1219..921467779 100644 --- a/src/runtime/providers/static.ts +++ b/src/runtime/providers/static.ts @@ -4,3 +4,5 @@ export const getImage: typeof _getImage = (src, options, ctx) => ({ ..._getImage(src, options, ctx), isStatic: true }) + +export const supportsAlias = true diff --git a/src/types/image.ts b/src/types/image.ts index 93f77d143..e006c054a 100644 --- a/src/types/image.ts +++ b/src/types/image.ts @@ -24,6 +24,7 @@ export interface ImageProvider { defaults?: any getImage: ProviderGetImage validateDomains?: Boolean + supportsAlias?: Boolean } export interface CreateImageOptions { @@ -35,7 +36,8 @@ export interface CreateImageOptions { } presets: { [name: string]: ImageOptions } provider: string - screens?: Record, + screens: Record, + alias: Record, domains: string[] } diff --git a/src/types/module.ts b/src/types/module.ts index 79a0fbc6f..73ff573f8 100644 --- a/src/types/module.ts +++ b/src/types/module.ts @@ -32,7 +32,8 @@ export interface ModuleOptions extends ImageProviders { presets: { [name: string]: ImageOptions } dir: string domains: string[] - sharp: {} + sharp: any + alias: Record screens: CreateImageOptions['screens'], internalUrl: string providers: { [name: string]: InputProvider | any } & ImageProviders