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: add asImageSrc, asImageWidthSrcSet, asImagePixelDensitySrcSet #38

Merged
merged 33 commits into from
Feb 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
76fcf4c
feat: add `asImageSrc`, `asImageWidthSrcSet`, `asImagePixelDensitiesS…
angeloashmore Jan 22, 2022
6e6e0c2
refactor: rename `asImagePixelDensitiesSrcSet` file
angeloashmore Jan 22, 2022
8f333ba
test: image helpers
angeloashmore Jan 22, 2022
e699b79
chore(deps): update `imgix-url-builder`
angeloashmore Jan 22, 2022
4cb984f
feat: add responsive-view-based image srcset builder
angeloashmore Jan 26, 2022
8fdd824
Merge branch 'master' into aa/image-helpers
angeloashmore Jan 26, 2022
8f59e98
Merge branch 'master' into aa/image-helpers
angeloashmore Jan 26, 2022
a30ee9a
feat: add default pixel densities
angeloashmore Jan 26, 2022
7a182d1
refactor: `asImageWidthSrcSet()`
angeloashmore Jan 26, 2022
7d78895
feat: return src and srcset from srcset helpers
angeloashmore Jan 27, 2022
8aa3029
refactor: image helpers
angeloashmore Jan 27, 2022
6b6dc0e
docs: update examples
angeloashmore Jan 27, 2022
eed57bf
Merge branch 'master' into aa/image-helpers
angeloashmore Jan 28, 2022
4cc637c
chore(deps): update dependencies
angeloashmore Jan 29, 2022
e23e31e
test: remove snapshots
angeloashmore Jan 29, 2022
f827375
test: add snapshots
angeloashmore Jan 29, 2022
a37fe39
chore: force ava to run in non-ci mode
angeloashmore Jan 29, 2022
f4080e8
Revert "chore: force ava to run in non-ci mode"
angeloashmore Jan 29, 2022
b0612e0
chore: lock AVA to v4.0.0
angeloashmore Jan 29, 2022
a24b525
Revert "chore: lock AVA to v4.0.0"
angeloashmore Jan 29, 2022
a4c54fe
chore: remove nyc from `unit` script
angeloashmore Jan 29, 2022
d7a7b84
Revert "chore: remove nyc from `unit` script"
angeloashmore Jan 29, 2022
2706938
chore: move snapshots adjacent to test files
angeloashmore Jan 29, 2022
d10995b
chore: revert to AVA 3
angeloashmore Jan 29, 2022
4f272c9
Merge branch 'master' into aa/image-helpers
angeloashmore Jan 29, 2022
a36efc1
fix: use Next.js's default srcset widths
angeloashmore Jan 29, 2022
8cb2dde
docs: reformat `pixelDensities` default value description
angeloashmore Jan 29, 2022
61b4e9f
test: use non-default pixelDensities in test
angeloashmore Jan 29, 2022
79ed950
docs: update `asImagePixelDensitySrcSet()` example with custom pixel …
angeloashmore Jan 29, 2022
a0adc62
Merge branch 'master' into aa/image-helpers
angeloashmore Jan 29, 2022
c685252
chore: fix package-lock.json
angeloashmore Jan 29, 2022
80556b0
fix: apply width to base srcset image in `asImageWidthSrcSet()` with …
angeloashmore Jan 29, 2022
3b66b0a
fix: reduce number of default widths in `asImageWidthSrcSet()`
angeloashmore Feb 1, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Side note but we should at some point update the other helpers to use those isFilled() functions too, great move!

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;
};
angeloashmore marked this conversation as resolved.
Show resolved Hide resolved

/**
* 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);
angeloashmore marked this conversation as resolved.
Show resolved Hide resolved

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