UNPKG

@draftbox-co/gatsby-wordpress-inline-images

Version:

A Gatsby plugin to turn remote inline images to local static images

292 lines (257 loc) 6.97 kB
const _ = require(`lodash`); const React = require(`react`); const ReactDOMServer = require(`react-dom/server`); const cheerio = require(`cheerio`); const { createRemoteFileNode } = require(`gatsby-source-filesystem`); const { fluid } = require(`gatsby-plugin-sharp`); const sharp = require(`sharp`); const Img = require(`gatsby-image`); const sizeOf = require('image-size'); const parseWPImagePath = require(`./utils/parseWPImagePath`); exports.sourceNodes = async ({ getNodes, cache, reporter, store, actions, createNodeId }, pluginOptions) => { const { createNode } = actions; const defaults = { maxWidth: 650, wrapperStyle: ``, backgroundColor: `white`, postTypes: ["post", "page"], withWebp: false, useACF: false // linkImagesToOriginal: true, // showCaptions: false, // pathPrefix, // withWebp: false }; const options = _.defaults(pluginOptions, defaults); const nodes = getNodes(); // for now just get all posts and pages. // this will be dynamic later const entities = nodes.filter(node => node.internal.owner === "gatsby-source-wordpress" && options.postTypes.includes(node.type)); // we need to await transforming all the entities since we may need to get images remotely and generating fluid image data is async await Promise.all(entities.map(async entity => transformInlineImagestoStaticImages({ entity, cache, reporter, store, createNode, createNodeId }, options))); }; const transformInlineImagestoStaticImages = async ({ entity, cache, reporter, store, createNode, createNodeId, attribute }, options) => { const field = entity[attribute || "content"]; if (attribute) { // If attribute is defined, we're checking some ACF entity if (typeof field === "object" && field !== null) { // If the ACF entity is an object, parse all its entries recursively Object.keys(field).map(async key => { await transformInlineImagestoStaticImages({ entity: field, attribute: key, cache, reporter, store, createNode, createNodeId }, options); }); return; } // [implicit else] If ACF is not an object, "field" will be parsed later } else { // If attribute is not defined, we're parsing a top-level node // so we should check this entity's ACF attributes if (options.useACF && entity.acf) { await transformInlineImagestoStaticImages({ entity: entity, attribute: "acf", cache, reporter, store, createNode, createNodeId }, options); } } if (!field && typeof field !== "string" || !field.includes("<img")) return; const $ = cheerio.load(field); const imgs = $(`img`); if (imgs.length === 0) return; let imageRefs = []; imgs.each(function () { let img = $(this); if (img.attr("src")) { imageRefs.push(img); } }); await Promise.all(imageRefs.map(thisImg => replaceImage({ thisImg, options, cache, reporter, $, store, createNode, createNodeId }))); entity[attribute || "content"] = $.html(); }; const replaceImage = async ({ thisImg, options, cache, store, createNode, createNodeId, reporter, $ }) => { // find the full size image that matches, throw away WP resizes const parsedUrlData = parseWPImagePath(thisImg.attr("src")); const url = parsedUrlData.cleanUrl; const { originalUrl } = parsedUrlData; let imageNode; // Try to download the full size image without the WP resize parameters (removed on parse) try { imageNode = await downloadMediaFile({ url, cache, store, createNode, createNodeId }); } catch (e) { // If the image without WP resize parameters on the URL does not exist it means that the original file has sizes // Try to download the image with the original URL try { imageNode = await downloadMediaFile({ url: originalUrl, cache, store, createNode, createNodeId }); } catch (e) {// Do nothing } } if (!imageNode) return; let classes = thisImg.attr("class"); let formattedImgTag = {}; formattedImgTag.url = thisImg.attr(`src`); formattedImgTag.classList = classes ? classes.split(" ") : []; formattedImgTag.title = thisImg.attr(`title`); formattedImgTag.alt = thisImg.attr(`alt`); const dimensions = sizeOf(imageNode.absolutePath); if (dimensions.width) formattedImgTag.width = dimensions.width; if (dimensions.height) formattedImgTag.height = dimensions.height; if (!formattedImgTag.url) return; const fileType = imageNode.ext; // Ignore gifs as we can't process them, // svgs as they are already responsive by definition if (fileType !== `gif` && fileType !== `svg`) { const rawHTML = await generateImagesAndUpdateNode({ formattedImgTag, imageNode, options, cache, reporter, $ }); // Replace the image string if (rawHTML) thisImg.replaceWith(rawHTML); } }; // Takes a node and generates the needed images and then returns // the needed HTML replacement for the image const generateImagesAndUpdateNode = async function ({ formattedImgTag, imageNode, options, cache, reporter, $ }) { if (!imageNode || !imageNode.absolutePath) return; try { await sharp(imageNode.absolutePath).metadata(); let fluidResultWebp; let fluidResult = await fluid({ file: imageNode, args: { ...options, maxWidth: formattedImgTag.width || options.maxWidth }, reporter, cache }); if (options.withWebp) { fluidResultWebp = await fluid({ file: imageNode, args: { ...options, maxWidth: formattedImgTag.width || options.maxWidth, toFormat: "WEBP" }, reporter, cache }); } if (!fluidResult) return; if (options.withWebp) { fluidResult.srcSetWebp = fluidResultWebp.srcSet; } const imgOptions = { fluid: fluidResult, style: { maxWidth: "100%" }, // Force show full image instantly // critical: true, // depricated loading: "eager", alt: formattedImgTag.alt, // fadeIn: true, imgStyle: { opacity: 1 } }; if (formattedImgTag.width) imgOptions.style.width = formattedImgTag.width; const ReactImgEl = React.createElement(Img.default, imgOptions, null); return ReactDOMServer.renderToString(ReactImgEl); } catch (e) { console.log('err', e); return; } }; const downloadMediaFile = async ({ url, cache, store, createNode, createNodeId }) => { let fileNode = false; try { fileNode = await createRemoteFileNode({ url, store, cache, createNode, createNodeId }); } catch (e) { throw Error(e); } return fileNode; };