diff --git a/docs/content/3.providers/aws-amplify.md b/docs/content/3.providers/aws-amplify.md new file mode 100644 index 000000000..b18686a06 --- /dev/null +++ b/docs/content/3.providers/aws-amplify.md @@ -0,0 +1,52 @@ +--- +title: AWS Amplify +description: Nuxt Image has first class integration with AWS Amplify Hosting +links: + - label: Source + icon: i-simple-icons-github + to: https://github.com/nuxt/image/blob/main/src/runtime/providers/awsAmplify.ts + size: xs +--- + +Integration between [AWS Amplify Hosting](https://aws.amazon.com/amplify/) and the image module. + +This provider will be enabled by default in AWS Amplify deployments. + +::alert{type="warning"} +This is an experimental preset and will be available soon! 🚀 +:: + +## Domains + +To use external URLs (images not in `public/` directory), hostnames should be whitelisted. + +**Example:** + +```ts [nuxt.config] +export default { + image: { + domains: [ + 'avatars0.githubusercontent.com' + ] + } +} +``` + +## Sizes + +Specify any custom `width` property you use in ``, `` and `$img`. + +If a width is not defined, image will fallback to closest possible width. + +**Example:** + +```ts [nuxt.config] +export default { + image: { + screens: { + icon: 40, + avatar: 24 + } + } +} +``` diff --git a/docs/public/providers/aws-amplify.svg b/docs/public/providers/aws-amplify.svg new file mode 100644 index 000000000..fc29a2a00 --- /dev/null +++ b/docs/public/providers/aws-amplify.svg @@ -0,0 +1,19 @@ + + + + + Icon-Architecture/64/Arch_AWS-Amplify-Console_64 + Created with Sketch. + + + + + + + + + + + + + diff --git a/playground/nuxt.config.ts b/playground/nuxt.config.ts index 32b62b615..b3d85f1cd 100644 --- a/playground/nuxt.config.ts +++ b/playground/nuxt.config.ts @@ -28,6 +28,9 @@ export default defineNuxtConfig({ aliyun: { baseURL: 'https://assets.yanbot.tech' }, + awsAmplify: { + baseURL: 'https://example.amplifyapp.com/_amplify/image' + }, twicpics: { baseURL: 'https://demo.twic.pics/' }, diff --git a/playground/providers.ts b/playground/providers.ts index 683a523f4..460a2d5cf 100644 --- a/playground/providers.ts +++ b/playground/providers.ts @@ -109,6 +109,16 @@ export const providers: Provider[] = [ } ] }, + // AWS Amplify + { + name: 'awsAmplify', + samples: [ + { + src: '/test.jpg', + width: 300 + } + ] + }, // Cloudflare { name: 'cloudflare', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 16d92d7cc..9968feaec 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -88,7 +88,7 @@ importers: version: 3.8.1(@types/node@20.9.0)(eslint@8.53.0)(rollup@3.29.4)(typescript@5.2.2)(vite@4.5.0)(vue-tsc@1.8.22) nuxt-vitest: specifier: ^0.11.3 - version: 0.11.3(@vitejs/plugin-vue-jsx@3.0.2)(@vitejs/plugin-vue@4.4.0)(happy-dom@12.10.3)(rollup@3.29.4)(vite@4.5.0)(vitest@0.33.0)(vue-router@4.2.5)(vue@3.3.8) + version: 0.11.3(@vitejs/plugin-vue-jsx@3.0.2)(@vitejs/plugin-vue@4.4.1)(happy-dom@12.10.3)(rollup@3.29.4)(vite@4.5.0)(vitest@0.33.0)(vue-router@4.2.5)(vue@3.3.8) playwright: specifier: ^1.39.0 version: 1.39.0 @@ -118,7 +118,7 @@ importers: version: link:.. '@nuxt/ui-pro': specifier: ^0.4.2 - version: 0.4.2(rollup@3.29.4)(vue@3.3.8)(webpack@5.88.2) + version: 0.4.2(rollup@3.29.4)(vue@3.3.8)(webpack@5.89.0) '@nuxthq/studio': specifier: ^1.0.4 version: 1.0.4(rollup@3.29.4) @@ -1198,6 +1198,13 @@ packages: '@jridgewell/resolve-uri': 3.1.1 '@jridgewell/sourcemap-codec': 1.4.15 + /@jridgewell/trace-mapping@0.3.20: + resolution: {integrity: sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==} + dependencies: + '@jridgewell/resolve-uri': 3.1.1 + '@jridgewell/sourcemap-codec': 1.4.15 + dev: false + /@koa/router@12.0.1: resolution: {integrity: sha512-ribfPYfHb+Uw3b27Eiw6NPqjhIhTpVFzEWLwyc/1Xp+DCdwRRyIlAUODX+9bPARF6aQtUu1+/PHzdNvRzcs/+Q==} engines: {node: '>= 12'} @@ -1633,15 +1640,15 @@ packages: - typescript dev: true - /@nuxt/postcss8@1.1.3(webpack@5.88.2): + /@nuxt/postcss8@1.1.3(webpack@5.89.0): resolution: {integrity: sha512-CdHtErhvQwueNZPBOmlAAKrNCK7aIpZDYhtS7TzXlSgPHHox1g3cSlf+Ke9oB/8t4mNNjdB+prclme2ibuCOEA==} dependencies: autoprefixer: 10.4.16(postcss@8.4.31) - css-loader: 5.2.7(webpack@5.88.2) + css-loader: 5.2.7(webpack@5.89.0) defu: 3.2.2 postcss: 8.4.31 postcss-import: 13.0.0(postcss@8.4.31) - postcss-loader: 4.3.0(postcss@8.4.31)(webpack@5.88.2) + postcss-loader: 4.3.0(postcss@8.4.31)(webpack@5.89.0) postcss-url: 10.1.3(postcss@8.4.31) semver: 7.5.4 transitivePeerDependencies: @@ -1723,10 +1730,10 @@ packages: - supports-color dev: true - /@nuxt/ui-pro@0.4.2(rollup@3.29.4)(vue@3.3.8)(webpack@5.88.2): + /@nuxt/ui-pro@0.4.2(rollup@3.29.4)(vue@3.3.8)(webpack@5.89.0): resolution: {integrity: sha512-TmP2+dAaulIaxoAKB6lxIckluflTeanZ6NVdtL7TTxMduscVgzEWZ3ierDUTHKf8u4D3KtB9jXL0vZcNdnf5zg==} dependencies: - '@nuxt/ui': 2.10.0(rollup@3.29.4)(vue@3.3.8)(webpack@5.88.2) + '@nuxt/ui': 2.10.0(rollup@3.29.4)(vue@3.3.8)(webpack@5.89.0) '@vueuse/core': 10.6.1(vue@3.3.8) defu: 6.1.3 nuxt-icon: 0.6.1(rollup@3.29.4)(vue@3.3.8) @@ -1756,7 +1763,7 @@ packages: /@nuxt/ui-templates@1.3.1: resolution: {integrity: sha512-5gc02Pu1HycOVUWJ8aYsWeeXcSTPe8iX8+KIrhyEtEoOSkY0eMBuo0ssljB8wALuEmepv31DlYe5gpiRwkjESA==} - /@nuxt/ui@2.10.0(rollup@3.29.4)(vue@3.3.8)(webpack@5.88.2): + /@nuxt/ui@2.10.0(rollup@3.29.4)(vue@3.3.8)(webpack@5.89.0): resolution: {integrity: sha512-pMv0BWWkeUOAJ+YkXr6xirbg2iHXKerIk9hYS7blp0aBehBTBq1gxHdtl+iA2hq9+LFahO6WyA7SJnw3h0thvQ==} engines: {node: '>=v16.20.2'} dependencies: @@ -1766,7 +1773,7 @@ packages: '@iconify-json/heroicons': 1.1.13 '@nuxt/kit': 3.8.1(rollup@3.29.4) '@nuxtjs/color-mode': 3.3.0(rollup@3.29.4) - '@nuxtjs/tailwindcss': 6.9.4(rollup@3.29.4)(webpack@5.88.2) + '@nuxtjs/tailwindcss': 6.9.4(rollup@3.29.4)(webpack@5.89.0) '@popperjs/core': 2.11.8 '@tailwindcss/aspect-ratio': 0.4.2(tailwindcss@3.3.5) '@tailwindcss/container-queries': 0.1.1(tailwindcss@3.3.5) @@ -2003,11 +2010,11 @@ packages: - supports-color dev: false - /@nuxtjs/tailwindcss@6.9.4(rollup@3.29.4)(webpack@5.88.2): + /@nuxtjs/tailwindcss@6.9.4(rollup@3.29.4)(webpack@5.89.0): resolution: {integrity: sha512-T3B3P7RgJ/WTW3plHziLWqWbMzCWctUHpjqhW1WCXB/U3FOQxGH5dG4uEOmQkA6Gj4cbTNStIU/cRxpZhbBMhg==} dependencies: '@nuxt/kit': 3.8.1(rollup@3.29.4) - '@nuxt/postcss8': 1.1.3(webpack@5.88.2) + '@nuxt/postcss8': 1.1.3(webpack@5.89.0) autoprefixer: 10.4.16(postcss@8.4.31) chokidar: 3.5.3 clear-module: 4.1.2 @@ -2633,23 +2640,27 @@ packages: '@types/ms': 0.7.32 dev: false - /@types/eslint-scope@3.7.5: - resolution: {integrity: sha512-JNvhIEyxVW6EoMIFIvj93ZOywYFatlpu9deeH6eSx6PE3WHYvHaQtmHmQeNw7aA81bYGBPPQqdtBm6b1SsQMmA==} + /@types/eslint-scope@3.7.7: + resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} dependencies: - '@types/eslint': 8.44.3 - '@types/estree': 1.0.2 + '@types/eslint': 8.44.7 + '@types/estree': 1.0.5 dev: false - /@types/eslint@8.44.3: - resolution: {integrity: sha512-iM/WfkwAhwmPff3wZuPLYiHX18HI24jU8k1ZSH7P8FHwxTjZ2P6CoX2wnF43oprR+YXJM6UUxATkNvyv/JHd+g==} + /@types/eslint@8.44.7: + resolution: {integrity: sha512-f5ORu2hcBbKei97U73mf+l9t4zTGl74IqZ0GQk4oVea/VS8tQZYkUveSYojk+frraAVYId0V2WC9O4PTNru2FQ==} dependencies: - '@types/estree': 1.0.2 - '@types/json-schema': 7.0.13 + '@types/estree': 1.0.5 + '@types/json-schema': 7.0.15 dev: false /@types/estree@1.0.2: resolution: {integrity: sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA==} + /@types/estree@1.0.5: + resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + dev: false + /@types/fs-extra@11.0.3: resolution: {integrity: sha512-sF59BlXtUdzEAL1u0MSvuzWd7PdZvZEtnaVkzX5mjpdWTJ8brG0jUqve3jPCzSzvAKKMHTG8F8o/WMQLtleZdQ==} dependencies: @@ -2681,6 +2692,10 @@ packages: /@types/json-schema@7.0.13: resolution: {integrity: sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==} + /@types/json-schema@7.0.15: + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + dev: false + /@types/json5@0.0.29: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} dev: true @@ -3089,6 +3104,17 @@ packages: vite: 4.5.0(@types/node@20.9.0) vue: 3.3.8(typescript@5.2.2) + /@vitejs/plugin-vue@4.4.1(vite@4.5.0)(vue@3.3.8): + resolution: {integrity: sha512-HCQG8VDFDM7YDAdcj5QI5DvUi+r6xvo9LgvYdk7LSkUNwdpempdB5horkMSZsbdey9Ywsf5aaU8kEPw9M5kREA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.0.0 + vue: ^3.2.25 + dependencies: + vite: 4.5.0(@types/node@20.9.0) + vue: 3.3.8(typescript@5.2.2) + dev: true + /@vitest/coverage-v8@0.34.4(vitest@0.33.0): resolution: {integrity: sha512-TZ5ghzhmg3COQqfBShL+zRQEInHmV9TSwghTdfkHpCTyTOr+rxo6x41vCNcVfWysWULtqtBVpY6YFNovxnESfA==} peerDependencies: @@ -4574,7 +4600,7 @@ packages: resolution: {integrity: sha512-hZ8sTouIicEUhVqKEeeire6kePeCDoIBvWTSPbHc7dK4Za1odVmQ6gMgFcH701gkKGP6Uic3zcQACEqSP3sJig==} dev: false - /css-loader@5.2.7(webpack@5.88.2): + /css-loader@5.2.7(webpack@5.89.0): resolution: {integrity: sha512-Q7mOvpBNBG7YrVGMxRxcBJZFL75o+cH2abNASdibkj/fffYD8qWbInZrD0S9ccI6vZclF3DsHE7njGlLtaHbhg==} engines: {node: '>= 10.13.0'} peerDependencies: @@ -4590,7 +4616,7 @@ packages: postcss-value-parser: 4.2.0 schema-utils: 3.3.0 semver: 7.5.4 - webpack: 5.88.2 + webpack: 5.89.0 dev: false /css-select@5.1.0: @@ -5129,8 +5155,8 @@ packages: which-typed-array: 1.1.11 dev: true - /es-module-lexer@1.3.1: - resolution: {integrity: sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==} + /es-module-lexer@1.4.1: + resolution: {integrity: sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==} dev: false /es-set-tostringtag@2.0.1: @@ -9213,7 +9239,7 @@ packages: - vue dev: false - /nuxt-vitest@0.11.3(@vitejs/plugin-vue-jsx@3.0.2)(@vitejs/plugin-vue@4.4.0)(happy-dom@12.10.3)(rollup@3.29.4)(vite@4.5.0)(vitest@0.33.0)(vue-router@4.2.5)(vue@3.3.8): + /nuxt-vitest@0.11.3(@vitejs/plugin-vue-jsx@3.0.2)(@vitejs/plugin-vue@4.4.1)(happy-dom@12.10.3)(rollup@3.29.4)(vite@4.5.0)(vitest@0.33.0)(vue-router@4.2.5)(vue@3.3.8): resolution: {integrity: sha512-jGyrOXbgwlakVaDsQCLtdttXXY2bX4J5r22rr0CTXqIFO3jhnf8e3kAU+zCQe/WSp/xtM+2HZEwrU+jqy5zuDQ==} peerDependencies: '@vitejs/plugin-vue': '*' @@ -9222,7 +9248,7 @@ packages: vitest: ^0.24.5 || ^0.26.0 || ^0.27.0 || ^0.28.0 || ^0.29.0 || ^0.30.0 || ^0.33.0 dependencies: '@nuxt/kit': 3.8.1(rollup@3.29.4) - '@vitejs/plugin-vue': 4.4.0(vite@4.5.0)(vue@3.3.8) + '@vitejs/plugin-vue': 4.4.1(vite@4.5.0)(vue@3.3.8) '@vitejs/plugin-vue-jsx': 3.0.2(vite@4.5.0)(vue@3.3.8) '@vitest/ui': 0.33.0(vitest@0.33.0) defu: 6.1.3 @@ -9297,7 +9323,7 @@ packages: pkg-types: 1.0.3 radix3: 1.1.0 scule: 1.0.0 - std-env: 3.4.3 + std-env: 3.5.0 strip-literal: 1.3.0 ufo: 1.3.1 ultrahtml: 1.5.2 @@ -9902,7 +9928,7 @@ packages: yaml: 2.3.2 dev: false - /postcss-loader@4.3.0(postcss@8.4.31)(webpack@5.88.2): + /postcss-loader@4.3.0(postcss@8.4.31)(webpack@5.89.0): resolution: {integrity: sha512-M/dSoIiNDOo8Rk0mUqoj4kpGq91gcxCfb9PoyZVdZ76/AuhxylHDYZblNE8o+EQ9AMSASeMFEKxZf5aU6wlx1Q==} engines: {node: '>= 10.13.0'} peerDependencies: @@ -9915,7 +9941,7 @@ packages: postcss: 8.4.31 schema-utils: 3.3.0 semver: 7.5.4 - webpack: 5.88.2 + webpack: 5.89.0 dev: false /postcss-merge-longhand@6.0.0(postcss@8.4.31): @@ -11111,9 +11137,6 @@ packages: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} - /std-env@3.4.3: - resolution: {integrity: sha512-f9aPhy8fYBuMN+sNfakZV18U39PbalgjXG3lLB9WkaYTxijru61wb57V9wxxNthXM5Sd88ETBWi29qLAsHO52Q==} - /std-env@3.5.0: resolution: {integrity: sha512-JGUEaALvL0Mf6JCfYnJOTcobY+Nc7sG/TemDRBqCA0wEr4DER7zDchaaixTlmOxAjG1uRJmX82EQcxwTQTkqVA==} @@ -11446,7 +11469,7 @@ packages: ps-tree: 1.2.0 dev: false - /terser-webpack-plugin@5.3.9(webpack@5.88.2): + /terser-webpack-plugin@5.3.9(webpack@5.89.0): resolution: {integrity: sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==} engines: {node: '>= 10.13.0'} peerDependencies: @@ -11462,12 +11485,12 @@ packages: uglify-js: optional: true dependencies: - '@jridgewell/trace-mapping': 0.3.19 + '@jridgewell/trace-mapping': 0.3.20 jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.1 - terser: 5.21.0 - webpack: 5.88.2 + terser: 5.24.0 + webpack: 5.89.0 dev: false /terser@5.21.0: @@ -11480,6 +11503,17 @@ packages: commander: 2.20.3 source-map-support: 0.5.21 + /terser@5.24.0: + resolution: {integrity: sha512-ZpGR4Hy3+wBEzVEnHvstMvqpD/nABNelQn/z2r0fjVWGQsN3bpOLzQlqDxmb4CDZnXq5lpjnQ+mHQLAOpfM5iw==} + engines: {node: '>=10'} + hasBin: true + dependencies: + '@jridgewell/source-map': 0.3.5 + acorn: 8.11.2 + commander: 2.20.3 + source-map-support: 0.5.21 + dev: false + /test-exclude@6.0.0: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} @@ -12695,8 +12729,8 @@ packages: /webpack-virtual-modules@0.5.0: resolution: {integrity: sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==} - /webpack@5.88.2: - resolution: {integrity: sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==} + /webpack@5.89.0: + resolution: {integrity: sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==} engines: {node: '>=10.13.0'} hasBin: true peerDependencies: @@ -12705,8 +12739,8 @@ packages: webpack-cli: optional: true dependencies: - '@types/eslint-scope': 3.7.5 - '@types/estree': 1.0.2 + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.5 '@webassemblyjs/ast': 1.11.6 '@webassemblyjs/wasm-edit': 1.11.6 '@webassemblyjs/wasm-parser': 1.11.6 @@ -12715,7 +12749,7 @@ packages: browserslist: 4.22.1 chrome-trace-event: 1.0.3 enhanced-resolve: 5.15.0 - es-module-lexer: 1.3.1 + es-module-lexer: 1.4.1 eslint-scope: 5.1.1 events: 3.3.0 glob-to-regexp: 0.4.1 @@ -12726,7 +12760,7 @@ packages: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.9(webpack@5.88.2) + terser-webpack-plugin: 5.3.9(webpack@5.89.0) watchpack: 2.4.0 webpack-sources: 3.2.3 transitivePeerDependencies: diff --git a/src/provider.ts b/src/provider.ts index 97b880dc1..a9b52d6aa 100644 --- a/src/provider.ts +++ b/src/provider.ts @@ -4,7 +4,7 @@ import type { Nuxt } from '@nuxt/schema' import type { NitroConfig } from 'nitropack' import { createResolver, resolvePath } from '@nuxt/kit' import { hash } from 'ohash' -import { provider } from 'std-env' +import { provider, type ProviderName } from 'std-env' import type { InputProvider, ImageModuleProvider, ProviderSetup } from './types' import type { ModuleOptions } from './module' import { ipxSetup } from './ipx' @@ -12,6 +12,7 @@ import { ipxSetup } from './ipx' // Please add new providers alphabetically to the list below const BuiltInProviders = [ 'aliyun', + 'awsAmplify', 'cloudflare', 'cloudimage', 'cloudinary', @@ -40,9 +41,11 @@ const BuiltInProviders = [ 'vercel', 'wagtail', 'sirv' -] +] as const -export const providerSetup: Record = { +export type ImageProviderName = typeof BuiltInProviders[number] + +export const providerSetup: Partial> = { // IPX ipx: ipxSetup(), ipxStatic: ipxSetup({ isStatic: true }), @@ -60,6 +63,25 @@ export const providerSetup: Record = { } } satisfies NitroConfig['vercel'] }) + }, + + awsAmplify (_providerOptions, moduleOptions, nuxt: Nuxt) { + nuxt.options.nitro = defu(nuxt.options.nitro, { + awsAmplify: { + imageOptimization: { + path: '/_amplify/image', + cacheControl: 'public, max-age=300, immutable' + }, + imageSettings: { + sizes: Array.from(new Set(Object.values(moduleOptions.screens || {}))), + formats: ['image/jpeg', 'image/png', 'image/webp', 'image/avif'], + minimumCacheTTL: 60 * 5, + domains: moduleOptions.domains, + remotePatterns: [], // Provided by domains + dangerouslyAllowSVG: false // TODO + } + } + }) } } @@ -67,7 +89,7 @@ export async function resolveProviders (nuxt: any, options: ModuleOptions): Prom const providers: ImageModuleProvider[] = [] for (const key in options) { - if (BuiltInProviders.includes(key)) { + if (BuiltInProviders.includes(key as ImageProviderName)) { providers.push(await resolveProvider(nuxt, key, { provider: key, options: options[key] })) } } @@ -93,11 +115,11 @@ export async function resolveProvider (_nuxt: any, key: string, input: InputProv } const resolver = createResolver(import.meta.url) - input.provider = BuiltInProviders.includes(input.provider) + input.provider = BuiltInProviders.includes(input.provider as ImageProviderName) ? await resolver.resolve('./runtime/providers/' + input.provider) : await resolvePath(input.provider) - const setup = input.setup || providerSetup[input.name] + const setup = input.setup || providerSetup[input.name as ImageProviderName] return { ...input, @@ -108,7 +130,12 @@ export async function resolveProvider (_nuxt: any, key: string, input: InputProv } } -export function detectProvider (userInput?: string) { +const autodetectableProviders: Partial> = { + vercel: 'vercel', + aws_amplify: 'awsAmplify' +} + +export function detectProvider (userInput: string = '') { if (process.env.NUXT_IMAGE_PROVIDER) { return process.env.NUXT_IMAGE_PROVIDER } @@ -117,7 +144,7 @@ export function detectProvider (userInput?: string) { return userInput } - if (provider === 'vercel') { - return 'vercel' + if (provider in autodetectableProviders) { + return autodetectableProviders[provider] } } diff --git a/src/runtime/providers/awsAmplify.ts b/src/runtime/providers/awsAmplify.ts new file mode 100644 index 000000000..a6d997195 --- /dev/null +++ b/src/runtime/providers/awsAmplify.ts @@ -0,0 +1,36 @@ +import { stringifyQuery } from 'ufo' +import type { ProviderGetImage } from '../../types' + +export const getImage: ProviderGetImage = (src, { modifiers, baseURL = '/_amplify/image' } = {}, ctx) => { + const validWidths = Object.values(ctx.options.screens || {}).sort((a, b) => a - b) + const largestWidth = validWidths[validWidths.length - 1] + let width = Number(modifiers?.width || 0) + + if (!width) { + width = largestWidth + if (process.env.NODE_ENV === 'development') { + // eslint-disable-next-line + console.warn(`A defined width should be provided to use the \`awsAmplify\` provider. Defaulting to \`${largestWidth}\`. Warning originated from \`${src}\`.`) + } + } else if (!validWidths.includes(width)) { + width = validWidths.find(validWidth => validWidth > width) || largestWidth + if (process.env.NODE_ENV === 'development') { + // eslint-disable-next-line + console.warn(`The width being used (\`${modifiers?.width}\`) should be added to \`image.screens\`. Defaulting to \`${width}\`. Warning originated from \`${src}\`.`) + } + } + + if (process.env.NODE_ENV === 'development') { + return { url: src } + } + + return { + url: baseURL + '?' + stringifyQuery({ + url: src, + w: String(width), + q: String(modifiers?.quality || '100') + }) + } +} + +export const validateDomains = true diff --git a/test/e2e/__snapshots__/no-ssr.test.ts.snap b/test/e2e/__snapshots__/no-ssr.test.ts.snap index 8656ba778..606f4d7e2 100644 --- a/test/e2e/__snapshots__/no-ssr.test.ts.snap +++ b/test/e2e/__snapshots__/no-ssr.test.ts.snap @@ -18,6 +18,18 @@ exports[`browser (ssr: false) > aliyun should render images 2`] = ` ] `; +exports[`browser (ssr: false) > awsAmplify should render images 1`] = ` +[ + "https://example.amplifyapp.com/_amplify/image?url=/test.jpg&w=320&q=100", +] +`; + +exports[`browser (ssr: false) > awsAmplify should render images 2`] = ` +[ + "https://example.amplifyapp.com/_amplify/image?url=/test.jpg&w=320&q=100", +] +`; + 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", @@ -352,19 +364,6 @@ 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", @@ -412,16 +411,6 @@ 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 1dd87c094..4f5b52912 100644 --- a/test/e2e/__snapshots__/ssr.test.ts.snap +++ b/test/e2e/__snapshots__/ssr.test.ts.snap @@ -18,6 +18,18 @@ exports[`browser (ssr: true) > aliyun should render images 2`] = ` ] `; +exports[`browser (ssr: true) > awsAmplify should render images 1`] = ` +[ + "https://example.amplifyapp.com/_amplify/image?url=/test.jpg&w=320&q=100", +] +`; + +exports[`browser (ssr: true) > awsAmplify should render images 2`] = ` +[ + "https://example.amplifyapp.com/_amplify/image?url=/test.jpg&w=320&q=100", +] +`; + 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", @@ -326,24 +338,6 @@ 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", @@ -545,12 +539,6 @@ 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 2be396a6a..d9e24737e 100644 --- a/test/providers.ts +++ b/test/providers.ts @@ -4,6 +4,7 @@ export const images = [ none: { url: '/test.png' }, ipx: { url: '/_ipx/_/test.png' }, aliyun: { url: '/test.png' }, + awsAmplify: { url: '/?url=/test.png&w=1536&q=100' }, cloudflare: { url: '/test.png' }, cloudinary: { url: '/f_auto,q_auto/test' }, twicpics: { url: '/test.png' }, @@ -33,6 +34,7 @@ export const images = [ none: { url: '/test.png' }, ipx: { url: '/_ipx/w_200/test.png' }, aliyun: { url: '/test.png?image_process=resize,w_200' }, + awsAmplify: { url: '/?url=/test.png&w=320&q=100' }, 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-' }, @@ -62,6 +64,7 @@ export const images = [ none: { url: '/test.png' }, ipx: { url: '/_ipx/h_200/test.png' }, aliyun: { url: '/test.png?image_process=resize,h_200' }, + awsAmplify: { url: '/?url=/test.png&w=1536&q=100' }, 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' }, @@ -91,6 +94,7 @@ export const images = [ none: { url: '/test.png' }, ipx: { url: '/_ipx/s_200x200/test.png' }, aliyun: { url: '/test.png?image_process=resize,fw_200,fh_200' }, + awsAmplify: { url: '/?url=/test.png&w=320&q=100' }, 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' }, @@ -120,6 +124,7 @@ export const images = [ 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' }, + awsAmplify: { url: '/?url=/test.png&w=320&q=100' }, 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' }, @@ -149,6 +154,7 @@ export const images = [ 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' }, + awsAmplify: { url: '/?url=/test.png&w=320&q=100' }, 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 4e807ea55..acf478578 100644 --- a/test/unit/providers.test.ts +++ b/test/unit/providers.test.ts @@ -8,6 +8,7 @@ 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 awsAmplify from '#image/providers/awsAmplify' import * as cloudflare from '#image/providers/cloudflare' import * as cloudinary from '#image/providers/cloudinary' import * as twicpics from '#image/providers/twicpics' @@ -80,6 +81,16 @@ describe('Providers', () => { expect(generated).toMatchObject(image.aliyun) } }) + it('awsAmplify', () => { + const providerOptions = { + baseURL: '/' + } + for (const image of images) { + const [src, modifiers] = image.args + const generated = awsAmplify.getImage(src, { modifiers, ...providerOptions }, emptyContext) + expect(generated).toMatchObject(image.awsAmplify) + } + }) it('cloudflare', () => { const providerOptions = { baseURL: '/'