Giter Club home page Giter Club logo

unpic's Introduction

🖼 Unpic

Universal image CDN URL translator

There are many image CDNs that provide a URL API for transforming images. There is little consistency in these APIs, and it's often unclear what the API is for a given URL. This library aims to provide a consistent interface for detecting image CDN URLs and transforming them.

If you'd like to use this on the web, you might want to try Unpic img, a multi-framework image component, powered by Unpic.

It designed to work with image URLs from sources such as CMSs and other user-generated content, where the source image may or may not be from an image CDN, and may already have transforms applied. This allow different transforms to be applied for display on a website. A web framework may need to transform an image for display on a site. Rather than doing this by downloading and resizing it locally or re-processing it with a separate image service, this library can be used to transform the URL to use the original image CDN, which will then transform the image on the fly.

Usage

This library is available via URL imports for Deno and via npm for Node. To use it in Deno, import the module from deno.land:

import { transformUrl } from "https://deno.land/x/unpic/mod.ts";

To use it in Node, install it from npm:

npm install unpic

Then import it in your code:

import { transformUrl } from "unpic";

You can then use the transformUrl function to transform a URL:

const url = transformUrl(
  {
    url:
      "https://cdn.shopify.com/static/sample-images/bath_grande_crop_center.jpeg",
    width: 800,
    height: 600,
  },
);

console.log(url.toString());

// https://cdn.shopify.com/static/sample-images/bath.jpeg?width=800&height=600&crop=center

You can also use the parseUrl function to parse a URL and get the CDN and any params:

const parsedUrl = parseUrl(
  "https://cdn.shopify.com/static/sample-images/bath_800x600_crop_center.jpeg",
);

console.log(parsedUrl);
// {
//   cdn: "shopify",
//   width: 800,
//   height: 600,
//   base: "https://cdn.shopify.com/static/sample-images/bath.jpeg",
//   params: {
//     crop: "center",
//   },
// }

You can bypass auto-detection by specifying the CDN:

const url = transformUrl(
  {
    url:
      "https://cdn.shopify.com/static/sample-images/bath_grande_crop_center.jpeg",
    width: 800,
    height: 600,
    cdn: "shopify",
  },
);

This is particularly useful if you are using the CDN with a custom domain which is not auto-detected.

Supported CDN APIs

  • Adobe Dynamic Media (Scene7)
  • Builder.io
  • Bunny.net, including caisy
  • Cloudflare
  • Contentful
  • Contentstack
  • Cloudinary
  • Directus
  • ImageEngine
  • Imgix, including Unsplash, DatoCMS, Sanity and Prismic
  • Kontent.ai
  • Shopify
  • Storyblok
  • Vercel / Next.js
  • WordPress.com and Jetpack Site Accelerator

Delegated URLs

Some transformers support URL delegation. This means that the source image URL is also checked, and if it matches a CDN then the transform is applied directly to the source image. For example: consider a next/image URL that points to an image on Shopify. The URL is detected as a nextjs URL because it starts with /_next/image. The nextjs transformer supports delegation, so the source image URL is then checked. As it matches a Shopify domain, the transform is applied directly to the Shopify URL. This means that the image is transformed on the fly by Shopify, rather than by Next.js. However if the source image is not a supported CDN, or is a local image then the nextjs transformer will return a /_next/image URL.

FAQs

  • What is an image CDN? An image CDN is a service that provides a URL API for transforming images. This is often used to resize images on the fly, but can also be used to apply other transforms such as cropping, rotation, compression, etc. This includes dedicated image CDNs such as Imgix and Cloudinary, CMSs such as Contentful, Builder.io and Sanity, general CDNs such as Bunny.net that provide an image API, but also other service providers such as Shopify. The CMSs and other service providers often use a dedicated image CDN to provide the image API, most commonly Imgix. In most cases they support the same API, but in others they may proxy the image through their own CDN, or use a different API.
  • Why would I use this instead of the CDN's own SDK? If you you know that your images will all come from one CDN, then you probably should use the CDN's own SDK. This library is designed to work with images from multiple CDNs, and to work with images that may or may not be from a CDN. It is particularly useful for images that may come from an arbitrary source, such as a CMS. It is also useful for parsing URLs that may already have transforms applied, because most CDN SDKs will not parse these URLs correctly.
  • Can you add support for CDN X? If it supports a URL API and doesn't require signed URLs then yes, please open an issue or PR.
  • Can you add my domain to CDN X? If you provide a service where end-users use your URLs then probably. Examples may be image providers such as Unsplash, or CMSs. If it is just your own site then probably not. You can manually specify the CDN in the arguments to transformUrl and parseUrl.
  • Can you support more params? We deliberately just support the most common params that are shared between all CDNs. If you need more params then you can use the CDN-specific API directly.
  • Why do you set auto format? If the CDN support is, and no format is specified in transformUrl, the library will remove any format set in the source image, changing it to auto-format. In most cases, this is what you want. Almost all browsers now support modern formats such as WebP, and setting auto-format will allow the CDN to serve the best format for the browser. If you want to force a specific format, you can set it in transformUrl.
  • Do you support SVG, animated GIF etc? If the CDN supports it, then yes. We don't attempt to check if a format is valid - we will just pass it through to the CDN. If the CDN doesn't support it, then it will return an error or a default.
  • Do you support video, etc No, this library is only for images. If you pass a video URL to transformUrl, it will return undefined, as it will for any URL that is not recognised as an image CDN URL. It is up to you to handle this case.

Contributing

See the contributing guide.

unpic's People

Contributors

ascorbic avatar benmccann avatar brianbento avatar danestves avatar davedbase avatar davocg avatar eladroz avatar erikras avatar floydjones1 avatar gbouteiller avatar github-actions[bot] avatar guillaumelachaud avatar jlarmstrongiv avatar mixie-bot[bot] avatar monsterdeveloper avatar moritzgruber avatar nicksrandall avatar ref-thomasledoux1 avatar rohanraina513 avatar steve8708 avatar tiptenbrink avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

unpic's Issues

Add Deno basics to CONTRIBUTING.md

I can't figure out how to build or test this project. I'm assuming I need to install Deno, but then I'm lost after that. From what I've read on the deno site it seems I don't need to do the equivalent of npm install? But then there's also no package.json defining the scripts, so I don't know what to run to build or test the project.

Pass in own transformer

I was thinking it would be nice for a consumer to be able to pass their own transformer to the transformUrl function. Then in turn, allow unpic-img to do the same thing.

Thoughts?

getNumericParam adds 1 to value

This is your code for getNumericParam which is used to parse URLs of some CDNs like bunny and wordpress:

export const getNumericParam = (url: URL, key: string) => {
  const value = Number(url.searchParams.get(key));
  return isNaN(value) ? undefined : value + 1;
};

I noticed this as I wanted to add CDN support for KeyCDN and was designing tests. Why is 1 added to the value of the parameter? This is not caught in any tests as none of the CDNs which use this function to parse URLs actually test their URL parsers.

I also admit I'm unsure for what the URL parsers are designed to accomplish. This would help me to know what I need to add.

Thanks!

Core as a set of plugins

We just launched @sveltejs/enhanced-img for static images included in the user's project and are now trying to figure out how to handle images from CDNs. I had proposed using @unpic/svelte as our solution there. However, an objection came up that it includes transformers and parsers for all CDNs instead of just the one that the user needs.

It would be nice if we could make core a set of plugins where the user could register just the CDNs they're going to use. Optionally, we could provide a convenience method which registers all providers.

It looks like including the library currently adds about 38 kB unminified and we could probably cut that down a fair amount.

#82 is a bit related to this idea as well

If there's some level of interest, I may be able to contribute to this

Can't recognize a valid cloudinary url

Describe the bug
Using @unpic/svelte (but also happens with other unpic libs) there are some cloudinary URL that are not recognized and flagged as invalid

To Reproduce
Steps to reproduce the behavior:

  1. Go to https://stackblitz.com/edit/vitejs-vite-qvjc1v?file=src/App.svelte
  2. Check the console output
  3. See error

Expected behavior
The image should be rendered
Find the image here

Screenshots

Screenshot 2023-05-15 at 21 12 07

Desktop (please complete the following information):

  • OS: macos
  • Browser Arc
  • Version Version 0.101.2 (38549)
  • Chromium Engine Version 112.0.5615.137

Cloudinary SEO Suffix

Cloudinary has support for something called an SEO suffix. The idea is, you can customize the end of your image URL where the public ID would normally be, to add something SEO friendly.

Think instead of something like .../image/upload/oaisnd143, you can do .../images/oaisnd143/cool-image-with-stuff

I just added support for it in my little tool: cloudinary-community/cloudinary-util#19

Here's the updated regex:

https://github.com/colbyfayock/cloudinary-util/blob/main/packages/util/src/lib/cloudinary.ts#L2

HOWEVER

my regex requires a version in the URL so make parsing more predictible which I dont think yours is currently doing. Might be able to still get away with it, but regex isn't my thing, which is why I'm not putting in a PR instead of this 🙈

Relevant docs: https://cloudinary.com/documentation/advanced_url_delivery_options#seo_friendly_media_asset_urls

Empty types file

I noticed that in the distributed package that esm/src/mod.ts exports esm/src/types.js which is basically an empty file which just has export {}; so I thought maybe this could be removed

Bug: regex won't match valid cloudflare image url's

Hello,

Very nice library.
I'm trying to setup unpic img on my project but I'm hitting 1 problems with my url, and would like to check if it's by design or am I doing something wrong?

This is what my project uses on cloudflare images:
https://www.domain.com/cdn-cgi/imagedelivery/96E5KstIXRryMlVJjlvC4g/imagens/10382508/07aa3288-fb73-4bf7-af55-3f6cb10a6416/public

This is the regex being used to cloudflare matching that I could find on the code
/https?:\/\/(?<host>[^\/]+)\/cdn-cgi\/imagedelivery\/(?<accountHash>[^\/]+)\/(?<imageId>[^\/]+)\/*(?<transformations>[^\/]+)*$/g

host = https://www.domain.com/
static cloudflare path = /cdn-cgi/imagedelivery/
account hash = 96E5KstIXRryMlVJjlvC4g
image patch = /imagens/10382508/07aa3288-fb73-4bf7-af55-3f6cb10a6416
transformations = /public

On cloudflare side, this url works perfectly. but it looks like this regex only allows single path image, If I try to pass only a single path image to unpic it works. (ex https://www.domain.com/cdn-cgi/imagedelivery/96E5KstIXRryMlVJjlvC4g/07aa3288-fb73-4bf7-af55-3f6cb10a6416/public works)

Is this something that could be fixed on unpic side?

Thanks for your time.

Astro types broken

image
../../node_modules/@unpic/astro/index.ts:4:34 - error TS2307: Cannot find module './src/Image.astro' or its corresponding type declarations.

4 export { default as Image } from "./src/Image.astro";
                                   ~~~~~~~~~~~~~~~~~~~

../../node_modules/@unpic/astro/index.ts:5:35 - error TS2307: Cannot find module './src/Source.astro' or its corresponding type declarations.

5 export { default as Source } from "./src/Source.astro";
                                    ~~~~~~~~~~~~~~~~~~~~


Found 2 errors in the same file, starting at: ../../node_modules/@unpic/astro/index.ts:4

npm ERR! Lifecycle script `typecheck` failed with error:
npm ERR! Error: command failed

feature: support Astro transformer

Implementation examples:

Astro image

Example

I will work on a PR to support the Astro _image endpoint with unpic. Astro supports many of the frontend frameworks that unpic supports, so making them work together would be a win-win

How can I display private domain cdn images?

Hello,

I'm trying to use import { Image } from '@unpic/react/nextjs';

My source comes from a private domain on my customer network, how can I disable cdn check so that my image is well displayed ? For now it comes with next/image and the url is encoded

Thanks for help

Support for Vercel

Docs here: https://vercel.com/docs/concepts/image-optimization

I'd love to see support for Vercel. I've been thinking about making a semi-official Svelte image component for awhile. I like the idea of how unpic works and was thinking it might be cool to use it if deploying to Vercel or Cloudflare and fallback to vite-imagetools if the user doesn't have a CDN.

Support Cloudflare Images

/https?:\/\/(?<host>[^\/]+)\/cdn-cgi\/image\/(?<transformations>[^\/]+)\/(?<path>.*)$/g;

I believe that Cloudflare images, when served via a custom domain, are delivered with:

https://example.com/cdn-cgi/imagedelivery/<ACCOUNT_HASH>/<IMAGE_ID>/<VARIANT_NAME>

Correct me if I'm wrong, but I believe this RegEx is only checking for /cdn-cgi/image rather than /cdn-cgi/imagedelivery. Would also be great to also check for the default Cloudflare image delivery URL if you're not using a custom domain:

https://imagedelivery.net/<ACCOUNT_HASH>/<IMAGE_ID>/<VARIANT_NAME>

Link to relevant docs

Working with Cloudinary video thumbnails

There are two different ways to convert a Cloudinary video URL to a thumbnail image of the video:

  • Replace the file extension
  • Keep the file extension, but use f_jpg (or similar) transformation

In both cases, the assetType captured by this regex is video, which leads to an unsupported error.

Video thumbnails feel like a valid use case, and I'm happy to do the work to add the support.

But what's the right logic? Is it too opinionated to say if it's a video, then we check for a valid image extension on the URL?

Add support for wsrv.nl

wsrv.nl is a free serice that's been running since 2007. Works great and is also open source. Would love to see support for it in unpic.

Dynamic Cloudinary url gets truncated in Unpic srcset

Hi,
I'm effectively using Unpic React in my project but I'm encountering an issue in using a "dynamic url".
An example of such url is the following:

<Image
src='https://res.cloudinary.com/dxfqd0qn4/image/upload/w_1200,h_630,c_fill,f_auto/l_00_excel-dashboard-slicers-filtri-interattivi_vbztau.png/c_scale,fl_relative,w_0.37/fl_layer_apply,g_east,x_50/w_570,h_450,c_fit,co_rgb:FFFFFF,g_west,x_45,y_-50,l_text:montserrat_50_bold:Excel%20Dashboard%3A%20Filtri%20dati%20interattivi%20(slicer)%20per%20tabelle%20e%20grafici%20pivot./og-image-blog-article-template'
width={1200}
height={630}
/>

inspecting with the browser I see that it gets truncated after the dot in pivot. and becomes:

https://res.cloudinary.com/dxfqd0qn4/image/upload/c_fill,w_640,h_384,f_auto/l_00_excel-dashboard-slicers-filtri-interattivi_vbztau.png/c_scale,fl_relative,w_0.37/fl_layer_apply,g_east,x_50/w_570,h_450,c_fit,co_rgb:FFFFFF,g_west,x_45,y_-50,l_text:montserrat_50_bold:Excel%20Dashboard%3A%20Filtri%20dati%20interattivi%20(slicer)%20per%20tabelle%20e%20grafici%20pivot

Strangely, If i use a ts function to generate the url, as I should:

<Image
src={generateOgImage({
title: post.title,
featuredImage: post.featuredImageUrl.slice(
post.featuredImageUrl.lastIndexOf('/') + 1
),
cloudName: 'dxfqd0qn4',
imagePublicID: 'og-image-blog-article-template',
})}
width={1200}
height={630}
/>

it gets truncated after the prevoius dot in w_0.37 and thus becomes:

https://res.cloudinary.com/dxfqd0qn4/image/upload/c_fill,w_640,h_384,f_auto/l_00_ciclo-for-each-macro-excel_kxhmym.png/c_scale,fl_relative,w_0

cloudinaryRegex not covering all requirements

https://cloudinary.com/documentation/advanced_url_delivery_options#private_cdns_and_cnames

I am horrible at Regex or I would help fix this

const cloudinaryRegex =

Test Cases

Basic
https://res.cloudinary.com/demo/image/upload/c_lfill/dog

Private
https://private-name.cloudinary.com/demo/image/upload/c_lfill/dog

Custom Cname
https://media.codingcat.dev/ajonp/image/upload/v1658570793/main-codingcatdev-photo/CSS-Animations.png

Because I leave mine open this is equivalent to
https://res.cloudinary.com/ajonp/image/upload/v1658570793/main-codingcatdev-photo/CSS-Animations.png

Right now this does not pass the regex test so it is not getting the correct srcsets

Sanity: "Parameter \"w\" must be a valid integer"

Using this with sanity result in this error {"statusCode":400,"error":"Bad Request","message":"Parameter \"w\" must be a valid integer"}. This is due to the URL being regenerated containing a non-integer: https://cdn.sanity.io/images/[REMOVED].jpg?w=230.1779359430605&h=442&fit=min&auto=format.

import { Image } from "@unpic/react";

<Image
	src={image.asset.url}
	layout="constrained"
	height={120}
	aspectRatio={getImageDimensions(image).aspectRatio}
/>

Astro transformer format

I may be misunderstanding how the transformers or Astro components work, but I’m not sure how to set webp as the format. The url param f=webp doesn’t work, and I can’t seem to get the <Source /> component to work either (maybe I’m setting type="image/webp" incorrectly?). Anyway, tips to get the webp format working are much appreciated.

The <Image /> component works great otherwise (once the typing is fixed)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.