Skip to content

Commit

Permalink
feat(gatsby-source-url): implement base64/LQIP image loading
Browse files Browse the repository at this point in the history
  • Loading branch information
frederickfogerty committed Aug 24, 2020
1 parent d2f1c32 commit 26cb571
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 29 deletions.
29 changes: 29 additions & 0 deletions packages/gatsby-source-url/src/api/fetchBase64Image.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { pipe } from 'fp-ts/lib/function';
import * as TE from 'fp-ts/lib/TaskEither';
import { GatsbyCache } from 'gatsby';
import { withCache } from '../cache';
import { fetch } from '../utils';

export const buildBase64URL = (contentType: string, base64: string): string =>
`data:${contentType};base64,${base64}`;

export const fetchImgixBase64Image = (cache: GatsbyCache) => (
url: string,
): TE.TaskEither<Error, string> =>
withCache(`gatsby-plugin-imgix-base64-url-${url}`, cache, () =>
pipe(
url,
fetch,
TE.chain((res) =>
pipe(
TE.rightTask<Error, Buffer>(() => res.buffer()),
TE.chain((buffer) => TE.right(buffer.toString('base64'))),
TE.chain((base64) =>
TE.right(
buildBase64URL(String(res.headers.get('content-type')), base64),
),
),
),
),
),
);
40 changes: 40 additions & 0 deletions packages/gatsby-source-url/src/createImgixBase64FieldConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { pipe } from 'fp-ts/lib/pipeable';
import * as T from 'fp-ts/lib/Task';
import * as TE from 'fp-ts/lib/TaskEither';
import { GatsbyCache } from 'gatsby';
import { GraphQLFieldConfig, GraphQLNonNull, GraphQLString } from 'graphql';
import { fetchImgixBase64Image } from './api/fetchBase64Image';
import {
ImgixSourceDataResolver,
taskEitherFromSourceDataResolver,
} from './utils';

interface CreateImgixBase64UrlFieldConfigArgsWithResolver<TSource> {
resolveUrl: ImgixSourceDataResolver<TSource, string>;
cache: GatsbyCache;
}
interface CreateImgixBase64UrlFieldConfigArgs<TSource> {
resolveUrl?: ImgixSourceDataResolver<TSource, string>;
cache: GatsbyCache;
}
export function createImgixBase64FieldConfig<TSource, TContext = unknown>({
resolveUrl,
cache,
}: CreateImgixBase64UrlFieldConfigArgsWithResolver<
TSource
>): GraphQLFieldConfig<TSource, TContext> {
return {
type: new GraphQLNonNull(GraphQLString),
resolve: (obj: TSource): Promise<string> =>
pipe(
obj,
taskEitherFromSourceDataResolver<TSource, string>(resolveUrl),
TE.chain(fetchImgixBase64Image(cache)),
TE.getOrElse(
(e): T.Task<string> => {
throw e;
},
),
)(),
};
}
4 changes: 2 additions & 2 deletions packages/gatsby-source-url/src/createImgixFluidFieldConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { ComposeFieldConfigAsObject } from 'graphql-compose';
import ImgixClient from 'imgix-core-js';
import { trace } from './common/log';
import {
gatsbySourceImgixFluidFieldType,
createGatsbySourceImgixFluidFieldType,
ImgixUrlParamsInputType,
} from './graphqlTypes';
import { buildFluidObject } from './objectBuilders';
Expand Down Expand Up @@ -48,7 +48,7 @@ export const createImgixFluidFieldConfig = <TSource, TContext>({
TContext,
ImgixFluidArgsResolved
> => ({
type: gatsbySourceImgixFluidFieldType,
type: createGatsbySourceImgixFluidFieldType(cache),
args: {
imgixParams: {
type: ImgixUrlParamsInputType,
Expand Down
2 changes: 1 addition & 1 deletion packages/gatsby-source-url/src/createRootImgixImageType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const createRootImgixImageType = (
fluid: createImgixFluidFieldConfig<IRootSource, unknown>({
imgixClient,
resolveUrl: R.prop('rawUrl'),
cache: cache,
cache,
}),
},
}),
Expand Down
42 changes: 26 additions & 16 deletions packages/gatsby-source-url/src/graphqlTypes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { camelCase } from 'camel-case';
import { GatsbyCache } from 'gatsby';
/**
* The GraphQL type of the fluid field.
* Corresponding TS type is FluidObject from gatsby-image.
*/
import { FluidObject } from 'gatsby-image';
import {
GraphQLBoolean,
GraphQLFloat,
Expand All @@ -10,6 +16,7 @@ import {
GraphQLString,
} from 'graphql';
import imgixUrlParameters from 'imgix-url-params/dist/parameters.json';
import { createImgixBase64FieldConfig } from './createImgixBase64FieldConfig';

export const ImgixUrlParamsInputType = new GraphQLInputObjectType({
name: 'GatsbySourceImgixParamsInput',
Expand Down Expand Up @@ -75,22 +82,25 @@ export const ImgixUrlParamsInputType = new GraphQLInputObjectType({
}, {} as GraphQLInputFieldConfigMap),
});

/**
* The GraphQL type of the fluid field.
* Corresponding TS type is FluidObject from gatsby-image.
*/
export const gatsbySourceImgixFluidFieldType = new GraphQLObjectType({
name: 'SourceImgixFluid',
fields: {
// base64: createImgixBase64UrlFieldConfig({ cache }),
src: { type: new GraphQLNonNull(GraphQLString) },
srcSet: { type: new GraphQLNonNull(GraphQLString) },
srcWebp: { type: new GraphQLNonNull(GraphQLString) },
srcSetWebp: { type: new GraphQLNonNull(GraphQLString) },
sizes: { type: new GraphQLNonNull(GraphQLString) },
aspectRatio: { type: new GraphQLNonNull(GraphQLFloat) },
},
});
const createBase64ConfigWithFluidResolver = (cache: GatsbyCache) =>
createImgixBase64FieldConfig<FluidObject>({
resolveUrl: (obj) => obj.base64,
cache,
});

export const createGatsbySourceImgixFluidFieldType = (cache: GatsbyCache) =>
new GraphQLObjectType({
name: 'SourceImgixFluid',
fields: {
base64: createBase64ConfigWithFluidResolver(cache),
src: { type: new GraphQLNonNull(GraphQLString) },
srcSet: { type: new GraphQLNonNull(GraphQLString) },
srcWebp: { type: new GraphQLNonNull(GraphQLString) },
srcSetWebp: { type: new GraphQLNonNull(GraphQLString) },
sizes: { type: new GraphQLNonNull(GraphQLString) },
aspectRatio: { type: new GraphQLNonNull(GraphQLFloat) },
},
});

export type IGatsbySourceImgixUrlField = string;
export const gatsbySourceImgixUrlFieldType = GraphQLString;
20 changes: 10 additions & 10 deletions packages/gatsby-source-url/src/objectBuilders.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { FluidObject } from 'gatsby-image';
import ImgixClient from 'imgix-core-js';
import * as R from 'ramda';
import { ImgixFluidArgsResolved } from './publicTypes';
import { ImgixFluidArgsResolved, ImgixUrlParams } from './publicTypes';
export type BuildImgixFluidArgs = {
client: ImgixClient;
url: string;
Expand All @@ -18,6 +18,8 @@ const parseAspectRatioFloatFromString = R.pipe<
number
>(R.split(':'), R.head, (v) => parseInt(v));

const DEFAULT_LQIP_PARAMS: ImgixUrlParams = { w: 20, blur: 15, q: 20 };

export const buildFluidObject = ({
client,
url,
Expand Down Expand Up @@ -46,14 +48,12 @@ export const buildFluidObject = ({
}),
};

// TODO
// const base64 = buildImgixLqipUrl(
// url,
// secureUrlToken,
// )({
// ...args.imgixParams,
// ...args.placeholderImgixParams,
// });
// This base64 url will be resolved by this resolver, and then be resolved again by the base64 resolver which is set on the field. See createImgixBase64FieldConfig
const base64 = client.buildURL(url, {
...DEFAULT_LQIP_PARAMS,
...args.imgixParams,
...args.placeholderImgixParams,
});

const srcImgixParams = {
...imgixParams,
Expand Down Expand Up @@ -88,7 +88,7 @@ export const buildFluidObject = ({
);

return {
// base64,
base64,
aspectRatio,
src,
srcWebp: srcWebp,
Expand Down
3 changes: 3 additions & 0 deletions packages/gatsby-source-url/src/privateTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { FluidObject } from 'gatsby-image';
type ImgixFluidObject = Omit<FluidObject, 'base64'> &
Required<Pick<FluidObject, 'base64'>>;

0 comments on commit 26cb571

Please sign in to comment.