Skip to content

Commit

Permalink
feat: add asImageSrc(), asImageWidthSrcSet(), `asImagePixelDensit…
Browse files Browse the repository at this point in the history
…ySrcSet()` (#38)

* feat: add `asImageSrc`, `asImageWidthSrcSet`, `asImagePixelDensitiesSrcSet`

* refactor: rename `asImagePixelDensitiesSrcSet` file

* test: image helpers

* chore(deps): update `imgix-url-builder`

* feat: add responsive-view-based image srcset builder

* feat: add default pixel densities

* refactor: `asImageWidthSrcSet()`

* feat: return src and srcset from srcset helpers

* refactor: image helpers

* docs: update examples

* chore(deps): update dependencies

* test: remove snapshots

* test: add snapshots

* chore: force ava to run in non-ci mode

* Revert "chore: force ava to run in non-ci mode"

This reverts commit a37fe39.

* chore: lock AVA to v4.0.0

* Revert "chore: lock AVA to v4.0.0"

This reverts commit b0612e0.

* chore: remove nyc from `unit` script

* Revert "chore: remove nyc from `unit` script"

This reverts commit a4c54fe.

* chore: move snapshots adjacent to test files

* chore: revert to AVA 3

* fix: use Next.js's default srcset widths

* docs: reformat `pixelDensities` default value description

* test: use non-default pixelDensities in test

* docs: update `asImagePixelDensitySrcSet()` example with custom pixel densities

* chore: fix package-lock.json

* fix: apply width to base srcset image in `asImageWidthSrcSet()` with responsive views

* fix: reduce number of default widths in `asImageWidthSrcSet()`
  • Loading branch information
angeloashmore authored Feb 1, 2022
1 parent 915af37 commit 2b4984a
Show file tree
Hide file tree
Showing 9 changed files with 450 additions and 2 deletions.
16 changes: 15 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@
"dependencies": {
"@prismicio/richtext": "^2.0.1",
"@prismicio/types": "^0.1.22",
"escape-html": "^1.0.3"
"escape-html": "^1.0.3",
"imgix-url-builder": "^0.0.2"
},
"devDependencies": {
"@prismicio/mock": "^0.0.6",
Expand Down
77 changes: 77 additions & 0 deletions src/asImagePixelDensitySrcSet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { ImageFieldImage } from "@prismicio/types";
import {
buildPixelDensitySrcSet,
BuildPixelDensitySrcSetParams,
buildURL,
} from "imgix-url-builder";

import { imageThumbnail as isImageThumbnailFilled } from "./isFilled";

/**
* The return type of `asImagePixelDensitySrcSet()`.
*/
type AsImagePixelDensitySrcSetReturnType<Field extends ImageFieldImage> =
Field extends ImageFieldImage<"empty">
? null
: {
/**
* The Image field's image URL with Imgix URL parameters (if given).
*/
src: string;

/**
* A pixel-densitye-based `srcset` attribute value for the Image field's
* image with Imgix URL parameters (if given).
*/
srcset: string;
};

/**
* Creates a pixel-density-based `srcset` from an Image field with optional
* image transformations (via Imgix URL parameters).
*
* If a `pixelDensities` parameter is not given, the following pixel densities
* will be used by default: 1, 2, 3.
*
* @example
*
* ```ts
* const srcset = asImagePixelDensitySrcSet(document.data.imageField, {
* pixelDensities: [1, 2],
* sat: -100,
* });
* // => {
* // src: 'https://images.prismic.io/repo/image.png?sat=-100',
* // srcset: 'https://images.prismic.io/repo/image.png?sat=-100&dpr=1 1x, ' +
* // 'https://images.prismic.io/repo/image.png?sat=-100&dpr=2 2x'
* // }
* ```
*
* @param field - Image field (or one of its responsive views) from which to get
* an image URL.
* @param params - An object of Imgix URL API parameters. The `pixelDensities`
* parameter defines the resulting `srcset` widths.
*
* @returns A `srcset` attribute value for the Image field with Imgix URL
* parameters (if given). If the Image field is empty, `null` is returned.
* @see Imgix URL parameters reference: https://docs.imgix.com/apis/rendering
*/
export const asImagePixelDensitySrcSet = <Field extends ImageFieldImage>(
field: Field,
params: Omit<BuildPixelDensitySrcSetParams, "pixelDensities"> &
Partial<Pick<BuildPixelDensitySrcSetParams, "pixelDensities">> = {},
): AsImagePixelDensitySrcSetReturnType<Field> => {
if (isImageThumbnailFilled(field)) {
const { pixelDensities = [1, 2, 3], ...imgixParams } = params;

return {
src: buildURL(field.url, imgixParams),
srcset: buildPixelDensitySrcSet(field.url, {
...imgixParams,
pixelDensities,
}),
} as AsImagePixelDensitySrcSetReturnType<Field>;
} else {
return null as AsImagePixelDensitySrcSetReturnType<Field>;
}
};
40 changes: 40 additions & 0 deletions src/asImageSrc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { ImageFieldImage } from "@prismicio/types";
import { buildURL, ImgixURLParams } from "imgix-url-builder";

import { imageThumbnail as isImageThumbnailFilled } from "./isFilled";

/**
* The return type of `asImageSrc()`.
*/
type AsImageSrcReturnType<Field extends ImageFieldImage> =
Field extends ImageFieldImage<"empty"> ? null : string;

/**
* Returns the URL of an Image field with optional image transformations (via
* Imgix URL parameters).
*
* @example
*
* ```ts
* const src = asImageSrc(document.data.imageField, { sat: -100 });
* // => https://images.prismic.io/repo/image.png?sat=-100
* ```
*
* @param field - Image field (or one of its responsive views) from which to get
* an image URL.
* @param params - An object of Imgix URL API parameters to transform the image.
*
* @returns The Image field's image URL with transformations applied (if given).
* If the Image field is empty, `null` is returned.
* @see Imgix URL parameters reference: https://docs.imgix.com/apis/rendering
*/
export const asImageSrc = <Field extends ImageFieldImage>(
field: Field,
params: ImgixURLParams = {},
): AsImageSrcReturnType<Field> => {
if (isImageThumbnailFilled(field)) {
return buildURL(field.url, params) as AsImageSrcReturnType<Field>;
} else {
return null as AsImageSrcReturnType<Field>;
}
};
106 changes: 106 additions & 0 deletions src/asImageWidthSrcSet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { ImageFieldImage } from "@prismicio/types";
import {
buildURL,
buildWidthSrcSet,
BuildWidthSrcSetParams,
} from "imgix-url-builder";

import { imageThumbnail as isImageThumbnailFilled } from "./isFilled";

/**
* The return type of `asImageWidthSrcSet()`.
*/
type AsImageWidthSrcSetReturnType<Field extends ImageFieldImage> =
Field extends ImageFieldImage<"empty">
? null
: {
/**
* The Image field's image URL with Imgix URL parameters (if given).
*/
src: string;

/**
* A width-based `srcset` attribute value for the Image field's image
* with Imgix URL parameters (if given).
*/
srcset: string;
};

/**
* Creates a width-based `srcset` from an Image field with optional image
* transformations (via Imgix URL parameters).
*
* If the Image field contains responsive views, each responsive view is used as
* a width in the resulting `srcset`.
*
* If a `widths` parameter is not given, the following widths will be used by
* default: 640, 750, 828, 1080, 1200, 1920, 2048, 3840.
*
* @example
*
* ```ts
* const srcset = asImageWidthSrcSet(document.data.imageField, {
* widths: [400, 800, 1600],
* sat: -100,
* });
* // => {
* // src: 'https://images.prismic.io/repo/image.png?sat=-100',
* // srcset: 'https://images.prismic.io/repo/image.png?sat=-100&width=400 400w, ' +
* // 'https://images.prismic.io/repo/image.png?sat=-100&width=800 800w,' +
* // 'https://images.prismic.io/repo/image.png?sat=-100&width=1600 1600w'
* // }
* ```
*
* @param field - Image field (or one of its responsive views) from which to get
* an image URL.
* @param params - An object of Imgix URL API parameters. The `widths` parameter
* defines the resulting `srcset` widths.
*
* @returns A `srcset` attribute value for the Image field with Imgix URL
* parameters (if given). If the Image field is empty, `null` is returned.
* @see Imgix URL parameters reference: https://docs.imgix.com/apis/rendering
*/
export const asImageWidthSrcSet = <Field extends ImageFieldImage>(
field: Field,
params: Omit<BuildWidthSrcSetParams, "widths"> &
Partial<Pick<BuildWidthSrcSetParams, "widths">> = {},
): AsImageWidthSrcSetReturnType<Field> => {
if (isImageThumbnailFilled(field)) {
const { widths = [640, 828, 1200, 2048, 3840], ...urlParams } = params;
const {
url,
dimensions,
alt: _alt,
copyright: _copyright,
...responsiveViews
} = field;

// The Prismic Rest API will always return thumbnail values if
// the base size is filled.
const responsiveViewObjects: ImageFieldImage<"filled">[] =
Object.values(responsiveViews);

return {
src: buildURL(url, urlParams),
srcset: responsiveViewObjects.length
? [
buildWidthSrcSet(url, {
...urlParams,
widths: [dimensions.width],
}),
...responsiveViewObjects.map((thumbnail) => {
return buildWidthSrcSet(thumbnail.url, {
...urlParams,
widths: [thumbnail.dimensions.width],
});
}),
].join(", ")
: buildWidthSrcSet(field.url, {
...urlParams,
widths,
}),
} as AsImageWidthSrcSetReturnType<Field>;
} else {
return null as AsImageWidthSrcSetReturnType<Field>;
}
};
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ export { asDate } from "./asDate";
export { asLink } from "./asLink";
export { asText } from "./asText";
export { asHTML } from "./asHTML";
export { asImageSrc } from "./asImageSrc";
export { asImageWidthSrcSet } from "./asImageWidthSrcSet";
export { asImagePixelDensitySrcSet } from "./asImagePixelDensitySrcSet";
export * as isFilled from "./isFilled";

export { documentToLinkField } from "./documentToLinkField";
Expand Down
71 changes: 71 additions & 0 deletions test/asImagePixelDensitySrcSet.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { ImageField } from "@prismicio/types";
import test from "ava";

import { asImagePixelDensitySrcSet } from "../src";

test("returns an image field pixel-density-based srcset with [1, 2, 3] pxiel densities by default", (t) => {
const field: ImageField = {
url: "https://images.prismic.io/qwerty/image.png?auto=compress%2Cformat",
alt: null,
copyright: null,
dimensions: { width: 400, height: 300 },
};

t.deepEqual(asImagePixelDensitySrcSet(field), {
src: field.url,
srcset:
`${field.url}&dpr=1 1x, ` +
`${field.url}&dpr=2 2x, ` +
`${field.url}&dpr=3 3x`,
});
});

test("supports custom pixel densities", (t) => {
const field: ImageField = {
url: "https://images.prismic.io/qwerty/image.png?auto=compress%2Cformat",
alt: null,
copyright: null,
dimensions: { width: 400, height: 300 },
};

t.deepEqual(
asImagePixelDensitySrcSet(field, {
pixelDensities: [2, 4, 6],
}),
{
src: field.url,
srcset:
`${field.url}&dpr=2 2x, ` +
`${field.url}&dpr=4 4x, ` +
`${field.url}&dpr=6 6x`,
},
);
});

test("applies given Imgix URL parameters", (t) => {
const field: ImageField = {
url: "https://images.prismic.io/qwerty/image.png?auto=compress%2Cformat",
alt: null,
copyright: null,
dimensions: { width: 400, height: 300 },
};

t.deepEqual(
asImagePixelDensitySrcSet(field, {
sat: 100,
}),
{
src: `${field.url}&sat=100`,
srcset:
`${field.url}&sat=100&dpr=1 1x, ` +
`${field.url}&sat=100&dpr=2 2x, ` +
`${field.url}&sat=100&dpr=3 3x`,
},
);
});

test("returns null when image field is empty", (t) => {
const field: ImageField<null, "empty"> = {};

t.is(asImagePixelDensitySrcSet(field), null);
});
32 changes: 32 additions & 0 deletions test/asImageSrc.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { ImageField } from "@prismicio/types";
import test from "ava";

import { asImageSrc } from "../src";

test("returns an image field URL", (t) => {
const field: ImageField = {
url: "https://images.prismic.io/qwerty/image.png?auto=compress%2Cformat",
alt: null,
copyright: null,
dimensions: { width: 400, height: 300 },
};

t.is(asImageSrc(field), field.url);
});

test("applies given Imgix URL parameters", (t) => {
const field: ImageField = {
url: "https://images.prismic.io/qwerty/image.png?auto=compress%2Cformat",
alt: null,
copyright: null,
dimensions: { width: 400, height: 300 },
};

t.is(asImageSrc(field, { sat: 100 }), `${field.url}&sat=100`);
});

test("returns null when image field is empty", (t) => {
const field: ImageField<null, "empty"> = {};

t.is(asImageSrc(field), null);
});
Loading

0 comments on commit 2b4984a

Please sign in to comment.