Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(vercel): Use Sharp in dev instead of Squoosh by default #8445

Merged
merged 10 commits into from
Sep 13, 2023
5 changes: 5 additions & 0 deletions .changeset/cold-flies-clean.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/vercel': major
---

Use Sharp in dev mode instead of Squoosh by default
Princesseuh marked this conversation as resolved.
Show resolved Hide resolved
21 changes: 21 additions & 0 deletions packages/integrations/vercel/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,27 @@ import astroLogo from '../assets/logo.png';
/>
```

### useSquooshDev

**Type:** `boolean`<br>
**Available for:** Serverless, Static
**Added in:** `@astrojs/[email protected]`
Princesseuh marked this conversation as resolved.
Show resolved Hide resolved

When enabled, in development mode a Squoosh-based image service will be used in development instead of Sharp. This is useful if you cannot install Sharp's dependencies on your development machine.
Princesseuh marked this conversation as resolved.
Show resolved Hide resolved

```js
import { defineConfig } from 'astro/config';
import vercel from '@astrojs/vercel/serverless';

export default defineConfig({
output: 'server',
adapter: vercel({
imageService: true,
useSquooshDev: true,
}),
});
```

### includeFiles

**Type:** `string[]`<br>
Expand Down
1 change: 1 addition & 0 deletions packages/integrations/vercel/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"./analytics": "./dist/analytics.js",
"./build-image-service": "./dist/image/build-service.js",
"./dev-image-service": "./dist/image/dev-service.js",
"./squoosh-dev-service": "./dist/image/squoosh-dev-service.js",
"./package.json": "./package.json"
},
"typesVersions": {
Expand Down
5 changes: 3 additions & 2 deletions packages/integrations/vercel/src/image/build-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ const service: ExternalImageService = {
};
},
getURL(options) {
const fileSrc =
typeof options.src === 'string' ? options.src : removeLeadingForwardSlash(options.src.src);
const fileSrc = isESMImportedImage(options.src)
? removeLeadingForwardSlash(options.src.src)
: options.src;

const searchParams = new URLSearchParams();
searchParams.append('url', fileSrc);
Expand Down
43 changes: 8 additions & 35 deletions packages/integrations/vercel/src/image/dev-service.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import type { LocalImageService } from 'astro';
import squooshService from 'astro/assets/services/squoosh';
import { sharedValidateOptions } from './shared';
import sharpService from 'astro/assets/services/sharp';
import { baseDevService } from './shared-dev-service';

const service: LocalImageService = {
validateOptions: (options, serviceOptions) =>
sharedValidateOptions(options, serviceOptions.service.config, 'development'),
...baseDevService,
getHTMLAttributes(options, serviceOptions) {
const { inputtedWidth, ...props } = options;

Expand All @@ -13,45 +12,19 @@ const service: LocalImageService = {
props.width = inputtedWidth;
}

return squooshService.getHTMLAttributes
? squooshService.getHTMLAttributes(props, serviceOptions)
return sharpService.getHTMLAttributes
? sharpService.getHTMLAttributes(props, serviceOptions)
: {};
},
getURL(options) {
const fileSrc = typeof options.src === 'string' ? options.src : options.src.src;

const searchParams = new URLSearchParams();
searchParams.append('href', fileSrc);

options.width && searchParams.append('w', options.width.toString());
options.quality && searchParams.append('q', options.quality.toString());

return '/_image?' + searchParams;
},
parseURL(url) {
const params = url.searchParams;

if (!params.has('href')) {
return undefined;
}

const transform = {
src: params.get('href')!,
width: params.has('w') ? parseInt(params.get('w')!) : undefined,
quality: params.get('q'),
};

return transform;
},
transform(inputBuffer, transform, serviceOptions) {
// NOTE: Hardcoding webp here isn't accurate to how the Vercel Image Optimization API works, normally what we should
// do is setup a custom endpoint that sniff the user's accept-content header and serve the proper format based on the
// user's Vercel config. However, that's: a lot of work for: not much. The dev service is inaccurate to the prod service
// in many more ways, this is one of the less offending cases and is, imo, okay, erika - 2023-04-27
transform.format = 'webp';
transform.format = transform.src.endsWith('svg') ? 'svg' : 'webp';

// The base Squoosh service works the same way as the Vercel Image Optimization API, so it's a safe fallback in local
return squooshService.transform(inputBuffer, transform, serviceOptions);
// The base sharp service works the same way as the Vercel Image Optimization API, so it's a safe fallback in local
return sharpService.transform(inputBuffer, transform, serviceOptions);
},
};

Expand Down
33 changes: 33 additions & 0 deletions packages/integrations/vercel/src/image/shared-dev-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { LocalImageService } from 'astro';
import { sharedValidateOptions } from './shared';

export const baseDevService: Omit<LocalImageService, 'transform'> = {
validateOptions: (options, serviceOptions) =>
sharedValidateOptions(options, serviceOptions.service.config, 'development'),
getURL(options) {
const fileSrc = typeof options.src === 'string' ? options.src : options.src.src;

const searchParams = new URLSearchParams();
searchParams.append('href', fileSrc);

options.width && searchParams.append('w', options.width.toString());
options.quality && searchParams.append('q', options.quality.toString());

return '/_image?' + searchParams;
},
parseURL(url) {
const params = url.searchParams;

if (!params.has('href')) {
return undefined;
}

const transform = {
src: params.get('href')!,
width: params.has('w') ? parseInt(params.get('w')!) : undefined,
quality: params.get('q'),
};

return transform;
},
};
5 changes: 4 additions & 1 deletion packages/integrations/vercel/src/image/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export function getAstroImageConfig(
images: boolean | undefined,
imagesConfig: VercelImageConfig | undefined,
command: string,
useSquoosh: boolean | undefined,
astroImageConfig: AstroConfig['image']
) {
if (images) {
Expand All @@ -72,7 +73,9 @@ export function getAstroImageConfig(
service: {
entrypoint:
command === 'dev'
? '@astrojs/vercel/dev-image-service'
? useSquoosh
? '@astrojs/vercel/squoosh-dev-image-service'
: '@astrojs/vercel/dev-image-service'
: '@astrojs/vercel/build-image-service',
config: imagesConfig ? imagesConfig : getDefaultImageConfig(astroImageConfig),
},
Expand Down
31 changes: 31 additions & 0 deletions packages/integrations/vercel/src/image/squoosh-dev-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { LocalImageService } from 'astro';
import squooshService from 'astro/assets/services/squoosh';
import { baseDevService } from './shared-dev-service';

const service: LocalImageService = {
...baseDevService,
getHTMLAttributes(options, serviceOptions) {
const { inputtedWidth, ...props } = options;

// If `validateOptions` returned a different width than the one of the image, use it for attributes
if (inputtedWidth) {
props.width = inputtedWidth;
}

return squooshService.getHTMLAttributes
? squooshService.getHTMLAttributes(props, serviceOptions)
: {};
},
transform(inputBuffer, transform, serviceOptions) {
// NOTE: Hardcoding webp here isn't accurate to how the Vercel Image Optimization API works, normally what we should
// do is setup a custom endpoint that sniff the user's accept-content header and serve the proper format based on the
// user's Vercel config. However, that's: a lot of work for: not much. The dev service is inaccurate to the prod service
// in many more ways, this is one of the less offending cases and is, imo, okay, erika - 2023-04-27
transform.format = transform.src.endsWith('svg') ? 'svg' : 'webp';

// The base squoosh service works the same way as the Vercel Image Optimization API, so it's a safe fallback in local
return squooshService.transform(inputBuffer, transform, serviceOptions);
},
};

export default service;
4 changes: 3 additions & 1 deletion packages/integrations/vercel/src/serverless/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export interface VercelServerlessConfig {
analytics?: boolean;
imageService?: boolean;
imagesConfig?: VercelImageConfig;
useSquooshDev?: boolean;
Princesseuh marked this conversation as resolved.
Show resolved Hide resolved
edgeMiddleware?: boolean;
functionPerRoute?: boolean;
}
Expand All @@ -78,6 +79,7 @@ export default function vercelServerless({
analytics,
imageService,
imagesConfig,
useSquooshDev,
functionPerRoute = true,
edgeMiddleware = false,
}: VercelServerlessConfig = {}): AstroIntegration {
Expand Down Expand Up @@ -147,7 +149,7 @@ export default function vercelServerless({
external: ['@vercel/nft'],
},
},
...getAstroImageConfig(imageService, imagesConfig, command, config.image),
...getAstroImageConfig(imageService, imagesConfig, command, useSquooshDev, config.image),
});
},
'astro:config:done': ({ setAdapter, config, logger }) => {
Expand Down
4 changes: 3 additions & 1 deletion packages/integrations/vercel/src/static/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,14 @@ export interface VercelStaticConfig {
analytics?: boolean;
imageService?: boolean;
imagesConfig?: VercelImageConfig;
useSquooshDev?: boolean;
}

export default function vercelStatic({
analytics,
imageService,
imagesConfig,
useSquooshDev,
}: VercelStaticConfig = {}): AstroIntegration {
let _config: AstroConfig;

Expand All @@ -63,7 +65,7 @@ export default function vercelStatic({
vite: {
define: viteDefine,
},
...getAstroImageConfig(imageService, imagesConfig, command, config.image),
...getAstroImageConfig(imageService, imagesConfig, command, useSquooshDev, config.image),
});
},
'astro:config:done': ({ setAdapter, config }) => {
Expand Down
3 changes: 3 additions & 0 deletions packages/integrations/vercel/test/fixtures/image/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
"name": "@test/astro-vercel-image",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "astro dev"
},
"dependencies": {
"@astrojs/vercel": "workspace:*",
"astro": "workspace:*"
Expand Down
Loading