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(gatsby-source-contentful): Add gatsbyImageData resolver #28236

Merged
merged 19 commits into from
Jan 28, 2021
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
3 changes: 2 additions & 1 deletion packages/gatsby-plugin-sharp/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const { memoizedTraceSVG, notMemoizedtraceSVG } = require(`./trace-svg`)
const duotone = require(`./duotone`)
const { IMAGE_PROCESSING_JOB_NAME } = require(`./gatsby-worker`)
const { getDimensionsAndAspectRatio } = require(`./utils`)
// const { rgbToHex } = require(`./utils`)
const { rgbToHex } = require(`./utils`)

const imageSizeCache = new Map()

Expand Down Expand Up @@ -777,6 +777,7 @@ exports.fluid = fluid
exports.fixed = fixed
exports.getImageSize = getImageSize
exports.getImageSizeAsync = getImageSizeAsync
exports.rgbToHex = rgbToHex
exports.stats = stats
exports._unstable_createJob = createJob
exports._lazyJobsEnabled = lazyJobsEnabled
4 changes: 3 additions & 1 deletion packages/gatsby-source-contentful/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"contentful": "^7.14.12",
"fs-extra": "^9.0.1",
"gatsby-core-utils": "^1.10.0-next.0",
"gatsby-plugin-image": "^0.7.0-next.0",
"gatsby-plugin-utils": "^0.9.0-next.0",
"gatsby-source-filesystem": "^2.11.0-next.0",
"is-online": "^8.5.1",
Expand All @@ -40,7 +41,8 @@
"license": "MIT",
"peerDependencies": {
"gatsby": "^2.12.1",
"gatsby-plugin-sharp": "^2.6.14"
"gatsby-plugin-sharp": "^2.6.14",
"sharp": "^0.26.0"
},
"repository": {
"type": "git",
Expand Down
179 changes: 165 additions & 14 deletions packages/gatsby-source-contentful/src/extend-node-type.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// @ts-check
const fs = require(`fs`)
const path = require(`path`)
const crypto = require(`crypto`)

const _ = require(`lodash`)
ascorbic marked this conversation as resolved.
Show resolved Hide resolved
const axios = require(`axios`)
const {
GraphQLObjectType,
Expand All @@ -12,6 +14,11 @@ const {
GraphQLNonNull,
} = require(`gatsby/graphql`)
const qs = require(`qs`)
const { generateImageData } = require(`gatsby-plugin-image`)
const {
getGatsbyImageFieldConfig,
} = require(`gatsby-plugin-image/graphql-utils`)
const { stripIndent } = require(`common-tags`)

const cacheImage = require(`./cache-image`)

Expand Down Expand Up @@ -42,6 +49,9 @@ const inFlightBase64Cache = new Map()
// The images are based on urls with w=20 and should be relatively small (<2kb) but it does stick around in memory
const resolvedBase64Cache = new Map()

// Caches dominat colors per cached image file
const dominantColorCache = new Map()

const {
ImageFormatType,
ImageResizingBehavior,
Expand Down Expand Up @@ -157,7 +167,10 @@ const createUrl = (imgUrl, options = {}) => {
const urlArgs = {
w: options.width || undefined,
h: options.height || undefined,
fl: options.jpegProgressive ? `progressive` : undefined,
fl:
options.toFormat === `jpg` && options.jpegProgressive
? `progressive`
: undefined,
q: options.quality || undefined,
fm: options.toFormat || undefined,
fit: options.resizingBehavior || undefined,
Expand All @@ -170,6 +183,37 @@ const createUrl = (imgUrl, options = {}) => {
}
exports.createUrl = createUrl

const generateImageSource = (
axe312ger marked this conversation as resolved.
Show resolved Hide resolved
filename,
width,
height,
toFormat,
_fit, // We use resizingBehavior instead
{ jpegProgressive, quality, cropFocus, backgroundColor, resizingBehavior }
) => {
const src = createUrl(filename, {
width,
height,
toFormat,
resizingBehavior,
background: backgroundColor?.replace(`#`, `rgb:`),
quality,
jpegProgressive,
cropFocus,
})
return { width, height, format: toFormat, src }
}

exports.generateImageSource = generateImageSource

const fitMap = new Map([
[`pad`, `contain`],
[`fill`, `cover`],
[`scale`, `fill`],
[`crop`, `cover`],
[`thumb`, `cover`],
])

const resolveFixed = (image, options) => {
if (!isImage(image)) return null

Expand Down Expand Up @@ -223,8 +267,11 @@ const resolveFixed = (image, options) => {
)
})

// Sort sizes for prettiness.
const sortedSizes = _.sortBy(filteredSizes)

// Create the srcSet.
const srcSet = filteredSizes
const srcSet = sortedSizes
.map((size, i) => {
let resolution
switch (i) {
Expand Down Expand Up @@ -328,17 +375,19 @@ const resolveFluid = (image, options) => {

// Add the original image (if it isn't already in there) to ensure the largest image possible
// is available for small images.
const pwidth = parseInt(width, 10)
if (
!filteredSizes.includes(pwidth) &&
pwidth < CONTENTFUL_IMAGE_MAX_SIZE &&
Math.round(pwidth / desiredAspectRatio) < CONTENTFUL_IMAGE_MAX_SIZE
!filteredSizes.includes(parseInt(width)) &&
ascorbic marked this conversation as resolved.
Show resolved Hide resolved
parseInt(width) < CONTENTFUL_IMAGE_MAX_SIZE &&
Math.round(width / desiredAspectRatio) < CONTENTFUL_IMAGE_MAX_SIZE
) {
filteredSizes.push(pwidth)
filteredSizes.push(width)
}

// Sort sizes for prettiness.
const sortedSizes = _.sortBy(filteredSizes)

// Create the srcSet.
const srcSet = filteredSizes
const srcSet = sortedSizes
.map(width => {
const h = Math.round(width / desiredAspectRatio)
return `${createUrl(image.file.url, {
Expand Down Expand Up @@ -423,7 +472,7 @@ const fixedNodeType = ({ name, getTracedSVG }) => {
srcSet: { type: new GraphQLNonNull(GraphQLString) },
srcWebp: {
type: GraphQLString,
resolve({ image, options, context }) {
resolve({ image, options }) {
if (
image?.file?.contentType === `image/webp` ||
options.toFormat === `webp`
Expand All @@ -440,7 +489,7 @@ const fixedNodeType = ({ name, getTracedSVG }) => {
},
srcSetWebp: {
type: GraphQLString,
resolve({ image, options, context }) {
resolve({ image, options }) {
if (
image?.file?.contentType === `image/webp` ||
options.toFormat === `webp`
Expand Down Expand Up @@ -516,7 +565,7 @@ const fluidNodeType = ({ name, getTracedSVG }) => {
srcSet: { type: new GraphQLNonNull(GraphQLString) },
srcWebp: {
type: GraphQLString,
resolve({ image, options, context }) {
resolve({ image, options }) {
if (
image?.file?.contentType === `image/webp` ||
options.toFormat === `webp`
Expand All @@ -533,7 +582,7 @@ const fluidNodeType = ({ name, getTracedSVG }) => {
},
srcSetWebp: {
type: GraphQLString,
resolve({ image, options, context }) {
resolve({ image, options }) {
if (
image?.file?.contentType === `image/webp` ||
options.toFormat === `webp`
Expand Down Expand Up @@ -595,7 +644,9 @@ const fluidNodeType = ({ name, getTracedSVG }) => {
}
}

exports.extendNodeType = ({ type, store, cache, getNodesByType }) => {
let warnedForBeta = false

exports.extendNodeType = ({ type, store, reporter }) => {
if (type.name !== `ContentfulAsset`) {
return {}
}
Expand Down Expand Up @@ -627,6 +678,74 @@ exports.extendNodeType = ({ type, store, cache, getNodesByType }) => {
})
}

const getDominantColor = async ({ image, options }) => {
try {
const { rgbToHex } = require(`gatsby-plugin-sharp`)
const sharp = require(`gatsby-plugin-sharp/safe-sharp`)

const absolutePath = await cacheImage(store, image, options)

const pipeline = sharp(absolutePath)
const { dominant } = await pipeline.stats()

// Fallback in case sharp doesn't support dominant
const dominantColor = dominant
? rgbToHex(dominant.r, dominant.g, dominant.b)
: `rgba(0,0,0,0.5)`

dominantColorCache.set(absolutePath, dominantColor)

return dominantColor
} catch (e) {
console.error(
`[gatsby-source-contentful] Please install gatsby-plugin-sharp`
)
return `rgba(0,0,0,0.5)`
}
ascorbic marked this conversation as resolved.
Show resolved Hide resolved
}

const resolveGatsbyImageData = async (image, options) => {
const { baseUrl, ...sourceMetadata } = getBasicImageProps(image, options)

const imageProps = generateImageData({
...options,
pluginName: `gatsby-source-contentful`,
sourceMetadata,
filename: baseUrl,
generateImageSource,
fit: fitMap.get(options.resizingBehavior),
options,
})

let placeholderDataURI = null

if (options.placeholder === `dominantColor`) {
imageProps.backgroundColor = await getDominantColor({
image,
options,
})
}

if (options.placeholder === `blurred`) {
placeholderDataURI = await getBase64Image({
baseUrl,
})
}

if (options.placeholder === `tracedSVG`) {
placeholderDataURI = await getTracedSVG({
image,
options,
})
}

if (placeholderDataURI) {
imageProps.placeholder = { fallback: placeholderDataURI }
}

return imageProps
}

// TODO: Remove resolutionsNode and sizesNode for Gatsby v3
const fixedNode = fixedNodeType({ name: `ContentfulFixed`, getTracedSVG })
const resolutionsNode = fixedNodeType({
Expand All @@ -639,11 +758,43 @@ exports.extendNodeType = ({ type, store, cache, getNodesByType }) => {
const sizesNode = fluidNodeType({ name: `ContentfulSizes`, getTracedSVG })
sizesNode.deprecationReason = `Sizes was deprecated in Gatsby v2. It's been renamed to "fluid" https://example.com/write-docs-and-fix-this-example-link`
axe312ger marked this conversation as resolved.
Show resolved Hide resolved

// gatsby-plugin-image
const getGatsbyImageData = () => {
if (!warnedForBeta) {
reporter.warn(
stripIndent`
Thank you for trying the beta version of the \`gatsbyImageData\` API. Please provide feedback and report any issues at: https://github.com/gatsbyjs/gatsby/discussions/27950`
)
warnedForBeta = true
}

return getGatsbyImageFieldConfig(resolveGatsbyImageData, {
jpegProgressive: {
type: GraphQLBoolean,
defaultValue: true,
},
resizingBehavior: {
type: ImageResizingBehavior,
},
cropFocus: {
type: ImageCropFocusType,
},
quality: {
type: GraphQLInt,
defaultValue: 50,
},
backgroundColor: {
type: GraphQLString,
},
})
}

return {
fixed: fixedNode,
resolutions: resolutionsNode,
fluid: fluidNode,
sizes: sizesNode,
gatsbyImageData: getGatsbyImageData(),
resize: {
type: new GraphQLObjectType({
name: `ContentfulResize`,
Expand Down Expand Up @@ -693,7 +844,7 @@ exports.extendNodeType = ({ type, store, cache, getNodesByType }) => {
defaultValue: null,
},
},
resolve(image, options, context) {
resolve(image, options) {
return resolveResize(image, options)
},
},
Expand Down