11ty / eleventy-img

Utility to perform build-time image transformations.
https://www.11ty.dev/docs/plugins/image/
435 stars 54 forks source link

Double slashes removed from urlPath #89

Closed rjdusk closed 3 years ago

rjdusk commented 3 years ago

Hiya gang, no doubt this is something with me not correctly escaping something somewhere, but it's driving me around the bend. What I'm trying to do is offload some of the compression work to Cloudinary by frankensteining a CSS Tricks solution with eleventy-img. See below:

async function imageShortcode(src, cls, alt, sizes = "100vw", widths) {
// Variables used for writing out Cloudinary fetch URL if site is in production
// falls back to local is in dev
const imageLocation = process.env.CONTEXT === "production" ? "https://www.madebydusk.com" : "";
const urlPrefix = process.env.CONTEXT === "production" ? "https://res.cloudinary.com/rjdusk/image/fetch/q_auto,f_auto,w_auto,dpr_auto/" : "https:\/"+"\/res.cloudinary.com/rjdusk/image/fetch/q_auto,f_auto,w_auto,dpr_auto/";

    if(alt === undefined) {
        // You bet we throw an error on missing alt (alt="" works okay)
        throw new Error(`Missing \`alt\` on myImage from: ${src}`);
      }

      let metadata = await Image(src, {
        widths: widths,
        formats: ["jpeg"],
        urlPath: "https:\/"+"\/res.cloudinary.com/rjdusk/image/fetch/q_auto,f_auto,w_auto,dpr_auto/images/",
        outputDir: "./dist/images/",
        filenameFormat: function (id, src, width, format, options) {
            const extension = path.extname(src);
            const name = path.basename(src, extension);
            return `${name}-${width}w.${format}`;
        }
      });

      let data = metadata.jpeg[metadata.jpeg.length - 1];
      return `<img src="${data.url}" width="${data.width}" height="${data.height}" class="${cls}" alt="${alt}" loading="lazy" decoding="async">`;
}

From the code above, which has got some debugging code in it, like the URL being split at the double slashes //. But this is the problem, in the final image URL it ends up being a single / vs a double //. Why are my double // becoming a single / ? Is something being escaped somewhere in this plugin? Thanks for any insights!

rjdusk commented 3 years ago

Just going to expand on this as I have a working solution, but oh boy is it a doozy! I'm sure there is a much more elegant way to achieving this, but instead on constantly slamming my head against a brick wall, I used the old gray matter to come up with "a solution". Again this is not good, but it works for now until I find something better:

async function imageShortcode(src, cls, alt, sizes = "100vw", widths) {
  // Variables used for writing out Cloudinary fetch URL if site is in production
  // falls back to local is in dev
  const imageLocation =
    process.env.CONTEXT === "production"
      ? "www.madebydusk.com"
      : "";
  const urlPrefix =
    process.env.CONTEXT === "production"
      ? "res.cloudinary.com/rjdusk/image/fetch/q_auto,f_auto,w_auto,dpr_auto/"
      : "";

  if (alt === undefined) {
    // You bet we throw an error on missing alt (alt="" works okay)
    throw new Error(`Missing \`alt\` on responsiveimage from: ${src}`);
  }

  let metadata = await Image(src, {
    widths: widths,
    formats: ["jpeg"],
    urlPath: "/images/",
    outputDir: "./dist/images/",
    filenameFormat: function (id, src, width, format, options) {
      const extension = path.extname(src);
      const name = path.basename(src, extension);
      return `${name}-${width}w.${format}`;
    },
  });

  let lowsrc = metadata.jpeg[0];

  return process.env.CONTEXT === "production"
    ? `<img
          src="https://${urlPrefix}https://${imageLocation}${lowsrc.url}"
          ${Object.values(metadata)
            .map((imageFormat) => {
              return ` srcset="https://${urlPrefix}https://${imageLocation}${imageFormat
                .map((entry) => entry.srcset)
                .join(`, https://${urlPrefix}https://${imageLocation}`)}" sizes="${sizes}" `;
            })
            .join("\n")}
          width="${lowsrc.width}"
          height="${lowsrc.height}"
          alt="${alt}"
          class="${cls}"
          loading="lazy"
          decoding="async">`
    : `<img
          src="${lowsrc.url}"
          ${Object.values(metadata)
            .map((imageFormat) => {
              return ` srcset="${imageFormat
                .map((entry) => entry.srcset)
                .join(", ")}" sizes="${sizes}" `;
            })
            .join("\n")}
          width="${lowsrc.width}"
          height="${lowsrc.height}"
          alt="${alt}"
          class="${cls}"
          loading="lazy"
          decoding="async">`;
}

I'm publishing this site to Netlify, hence the process.env.CONTEXT === "production" stuff. You can see this working here - the first B&W image of me. I'm still yet to roll this out to the rest of the site, I want to get this perfect before doing so.

Again any insights on this would be most welcome. I was trying to pair this plugin with Netlify Dev and proxy the requests to Cloudinary through Netlify, but could never get the redirects working in my local development environment - like mentioned here. Save that one for another day.

zachleat commented 3 years ago

Sorry for the late response here, you want the urlFormat option to override with a hosted solution URL: https://github.com/11ty/eleventy-img/blob/956bbd9c90df3d5f96c0e2c30f59aef02264c87a/img.js#L314

Related: #113

This is an automated message to let you know that a helpful response was posted to your issue and for the health of the repository issue tracker the issue will be closed. This is to help alleviate issues hanging open waiting for a response from the original poster.

If the response works to solve your problem—great! But if you’re still having problems, do not let the issue’s closing deter you if you have additional questions! Post another comment and we will reopen the issue. Thanks!

zachleat commented 3 years ago

If you’d like an example, you can find one here: https://github.com/11ty/11ty-website/blob/5fa98898403e9df867c7a71a37f48b3291dadfeb/.eleventy.js#L75

pmpinto commented 1 year ago

@zachleat The urlFormat option worked great for a use case I had where I needed the image URL only, without the need for generating it once again.

But I have another use case where I'm failing to find a solution: RSS feeds!

See, the images are still hosted locally, there's no 3rd party service taking care of anything. When I inject an image on a page, it works just fine, with a url something like /static_assets/images…. But on an RSS feed, the image URLs need to be absolute, so I would need to do something like:

urlPath: isRSS ? 'https://my-domain-name.com/static_assets/images/' : '/static_assets/images/',

Unfortunately, https:// becomes https:/, which won't quite work.

Also, in this case, if I use the urlFormat instead of urlPath, I find that the images aren't written/transformed at all. Is there a way to work around this?

pmpinto commented 1 year ago

As a quick and dirty (and hopefully temporary) workaround:

const imageHTML = Image.generateHTML(metadata, imageAttributes)

return isRSS ? he.escape(imageHTML).replaceAll('https:/domain.com', 'https://domain.com') : imageHTML;