diff --git a/docs/pages/en/1.getting-started/1.installation.md b/docs/pages/en/1.getting-started/1.installation.md index 2863677d1..70c2d7c69 100644 --- a/docs/pages/en/1.getting-started/1.installation.md +++ b/docs/pages/en/1.getting-started/1.installation.md @@ -23,14 +23,29 @@ Add `@nuxt/image` devDependency to your project: :::: ::::: -Add the module to the `buildModules` of your `nuxt.config`: +Add the module to `buildModules` in your `nuxt.config`: ```ts [nuxt.config.js] export default { - buildModules: ['@nuxt/image'] + target: 'static', + buildModules: [ + '@nuxt/image', + ] } ``` +:::alert{type="info"} +If you have a `server` target and are using the default provider ([`ipx`](/providers/ipx)), make sure you add `@nuxt/image` to your `modules` instead to self-host your `/_ipx` endpoint. + +```ts [nuxt.config.js] +export default { + modules: [ + '@nuxt/image', + ] +} +``` +::: + :::alert{type="success"} You can now start using [``](/components/nuxt-img) and [``](/components/nuxt-picture) components in your Nuxt app ✨ ::: diff --git a/docs/pages/en/1.getting-started/2.providers.md b/docs/pages/en/1.getting-started/2.providers.md index b965f751e..861546140 100644 --- a/docs/pages/en/1.getting-started/2.providers.md +++ b/docs/pages/en/1.getting-started/2.providers.md @@ -3,27 +3,26 @@ title: Providers description: Nuxt Image supports multiple providers for high performances. --- -## Default provider +Providers are integrations between Nuxt Image and third-party image transformation services. Each provider is responsible for generating correct URLs for that image transformation service. -Nuxt Image comes with a preconfigured instance of [IPX](/providers/ipx) to provide images transformations based on the [Sharp](https://www.npmjs.com/package/sharp) package. This default provider can be used without any configuration. The module will automatically optimize `` or `` sources and accepts all [options](/api/options/), except for any modifiers that are specific to other providers. - -With a `server` target, IPX will generate images at runtime. With a `static` target, IPX will generate all optimized assets on running `nuxt generate` and store them in the `dist` folder. - -[Learn more about Nuxt deployment targets](https://nuxtjs.org/docs/2.x/features/deployment-targets) - -## Image Providers - -Providers are integrations between the Nuxt Image module and third-party image transformation services (or, as in the case of `ipx`, a self-hosted/generate-time image transformation service). Each provider is responsible for generating correct URLs for that image transformation service. - -Nuxt Image can be configured to work with any external image transformation service. Here is a complete list of providers that are supported out-of-the-box and can be easily configured to use: +Nuxt Image can be configured to work with any external image transformation service. Here is a list of providers that are supported out-of-the-box and can be easily configured to use: +- [`IPX`](/providers/ipx) (default) - [`Cloudinary`](/providers/cloudinary) - [`Fastly`](/providers/fastly) - [`Imgix`](/providers/imgix) -- [`IPX`](/providers/ipx) (selfhosted) - [`Prismic`](/providers/prismic) - [`Sanity`](/providers/sanity) - [`Twicpics`](/providers/twicpics) - [`Storyblok`](/providers/storyblok) If you are looking for a specific provider outside of this list, you can [create your own provider](/advanced/custom-provider). + +Nuxt Image will automatically optimize `` or `` sources and accepts all [options](/api/options/) for specified target, except for modifiers that are specific to other providers. + +## Default Provider + +The default provider for Nuxt Image is [ipx provider](/providers/ipx) or [static images](/getting-started/static) (for `target: static`). Either option can be used without any configuration. + +[Learn more about Nuxt deployment targets](https://nuxtjs.org/docs/2.x/features/deployment-targets) + diff --git a/docs/pages/en/1.getting-started/3.static.md b/docs/pages/en/1.getting-started/3.static.md new file mode 100644 index 000000000..19902acc7 --- /dev/null +++ b/docs/pages/en/1.getting-started/3.static.md @@ -0,0 +1,10 @@ +--- +title: Static images +description: 'Optimizing images for static websites' +--- + +If you are building a static site, Nuxt Image will optimize and save your images locally when your site is generated - and deploy them alongside your generated pages. + +:::alert{type="info"} +Even if you are using another provider, you can opt-in to this generate behaviour for a particular image by setting `provider="static"` directly. (See [component documentation](/components/nuxt-img) for more information.) +::: diff --git a/docs/pages/en/4.providers/ipx.md b/docs/pages/en/4.providers/ipx.md index 76db77724..3b4abff80 100644 --- a/docs/pages/en/4.providers/ipx.md +++ b/docs/pages/en/4.providers/ipx.md @@ -1,23 +1,68 @@ --- -title: IPX Provider (static) -description: 'Nuxt Image internally use IPX as static provider.' +title: IPX Provider +description: 'Self hosted image provider' navigation: - title: IPX (static) + title: IPX --- -When no provider is specified globally, the default provider is `static` which uses [IPX](https://github.com/nuxt-contrib/ipx). -IPX is an opensource and self-hosted image optimizer based on [Sharp](https://github.com/lovell/sharp) developed by the Nuxt team. +Nuxt Image comes with a preconfigured instance of [ipx](/providers/ipx) to provide image transformations based on [sharp](https://github.com/lovell/sharp). +IPX is an open source, self-hosted image optimizer based on [sharp](https://github.com/lovell/sharp). -The image module internally uses IPX for static image optimization but you can also self-host it as a runtime optimizer by enabling the provider. +## Self-hosting `ipx` in production -### Static assets +### Using CDN -It's common if you are using a third-party provider that you may want to also include some images -that are stored locally within your repo. +This approach is recommended if you are planning to use images in a high load production and using other providers is not suitable. -```vue - +### Add `ipx` dependency + +You'll need to ensure that `ipx` is in your production dependencies. + + + + +```bash +yarn add ipx +``` + + + + +```bash +npm install ipx ``` -This will load the image in as `/static/logo.png` and apply the IPX optimizations if applicable. + + + +### Add `serverMiddleware` handler + +Finally, just add `@nuxt/image` to your `modules` (instead of `buildModules`) in `nuxt.config`. This will ensure that the `/_ipx` endpoint continues to work at runtime. + + +### Programmatic middidleware + +If you have an advanced use case, you may instead add the following code to your `nuxt.config` (or create a custom server middleware file directly that handles the `/_ipx` endpoint): + +```js [nuxt.config.js] +import path from 'path' +import { createIPX, createIPXMiddleware } from 'ipx' + +const ipx = createIPX({ + dir: path.join(__dirname, 'static'), + // https://image.nuxtjs.org/api/options#domains + domains: [], + // Any options you need to pass to sharp + sharp: {} +}) + +export default { + serverMiddleware: [ + { + path: '/_ipx', + handler: createIPXMiddleware(ipx) + } + ] +} +``` diff --git a/package.json b/package.json index 26d090bb4..f836a4da3 100755 --- a/package.json +++ b/package.json @@ -24,7 +24,6 @@ "test:unit": "jest test/unit --forceExit" }, "dependencies": { - "consola": "^2.15.3", "defu": "^5.0.0", "fs-extra": "^10.0.0", "hasha": "^5.2.2", @@ -34,6 +33,7 @@ "lru-cache": "^6.0.0", "node-fetch": "^2.6.1", "p-limit": "^3.1.0", + "rc9": "^1.2.0", "requrl": "^3.0.2", "ufo": "^0.7.5", "upath": "^2.0.1" @@ -51,6 +51,7 @@ "@types/jest": "latest", "@types/lru-cache": "latest", "@types/node-fetch": "latest", + "@types/semver": "^7.3.5", "@vue/test-utils": "latest", "babel-core": "^7.0.0-bridge.0", "babel-eslint": "latest", diff --git a/playground/nuxt.config.ts b/playground/nuxt.config.ts index c94e7353c..2b5901e29 100644 --- a/playground/nuxt.config.ts +++ b/playground/nuxt.config.ts @@ -9,10 +9,8 @@ export default { { name: 'viewport', content: 'width=device-width, initial-scale=1' } ] }, - modules: [ - '../src/module.ts' - ], buildModules: [ + '../src/module.ts', '@nuxt/typescript-build' ], image: { diff --git a/src/ipx.ts b/src/ipx.ts index f7a643637..0e62851fb 100644 --- a/src/ipx.ts +++ b/src/ipx.ts @@ -1,7 +1,57 @@ -import type { IPXOptions } from 'ipx' +import { relative, resolve } from 'upath' +import { update as updaterc } from 'rc9' +import { mkdirp, readFile, writeFile } from 'fs-extra' +import { lt } from 'semver' -export function createIPXMiddleware (ipxOptions: IPXOptions) { - const { createIPX, createIPXMiddleware } = require('ipx') as typeof import('ipx') - const ipx = createIPX(ipxOptions) - return createIPXMiddleware(ipx) +import type { ProviderSetup } from './types' + +export const ipxSetup: ProviderSetup = async (_providerOptions, moduleOptions, nuxt) => { + const isStatic = nuxt.options.target === 'static' + const runtimeDir = resolve(__dirname, 'runtime') + const ipxOptions = { + dir: resolve(nuxt.options.rootDir, moduleOptions.dir), + domains: moduleOptions.domains, + sharp: moduleOptions.sharp + } + + // Add IPX middleware unless nuxtrc or user added a custom middleware + const hasUserProvidedIPX = !!nuxt.options.serverMiddleware + .find((mw: { path: string }) => mw.path && mw.path.startsWith('/_ipx')) + + if (!hasUserProvidedIPX) { + const { createIPX, createIPXMiddleware } = await import('ipx') + const ipx = createIPX(ipxOptions) + nuxt.options.serverMiddleware.push({ + path: '/_ipx', + handle: createIPXMiddleware(ipx) + }) + } + + // Warn if unhandled /_ipx endpoint only if not using `modules` + const installedInModules = nuxt.options.modules.some( + (mod: string | (() => any)) => typeof mod === 'string' && mod.includes('@nuxt/image') + ) + + if (!isStatic && !hasUserProvidedIPX && !installedInModules && lt(nuxt.constructor.version, '2.16.0')) { + // eslint-disable-next-line no-console + console.warn('[@nuxt/image] If you would like to use the `ipx` provider at runtime.\nMake sure to follow the instructions at https://image.nuxtjs.org/providers/ipx .') + } + + if (nuxt.options.dev || hasUserProvidedIPX) { + return + } + + // In production, add IPX module to nuxtrc (used in Nuxt 2.16+) + nuxt.hook('build:done', async () => { + const handler = await readFile(resolve(runtimeDir, 'ipx.js'), 'utf-8') + const distDir = resolve(nuxt.options.buildDir, 'dist') + const apiDir = resolve(distDir, 'api') + const apiFile = resolve(apiDir, 'ipx.js') + const relativeApiFile = '~~/' + relative(nuxt.options.rootDir, apiFile) + + await mkdirp(apiDir) + await writeFile(apiFile, handler.replace(/.__IPX_OPTIONS__./, JSON.stringify(ipxOptions))) + + updaterc({ serverMiddleware: [{ path: '/_ipx', handler: relativeApiFile }] }, { dir: distDir, name: 'nuxtrc' }) + }) } diff --git a/src/module.ts b/src/module.ts index f38d48aed..57003b522 100644 --- a/src/module.ts +++ b/src/module.ts @@ -1,16 +1,14 @@ import { resolve } from 'upath' - import defu from 'defu' +import type { Module } from '@nuxt/types' import { setupStaticGeneration } from './generate' -import { createIPXMiddleware } from './ipx' import { resolveProviders, detectProvider } from './provider' import { pick, pkg } from './utils' import type { ModuleOptions, CreateImageOptions } from './types' -import type { Module } from '@nuxt/types' const imageModule: Module = async function imageModule (moduleOptions) { - const { nuxt, addPlugin, addServerMiddleware } = this + const { nuxt, addPlugin } = this const defaults: ModuleOptions = { staticFilename: '[publicPath]/image/[hash][ext]', @@ -36,8 +34,7 @@ const imageModule: Module = async function imageModule (moduleOpt const options: ModuleOptions = defu(moduleOptions, nuxt.options.image, defaults) - options.provider = detectProvider(options.provider) - + options.provider = detectProvider(options.provider, nuxt.options.target === 'static') options[options.provider] = options[options.provider] || {} const imageOptions: Omit = pick(options, [ @@ -73,16 +70,7 @@ const imageModule: Module = async function imageModule (moduleOpt } }) - addServerMiddleware({ - path: '/_ipx', - handle: createIPXMiddleware({ - dir: options.dir, - domains: options.domains, - sharp: options.sharp - }) - }) - - // transform asset urls that pass to `src` attribute on image components + // Transform asset urls that pass to `src` attribute on image components nuxt.options.build.loaders = defu({ vue: { transformAssetUrls: { 'nuxt-img': 'src', 'nuxt-picture': 'src', NuxtPicture: 'src', NuxtImg: 'src' } } }, nuxt.options.build.loaders || {}) @@ -102,6 +90,6 @@ const imageModule: Module = async function imageModule (moduleOpt }) } - ; (imageModule as any).meta = pkg +; (imageModule as any).meta = pkg export default imageModule diff --git a/src/provider.ts b/src/provider.ts index c010f6018..7fbeb0617 100644 --- a/src/provider.ts +++ b/src/provider.ts @@ -3,6 +3,7 @@ import { writeJson, mkdirp } from 'fs-extra' import { parseURL } from 'ufo' import { hash } from './utils' import type { ModuleOptions, InputProvider, ImageModuleProvider, ProviderSetup } from './types' +import { ipxSetup } from './ipx' const BuiltInProviders = [ 'cloudinary', @@ -19,6 +20,10 @@ const BuiltInProviders = [ ] export const providerSetup: Record = { + // IPX + ipx: ipxSetup, + static: ipxSetup, + // https://vercel.com/docs/more/adding-your-framework#images async vercel (_providerOptions, moduleOptions, nuxt) { const imagesConfig = resolve(nuxt.options.rootDir, '.vercel_build_output/config/images.json') @@ -74,7 +79,7 @@ export function resolveProvider (nuxt: any, key: string, input: InputProvider): } } -export function detectProvider (userInput?: string) { +export function detectProvider (userInput?: string, isStatic: boolean = false) { if (process.env.NUXT_IMAGE_PROVIDER) { return process.env.NUXT_IMAGE_PROVIDER } @@ -87,5 +92,5 @@ export function detectProvider (userInput?: string) { return 'vercel' } - return 'static' + return isStatic ? 'static' : 'ipx' } diff --git a/src/runtime/components/nuxt-img.vue b/src/runtime/components/nuxt-img.vue index c86184dda..eadb6c00c 100644 --- a/src/runtime/components/nuxt-img.vue +++ b/src/runtime/components/nuxt-img.vue @@ -7,9 +7,9 @@