Repository: nuxt/image
Pull request #774 (2023-03-19 01:45:38)

feat: add `wagtail` provider
I created a provider to support the URL structure for the Wagtail CMS image template ([https://docs.wagtail.org/en/v4.2.1/topics/images.html](https://docs.wagtail.org/en/v4.2.1/topics/images.html))

I think I got everything set up correctly. Thanks 

netlify[bot]: ### <span aria-hidden="true">👷</span> Deploy request for *nuxt-image-v1* pending review.
[Visit the deploys page to approve it](https://app.netlify.com/sites/nuxt-image-v1/deploys)

|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | 6927082a8d025e9eb071c957f1986c06bf0e69c9 |
danielroe: Would you be able to merge the latest main and add snapshots/expected values for wagtail? 🙏
nuxt-studio[bot]: 
### <span aria-hidden="true">✅</span> Live Preview ready!

| Name | Edit | Preview | Latest Commit |
| :--- | :----- | :------ | :------ |
| **Image** | [Edit on Studio ↗︎](https://nuxt.studio/@nuxt/image/studio/content?refId=%23774) | [View Live Preview](https://v1.image.nuxtjs.org?preview=37b0fbd020985a8f0599431e633dcd59) | ae379375f21eb56fdd14d4b97a8e2ffce2cced7e |

<!-- This comment is generated by [Nuxt Studio](https://nuxt.studio) -->
<!-- Do not remove or edit this comment. -->

bonomite: @danielroe, I think we are good. Except it looks like the IPX provider test is failing
![image](https://github.com/nuxt/image/assets/13803343/eece476b-3c42-4296-9e51-591efe078a21)

I'm pretty sure I had nothing to do with it. I hope. 
bonomite: Thanks! Sorry about the typo 😬
pi0: Ahh we have merge conflicts (trying to fix)

Patch:
diff --git a/docs/package.json b/docs/package.json
index 1143eeb..1add3cd 100755
--- a/docs/package.json
+++ b/docs/package.json
@@ -6,7 +6,7 @@
     "generate": "nuxi generate"
   },
   "devDependencies": {
-    "@nuxt-themes/docus": "^1.12.1",
+    "@nuxt-themes/docus": "^1.12.2",
     "@nuxtjs/plausible": "latest",
     "nuxt": "^3.5.3"
   }
diff --git a/playground/nuxt.config.ts b/playground/nuxt.config.ts
index 062009a..c3b802b 100644
--- a/playground/nuxt.config.ts
+++ b/playground/nuxt.config.ts
@@ -23,6 +23,9 @@ export default defineNuxtConfig({
     storyblok: {
       baseURL: 'https://a.storyblok.com/'
     },
+    wagtail: {
+      baseURL: 'https://cms.demo.nypr.digital/images/'
+    },
     cloudflare: {
       baseURL: 'https://that-test.site'
     },
diff --git a/playground/pages/provider/[provider].vue b/playground/pages/provider/[provider].vue
index 35a5ad1..35248f8 100644
--- a/playground/pages/provider/[provider].vue
+++ b/playground/pages/provider/[provider].vue
@@ -40,5 +40,6 @@ if (route.params.provider === 'ipx') {
 <style scoped>
 img {
   max-width: 80vw;
+  height: auto;
 }
 </style>
diff --git a/playground/providers.ts b/playground/providers.ts
index 888bd2b..77e67af 100644
--- a/playground/providers.ts
+++ b/playground/providers.ts
@@ -729,6 +729,57 @@ export const providers: Provider[] = [
       }
     ]
   },
+  // wagtail
+  {
+    name: 'wagtail',
+    samples: [
+      {
+        alt: 'no specified width or height uses original maximum image size',
+        src: '329944'
+      },
+      {
+        alt: 'Resize',
+        width: 600,
+        height: 600,
+        src: '329944'
+      },
+      {
+        alt: 'Just width, height is proportional',
+        width: 250,
+        src: '329944'
+      },
+      {
+        alt: 'Just height, width is proportional',
+        height: 250,
+        src: '329944'
+      },
+      {
+        alt: 'Format to jpeg (webp{default}, jpeg, png)',
+        width: 600,
+        height: 600,
+        format: 'jpeg',
+        src: '329944'
+      },
+      {
+        alt: 'low quality jpeg (quality default = 70)',
+        width: 600,
+        height: 600,
+        format: 'jpeg',
+        quality: 2,
+        src: '329944'
+      },
+      {
+        alt: 'Focus Zoom on the CMS assigned focal point of the image (0 - 100) CMS provided focal point is around the glass dorrs and 3 winddows on the very left of the house',
+        width: 200,
+        height: 200,
+        src: '329944',
+        modifiers:
+        {
+          focusZoom: 100
+        }
+      }
+    ]
+  },
   // Directus
   {
     name: 'directus',
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index dc9608d..dc31809 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -102,8 +102,8 @@ importers:
   docs:
     devDependencies:
       '@nuxt-themes/docus':
-        specifier: ^1.12.1
-        version: 1.12.1(nuxt@3.5.3)(postcss@8.4.24)(rollup@3.23.0)(vue@3.3.4)
+        specifier: ^1.12.2
+        version: 1.12.2(nuxt@3.5.3)(postcss@8.4.24)(rollup@3.23.0)(vue@3.3.4)
       '@nuxtjs/plausible':
         specifier: latest
         version: 0.2.1(rollup@3.23.0)
@@ -812,14 +812,14 @@ packages:
       '@nodelib/fs.scandir': 2.1.5
       fastq: 1.15.0
 
-  /@nuxt-themes/docus@1.12.1(nuxt@3.5.3)(postcss@8.4.24)(rollup@3.23.0)(vue@3.3.4):
-    resolution: {integrity: sha512-exSHLVzhhIuT0FdJFLClXcPyKuvqHC6diAYS6OEtggu8TQzbj9Y9idewCtr/Rsv3UEYM4S6Xj5+dqi86WugHMg==}
+  /@nuxt-themes/docus@1.12.2(nuxt@3.5.3)(postcss@8.4.24)(rollup@3.23.0)(vue@3.3.4):
+    resolution: {integrity: sha512-7TLPZ7ulThfVfKkk8qkQh09Zbm+2ydwA3FR5LvDsyZlRapi/sQO2SuytEREtI3KzxlVsL2VVf0zEKLDo8Q8H/w==}
     dependencies:
       '@nuxt-themes/elements': 0.9.4(postcss@8.4.24)(rollup@3.23.0)(vue@3.3.4)
       '@nuxt-themes/tokens': 1.9.1(postcss@8.4.24)(rollup@3.23.0)(vue@3.3.4)
       '@nuxt-themes/typography': 0.11.0(postcss@8.4.24)(rollup@3.23.0)(vue@3.3.4)
       '@nuxt/content': 2.6.0(rollup@3.23.0)
-      '@nuxthq/studio': 0.12.1(rollup@3.23.0)
+      '@nuxthq/studio': 0.13.2(rollup@3.23.0)
       '@vueuse/nuxt': 10.1.2(nuxt@3.5.3)(rollup@3.23.0)(vue@3.3.4)
     transitivePeerDependencies:
       - '@azure/app-configuration'
@@ -1182,8 +1182,8 @@ packages:
       - vue-tsc
     dev: true
 
-  /@nuxthq/studio@0.12.1(rollup@3.23.0):
-    resolution: {integrity: sha512-g9GV8JkQqqYLtP+gsNtPr4fh49jc3eIdAB2ZfiudZKkFdW0BxtT/xPx8RjNYQwyInKssDDB26zzYrRYH9hhHCQ==}
+  /@nuxthq/studio@0.13.2(rollup@3.23.0):
+    resolution: {integrity: sha512-lSjMDSq0yxX3I3O8EVJWoU+kA9iEwUuTS1sZfyha625FhiU2qDIGGkc7gEeA1ubBNeZmJMXTl2hIauxOVTSfgQ==}
     dependencies:
       '@nuxt/kit': 3.5.3(rollup@3.23.0)
       defu: 6.1.2
diff --git a/src/provider.ts b/src/provider.ts
index f868f77..a90becf 100644
--- a/src/provider.ts
+++ b/src/provider.ts
@@ -33,7 +33,8 @@ const BuiltInProviders = [
   'strapi',
   'twicpics',
   'unsplash',
-  'vercel'
+  'vercel',
+  'wagtail'
 ]
 
 export const providerSetup: Record<string, ProviderSetup> = {
diff --git a/src/runtime/providers/wagtail.ts b/src/runtime/providers/wagtail.ts
new file mode 100644
index 0000000..bfbe0c6
--- /dev/null
+++ b/src/runtime/providers/wagtail.ts
@@ -0,0 +1,32 @@
+import { withBase, joinURL } from 'ufo'
+import type { ProviderGetImage } from '../../types'
+
+// https://docs.wagtail.org/en/v4.2.1/topics/images.html
+
+export const getImage: ProviderGetImage = (src, { modifiers = {}, baseURL } = {}) => {
+  const {
+    width = '0',
+    height = '0',
+    focusZoom = '0',
+    format = 'webp',
+    quality = '70'
+  } = modifiers
+
+  const doFill = width !== '0' && height !== '0'
+  const doWidth = width !== '0' && height === '0'
+  const doHeight = width === '0' && height !== '0'
+  const doOriginal = width === '0' && height === '0'
+
+  const formatting = `|format-${format}|${format}quality-${quality}`
+  const options = joinURL(
+    doFill ? `fill-${width}x${height}-c${focusZoom}${formatting}` : '',
+    doWidth ? `width-${width}${formatting}` : '',
+    doHeight ? `height-${height}${formatting}` : '',
+    doOriginal ? `original${formatting}` : ''
+  )
+
+  const url = withBase(joinURL(src, options), baseURL)
+  return {
+    url
+  }
+}
diff --git a/src/types/module.ts b/src/types/module.ts
index d38a538..bef8623 100644
--- a/src/types/module.ts
+++ b/src/types/module.ts
@@ -56,6 +56,7 @@ export interface ImageProviders {
   prismic?: any
   twicpics?: any
   storyblok?: any,
+  wagtail?: any,
   strapi?: any,
   imageengine?: any,
   ipx?: Partial<IPXOptions>


Test patch:
diff --git a/test/e2e/__snapshots__/no-ssr.test.ts.snap b/test/e2e/__snapshots__/no-ssr.test.ts.snap
index fdc29f2..5f19ab9 100644
--- a/test/e2e/__snapshots__/no-ssr.test.ts.snap
+++ b/test/e2e/__snapshots__/no-ssr.test.ts.snap
@@ -428,3 +428,27 @@ exports[`browser (ssr: false) > vercel should render images 2`] = `
   "https://image-component.nextjs.gallery/_next/image?url=/colors.jpg&w=750&q=75",
 ]
 `;
+
+exports[`browser (ssr: false) > wagtail should render images 1`] = `
+[
+  "https://cms.demo.nypr.digital/images/329944/original|format-webp|webpquality-70",
+  "https://cms.demo.nypr.digital/images/329944/fill-600x600-c0|format-webp|webpquality-70",
+  "https://cms.demo.nypr.digital/images/329944/width-250|format-webp|webpquality-70",
+  "https://cms.demo.nypr.digital/images/329944/height-250|format-webp|webpquality-70",
+  "https://cms.demo.nypr.digital/images/329944/fill-600x600-c0|format-jpeg|jpegquality-70",
+  "https://cms.demo.nypr.digital/images/329944/fill-600x600-c0|format-jpeg|jpegquality-2",
+  "https://cms.demo.nypr.digital/images/329944/fill-200x200-c100|format-webp|webpquality-70",
+]
+`;
+
+exports[`browser (ssr: false) > wagtail should render images 2`] = `
+[
+  "https://cms.demo.nypr.digital/images/329944/original%7Cformat-webp%7Cwebpquality-70",
+  "https://cms.demo.nypr.digital/images/329944/fill-600x600-c0%7Cformat-webp%7Cwebpquality-70",
+  "https://cms.demo.nypr.digital/images/329944/width-250%7Cformat-webp%7Cwebpquality-70",
+  "https://cms.demo.nypr.digital/images/329944/height-250%7Cformat-webp%7Cwebpquality-70",
+  "https://cms.demo.nypr.digital/images/329944/fill-600x600-c0%7Cformat-jpeg%7Cjpegquality-70",
+  "https://cms.demo.nypr.digital/images/329944/fill-600x600-c0%7Cformat-jpeg%7Cjpegquality-2",
+  "https://cms.demo.nypr.digital/images/329944/fill-200x200-c100%7Cformat-webp%7Cwebpquality-70",
+]
+`;
diff --git a/test/e2e/__snapshots__/ssr.test.ts.snap b/test/e2e/__snapshots__/ssr.test.ts.snap
index 0b607d8..a0114b3 100644
--- a/test/e2e/__snapshots__/ssr.test.ts.snap
+++ b/test/e2e/__snapshots__/ssr.test.ts.snap
@@ -428,3 +428,27 @@ exports[`browser (ssr: true) > vercel should render images 2`] = `
   "https://image-component.nextjs.gallery/_next/image?url=/colors.jpg&w=750&q=75",
 ]
 `;
+
+exports[`browser (ssr: true) > wagtail should render images 1`] = `
+[
+  "https://cms.demo.nypr.digital/images/329944/original|format-webp|webpquality-70",
+  "https://cms.demo.nypr.digital/images/329944/fill-600x600-c0|format-webp|webpquality-70",
+  "https://cms.demo.nypr.digital/images/329944/width-250|format-webp|webpquality-70",
+  "https://cms.demo.nypr.digital/images/329944/height-250|format-webp|webpquality-70",
+  "https://cms.demo.nypr.digital/images/329944/fill-600x600-c0|format-jpeg|jpegquality-70",
+  "https://cms.demo.nypr.digital/images/329944/fill-600x600-c0|format-jpeg|jpegquality-2",
+  "https://cms.demo.nypr.digital/images/329944/fill-200x200-c100|format-webp|webpquality-70",
+]
+`;
+
+exports[`browser (ssr: true) > wagtail should render images 2`] = `
+[
+  "https://cms.demo.nypr.digital/images/329944/original%7Cformat-webp%7Cwebpquality-70",
+  "https://cms.demo.nypr.digital/images/329944/fill-600x600-c0%7Cformat-webp%7Cwebpquality-70",
+  "https://cms.demo.nypr.digital/images/329944/width-250%7Cformat-webp%7Cwebpquality-70",
+  "https://cms.demo.nypr.digital/images/329944/height-250%7Cformat-webp%7Cwebpquality-70",
+  "https://cms.demo.nypr.digital/images/329944/fill-600x600-c0%7Cformat-jpeg%7Cjpegquality-70",
+  "https://cms.demo.nypr.digital/images/329944/fill-600x600-c0%7Cformat-jpeg%7Cjpegquality-2",
+  "https://cms.demo.nypr.digital/images/329944/fill-200x200-c100%7Cformat-webp%7Cwebpquality-70",
+]
+`;
diff --git a/test/providers.ts b/test/providers.ts
index 69946ea..3119837 100644
--- a/test/providers.ts
+++ b/test/providers.ts
@@ -21,6 +21,7 @@ export const images = [
     edgio: { url: 'https://opt.moovweb.net/?img=/test.png' },
     storyblok: { url: 'https://a.storyblok.com/test.png' },
     vercel: { url: '/_vercel/image?url=/test.png&w=1536&q=100' },
+    wagtail: { url: '329944/original|format-webp|webpquality-70' },
     directus: { url: '/assets/1ac73658-8b62-4dea-b6da-529fbc9d01a4' }
   },
   {
@@ -45,6 +46,7 @@ export const images = [
     edgio: { url: 'https://opt.moovweb.net/?img=/test.png&width=200' },
     storyblok: { url: 'https://a.storyblok.com/test.png/m/200x0' },
     vercel: { url: '/_vercel/image?url=/test.png&w=320&q=100' },
+    wagtail: { url: '329944/width-200|format-webp|webpquality-70' },
     directus: { url: '/assets/1ac73658-8b62-4dea-b6da-529fbc9d01a4?width=200' }
   },
   {
@@ -69,6 +71,7 @@ export const images = [
     edgio: { url: 'https://opt.moovweb.net/?img=/test.png&height=200' },
     storyblok: { url: 'https://a.storyblok.com/test.png/m/0x200' },
     vercel: { url: '/_vercel/image?url=/test.png&w=1536&q=100' },
+    wagtail: { url: '329944/height-200|format-webp|webpquality-70' },
     directus: { url: '/assets/1ac73658-8b62-4dea-b6da-529fbc9d01a4?height=200' }
   },
   {
@@ -93,6 +96,7 @@ export const images = [
     edgio: { url: 'https://opt.moovweb.net/?img=/test.png&width=200&height=200' },
     storyblok: { url: 'https://a.storyblok.com/test.png/m/200x200' },
     vercel: { url: '/_vercel/image?url=/test.png&w=320&q=100' },
+    wagtail: { url: '329944/fill-200x200-c0|format-webp|webpquality-70' },
     directus: { url: '/assets/1ac73658-8b62-4dea-b6da-529fbc9d01a4?width=200&height=200' }
   },
   {
@@ -117,6 +121,7 @@ export const images = [
     edgio: { url: 'https://opt.moovweb.net/?img=/test.png&width=200&height=200&fit=contain' },
     storyblok: { url: 'https://a.storyblok.com/test.png/m/fit-contain/200x200' },
     vercel: { url: '/_vercel/image?url=/test.png&w=320&q=100' },
+    wagtail: { url: '329944/fill-200x200-c0|format-webp|webpquality-70' },
     directus: { url: '/assets/1ac73658-8b62-4dea-b6da-529fbc9d01a4?width=200&height=200&fit=contain' }
   },
   {
@@ -141,6 +146,7 @@ export const images = [
     edgio: { url: 'https://opt.moovweb.net/?img=/test.png&width=200&height=200&fit=contain&format=jpeg' },
     storyblok: { url: 'https://a.storyblok.com/test.png/m/fit-contain/200x200/filters:format(jpeg)' },
     vercel: { url: '/_vercel/image?url=/test.png&w=320&q=100' },
+    wagtail: { url: '329944/fill-200x200-c0|format-jpeg|jpegquality-70' },
     directus: { url: '/assets/1ac73658-8b62-4dea-b6da-529fbc9d01a4?width=200&height=200&fit=contain&format=jpg' }
   }
 ] as const
diff --git a/test/unit/providers.test.ts b/test/unit/providers.test.ts
index 9a751bc..4c8eb05 100644
--- a/test/unit/providers.test.ts
+++ b/test/unit/providers.test.ts
@@ -27,6 +27,7 @@ import * as layer0 from '#image/providers/layer0'
 import * as storyblok from '#image/providers/storyblok'
 import * as strapi from '#image/providers/strapi'
 import * as vercel from '#image/providers/vercel'
+import * as wagtail from '#image/providers/wagtail'
 
 const emptyContext = {
   options: {
@@ -353,6 +354,17 @@ describe('Providers', () => {
     }
   })
 
+  it('wagtail', () => {
+    const providerOptions = {}
+    const testImageId = '329944'
+    for (const image of images) {
+      // eslint-disable-next-line @typescript-eslint/no-unused-vars
+      const [_src, modifiers] = image.args
+      const generated = wagtail.getImage(testImageId, { modifiers, ...providerOptions }, emptyContext)
+      expect(generated).toMatchObject(image.wagtail)
+    }
+  })
+
   it('none', () => {
     const providerOptions = {
     }