diff --git a/integration-tests/gatsby-source-wordpress/__tests__/__snapshots__/index.js.snap b/integration-tests/gatsby-source-wordpress/__tests__/__snapshots__/index.js.snap index 3a3037af00f00..7e5d8c5881db4 100644 --- a/integration-tests/gatsby-source-wordpress/__tests__/__snapshots__/index.js.snap +++ b/integration-tests/gatsby-source-wordpress/__tests__/__snapshots__/index.js.snap @@ -3915,6 +3915,13 @@ Array [ "parent", "children", "internal", + "filename", + "filesize", + "width", + "height", + "publicUrl", + "resize", + "gatsbyImageData", ], "name": "WpMediaItem", }, diff --git a/integration-tests/gatsby-source-wordpress/__tests__/index.js b/integration-tests/gatsby-source-wordpress/__tests__/index.js index 421c2dfef5ae4..6f20b16ad07dc 100644 --- a/integration-tests/gatsby-source-wordpress/__tests__/index.js +++ b/integration-tests/gatsby-source-wordpress/__tests__/index.js @@ -60,6 +60,10 @@ describe(`[gatsby-source-wordpress] Run tests on develop build`, () => { let gatsbyDevelopProcess beforeAll(async () => { + if (process.env.SKIP_BEFORE_ALL) { + return + } + if (!isWarmCache) { await gatsbyCleanBeforeAll() } @@ -105,7 +109,10 @@ describe(`[gatsby-source-wordpress] Run tests on develop build`, () => { require(`../test-fns/index`) afterAll(done => { - gatsbyDevelopProcess.kill() + if (gatsbyDevelopProcess) { + gatsbyDevelopProcess.kill() + } + done() }) }) diff --git a/integration-tests/gatsby-source-wordpress/docker-compose.yml b/integration-tests/gatsby-source-wordpress/docker-compose.yml index d3d168f113cb2..7ced65baebc45 100644 --- a/integration-tests/gatsby-source-wordpress/docker-compose.yml +++ b/integration-tests/gatsby-source-wordpress/docker-compose.yml @@ -2,6 +2,7 @@ version: "3.2" services: db: + platform: linux/x86_64 image: mysql:8 environment: MYSQL_ROOT_PASSWORD: gtsb-wp-dckr diff --git a/integration-tests/gatsby-source-wordpress/test-fns/data-resolution.js b/integration-tests/gatsby-source-wordpress/test-fns/data-resolution.js index ee7736045c3d4..5d024d09b52f6 100644 --- a/integration-tests/gatsby-source-wordpress/test-fns/data-resolution.js +++ b/integration-tests/gatsby-source-wordpress/test-fns/data-resolution.js @@ -531,4 +531,44 @@ describe(`data resolution`, () => { expect(wpMediaItem).toBeNull() } }) + + it(`Resolves Gatsby Image CDN data`, async () => { + const { + data: { allWpPost }, + } = await fetchGraphql({ + url, + query: /* GraphQL */ ` + query { + allWpPost { + nodes { + featuredImage { + node { + mediaItemUrl + resize(width: 100, height: 100, quality: 100) { + width + height + src + } + } + } + } + } + } + `, + }) + + allWpPost.nodes.forEach(node => { + if (!node.featuredImage?.node) { + return + } + + const { resize } = node.featuredImage.node + const [, , , sourceUrl64, _args64, filename] = resize.src.split(`/`) + + const sourceUrl = Buffer.from(sourceUrl64, `base64`).toString(`ascii`) + + expect(node.featuredImage.node.mediaItemUrl).toEqual(sourceUrl) + expect(node.featuredImage.node.mediaItemUrl).toContain(filename) + }) + }) }) diff --git a/packages/gatsby-source-wordpress/__tests__/process-node.fixture.js b/packages/gatsby-source-wordpress/__tests__/process-node.fixture.js new file mode 100644 index 0000000000000..96f0ed3be6f00 --- /dev/null +++ b/packages/gatsby-source-wordpress/__tests__/process-node.fixture.js @@ -0,0 +1,77 @@ +export const referencedMediaItems = [ + { + "id": `cG9zdDoxMTU=`, + "mediaDetails": { + "file": `2022/02/sasha-set-GURzQwO8Li0-unsplash-scaled.jpg`, + "height": 1920, + "sizes": [ + { + "file": `sasha-set-GURzQwO8Li0-unsplash-300x225.jpg`, + "fileSize": 18876, + "height": `225`, + "mimeType": `image/jpeg`, + "name": `medium`, + "sourceUrl": `http://wpgatsby.local/wp-content/uploads/2022/02/sasha-set-GURzQwO8Li0-unsplash-300x225.jpg`, + "width": `300` + }, + { + "file": `sasha-set-GURzQwO8Li0-unsplash-1024x768.jpg`, + "fileSize": 214590, + "height": `768`, + "mimeType": `image/jpeg`, + "name": `large`, + "sourceUrl": `http://wpgatsby.local/wp-content/uploads/2022/02/sasha-set-GURzQwO8Li0-unsplash-1024x768.jpg`, + "width": `1024` + }, + { + "file": `sasha-set-GURzQwO8Li0-unsplash-150x150.jpg`, + "fileSize": 7088, + "height": `150`, + "mimeType": `image/jpeg`, + "name": `thumbnail`, + "sourceUrl": `http://wpgatsby.local/wp-content/uploads/2022/02/sasha-set-GURzQwO8Li0-unsplash-150x150.jpg`, + "width": `150` + }, + { + "file": `sasha-set-GURzQwO8Li0-unsplash-768x576.jpg`, + "fileSize": 118086, + "height": `576`, + "mimeType": `image/jpeg`, + "name": `medium_large`, + "sourceUrl": `http://wpgatsby.local/wp-content/uploads/2022/02/sasha-set-GURzQwO8Li0-unsplash-768x576.jpg`, + "width": `768` + }, + { + "file": `sasha-set-GURzQwO8Li0-unsplash-1536x1152.jpg`, + "fileSize": 490613, + "height": `1152`, + "mimeType": `image/jpeg`, + "name": `1536x1536`, + "sourceUrl": `http://wpgatsby.local/wp-content/uploads/2022/02/sasha-set-GURzQwO8Li0-unsplash-1536x1152.jpg`, + "width": `1536` + }, + { + "file": `sasha-set-GURzQwO8Li0-unsplash-2048x1536.jpg`, + "fileSize": 858627, + "height": `1536`, + "mimeType": `image/jpeg`, + "name": `2048x2048`, + "sourceUrl": `http://wpgatsby.local/wp-content/uploads/2022/02/sasha-set-GURzQwO8Li0-unsplash-2048x1536.jpg`, + "width": `2048` + } + ], + "width": 2560 + }, + "mediaItemUrl": `http://wpgatsby.local/wp-content/uploads/2022/02/sasha-set-GURzQwO8Li0-unsplash-scaled.jpg`, + "mediaType": `image`, + "mimeType": `image/jpeg`, + "sourceUrl": `http://wpgatsby.local/wp-content/uploads/2022/02/sasha-set-GURzQwO8Li0-unsplash-scaled.jpg`, + "srcSet": `http://wpgatsby.local/wp-content/uploads/2022/02/sasha-set-GURzQwO8Li0-unsplash-300x225.jpg 300w, http://wpgatsby.local/wp-content/uploads/2022/02/sasha-set-GURzQwO8Li0-unsplash-1024x768.jpg 1024w, http://wpgatsby.local/wp-content/uploads/2022/02/sasha-set-GURzQwO8Li0-unsplash-768x576.jpg 768w, http://wpgatsby.local/wp-content/uploads/2022/02/sasha-set-GURzQwO8Li0-unsplash-1536x1152.jpg 1536w, http://wpgatsby.local/wp-content/uploads/2022/02/sasha-set-GURzQwO8Li0-unsplash-2048x1536.jpg 2048w`, + "width": 2560, + "height": 1920, + "placeholderUrl": `http://wpgatsby.local/wp-content/uploads/2022/02/sasha-set-GURzQwO8Li0-unsplash-300x225.jpg`, + "internal": { + "contentDigest": `5fa24d43c7fc603f9016933ad6bb3ee4`, + } + } +] diff --git a/packages/gatsby-source-wordpress/__tests__/process-node.test.js b/packages/gatsby-source-wordpress/__tests__/process-node.test.js index b61376194def5..bf8f38e0026be 100644 --- a/packages/gatsby-source-wordpress/__tests__/process-node.test.js +++ b/packages/gatsby-source-wordpress/__tests__/process-node.test.js @@ -1,8 +1,10 @@ import execall from "execall" +import { replaceNodeHtmlImages } from "../dist/steps/source-nodes/create-nodes/process-node" + import { getImgSrcRemoteFileMatchesFromNodeString, - getImgTagMatchesWithUrl, + getImgTagMatches, getWpLinkRegex, searchAndReplaceNodeStrings, } from "../dist/steps/source-nodes/create-nodes/process-node" @@ -20,7 +22,7 @@ test(`HTML image transformation regex matches images`, async () => { expect(matches.length).toBe(3) - const imgTagMatches = getImgTagMatchesWithUrl({ + const imgTagMatches = getImgTagMatches({ nodeString, wpUrl: `https://${wpUrl}`, }) @@ -69,3 +71,68 @@ test(`Search and replace node strings using regex matches`, async () => { We need to test some link as well!`) }) + +jest.mock(`../dist/steps/source-nodes/fetch-nodes/fetch-referenced-media-items.js`, () => { + return { + __esModule: true, + ...jest.requireActual(`../dist/steps/source-nodes/fetch-nodes/fetch-referenced-media-items.js`), + default: jest.fn(() => require(`./process-node.fixture`).referencedMediaItems) + } +}) + + +test(`Gatsby Image service works in html fields via replaceNodeHtmlImages`, async () => { + const node = { + content: `\n
Welcome to WordPress. This is your first post. Edit or deleteit, then start writing!
\n\n\n\n\n\n\n\n.*(<\/pre>)/gim, ``) + .replace(/(?:(?!<\/pre>).)+(<\/pre>)/gim, ``) // and code tags, so temporarily remove those tags and everything inside them - .replace(/.*(<\/code>)/gim, ``) - ).filter(filterMatches(wpUrl)) + .replace(/
(?:(?!<\/code>).)+(<\/code>)/gim, ``) + ) -const replaceNodeHtmlImages = async ({ +export const replaceNodeHtmlImages = async ({ nodeString, node, helpers, @@ -500,7 +478,7 @@ const replaceNodeHtmlImages = async ({ const imageUrlMatches = getImgSrcRemoteFileMatchesFromNodeString(nodeString) - const imgTagMatches = getImgTagMatchesWithUrl({ nodeString, wpUrl }) + const imgTagMatches = getImgTagMatches({ nodeString }) if (imageUrlMatches.length && imgTagMatches.length) { const cheerioImages = getCheerioElementsFromMatches({ @@ -541,11 +519,12 @@ const replaceNodeHtmlImages = async ({ ? // this will already be a file node imageNode : // otherwise grab the file node - helpers.getNode(imageNode.localFile.id) + helpers.getNode(imageNode?.localFile?.id) - if (!fileNode) { - return null - } + const extension = imageNode?.mimeType?.replace( + `${imageNode?.mediaType}/`, + `` + ) const imgTagMaxWidth = findImgTagMaxWidthFromCheerioImg(cheerioImg) @@ -575,7 +554,7 @@ const replaceNodeHtmlImages = async ({ // and we have a media item node to know it's full size max width mediaItemNodeWidth && // and this isn't an svg which has no maximum width - fileNode.extension !== `svg` && + extension !== `svg` && // and the media item node max width is smaller than what we inferred // from html mediaItemNodeWidth < imgTagMaxWidth @@ -597,31 +576,50 @@ const replaceNodeHtmlImages = async ({ const quality = pluginOptions?.html?.imageQuality - const { reporter, cache, pathPrefix } = helpers + const { reporter } = helpers const gatsbyTransformerSharpSupportsThisFileType = - supportedExtensions[fileNode?.extension] + supportedExtensions[extension] || extension === `gif` - let sharpImageData = null + let imageResize = null if (gatsbyTransformerSharpSupportsThisFileType) { + const placeholderUrl = getPlaceholderUrlFromMediaItemNode( + imageNode, + pluginOptions + ) + + const imageUrl = + imageNode.mediaItemUrl || imageNode.sourceUrl || imageNode.url + try { - sharpImageData = await generateImageData({ - file: fileNode, - args: { + imageResize = await gatsbyImageResolver( + { + url: imageUrl, + placeholderUrl, + mimeType: imageNode.mimeType, + width: imageNode.mediaDetails.width, + height: imageNode.mediaDetails.height, + filename: path.basename(imageNode.mediaDetails.file), + internal: { + contentDigest: imageNode.modifiedGmt, + }, + }, + { width: maxWidth, + layout: `constrained`, + placeholder: !placeholderUrl + ? `none` + : pluginOptions?.html?.placeholderType || `blurred`, quality, - ...pluginOptions?.html?.gatsbyImageOptions, }, - pathPrefix, - reporter, - cache, - }) + helpers.actions + ) } catch (e) { reporter.error(e) reporter.warn( formatLogMessage( - `${node.__typename} ${node.id} couldn't process inline html image ${fileNode.url}` + `${node.__typename} ${node.id} couldn't process inline html image ${imageUrl}` ) ) return null @@ -632,7 +630,7 @@ const replaceNodeHtmlImages = async ({ match, cheerioImg, fileNode, - imageResize: sharpImageData, + imageResize, maxWidth, } }) @@ -645,14 +643,12 @@ const replaceNodeHtmlImages = async ({ continue } - const { match, imageResize, cheerioImg, maxWidth } = matchResize - - // @todo retain img tag classes and attributes from cheerioImg + const { match, imageResize, cheerioImg } = matchResize let ReactGatsbyImage // used to create hydration data for images let gatsbyImageHydrationData = null - if (imageResize) { + if (imageResize && imageResize.images.sources.length > 0) { gatsbyImageHydrationData = { image: imageResize, alt: cheerioImg?.attribs?.alt, @@ -666,49 +662,28 @@ const replaceNodeHtmlImages = async ({ gatsbyImageHydrationData, null ) - } else { - const { fileNode } = matchResize - const relativeUrl = await copyFileToStaticAndReturnUrlPath( - fileNode, - helpers - ) - - const imgOptions = { - style: { - // these styles make it so that the image wont be stretched - // beyond it's max width, but it also wont exceed the width - // of it's parent element - maxWidth: `100%`, - width: `${maxWidth}px`, - }, - className: `${ - cheerioImg?.attribs?.class || `` - } inline-gatsby-image-wrapper`, - loading: `eager`, - alt: cheerioImg?.attribs?.alt, - src: relativeUrl, - } - - ReactGatsbyImage = React.createElement(`img`, imgOptions, null) } - let gatsbyImageStringRaw = ReactDOMServer.renderToString(ReactGatsbyImage) + if (ReactGatsbyImage) { + let gatsbyImageStringRaw = + ReactDOMServer.renderToString(ReactGatsbyImage) - // gatsby-plugin-image needs hydration data to work on navigations - we add the hydration data to the DOM to use it in gatsby-browser.ts - if (gatsbyImageHydrationData) { - gatsbyImageStringRaw += `` - } - // need to remove the JSON stringify quotes around our image since we're - // threading this JSON string back into a larger JSON object string - const gatsbyImageStringJSON = JSON.stringify(gatsbyImageStringRaw) - const gatsbyImageString = gatsbyImageStringJSON.substring( - 1, - gatsbyImageStringJSON.length - 1 - ) + // gatsby-plugin-image needs hydration data to work on navigations - we add the hydration data to the DOM to use it in gatsby-browser.ts + if (gatsbyImageHydrationData) { + gatsbyImageStringRaw += `` + } + // need to remove the JSON stringify quotes around our image since we're + // threading this JSON string back into a larger JSON object string + const gatsbyImageStringJSON = JSON.stringify(gatsbyImageStringRaw) + const gatsbyImageString = gatsbyImageStringJSON.substring( + 1, + gatsbyImageStringJSON.length - 1 + ) - nodeString = replaceAll(match, gatsbyImageString, nodeString) + nodeString = replaceAll(match, gatsbyImageString, nodeString) + } } } diff --git a/packages/gatsby-source-wordpress/src/steps/source-nodes/fetch-nodes/fetch-referenced-media-items.js b/packages/gatsby-source-wordpress/src/steps/source-nodes/fetch-nodes/fetch-referenced-media-items.js index 385114e89a23a..526b3e13d3d22 100644 --- a/packages/gatsby-source-wordpress/src/steps/source-nodes/fetch-nodes/fetch-referenced-media-items.js +++ b/packages/gatsby-source-wordpress/src/steps/source-nodes/fetch-nodes/fetch-referenced-media-items.js @@ -13,6 +13,7 @@ import urlUtil from "url" import path from "path" import { getPluginOptions } from "~/utils/get-gatsby-api" import { formatLogMessage } from "~/utils/format-log-message" +import { getPlaceholderUrlFromMediaItemNode } from "../create-nodes/process-node" const nodeFetchConcurrency = 2 @@ -179,11 +180,28 @@ export const createMediaItemNode = async ({ ) } + const placeholderUrl = getPlaceholderUrlFromMediaItemNode( + node, + pluginOptions + ) + + const url = node.sourceUrl || node.mediaItemUrl + + const filename = + node?.mediaDetails?.file?.split(`/`)?.pop() || + path.basename(urlUtil.parse(url).pathname) + node = { ...node, - localFile: { - id: localFileNode?.id, - }, + url, + contentType: node.contentType, + mimeType: node.mimeType, + filename, + filesize: node?.mediaDetails?.fileSize, + width: node?.mediaDetails?.width, + height: node?.mediaDetails?.height, + placeholderUrl: + placeholderUrl ?? node?.mediaDetails?.sizes?.[0]?.sourceUrl ?? url, parent: null, internal: { contentDigest: createContentDigest(node), @@ -191,6 +209,10 @@ export const createMediaItemNode = async ({ }, } + if (localFileNode) { + node.localFile = localFileNode?.id + } + const normalizedNode = normalizeNode({ node, nodeTypeName: `MediaItem` }) await actions.createNode(normalizedNode) @@ -394,7 +416,7 @@ export const fetchMediaItemsBySourceUrl = async ({ ) nodes.forEach((node, index) => { - if (!node) { + if (!node || !node?.localFile?.id) { return } diff --git a/packages/gatsby-source-wordpress/src/utils/gatsby-types.ts b/packages/gatsby-source-wordpress/src/utils/gatsby-types.ts index 66d757f2d6fcf..e562360824aac 100644 --- a/packages/gatsby-source-wordpress/src/utils/gatsby-types.ts +++ b/packages/gatsby-source-wordpress/src/utils/gatsby-types.ts @@ -1,5 +1,6 @@ import { IPreviewData } from "./../steps/preview/index" import type { NodePluginArgs, Reporter } from "gatsby" +import { Application } from "express" export type GatsbyNodeApiHelpers = NodePluginArgs & { Joi?: any webhookBody?: IPreviewData @@ -10,6 +11,7 @@ export type GatsbyNodeApiHelpers = NodePluginArgs & { updatedAt: number } refetchAll?: boolean + app?: Application } export type GatsbyHelpers = GatsbyNodeApiHelpers export type GatsbyReporter = Reporter