diff --git a/docs/content/en/4.providers/cloudflare.md b/docs/content/en/4.providers/cloudflare.md new file mode 100644 index 000000000..fb0f1f735 --- /dev/null +++ b/docs/content/en/4.providers/cloudflare.md @@ -0,0 +1,38 @@ +--- +title: Cloudflare Provider +description: 'Nuxt Image has first class integration with Cloudflare' +navigation: + title: Cloudflare +--- + +Integration between [Cloudflare](https://developers.cloudflare.com/images/) and the image module. + +:::alert{type="info"} +Availability only for Business and Enterprise Customers. Normally Cloudflare [pages](https://pages.cloudflare.com/) users can already benefit from build-time image optimization. +::: + +To use this provider you just need to specify the base url (zone) of your service: + +```js{}[nuxt.config.js] +export default { + image: { + baseURL: 'https://that-test.site' + } +} +``` + +**Example:** + +```vue + +``` + +## Options + +### `baseURL` + +Default: `/` + +Your deployment's domain (zone). + +**Note:** `/cdn-cgi/image/` will be automatically appended for generating URLs. diff --git a/playground/nuxt.config.ts b/playground/nuxt.config.ts index 0bc5fb655..30c741c3d 100644 --- a/playground/nuxt.config.ts +++ b/playground/nuxt.config.ts @@ -32,6 +32,9 @@ export default { storyblok: { baseURL: 'https://a.storyblok.com/' }, + cloudflare: { + baseURL: 'https://that-test.site' + }, cloudinary: { baseURL: 'https://res.cloudinary.com/nuxt/image/upload/' }, diff --git a/playground/providers.ts b/playground/providers.ts index 1aa27170e..c3f827b04 100644 --- a/playground/providers.ts +++ b/playground/providers.ts @@ -45,6 +45,17 @@ export const providers: Provider[] = [ } ] }, + // Cloudflare + { + name: 'cloudflare', + samples: [ + { + src: 'https://s3.that-test.site/burger.jpeg', + height: 300, + fit: 'contain' + } + ] + }, // Cloudinary { name: 'cloudinary', diff --git a/src/provider.ts b/src/provider.ts index f3f4984e1..73893b5d2 100644 --- a/src/provider.ts +++ b/src/provider.ts @@ -5,6 +5,7 @@ import type { ModuleOptions, InputProvider, ImageModuleProvider, ProviderSetup } import { ipxSetup } from './ipx' const BuiltInProviders = [ + 'cloudflare', 'cloudinary', 'contentful', 'cloudimage', diff --git a/src/runtime/providers/cloudflare.ts b/src/runtime/providers/cloudflare.ts new file mode 100644 index 000000000..e3a48cece --- /dev/null +++ b/src/runtime/providers/cloudflare.ts @@ -0,0 +1,49 @@ +import { joinURL, encodeQueryItem } from 'ufo' +import { ProviderGetImage } from 'src' +import { createOperationsGenerator } from '~image' + +const operationsGenerator = createOperationsGenerator({ + keyMap: { + width: 'w', + height: 'h', + dpr: 'dpr', + fit: 'fit', + gravity: 'g', + quality: 'q', + format: 'f', + sharpen: 'sharpen' + }, + valueMap: { + fit: { + cover: 'cover', + contain: 'contain', + fill: 'scale-down', + outside: 'crop', + inside: 'pad' + }, + gravity: { + auto: 'auto', + side: 'side' + } + }, + joinWith: ',', + formatter: (key, val) => encodeQueryItem(key, val) +}) + +const defaultModifiers = {} + +// https://developers.cloudflare.com/images/image-resizing/url-format/ +export const getImage: ProviderGetImage = (src, { + modifiers = {}, + baseURL = '/' +} = {}) => { + const mergeModifiers = { ...defaultModifiers, ...modifiers } + const operations = operationsGenerator(mergeModifiers as any) + + // https:///cdn-cgi/image// + const url = operations ? joinURL(baseURL, 'cdn-cgi/image', operations, src) : joinURL(baseURL, src) + + return { + url + } +} diff --git a/src/types/module.ts b/src/types/module.ts index acb60b086..3521a08a5 100644 --- a/src/types/module.ts +++ b/src/types/module.ts @@ -40,6 +40,7 @@ export interface CloudinaryOptions { } export interface ImageProviders { + cloudflare?: any cloudinary?: Partial contentful?: any cloudimage?: any diff --git a/test/providers.ts b/test/providers.ts index 0c4c752c9..9cc0d4208 100644 --- a/test/providers.ts +++ b/test/providers.ts @@ -2,6 +2,7 @@ export const images = [ { args: ['/test.png', {}], ipx: { url: '/_ipx/_/test.png' }, + cloudflare: { url: '/test.png' }, cloudinary: { url: '/f_auto,q_auto/test' }, twicpics: { url: '/test.png' }, fastly: { url: '/test.png' }, @@ -20,6 +21,7 @@ export const images = [ { args: ['/test.png', { width: 200 }], ipx: { url: '/_ipx/w_200/test.png' }, + cloudflare: { url: '/cdn-cgi/image/w=200/test.png' }, cloudinary: { url: '/f_auto,q_auto,w_200/test' }, twicpics: { url: '/test.png?twic=v1/cover=200x-' }, fastly: { url: '/test.png?width=200' }, @@ -38,6 +40,7 @@ export const images = [ { args: ['/test.png', { height: 200 }], ipx: { url: '/_ipx/h_200/test.png' }, + cloudflare: { url: '/cdn-cgi/image/h=200/test.png' }, cloudinary: { url: '/f_auto,q_auto,h_200/test' }, twicpics: { url: '/test.png?twic=v1/cover=-x200' }, fastly: { url: '/test.png?height=200' }, @@ -56,6 +59,7 @@ export const images = [ { args: ['/test.png', { width: 200, height: 200 }], ipx: { url: '/_ipx/s_200x200/test.png' }, + cloudflare: { url: '/cdn-cgi/image/w=200,h=200/test.png' }, cloudinary: { url: '/f_auto,q_auto,w_200,h_200/test' }, twicpics: { url: '/test.png?twic=v1/cover=200x200' }, fastly: { url: '/test.png?width=200&height=200' }, @@ -74,6 +78,7 @@ export const images = [ { args: ['/test.png', { width: 200, height: 200, fit: 'contain' }], ipx: { url: '/_ipx/fit_contain,s_200x200/test.png' }, + cloudflare: { url: '/cdn-cgi/image/w=200,h=200,fit=contain/test.png' }, cloudinary: { url: '/f_auto,q_auto,w_200,h_200,c_scale/test' }, twicpics: { url: '/test.png?twic=v1/inside=200x200' }, fastly: { url: '/test.png?width=200&height=200&fit=bounds' }, @@ -92,6 +97,7 @@ export const images = [ { args: ['/test.png', { width: 200, height: 200, fit: 'contain', format: 'jpeg' }], ipx: { url: '/_ipx/fit_contain,f_jpeg,s_200x200/test.png' }, + cloudflare: { url: '/cdn-cgi/image/w=200,h=200,fit=contain,f=jpeg/test.png' }, cloudinary: { url: '/f_jpg,q_auto,w_200,h_200,c_scale/test' }, twicpics: { url: '/test.png?twic=v1/output=jpeg/inside=200x200' }, fastly: { url: '/test.png?width=200&height=200&fit=bounds&format=jpeg' }, diff --git a/test/unit/providers.test.ts b/test/unit/providers.test.ts index 66b9dc77b..1b1f7c8aa 100644 --- a/test/unit/providers.test.ts +++ b/test/unit/providers.test.ts @@ -2,6 +2,7 @@ import { images } from '../providers' import { cleanDoubleSlashes } from '~/runtime/utils' import * as ipx from '~/runtime/providers/ipx' +import * as cloudflare from '~/runtime/providers/cloudflare' import * as cloudinary from '~/runtime/providers/cloudinary' import * as twicpics from '~/runtime/providers/twicpics' import * as fastly from '~/runtime/providers/fastly' @@ -42,6 +43,17 @@ describe('Providers', () => { }) }) + test('cloudflare', () => { + const providerOptions = { + baseURL: '/' + } + for (const image of images) { + const [src, modifiers] = image.args + const generated = cloudflare.getImage(src, { modifiers, ...providerOptions }, emptyContext) + expect(generated).toMatchObject(image.cloudflare) + } + }) + test('cloudinary', () => { const providerOptions = { baseURL: '/'