Repository: nuxt/image
Pull request #1091 (2023-11-06 09:31:57)

feat: add aliyun provider
feat: add ` aliyun ` provider

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=%231091) | [View Live Preview](https://image.nuxt.com?preview=9abd4f1918aadb1e5dcde5266b93f4ff) | 94210c69a0a5ffa86227c9500bc14cf0324aa067 |

<!-- 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/1091?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nuxt) Report
Attention: `5 lines` in your changes are missing coverage. Please review.
> Comparison is base [(`cdc8c2e`)](https://app.codecov.io/gh/nuxt/image/commit/cdc8c2e6a2090540367faf1beb5f358702b7576a?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nuxt) 89.94% compared to head [(`599ed3c`)](https://app.codecov.io/gh/nuxt/image/pull/1091?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nuxt) 89.94%.

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


```diff
@@            Coverage Diff             @@
##             main    #1091      +/-   ##
==========================================
- Coverage   89.94%   89.94%   -0.01%     
==========================================
  Files          45       46       +1     
  Lines        3044     3093      +49     
  Branches      335      347      +12     
==========================================
+ Hits         2738     2782      +44     
- Misses        305      310       +5     
  Partials        1        1              
```


| [Files](https://app.codecov.io/gh/nuxt/image/pull/1091?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nuxt) | Coverage Δ | |
|---|---|---|
| [src/provider.ts](https://app.codecov.io/gh/nuxt/image/pull/1091?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nuxt#diff-c3JjL3Byb3ZpZGVyLnRz) | `94.30% <100.00%> (+0.04%)` | :arrow_up: |
| [src/runtime/providers/aliyun.ts](https://app.codecov.io/gh/nuxt/image/pull/1091?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nuxt#diff-c3JjL3J1bnRpbWUvcHJvdmlkZXJzL2FsaXl1bi50cw==) | `89.58% <89.58%> (ø)` | |


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/nuxt/image/pull/1091?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).


Patch:
diff --git a/docs/content/3.providers/aliyun.md b/docs/content/3.providers/aliyun.md
new file mode 100644
index 0000000..78489b4
--- /dev/null
+++ b/docs/content/3.providers/aliyun.md
@@ -0,0 +1,60 @@
+---
+title: Aliyun
+description: Nuxt Image has first class integration with Aliyun.
+links:
+  - label: Source
+    icon: i-simple-icons-github
+    to: https://github.com/nuxt/image/blob/main/src/runtime/providers/aliyun.ts
+    size: xs
+---
+
+Integration between [Aliyun CDN](https://cdn.console.aliyun.com/) and the image module.
+
+To use this provider you just need to specify the base url (zone) of your service:
+
+```ts [nuxt.config.ts]
+export default defineNuxtConfig({
+  image: {
+    aliyun: {
+      baseURL: "https://that-test.site",
+    },
+  },
+});
+```
+
+**Example:**
+
+```vue
+<NuxtImg
+  provider="aliyun"
+  src="/burger.jpeg"
+  height="300"
+  :modifiers="{ fit: 'contain' }"
+/>
+```
+
+## Options
+
+### `baseURL`
+
+Default: `/`
+
+Your deployment's domain (zone).
+
+## modifiers
+
+**Example:**
+
+```js
+{
+  resize: {
+    fw: 900,
+    fh: 200
+  },
+  rotate:180,
+  bright:50
+  ...
+}
+```
+
+For more modifiers configuration items, see [aliyun cdn docs](https://help.aliyun.com/zh/cdn/user-guide/image-editing/)
diff --git a/playground/nuxt.config.ts b/playground/nuxt.config.ts
index b7c18fb..32b62b6 100644
--- a/playground/nuxt.config.ts
+++ b/playground/nuxt.config.ts
@@ -25,6 +25,9 @@ export default defineNuxtConfig({
       unsplash: 'https://images.unsplash.com', // ipx
       blog: '/remote/nuxt-org/blog' // cloudinary
     },
+    aliyun: {
+      baseURL: 'https://assets.yanbot.tech'
+    },
     twicpics: {
       baseURL: 'https://demo.twic.pics/'
     },
diff --git a/playground/providers.ts b/playground/providers.ts
index 4231c0c..683a523 100644
--- a/playground/providers.ts
+++ b/playground/providers.ts
@@ -79,6 +79,36 @@ export const providers: Provider[] = [
       }
     ]
   },
+  // Aliyun
+  {
+    name: 'aliyun',
+    samples: [
+      {
+        src: '/nuxt.png',
+        height: 200
+      },
+      {
+        src: '/nuxt.png',
+        height: 300,
+        width: 300
+      },
+      {
+        src: '/nuxt.png',
+        quality: 50
+      },
+      {
+        src: '/nuxt.png',
+        format: 'webp',
+        quality: 80,
+        modifiers: {
+          resize: {
+            fw: 900,
+            fh: 200
+          }
+        }
+      }
+    ]
+  },
   // Cloudflare
   {
     name: 'cloudflare',
diff --git a/src/provider.ts b/src/provider.ts
index 8100400..97b880d 100644
--- a/src/provider.ts
+++ b/src/provider.ts
@@ -11,6 +11,7 @@ import { ipxSetup } from './ipx'
 
 // Please add new providers alphabetically to the list below
 const BuiltInProviders = [
+  'aliyun',
   'cloudflare',
   'cloudimage',
   'cloudinary',
diff --git a/src/runtime/providers/aliyun.ts b/src/runtime/providers/aliyun.ts
new file mode 100644
index 0000000..875af0d
--- /dev/null
+++ b/src/runtime/providers/aliyun.ts
@@ -0,0 +1,48 @@
+import { joinURL } from 'ufo'
+import type { ProviderGetImage } from '@nuxt/image'
+import { createOperationsGenerator } from '#image'
+
+const operationsGenerator = createOperationsGenerator({
+  joinWith: '/',
+  formatter: (key: any, value: any) => {
+    if (typeof value === 'object') {
+      return `${key},${Object.entries(value).map(([k, v]) => `${k}_${v}`).join(',')}`
+    }
+    return `${key},${value}`
+  }
+})
+
+export const getImage: ProviderGetImage = (
+  src,
+  { modifiers = {}, baseURL } = {}
+) => {
+  if (!baseURL) {
+    // also support runtime config
+    baseURL = useRuntimeConfig().public.siteUrl
+  }
+  const _modifiers = { ...modifiers }
+  const { resize, width, height, quality } = _modifiers
+
+  let resizeValue = {}
+  if (width && height) {
+    resizeValue = { fw: width, fh: height }
+  } else if (width) {
+    resizeValue = { w: width }
+  } else if (height) {
+    resizeValue = { h: height }
+  }
+  if (!resize && Object.keys(resizeValue).length) {
+    _modifiers.resize = resizeValue
+  }
+  delete _modifiers.width
+  delete _modifiers.height
+
+  if (quality) {
+    _modifiers.quality = `Q_${quality}`
+  }
+
+  const operations = operationsGenerator(_modifiers)
+  return {
+    url: joinURL(baseURL, src + (operations ? '?image_process=' + operations : ''))
+  }
+}


Test patch:
diff --git a/test/e2e/__snapshots__/no-ssr.test.ts.snap b/test/e2e/__snapshots__/no-ssr.test.ts.snap
index 9e120d8..8656ba7 100644
--- a/test/e2e/__snapshots__/no-ssr.test.ts.snap
+++ b/test/e2e/__snapshots__/no-ssr.test.ts.snap
@@ -1,5 +1,23 @@
 // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
 
+exports[`browser (ssr: false) > aliyun should render images 1`] = `
+[
+  "https://assets.yanbot.tech/nuxt.png?image_process=resize,h_200",
+  "https://assets.yanbot.tech/nuxt.png?image_process=resize,fw_300,fh_300",
+  "https://assets.yanbot.tech/nuxt.png?image_process=quality,Q_50",
+  "https://assets.yanbot.tech/nuxt.png?image_process=resize,fw_900,fh_200/format,webp/quality,Q_80",
+]
+`;
+
+exports[`browser (ssr: false) > aliyun should render images 2`] = `
+[
+  "https://assets.yanbot.tech/nuxt.png?image_process=resize,h_200",
+  "https://assets.yanbot.tech/nuxt.png?image_process=resize,fw_300,fh_300",
+  "https://assets.yanbot.tech/nuxt.png?image_process=quality,Q_50",
+  "https://assets.yanbot.tech/nuxt.png?image_process=resize,fw_900,fh_200/format,webp/quality,Q_80",
+]
+`;
+
 exports[`browser (ssr: false) > cloudflare should render images 1`] = `
 [
   "https://that-test.site/cdn-cgi/image/h=300,fit=contain/https://s3.that-test.site/burger.jpeg",
@@ -334,6 +352,19 @@ exports[`browser (ssr: false) > sirv should render images 2`] = `
 ]
 `;
 
+exports[`browser (ssr: false) > sirv should render images 3`] = `
+[
+  "https://demo.sirv.com/test.png?w=750&q=75",
+  "https://demo.sirv.com/harris-large.jpg?w=500&format=png",
+  "https://demo.sirv.com/lacoste.jpg?crop.type=trim&w=500&q=95",
+  "https://demo.sirv.com/look-big.jpg?h=500&scale.option=fit",
+  "https://demo.sirv.com/look-big.jpg?crop.type=face&h=500",
+  "https://demo.sirv.com/QW.pdf?page=1&w=500",
+  "https://demo.sirv.com/look-big.jpg?text=Hello&text.align=center&text.position.gravity=south&text.background.color=ffff&text.size=60&text.font.family=Arial&text.color=white&h=500",
+  "https://demo.sirv.com/t-shirt-man.jpg?watermark=/watermark-v1.png&watermark.position=center&watermark.scale.width=30%&h=500",
+]
+`;
+
 exports[`browser (ssr: false) > storyblok should render images 1`] = `
 [
   "https://a.storyblok.com/f/39898/3310x2192/e4ec08624e/demo-image.jpeg",
@@ -381,6 +412,16 @@ exports[`browser (ssr: false) > strapi should render images 2`] = `
 ]
 `;
 
+exports[`browser (ssr: false) > strapi should render images 3`] = `
+[
+  "http://localhost:1337/uploads/4d9z1eiyo2gmf6gd7xhp_823ae510e8.png",
+  "http://localhost:1337/uploads/thumbnail_4d9z1eiyo2gmf6gd7xhp_823ae510e8.png",
+  "http://localhost:1337/uploads/small_4d9z1eiyo2gmf6gd7xhp_823ae510e8.png",
+  "http://localhost:1337/uploads/medium_4d9z1eiyo2gmf6gd7xhp_823ae510e8.png",
+  "http://localhost:1337/uploads/large_4d9z1eiyo2gmf6gd7xhp_823ae510e8.png",
+]
+`;
+
 exports[`browser (ssr: false) > twicpics should render images 1`] = `
 [
   "https://demo.twic.pics/football.jpg",
diff --git a/test/e2e/__snapshots__/ssr.test.ts.snap b/test/e2e/__snapshots__/ssr.test.ts.snap
index 64d85a9..1dd87c0 100644
--- a/test/e2e/__snapshots__/ssr.test.ts.snap
+++ b/test/e2e/__snapshots__/ssr.test.ts.snap
@@ -1,5 +1,23 @@
 // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
 
+exports[`browser (ssr: true) > aliyun should render images 1`] = `
+[
+  "https://assets.yanbot.tech/nuxt.png?image_process=resize,h_200",
+  "https://assets.yanbot.tech/nuxt.png?image_process=resize,fw_300,fh_300",
+  "https://assets.yanbot.tech/nuxt.png?image_process=quality,Q_50",
+  "https://assets.yanbot.tech/nuxt.png?image_process=resize,fw_900,fh_200/format,webp/quality,Q_80",
+]
+`;
+
+exports[`browser (ssr: true) > aliyun should render images 2`] = `
+[
+  "https://assets.yanbot.tech/nuxt.png?image_process=resize,h_200",
+  "https://assets.yanbot.tech/nuxt.png?image_process=resize,fw_300,fh_300",
+  "https://assets.yanbot.tech/nuxt.png?image_process=quality,Q_50",
+  "https://assets.yanbot.tech/nuxt.png?image_process=resize,fw_900,fh_200/format,webp/quality,Q_80",
+]
+`;
+
 exports[`browser (ssr: true) > cloudflare should render images 1`] = `
 [
   "https://that-test.site/cdn-cgi/image/h=300,fit=contain/https://s3.that-test.site/burger.jpeg",
@@ -308,6 +326,24 @@ exports[`browser (ssr: true) > sanity should render images 2`] = `
 ]
 `;
 
+exports[`browser (ssr: true) > should emit load and error events 1`] = `
+[
+  "https://images.prismic.io/200629-sms-hoy/f596a543-d593-4296-9abd-3d3ac15f1e39_ray-hennessy-mpw37yXc_WQ-unsplash.jpg?auto=compress,format&w=600&h=900",
+  "https://images.prismic.io/200629-sms-hoy/f596a543-d593-4296-9abd-3d3ac15f1e39_ray-hennessy-mpw37yXc_WQ-unsplash.jpg?auto=compress,format&w=200&h=900",
+  "https://images.prismic.io/200629-sms-hoy/f596a543-d593-4296-9abd-3d3ac15f1e39_ray-hennessy-mpw37yXc_WQ-unsplash.jpg?auto=compress,format&w=600&h=200",
+  "https://images.prismic.io/200629-sms-hoy/f596a543-d593-4296-9abd-3d3ac15f1e39_ray-hennessy-mpw37yXc_WQ-unsplash.jpg?auto=compress,format&w=200&h=200&fit=crop",
+]
+`;
+
+exports[`browser (ssr: true) > should emit load and error events 2`] = `
+[
+  "https://images.prismic.io/200629-sms-hoy/f596a543-d593-4296-9abd-3d3ac15f1e39_ray-hennessy-mpw37yXc_WQ-unsplash.jpg?auto=compress,format&w=600&h=900",
+  "https://images.prismic.io/200629-sms-hoy/f596a543-d593-4296-9abd-3d3ac15f1e39_ray-hennessy-mpw37yXc_WQ-unsplash.jpg?auto=compress,format&w=200&h=900",
+  "https://images.prismic.io/200629-sms-hoy/f596a543-d593-4296-9abd-3d3ac15f1e39_ray-hennessy-mpw37yXc_WQ-unsplash.jpg?auto=compress,format&w=600&h=200",
+  "https://images.prismic.io/200629-sms-hoy/f596a543-d593-4296-9abd-3d3ac15f1e39_ray-hennessy-mpw37yXc_WQ-unsplash.jpg?auto=compress,format&w=200&h=200&fit=crop",
+]
+`;
+
 exports[`browser (ssr: true) > sirv should render images 1`] = `
 [
   "https://demo.sirv.com/test.png?w=750&q=75",
@@ -509,6 +545,12 @@ exports[`browser (ssr: true) > vercel should render images 2`] = `
 ]
 `;
 
+exports[`browser (ssr: true) > vercel should render images 3`] = `
+[
+  "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",
diff --git a/test/providers.ts b/test/providers.ts
index 4bc3735..2be396a 100644
--- a/test/providers.ts
+++ b/test/providers.ts
@@ -3,6 +3,7 @@ export const images = [
     args: ['/test.png', {}],
     none: { url: '/test.png' },
     ipx: { url: '/_ipx/_/test.png' },
+    aliyun: { url: '/test.png' },
     cloudflare: { url: '/test.png' },
     cloudinary: { url: '/f_auto,q_auto/test' },
     twicpics: { url: '/test.png' },
@@ -31,6 +32,7 @@ export const images = [
     args: ['/test.png', { width: 200 }],
     none: { url: '/test.png' },
     ipx: { url: '/_ipx/w_200/test.png' },
+    aliyun: { url: '/test.png?image_process=resize,w_200' },
     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-' },
@@ -59,6 +61,7 @@ export const images = [
     args: ['/test.png', { height: 200 }],
     none: { url: '/test.png' },
     ipx: { url: '/_ipx/h_200/test.png' },
+    aliyun: { url: '/test.png?image_process=resize,h_200' },
     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' },
@@ -87,6 +90,7 @@ export const images = [
     args: ['/test.png', { width: 200, height: 200 }],
     none: { url: '/test.png' },
     ipx: { url: '/_ipx/s_200x200/test.png' },
+    aliyun: { url: '/test.png?image_process=resize,fw_200,fh_200' },
     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' },
@@ -115,6 +119,7 @@ export const images = [
     args: ['/test.png', { width: 200, height: 200, fit: 'contain' }],
     none: { url: '/test.png' },
     ipx: { url: '/_ipx/fit_contain&s_200x200/test.png' },
+    aliyun: { url: '/test.png?image_process=fit,contain/resize,fw_200,fh_200' },
     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' },
@@ -143,6 +148,7 @@ export const images = [
     args: ['/test.png', { width: 200, height: 200, fit: 'contain', format: 'jpeg' }],
     none: { url: '/test.png' },
     ipx: { url: '/_ipx/fit_contain&f_jpeg&s_200x200/test.png' },
+    aliyun: { url: '/test.png?image_process=fit,contain/format,jpeg/resize,fw_200,fh_200' },
     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' },
diff --git a/test/unit/providers.test.ts b/test/unit/providers.test.ts
index f0700ab..4e807ea 100644
--- a/test/unit/providers.test.ts
+++ b/test/unit/providers.test.ts
@@ -7,6 +7,7 @@ import { images } from '../providers'
 import { cleanDoubleSlashes } from '#image/utils'
 import * as ipx from '#image/providers/ipx'
 import * as none from '~/src/runtime/providers/none'
+import * as aliyun from '#image/providers/aliyun'
 import * as cloudflare from '#image/providers/cloudflare'
 import * as cloudinary from '#image/providers/cloudinary'
 import * as twicpics from '#image/providers/twicpics'
@@ -69,7 +70,16 @@ describe('Providers', () => {
       url: '/_ipx/_/images/test.png'
     })
   })
-
+  it('aliyun', () => {
+    const providerOptions = {
+      baseURL: '/'
+    }
+    for (const image of images) {
+      const [src, modifiers] = image.args
+      const generated = aliyun.getImage(src, { modifiers, ...providerOptions }, emptyContext)
+      expect(generated).toMatchObject(image.aliyun)
+    }
+  })
   it('cloudflare', () => {
     const providerOptions = {
       baseURL: '/'