Repository: nuxt/image
Pull request #1152 (2023-12-14 14:43:12)

feat: add `hygraph` provider
To my surprise, there was no Nuxt/Image provider for Hygraph yet!

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=%231152) | [View Live Preview](https://image.nuxt.com?preview=4b9af10a50db0130b9f53c3a0be84af5) | e5235994cdcc1fc8ff8df8b84c4bf51b191ecc9b |

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

codecov-commenter: ## [Codecov](https://app.codecov.io/gh/nuxt/image/pull/1152?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nuxt) Report
Attention: `62 lines` in your changes are missing coverage. Please review.
> Comparison is base [(`2dee537`)](https://app.codecov.io/gh/nuxt/image/commit/2dee537adb4a6c4c0b300aabcb03837939a67d96?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nuxt) 70.75% compared to head [(`5e25eca`)](https://app.codecov.io/gh/nuxt/image/pull/1152?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nuxt) 69.67%.
> Report is 1 commits behind head on main.

| [Files](https://app.codecov.io/gh/nuxt/image/pull/1152?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nuxt) | Patch % | Lines |
|---|---|---|
| [src/runtime/providers/hygraph.ts](https://app.codecov.io/gh/nuxt/image/pull/1152?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nuxt#diff-c3JjL3J1bnRpbWUvcHJvdmlkZXJzL2h5Z3JhcGgudHM=) | 0.00% | [61 Missing and 1 partial :warning: ](https://app.codecov.io/gh/nuxt/image/pull/1152?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nuxt) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1152      +/-   ##
==========================================
- Coverage   70.75%   69.67%   -1.08%     
==========================================
  Files          71       72       +1     
  Lines        4065     4128      +63     
  Branches      384      385       +1     
==========================================
  Hits         2876     2876              
- Misses       1165     1226      +61     
- Partials       24       26       +2     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/nuxt/image/pull/1152?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nuxt).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nuxt).

timbenniks: @danielroe I also added an `undocumented-by-hygraph` quality setting to the provider. Tests still pass.

Patch:
diff --git a/docs/content/3.providers/hygraph.md b/docs/content/3.providers/hygraph.md
new file mode 100644
index 0000000..95a4e66
--- /dev/null
+++ b/docs/content/3.providers/hygraph.md
@@ -0,0 +1,40 @@
+---
+title: Hygraph
+description: Nuxt Image with Hygraph integration.
+links:
+  - label: Source
+    icon: i-simple-icons-github
+    to: https://github.com/nuxt/image/blob/main/src/runtime/providers/hygraph.ts
+    size: xs
+---
+
+Integration between [Hygraph](https://hygraph.com/) and the image module.
+
+To use this provider you just need to specify the base URL of your project.
+
+```ts [nuxt.config.ts]
+export default defineNuxtConfig({
+  image: {
+    hygraph: {
+      // If not filled out, the provider will add this as a default.
+      baseURL: "https://media.graphassets.com",
+    },
+  },
+});
+```
+
+## Modifiers
+
+All the default modifiers from [Hygraph's documentation](https://hygraph.com/docs/api-reference/content-api/assets) are available, plus additionally `auto_image` which lets Hygraph decide what image format is best for the user's browser and `quality` 0-100.
+
+```vue
+<NuxtImg
+  provider="hygraph"
+  src="https://media.graphassets.com/JL6e2yJERUyQtTiZIzPb"
+  height="512"
+  width="512"
+  fit="max"
+  format="jpg"
+  :quality="90"
+/>
+```
diff --git a/docs/pages/index.vue b/docs/pages/index.vue
index 414ac7f..1842580 100644
--- a/docs/pages/index.vue
+++ b/docs/pages/index.vue
@@ -16,7 +16,7 @@ useSeoMeta({
 const source = ref('npm i @nuxt/image')
 const { copy, copied } = useClipboard({ source })
 
-const providers = ['cloudflare', 'cloudimage', 'cloudinary', 'directus', 'edgio', 'fastly', 'glide', 'gumlet', 'imageengine', 'imagekit', 'imgix', 'ipx', 'netlify', 'prepr', 'prismic', 'sanity', 'storyblok', 'strapi', 'twicpics', 'unsplash', 'uploadcare', 'vercel']
+const providers = ['cloudflare', 'cloudimage', 'cloudinary', 'directus', 'edgio', 'fastly', 'glide', 'gumlet', 'hygraph', 'imageengine', 'imagekit', 'imgix', 'ipx', 'netlify', 'prepr', 'prismic', 'sanity', 'storyblok', 'strapi', 'twicpics', 'unsplash', 'uploadcare', 'vercel']
 // Disabling because svg to png does not work now with SSG
 // Related issue: https://github.com/unjs/ipx/issues/160
 // const img = useImage()
diff --git a/docs/public/providers/hygraph.svg b/docs/public/providers/hygraph.svg
new file mode 100644
index 0000000..99830d0
--- /dev/null
+++ b/docs/public/providers/hygraph.svg
@@ -0,0 +1,11 @@
+<svg width="72" height="72" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <g clip-path="url(#a)">
+    <path d="M64 0H8a8 8 0 0 0-8 8v56a8 8 0 0 0 8 8h56a8 8 0 0 0 8-8V8a8 8 0 0 0-8-8Z" fill="#fff"/>
+    <path fill-rule="evenodd" clip-rule="evenodd" d="m44 16.8-6 3.4-6 3.4-6 3.4v20.4l6-3.4 6-3.4v-6.8l-6 3.4v-6.8l6-3.4 6-3.4V44l-6 3.4-6 3.4-6 3.4-6 3.4 6 3.4 6-3.4 6-3.4 6-3.4 6-3.4v-34L44 10v6.8Z" fill="#081026"/>
+  </g>
+  <defs>
+    <clipPath id="a">
+      <path fill="#fff" d="M0 0h72v72H0z"/>
+    </clipPath>
+  </defs>
+</svg>
diff --git a/playground/nuxt.config.ts b/playground/nuxt.config.ts
index b3d85f1..59653ef 100644
--- a/playground/nuxt.config.ts
+++ b/playground/nuxt.config.ts
@@ -101,6 +101,9 @@ export default defineNuxtConfig({
     sirv: {
       baseURL: 'https://demo.sirv.com'
     },
+    hygraph: {
+      baseURL: 'https://media.graphassets.com'
+    },
     providers: {
       custom: {
         provider: '~/providers/custom',
diff --git a/playground/providers.ts b/playground/providers.ts
index 460a2d5..eeabc2a 100644
--- a/playground/providers.ts
+++ b/playground/providers.ts
@@ -997,5 +997,31 @@ export const providers: Provider[] = [
         modifiers: { watermark: '/watermark-v1.png', watermarkPosition: 'center', watermarkWidth: '30%' }
       }
     ]
+  },
+  // Hygraph
+  {
+    name: 'hygraph',
+    samples: [
+      {
+        src: 'https://media.graphassets.com/JL6e2yJERUyQtTiZIzPb',
+        width: 500,
+        height: 500,
+        fit: 'crop',
+        quality: 90
+      },
+      {
+        src: 'https://media.graphassets.com/JL6e2yJERUyQtTiZIzPb',
+        width: 500,
+        fit: 'max',
+        format: 'auto_image'
+      },
+      {
+        src: 'https://media.graphassets.com/JL6e2yJERUyQtTiZIzPb',
+        width: 300,
+        height: 300,
+        fit: 'clip',
+        format: 'jpeg'
+      }
+    ]
   }
 ]
diff --git a/src/provider.ts b/src/provider.ts
index edd3d26..42782d6 100644
--- a/src/provider.ts
+++ b/src/provider.ts
@@ -22,6 +22,7 @@ const BuiltInProviders = [
   'fastly',
   'glide',
   'gumlet',
+  'hygraph',
   'imageengine',
   'imagekit',
   'imgix',
diff --git a/src/runtime/providers/hygraph.ts b/src/runtime/providers/hygraph.ts
new file mode 100644
index 0000000..cbdbdc9
--- /dev/null
+++ b/src/runtime/providers/hygraph.ts
@@ -0,0 +1,65 @@
+import { joinURL } from 'ufo'
+import type { ProviderGetImage } from '@nuxt/image'
+
+type ImageOptimizations = {
+  width?: number
+  height?: number
+  fit?: string | 'clip' | 'crop' | 'scale' | 'max'
+  format?: string | 'jpg' | 'png' | 'webp' | 'avif' | 'auto_image',
+  quality?: number
+}
+
+export function getImageFormat (format?: string) {
+  let result = 'auto_image'
+
+  if (format && format !== 'auto_image') {
+    result = `output=format:${format === 'jpeg' ? 'jpg' : format}`
+  }
+
+  return result
+}
+
+export function optimizeHygraphImage (baseurl: string, url: string, optimizations: ImageOptimizations) {
+  baseurl = baseurl.replace(/\/+$/, '')
+  const imageId = url.split(`${baseurl}/`)[1]
+  const imageFormat = getImageFormat(optimizations.format)
+  const optimBase = 'resize'
+  const quality = optimizations.quality ? `quality=value:${optimizations.quality}/compress/` : ''
+
+  const optimList = []
+  for (const [key, value] of Object.entries(optimizations)) {
+    if (key !== 'format' && key !== 'quality' && value !== undefined) {
+      if (key === 'fit' && value === 'contain') {
+        optimList.push('fit:max')
+      } else {
+        optimList.push(`${key}:${value}`)
+      }
+    }
+  }
+
+  const optim = `${optimBase}=${optimList.join(',')}`
+  const result = joinURL(baseurl, imageFormat, optim, quality, imageId)
+
+  return result
+}
+
+export const getImage: ProviderGetImage = (
+  src,
+  { modifiers = {}, baseurl } = {}
+) => {
+  const {
+    width,
+    height,
+    fit,
+    format,
+    quality
+  } = modifiers
+
+  if (!baseurl) {
+    baseurl = 'https://media.graphassets.com'
+  }
+
+  return {
+    url: optimizeHygraphImage(baseurl, src, { width, height, fit, format, quality })
+  }
+}


Test patch:
diff --git a/test/e2e/__snapshots__/no-ssr.test.ts.snap b/test/e2e/__snapshots__/no-ssr.test.ts.snap
index 606f4d7..bd3edae 100644
--- a/test/e2e/__snapshots__/no-ssr.test.ts.snap
+++ b/test/e2e/__snapshots__/no-ssr.test.ts.snap
@@ -164,6 +164,22 @@ exports[`browser (ssr: false) > gumlet should render images 2`] = `
 ]
 `;
 
+exports[`browser (ssr: false) > hygraph should render images 1`] = `
+[
+  "https://media.graphassets.com/auto_image/resize=width:500,height:500,fit:crop/quality=value:90/compress/JL6e2yJERUyQtTiZIzPb",
+  "https://media.graphassets.com/auto_image/resize=width:500,fit:max/JL6e2yJERUyQtTiZIzPb",
+  "https://media.graphassets.com/output=format:jpg/resize=width:300,height:300,fit:clip/JL6e2yJERUyQtTiZIzPb",
+]
+`;
+
+exports[`browser (ssr: false) > hygraph should render images 2`] = `
+[
+  "https://media.graphassets.com/auto_image/resize=width:500,height:500,fit:crop/quality=value:90/compress/JL6e2yJERUyQtTiZIzPb",
+  "https://media.graphassets.com/auto_image/resize=width:500,fit:max/JL6e2yJERUyQtTiZIzPb",
+  "https://media.graphassets.com/output=format:jpg/resize=width:300,height:300,fit:clip/JL6e2yJERUyQtTiZIzPb",
+]
+`;
+
 exports[`browser (ssr: false) > imageengine should render images 1`] = `
 [
   "https://abc123.imgeng.in/images/image.jpg?imgeng=/meta_true/s_10/w_300/h_300/m_box",
diff --git a/test/e2e/__snapshots__/ssr.test.ts.snap b/test/e2e/__snapshots__/ssr.test.ts.snap
index 4f5b529..9bfd3da 100644
--- a/test/e2e/__snapshots__/ssr.test.ts.snap
+++ b/test/e2e/__snapshots__/ssr.test.ts.snap
@@ -164,6 +164,22 @@ exports[`browser (ssr: true) > gumlet should render images 2`] = `
 ]
 `;
 
+exports[`browser (ssr: true) > hygraph should render images 1`] = `
+[
+  "https://media.graphassets.com/auto_image/resize=width:500,height:500,fit:crop/quality=value:90/compress/JL6e2yJERUyQtTiZIzPb",
+  "https://media.graphassets.com/auto_image/resize=width:500,fit:max/JL6e2yJERUyQtTiZIzPb",
+  "https://media.graphassets.com/output=format:jpg/resize=width:300,height:300,fit:clip/JL6e2yJERUyQtTiZIzPb",
+]
+`;
+
+exports[`browser (ssr: true) > hygraph should render images 2`] = `
+[
+  "https://media.graphassets.com/auto_image/resize=width:500,height:500,fit:crop/quality=value:90/compress/JL6e2yJERUyQtTiZIzPb",
+  "https://media.graphassets.com/auto_image/resize=width:500,fit:max/JL6e2yJERUyQtTiZIzPb",
+  "https://media.graphassets.com/output=format:jpg/resize=width:300,height:300,fit:clip/JL6e2yJERUyQtTiZIzPb",
+]
+`;
+
 exports[`browser (ssr: true) > imageengine should render images 1`] = `
 [
   "https://abc123.imgeng.in/images/image.jpg?imgeng=/meta_true/s_10/w_300/h_300/m_box",
diff --git a/test/providers.ts b/test/providers.ts
index d9e2473..65eeea7 100644
--- a/test/providers.ts
+++ b/test/providers.ts
@@ -27,7 +27,8 @@ export const images = [
     wagtail: { url: '329944/original|format-webp|webpquality-70' },
     directus: { url: '/assets/1ac73658-8b62-4dea-b6da-529fbc9d01a4' },
     uploadcare: { url: 'https://ucarecdn.com/c160afba-8b42-45a9-a46a-d393248b0072/' },
-    sirv: { url: 'https://demo.sirv.com/test.png' }
+    sirv: { url: 'https://demo.sirv.com/test.png' },
+    hygraph: { url: 'https://media.graphassets.com/JL6e2yJERUyQtTiZIzPb' }
   },
   {
     args: ['/test.png', { width: 200 }],
@@ -57,7 +58,8 @@ export const images = [
     wagtail: { url: '329944/width-200|format-webp|webpquality-70' },
     directus: { url: '/assets/1ac73658-8b62-4dea-b6da-529fbc9d01a4?width=200' },
     uploadcare: { url: 'https://ucarecdn.com/c160afba-8b42-45a9-a46a-d393248b0072/-/resize/200x/' },
-    sirv: { url: 'https://demo.sirv.com/test.png?w=200' }
+    sirv: { url: 'https://demo.sirv.com/test.png?w=200' },
+    hygraph: { url: 'https://media.graphassets.com/auto_image/resize=width:200/JL6e2yJERUyQtTiZIzPb' }
   },
   {
     args: ['/test.png', { height: 200 }],
@@ -87,7 +89,8 @@ export const images = [
     wagtail: { url: '329944/height-200|format-webp|webpquality-70' },
     directus: { url: '/assets/1ac73658-8b62-4dea-b6da-529fbc9d01a4?height=200' },
     uploadcare: { url: 'https://ucarecdn.com/c160afba-8b42-45a9-a46a-d393248b0072/-/resize/x200/' },
-    sirv: { url: 'https://demo.sirv.com/test.png?h=200' }
+    sirv: { url: 'https://demo.sirv.com/test.png?h=200' },
+    hygraph: { url: 'https://media.graphassets.com/auto_image/resize=height:200/JL6e2yJERUyQtTiZIzPb' }
   },
   {
     args: ['/test.png', { width: 200, height: 200 }],
@@ -117,7 +120,8 @@ export const images = [
     wagtail: { url: '329944/fill-200x200-c0|format-webp|webpquality-70' },
     directus: { url: '/assets/1ac73658-8b62-4dea-b6da-529fbc9d01a4?width=200&height=200' },
     uploadcare: { url: 'https://ucarecdn.com/c160afba-8b42-45a9-a46a-d393248b0072/-/resize/200x200/' },
-    sirv: { url: 'https://demo.sirv.com/test.png?w=200&h=200' }
+    sirv: { url: 'https://demo.sirv.com/test.png?w=200&h=200' },
+    hygraph: { url: 'https://media.graphassets.com/auto_image/resize=width:200,height:200/JL6e2yJERUyQtTiZIzPb' }
   },
   {
     args: ['/test.png', { width: 200, height: 200, fit: 'contain' }],
@@ -147,7 +151,8 @@ export const images = [
     wagtail: { url: '329944/fill-200x200-c0|format-webp|webpquality-70' },
     directus: { url: '/assets/1ac73658-8b62-4dea-b6da-529fbc9d01a4?width=200&height=200&fit=contain' },
     uploadcare: { url: 'https://ucarecdn.com/c160afba-8b42-45a9-a46a-d393248b0072/-/resize/200x200/-/stretch/off/' },
-    sirv: { url: 'https://demo.sirv.com/test.png?w=200&h=200&scale.option=fit' }
+    sirv: { url: 'https://demo.sirv.com/test.png?w=200&h=200&scale.option=fit' },
+    hygraph: { url: 'https://media.graphassets.com/auto_image/resize=width:200,height:200,fit:max/JL6e2yJERUyQtTiZIzPb' }
   },
   {
     args: ['/test.png', { width: 200, height: 200, fit: 'contain', format: 'jpeg' }],
@@ -177,7 +182,8 @@ export const images = [
     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' },
     uploadcare: { url: 'https://ucarecdn.com/c160afba-8b42-45a9-a46a-d393248b0072/-/format/jpeg/-/resize/200x200/-/stretch/off/' },
-    sirv: { url: 'https://demo.sirv.com/test.png?w=200&h=200&scale.option=fit&format=jpg' }
+    sirv: { url: 'https://demo.sirv.com/test.png?w=200&h=200&scale.option=fit&format=jpg' },
+    hygraph: { url: 'https://media.graphassets.com/output=format:jpg/resize=width:200,height:200,fit:max/JL6e2yJERUyQtTiZIzPb' }
   }
 ] as const