gatsby-source-prismic
Version:
Gatsby source plugin for building websites using Prismic as a data source
171 lines (162 loc) • 5.14 kB
text/typescript
import * as gqlc from "graphql-compose";
import * as imgixGatsby from "@imgix/gatsby/dist/pluginHelpers";
import * as prismicT from "@prismicio/types";
import * as RTE from "fp-ts/ReaderTaskEither";
import * as O from "fp-ts/Option";
import * as S from "fp-ts/Semigroup";
import * as A from "fp-ts/Array";
import * as R from "fp-ts/Record";
import { constNull, pipe } from "fp-ts/function";
import { sanitizeImageURL } from "../lib/sanitizeImageURL";
import { stripURLQueryParameters } from "../lib/stripURLParameters";
import { Dependencies } from "../types";
/**
* Returns the URL of an image from the value of an Image field.
*
* @param source - Image field data.
*
* @returns The URL of the image if an image is provided, `null` otherwise.
*/
const resolveUrl = (source: prismicT.ImageField): string | null =>
source.url
? sanitizeImageURL(stripURLQueryParameters(source.url))
: source.url ?? null;
/**
* Returns the width of an image from the value of an Image field.
*
* @param source - Image field data.
*
* @returns The width of the image if an image is provided, `undefined` otherwise.
*/
const resolveWidth = (source: prismicT.ImageField): number | undefined =>
source.dimensions?.width;
/**
* Returns the height of an image from the value of an Image field.
*
* @param source - Image field data.
*
* @returns The height of the image if an image is provided, `undefined` otherwise.
*/
const resolveHeight = (source: prismicT.ImageField): number | undefined =>
source.dimensions?.height;
/**
* The minimum required GraphQL argument properties for an `@imgix/gatsby` field.
*/
interface ImgixGatsbyFieldArgsLike {
imgixParams: Record<string, string | number | boolean>;
}
/**
* Modifies an `@imgix/gatsby` GraphQL field config to retain existing Imgix
* parameters set on the source URL.
*
* This is needed if the source URL contains parameters like `rect` (crops an
* image). Without this config enhancer, the `rect` parameter would be removed.
*
* @param fieldConfig - GraphQL field config object to be enhanced.
*
* @returns `fieldConfig` with the ability to retain existing Imgix parameters
* on the source URL.
*/
const withExistingURLImgixParameters = <
TContext,
TArgs extends ImgixGatsbyFieldArgsLike,
>(
fieldConfig: gqlc.ObjectTypeComposerFieldConfigAsObjectDefinition<
prismicT.ImageField,
TContext,
TArgs
>,
): typeof fieldConfig => ({
...fieldConfig,
resolve: (source, args, ...rest) =>
pipe(
O.Do,
O.bind("url", () =>
O.fromNullable(source.url ? new URL(source.url) : null),
),
O.bind("existingImgixParams", (scope) =>
pipe(
[...scope.url.searchParams.entries()],
R.fromFoldable(S.last<string>(), A.Foldable),
O.of,
),
),
O.map((scope) =>
fieldConfig.resolve?.(
source,
{
...args,
imgixParams: {
...scope.existingImgixParams,
...args.imgixParams,
},
},
...rest,
),
),
O.getOrElseW(constNull),
),
});
/**
* Builds a GraphQL field configuration object to be used as part of another
* Image field GraphQL configuration object. For example, this base
* configuration object could be added to a config for the thumbnails of an Image field.
*/
export const buildImageBaseFieldConfigMap: RTE.ReaderTaskEither<
Dependencies,
never,
gqlc.ObjectTypeComposerFieldConfigMapDefinition<prismicT.ImageField, unknown>
> = pipe(
RTE.ask<Dependencies>(),
RTE.bind("imgixTypes", (scope) =>
RTE.right(
imgixGatsby.createImgixGatsbyTypes({
// IMPORTANT: These options need to be kept in sync with the options at
// packages/gatsby-source-prismic/src/builders/buildImgixImageTypes.ts
cache: scope.cache,
resolveUrl,
resolveWidth,
resolveHeight,
defaultParams: scope.pluginOptions.imageImgixParams,
namespace: "Imgix",
}),
),
),
RTE.bind("urlField", (scope) =>
RTE.right(withExistingURLImgixParameters(scope.imgixTypes.fields.url)),
),
RTE.bind("fixedField", (scope) =>
RTE.right(withExistingURLImgixParameters(scope.imgixTypes.fields.fixed)),
),
RTE.bind("fluidField", (scope) =>
RTE.right(withExistingURLImgixParameters(scope.imgixTypes.fields.fluid)),
),
RTE.bind("gatsbyImageDataField", (scope) =>
pipe(
RTE.right(
withExistingURLImgixParameters(scope.imgixTypes.fields.gatsbyImageData),
),
// This field is 'JSON!' by default (i.e. non-nullable). If an image is
// not set in Prismic, however, this field throws a GraphQL error saying a
// non-nullable field was returned a null value. This should not happen
// since the field is nested in a nullable object type, but it happens
// anyway.
//
// We're making the field nullable manually here.
RTE.chainFirst((field) => RTE.fromIO(() => (field.type = "JSON"))),
),
),
RTE.map((scope) => ({
alt: "String",
copyright: "String",
dimensions: scope.globalNodeHelpers.createTypeName("ImageDimensionsType"),
url: scope.urlField,
fixed: scope.fixedField,
fluid: scope.fluidField,
gatsbyImageData: scope.gatsbyImageDataField,
localFile: {
type: "File",
extensions: { link: {} },
},
})),
);