gatsby-transformer-cloudinary
Advanced tools
Comparing version 2.1.1 to 2.2.0
@@ -1,3 +0,12 @@ | ||
# Version Next | ||
# Version 2.2.0 | ||
Improvements: | ||
- Only throw an error on missing Cloudinary credentials if those credentials are actually needed to upload an image to Cloudinary. | ||
- base64 images are no longer generated unless a query requesting them is run. | ||
- defaultTracedSVG values are now passed along as tracedSVG values. | ||
- Improved base64 caching so that if a second request for the same base64 image is made before the first response is received, only one request is made. | ||
# Version 2.1.1 | ||
Additions: | ||
@@ -4,0 +13,0 @@ |
@@ -39,2 +39,3 @@ const stringify = require('fast-json-stable-stringify'); | ||
defaultBase64, | ||
defaultTracedSVG, | ||
}) => { | ||
@@ -72,2 +73,3 @@ let breakpoints = getDefaultBreakpoints(width); | ||
defaultBase64, | ||
defaultTracedSVG, | ||
@@ -74,0 +76,0 @@ // Add the required internal Gatsby node fields. |
@@ -164,2 +164,15 @@ const { createImageNode } = require('./create-image-node'); | ||
it('sets the defaultTracedSVG image', async () => { | ||
const options = getDefaultOptions(); | ||
getPluginOptions.mockReturnValue(options); | ||
const args = getDefaultArgs({ | ||
defaultTracedSVG: 'defaultTracedSVG', | ||
}); | ||
const actual = createImageNode(args); | ||
const expected = { defaultTracedSVG: 'defaultTracedSVG' }; | ||
expect(actual).toEqual(expect.objectContaining(expected)); | ||
}); | ||
it('creates a node ID', async () => { | ||
@@ -166,0 +179,0 @@ const options = getDefaultOptions(); |
@@ -13,2 +13,21 @@ import { graphql } from 'gatsby'; | ||
export const cloudinaryAssetFluidNoBase64 = graphql` | ||
fragment CloudinaryAssetFluid_noBase64 on CloudinaryAssetFluid { | ||
aspectRatio | ||
sizes | ||
src | ||
srcSet | ||
} | ||
`; | ||
export const cloudinaryAssetFluidTracedSVG = graphql` | ||
fragment CloudinaryAssetFluid_tracedSVG on CloudinaryAssetFluid { | ||
aspectRatio | ||
sizes | ||
src | ||
srcSet | ||
tracedSVG | ||
} | ||
`; | ||
export const cloudinaryAssetFluidLimitPresentationSize = graphql` | ||
@@ -30,1 +49,20 @@ fragment CloudinaryAssetFluidLimitPresentationSize on CloudinaryAssetFluid { | ||
`; | ||
export const cloudinaryAssetFixedNoBase64 = graphql` | ||
fragment CloudinaryAssetFixed_noBase64 on CloudinaryAssetFixed { | ||
height | ||
src | ||
srcSet | ||
width | ||
} | ||
`; | ||
export const cloudinaryAssetFixedTracedSVG = graphql` | ||
fragment CloudinaryAssetFixed_tracedSVG on CloudinaryAssetFixed { | ||
height | ||
src | ||
srcSet | ||
tracedSVG | ||
width | ||
} | ||
`; |
@@ -56,6 +56,7 @@ const fs = require('fs-extra'); | ||
aspectRatio: Float | ||
base64: String! | ||
base64: String | ||
height: Float | ||
src: String | ||
srcSet: String | ||
tracedSVG: String | ||
width: Float | ||
@@ -66,3 +67,3 @@ } | ||
aspectRatio: Float! | ||
base64: String! | ||
base64: String | ||
presentationHeight: Float | ||
@@ -73,2 +74,3 @@ presentationWidth: Float | ||
srcSet: String! | ||
tracedSVG: String | ||
} | ||
@@ -91,2 +93,3 @@ `); | ||
defaultBase64, | ||
defaultTracedSVG, | ||
}, | ||
@@ -102,4 +105,9 @@ { | ||
}, | ||
) => | ||
getFixedImageObject({ | ||
_context, | ||
info, | ||
) => { | ||
const fieldsToSelect = info.fieldNodes[0].selectionSet.selections.map( | ||
item => item.name.value, | ||
); | ||
return getFixedImageObject({ | ||
base64Transformations, | ||
@@ -110,2 +118,4 @@ base64Width, | ||
defaultBase64, | ||
fieldsToSelect, | ||
defaultTracedSVG, | ||
height, | ||
@@ -120,3 +130,4 @@ ignoreDefaultBase64, | ||
width, | ||
}), | ||
}); | ||
}, | ||
}, | ||
@@ -130,2 +141,3 @@ fluid: { | ||
defaultBase64, | ||
defaultTracedSVG, | ||
originalHeight, | ||
@@ -144,4 +156,9 @@ originalWidth, | ||
}, | ||
) => | ||
getFluidImageObject({ | ||
_context, | ||
info, | ||
) => { | ||
const fieldsToSelect = info.fieldNodes[0].selectionSet.selections.map( | ||
item => item.name.value, | ||
); | ||
return getFluidImageObject({ | ||
base64Transformations, | ||
@@ -153,2 +170,4 @@ base64Width, | ||
defaultBase64, | ||
fieldsToSelect, | ||
defaultTracedSVG, | ||
ignoreDefaultBase64, | ||
@@ -162,3 +181,4 @@ maxWidth, | ||
version, | ||
}), | ||
}); | ||
}, | ||
}, | ||
@@ -165,0 +185,0 @@ }, |
@@ -87,2 +87,3 @@ const flatMap = require('lodash/flatMap'); | ||
defaultBase64, | ||
defaultTracedSVG, | ||
originalHeight, | ||
@@ -114,2 +115,3 @@ originalWidth, | ||
defaultBase64, | ||
defaultTracedSVG, | ||
}); | ||
@@ -116,0 +118,0 @@ |
@@ -25,2 +25,3 @@ const { createAssetNodesFromData } = require('./create-asset-nodes-from-data'); | ||
defaultBase64: 'defaultBase64', | ||
defaultTracedSVG: 'defaultTracedSVG', | ||
}, | ||
@@ -103,2 +104,3 @@ }, | ||
defaultBase64: assetData.defaultBase64, | ||
defaultTracedSVG: assetData.defaultTracedSVG, | ||
}), | ||
@@ -105,0 +107,0 @@ ); |
@@ -19,2 +19,4 @@ const { getPluginOptions } = require('./options'); | ||
defaultBase64, | ||
fieldsToSelect, | ||
defaultTracedSVG, | ||
height, | ||
@@ -30,15 +32,2 @@ ignoreDefaultBase64 = false, | ||
}) => { | ||
const base64 = await getBase64({ | ||
base64Transformations, | ||
base64Width, | ||
chained, | ||
cloudName, | ||
defaultBase64, | ||
ignoreDefaultBase64, | ||
public_id, | ||
reporter, | ||
transformations, | ||
version, | ||
}); | ||
const src = getImageURL({ | ||
@@ -101,9 +90,26 @@ public_id, | ||
return { | ||
base64, | ||
const fixedImageObject = { | ||
height: Math.round(displayHeight), | ||
src, | ||
srcSet, | ||
tracedSVG: defaultTracedSVG, | ||
width: Math.round(displayWidth), | ||
}; | ||
if (fieldsToSelect.includes('base64')) { | ||
fixedImageObject.base64 = await getBase64({ | ||
base64Transformations, | ||
base64Width, | ||
chained, | ||
cloudName, | ||
defaultBase64, | ||
ignoreDefaultBase64, | ||
public_id, | ||
reporter, | ||
transformations, | ||
version, | ||
}); | ||
} | ||
return fixedImageObject; | ||
}; | ||
@@ -118,2 +124,4 @@ | ||
defaultBase64, | ||
fieldsToSelect, | ||
defaultTracedSVG, | ||
ignoreDefaultBase64 = false, | ||
@@ -135,14 +143,2 @@ maxWidth, | ||
const sizes = `(max-width: ${max}px) 100vw, ${max}px`; | ||
const base64 = await getBase64({ | ||
base64Transformations, | ||
base64Width, | ||
chained, | ||
cloudName, | ||
defaultBase64, | ||
ignoreDefaultBase64, | ||
public_id, | ||
reporter, | ||
transformations, | ||
version, | ||
}); | ||
const src = getImageURL({ | ||
@@ -183,5 +179,4 @@ public_id, | ||
return { | ||
const fluidImageObject = { | ||
aspectRatio, | ||
base64, | ||
presentationWidth, | ||
@@ -192,3 +187,21 @@ presentationHeight, | ||
srcSet, | ||
tracedSVG: defaultTracedSVG, | ||
}; | ||
if (fieldsToSelect.includes('base64')) { | ||
fluidImageObject.base64 = await getBase64({ | ||
base64Transformations, | ||
base64Width, | ||
chained, | ||
cloudName, | ||
defaultBase64, | ||
ignoreDefaultBase64, | ||
public_id, | ||
reporter, | ||
transformations, | ||
version, | ||
}); | ||
} | ||
return fluidImageObject; | ||
}; | ||
@@ -195,0 +208,0 @@ |
@@ -21,2 +21,4 @@ const { | ||
originalHeight: 1080, | ||
fieldsToSelect: ['base64'], | ||
defaultTracedSVG: 'defaultTracedSVG', | ||
...args, | ||
@@ -81,2 +83,22 @@ }; | ||
it('does not return base64 if base64 is not a field to select', async () => { | ||
const options = getDefaultOptions(); | ||
getPluginOptions.mockReturnValue(options); | ||
const args = getDefaultArgs({ fieldsToSelect: [] }); | ||
expect((await getFluidImageObject(args)).base64).toEqual(undefined); | ||
}); | ||
it('returns a tracedSVG image', async () => { | ||
const options = getDefaultOptions(); | ||
getPluginOptions.mockReturnValue(options); | ||
const args = getDefaultArgs(); | ||
const expectedTracedSVG = args.defaultTracedSVG; | ||
expect(await getFluidImageObject(args)).toEqual( | ||
expect.objectContaining({ tracedSVG: expectedTracedSVG }), | ||
); | ||
}); | ||
it('does not fetch base64 images multiple times', async () => { | ||
@@ -99,5 +121,6 @@ const options = getDefaultOptions(); | ||
cloudName: 'cloudName', | ||
// enableDefaultTranformations: true, | ||
originalWidth: 1920, | ||
originalHeight: 1080, | ||
fieldsToSelect: ['base64'], | ||
defaultTracedSVG: 'defaultTracedSVG', | ||
...args, | ||
@@ -234,2 +257,23 @@ }; | ||
it('does not return base64 if base64 is not a field to select', async () => { | ||
const options = getDefaultOptions(); | ||
getPluginOptions.mockReturnValue(options); | ||
const args = getDefaultArgs({ fieldsToSelect: [] }); | ||
expect((await getFluidImageObject(args)).base64).toEqual(undefined); | ||
}); | ||
it('returns a tracedSVG image', async () => { | ||
const options = getDefaultOptions(); | ||
getPluginOptions.mockReturnValue(options); | ||
const args = getDefaultArgs(); | ||
const expectedTracedSVG = args.defaultTracedSVG; | ||
expect(await getFluidImageObject(args)).toEqual( | ||
expect.objectContaining({ tracedSVG: expectedTracedSVG }), | ||
); | ||
}); | ||
it('does not fetch base64 images multiple times', async () => { | ||
@@ -236,0 +280,0 @@ const options = getDefaultOptions(); |
@@ -88,8 +88,7 @@ const axios = require('axios'); | ||
logBase64Retrieval(url, reporter); | ||
const result = await axios.get(url, { responseType: 'arraybuffer' }); | ||
const data = Buffer.from(result.data).toString('base64'); | ||
base64Cache[url] = `data:image/jpeg;base64,${data}`; | ||
base64Cache[url] = axios.get(url, { responseType: 'arraybuffer' }); | ||
} | ||
return base64Cache[url]; | ||
const response = await base64Cache[url]; | ||
const data = Buffer.from(response.data).toString('base64'); | ||
return `data:image/jpeg;base64,${data}`; | ||
} | ||
@@ -96,0 +95,0 @@ |
@@ -14,13 +14,3 @@ let options = null; | ||
const requiredOptions = ['apiKey', 'apiSecret', 'cloudName']; | ||
exports.setPluginOptions = ({ pluginOptions, reporter }) => { | ||
requiredOptions.forEach(optionKey => { | ||
if (pluginOptions[optionKey] == null) { | ||
reporter.panic( | ||
`[gatsby-transformer-cloudinary] "${optionKey}" is a required plugin option. You can add it to the options object for "gatsby-transformer-cloudinary" in your gatsby-config file.`, | ||
); | ||
} | ||
}); | ||
if ( | ||
@@ -27,0 +17,0 @@ pluginOptions.breakpointsMaxImages && |
{ | ||
"name": "gatsby-transformer-cloudinary", | ||
"version": "2.1.1", | ||
"version": "2.2.0", | ||
"description": "Transform local files into Cloudinary-managed assets for Gatsby sites.", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -164,2 +164,3 @@ # gatsby-transformer-cloudinary | ||
defaultBase64: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mMMXG/8HwAEwAI0Bj1bnwAAAABJRU5ErkJggg==", | ||
defaultTracedSVG: "data:image/svg+xml,%3Csvg%20height%3D%229999%22%20viewBox%3D%220%200%209999%209999%22%20width%3D%229999%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22m0%200h9999v9999h-9999z%22%20fill%3D%22%23f9fafb%22%2F%3E%3C%2Fsvg%3E", | ||
} | ||
@@ -175,2 +176,6 @@ } | ||
No API calls to Cloudinary for base64 images will be made if your GraphQL queries do not request base64 images. | ||
The property `defaultTracedSVG` in the node above can be used by your CMS/backend to provide precomputed or cached SVG placeholders for your images. The provided string must comply with [RFC 2397](https://tools.ietf.org/html/rfc2397). It should also be encoded with something like JavaScript's `encodeURIComponent()`. | ||
### Plugin options | ||
@@ -182,5 +187,5 @@ | ||
| ------------------------------ | --------- | -------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ||
| `cloudName` | `String` | true | n/a | Cloud name of your Cloudinary account, can be obtained from your [Cloudinary console](https://cloudinary.com/console/). This should be stored and retrieved as an environment variable. | | ||
| `apiKey` | `String` | true | n/a | API Key of your Cloudinary account, can be obtained from your [Cloudinary console](https://cloudinary.com/console/). This should be stored and retrieved as an environment variable. | | ||
| `apiSecret` | `String` | true | n/a | API Secret of your Cloudinary account, can be obtained from your [Cloudinary console](https://cloudinary.com/console/). This should be stored and retrieved as an environment variable. | | ||
| `cloudName` | `String` | false | n/a | Cloud name of your Cloudinary account, can be obtained from your [Cloudinary console](https://cloudinary.com/console/). This should be stored and retrieved as an environment variable. | | ||
| `apiKey` | `String` | false | n/a | API Key of your Cloudinary account, can be obtained from your [Cloudinary console](https://cloudinary.com/console/). This should be stored and retrieved as an environment variable. | | ||
| `apiSecret` | `String` | false | n/a | API Secret of your Cloudinary account, can be obtained from your [Cloudinary console](https://cloudinary.com/console/). This should be stored and retrieved as an environment variable. | | ||
| `uploadFolder` | `String` | false | n/a | An optional folder name where the uploaded assets will be stored on Cloudinary. | | ||
@@ -196,2 +201,4 @@ | `fluidMaxWidth` | `Int` | false | 1000 | The maximum width needed for an image. If specifying a width bigger than the original image, the width of the original image is used instead. Used when calculating breakpoints. | | ||
The options `cloudName`, `apiKey`, and `apiSecret` are required if any images will be uploaded to Cloudinary during the build process. If you're solely using images already uploaded to Cloudinary, then these options can be safely omitted. | ||
> Note: Each derived image created for a breakpoint will consume one Cloudinary transformation. Enable the `useCloudinaryBreakpoints` option with care. If the `createDerived` option is enabled, transformations will only be consumed when the images are first created. However, created images will consume Cloudinary storage space. If `overwriteExisting` is enabled, each image that you upload will consume one transformation each time your Gatsby cache gets cleared and the image gets re-uploaded. For this reason, it's recommended that you keep `overWriteExisting` disabled and instead set the `overwriteExisting` parameter of `createRemoteImageNode` on a per-image basis when you know that an image has actually been updated. | ||
@@ -225,2 +232,13 @@ | ||
### Fragments | ||
The fragments below can be used when querying your Cloudinary assets: | ||
- `CloudinaryAssetFluid` | ||
- `CloudinaryAssetFluid_noBase64` | ||
- `CloudinaryAssetFluid_tracedSVG` | ||
- `CloudinaryAssetFixed` | ||
- `CloudinaryAssetFixed_noBase64` | ||
- `CloudinaryAssetFixed_tracedSVG` | ||
### Avoiding stretched images using the fluid type | ||
@@ -227,0 +245,0 @@ |
@@ -15,2 +15,3 @@ const cloudinary = require('cloudinary').v2; | ||
}) => { | ||
verifyRequiredOptions(reporter); | ||
const { | ||
@@ -111,1 +112,13 @@ apiKey, | ||
}; | ||
function verifyRequiredOptions(reporter) { | ||
const requiredOptions = ['apiKey', 'apiSecret', 'cloudName']; | ||
const pluginOptions = getPluginOptions(); | ||
requiredOptions.forEach(optionKey => { | ||
if (pluginOptions[optionKey] == null) { | ||
reporter.panic( | ||
`[gatsby-transformer-cloudinary] "${optionKey}" is a required plugin option. You can add it to the options object for "gatsby-transformer-cloudinary" in your gatsby-config file.`, | ||
); | ||
} | ||
}); | ||
} |
@@ -12,2 +12,8 @@ const { | ||
const defaultPluginOptions = { | ||
apiKey: 'apiKey', | ||
apiSecret: 'apiSecret', | ||
cloudName: 'cloudName', | ||
}; | ||
describe('uploadImageToCloudinary', () => { | ||
@@ -178,3 +184,6 @@ function getDefaultArgs(args) { | ||
const overwriteExisting = 'overwriteExistingDouble'; | ||
getPluginOptions.mockReturnValue({ overwriteExisting }); | ||
getPluginOptions.mockReturnValue({ | ||
...defaultPluginOptions, | ||
overwriteExisting, | ||
}); | ||
@@ -190,2 +199,65 @@ await uploadImageNodeToCloudinary({ node, reporter }); | ||
}); | ||
it('requires the apiKey option', async () => { | ||
const reporter = { | ||
panic: jest.fn(() => { | ||
throw Error(); | ||
}), | ||
}; | ||
const node = { relativePath: 'relativePath.jpg' }; | ||
getPluginOptions.mockReturnValue({ | ||
...defaultPluginOptions, | ||
apiKey: null, | ||
}); | ||
try { | ||
await uploadImageNodeToCloudinary({ node, reporter }); | ||
} catch {} | ||
expect(reporter.panic).toHaveBeenCalledWith( | ||
'[gatsby-transformer-cloudinary] "apiKey" is a required plugin option. You can add it to the options object for "gatsby-transformer-cloudinary" in your gatsby-config file.', | ||
); | ||
}); | ||
it('requires the apiSecret option', async () => { | ||
const reporter = { | ||
panic: jest.fn(() => { | ||
throw Error(); | ||
}), | ||
}; | ||
const node = { relativePath: 'relativePath.jpg' }; | ||
getPluginOptions.mockReturnValue({ | ||
...defaultPluginOptions, | ||
apiSecret: null, | ||
}); | ||
try { | ||
await uploadImageNodeToCloudinary({ node, reporter }); | ||
} catch {} | ||
expect(reporter.panic).toHaveBeenCalledWith( | ||
'[gatsby-transformer-cloudinary] "apiSecret" is a required plugin option. You can add it to the options object for "gatsby-transformer-cloudinary" in your gatsby-config file.', | ||
); | ||
}); | ||
it('requires the cloudName option', async () => { | ||
const reporter = { | ||
panic: jest.fn(() => { | ||
throw Error(); | ||
}), | ||
}; | ||
const node = { relativePath: 'relativePath.jpg' }; | ||
getPluginOptions.mockReturnValue({ | ||
...defaultPluginOptions, | ||
cloudName: null, | ||
}); | ||
try { | ||
await uploadImageNodeToCloudinary({ node, reporter }); | ||
} catch {} | ||
expect(reporter.panic).toHaveBeenCalledWith( | ||
'[gatsby-transformer-cloudinary] "cloudName" is a required plugin option. You can add it to the options object for "gatsby-transformer-cloudinary" in your gatsby-config file.', | ||
); | ||
}); | ||
}); |
102287
2102
383