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: '/'