Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Helper for responsive images #24

Closed
JosefJezek opened this issue Jul 10, 2016 · 15 comments
Closed

Helper for responsive images #24

JosefJezek opened this issue Jul 10, 2016 · 15 comments
Labels
New Project Idea Ideas for a new, standalone module.

Comments

@JosefJezek
Copy link

JosefJezek commented Jul 10, 2016

Could you create helper for responsive images using DPR + WebP?

Check out this resources...

@jeffposnick jeffposnick added the New Project Idea Ideas for a new, standalone module. label Jul 13, 2016
@addyosmani
Copy link
Member

@mullens has kindly volunteered to work on a design doc for this one and was interested in hacking on it. (Lucas I've sent you an invite to the repo so we can get you assigned once accepted) :)

@addyosmani addyosmani modified the milestone: sw-goog / Service Worker Tooling.next Oct 18, 2016
@addyosmani
Copy link
Member

Talked to @mullens about status. A few docs have been worked on trying to explore the design. We're going to try to nail down some milestones around this work we can aim for shortly.

@addyosmani addyosmani removed the New Project Idea Ideas for a new, standalone module. label Mar 31, 2017
@addyosmani
Copy link
Member

@mullens when you get a chance, could we share an external friendly copy of the research + conclusions you reached and the next steps forward? ⭐️

@addyosmani
Copy link
Member

@mullens ended up implementing a proof-of-concept of this idea and is going to keep iterating on it as time allows. We're going to close this issue up as it was explored and will continue looking at how that piece fits into the overall workbox collection next.

@JosefJezek
Copy link
Author

@addyosmani any idea?

@jeffposnick
Copy link
Contributor

I'm going to reopen this as we never did end up shipping anything, and it's come up again recently.

@jeffposnick jeffposnick reopened this Aug 23, 2018
@jeffposnick jeffposnick added the New Project Idea Ideas for a new, standalone module. label Aug 23, 2018
@tpiros
Copy link

tpiros commented Aug 23, 2018

From my perspective it'd be a great addition to workbox to access the network information API as based on the results regarding the user's connection it'd be trivial to return optimised images. Here's what I'm thinking: I am using Cloudinary to store my images and Cloudinary has a feature which allows developers to return images in the most optimised way - for example if the user accesses the site via Chrome it returns a WebP image. Now, it'd be great if I could further control this and say, if the user is accessing the site via Chrome and from a 2G or a 3G connection, return a WebP with a quality of 40 for faster load, whereas if they have a 4G connection I wouldn't care much about the quality but still would return the WebP.

Based on this approach I could also cache the appropriate files - for 2G users, cache a lower quality version of the image using Workbox.

@jeffposnick
Copy link
Contributor

@tpiros, what's the mechanism for requesting specific formats/qualities of images in Cloudinary? Is it URL query parameters, headers, or something else?

@tpiros
Copy link

tpiros commented Aug 24, 2018

@jeffposnick it's based on the URL. So for example here's a full image https://res.cloudinary.com/idemo/image/upload/woman.jpg and here's https://res.cloudinary.com/idemo/image/upload/q_20/woman.jpg a lower quality version (quality reduced by 80%)

@jeffposnick
Copy link
Contributor

jeffposnick commented Aug 24, 2018

From my perspective it'd be a great addition to workbox to access the network information API as based on the results regarding the user's connection it'd be trivial to return optimised images.

Gotcha. Well, if you want to experiment, here's some completely untested code that I threw together which might be a starting point:

const cloudinaryPlugin = {
  requestWillFetch: async ({request}) => {
    // Proof-of-concept with hacky URL manipulation.
    const urlParts = request.url.split('/');
    let newPart;
    // Client hints, etc. could also be consulted.
    switch ((navigator && navigator.connection) ? navigator.connection.effectiveType : '') {
      case '4g':
        newPart = 'q_40';
      break;

      case '3g':
        newPart = 'q_30';
      break;

      case'2g':
      case 'slow-2g':
        newPart = 'q_20';
      break;

      default:
        // Come up with some sort of reasonable default.
        newPart = 'q_30';
      break;
    }

    const newUrl = urlParts
      .splice(urlParts.length - 1, 0, newPart)
      .join('/')
      // Hacky JPG => PNG replacement. Maybe check for WebP?
      .replace('.jpg', '.png');

    const newRequest = new Request(newUrl.href, {headers: request.headers});

    // I'm assuming Cloudinary supports CORS?
    // Forcing the image requests to use CORS would avoid opaque response
    // storage bloat: https://stackoverflow.com/a/39109790/385997
    newRequest.mode = 'cors';

    return newRequest;
  },
};

workbox.routing.registerRoute(
  // This RegExp could also be published in a module,
  // if there's a canonical one to use.
  new RegExp('^https://.+\.cloudinary\.com'),

  workbox.strategies.cacheFirst({
    cacheName: 'cloudinary-images',
    plugins: [
      // Add in the cloudinaryPlugin.
      cloudinaryPlugin,

      // Mix in any of the other plugins.
      workbox.expiration.Plugin({
        maxEntries: 50,
        purgeOnQuotaError: true,
      }),
    ],
  })
);

The nice thing is that this plugin is just building on top of the standard request/response lifecycle events that Workbox exposes, and doesn't have to do anything "official" to register with Workbox or anything. If Cloudinary thinks that code along those lines would be useful for your users (and it seems like it would be!) you could publish your plugin, and folks can just mix it in to their current Workbox code.

Based on this approach I could also cache the appropriate files - for 2G users, cache a lower quality version of the image using Workbox.

That's a very valid use case that we haven't had time to work on. We've been using #155 for discussion.

@tpiros
Copy link

tpiros commented Aug 28, 2018

@jeffposnick sorry for the late response - this looks great. I will give this a go. At the moment I'm juggling a few projects but will get to this as well and update you. I do like this approach. It's a similar one to what I saw here: https://deanhume.com/dynamic-resources-using-the-network-information-api-and-service-workers/

@tpiros
Copy link

tpiros commented Aug 30, 2018

@jeffposnick I tried your example (with a few changes) but unfortunately I couldn't get it working. The reason is that I can't seem to be able to modify the Request. I saw in the Workbox docs that I should be able to ("requestWillFetch: This is called whenever a fetch event is about to be made. You can alter the Request in this callback.") but it doesn't seem to work. Here's what I have observed.

  1. Load page
  2. Reload page with "Fast 3G" connection selected

I added a few log statements to print the URL and the old and new requests and as you can see the requests are the same even though the URL has changed

Connection:  NetworkInformation {downlink: 1.6, effectiveType: "3g", onchange: null, rtt: 600, saveData: false}
*new url  https://res.cloudinary.com/tamas-demo/image/upload/pwa/q_30/konta.jpg
*request Request {method: "GET", url: "https://res.cloudinary.com/tamas-demo/image/upload/pwa/konta.jpg", headers: Headers, destination: "image", referrer: "http://localhost:8081/", …}
*newRequest Request {method: "GET", url: "https://res.cloudinary.com/tamas-demo/image/upload/pwa/konta.jpg", headers: Headers, destination: "", referrer: "http://localhost:8081/", …}

Here's the corresponding code - I am assigning the new URL to the new request.

      console.log('new url ', final);
      
      const newRequest = new Request(request);
      newRequest.url = final;
      newRequest.mode = 'cors';
      console.log('request', request);
      console.log('newRequest', newRequest);
      return newRequest;

Any thoughts?

@tpiros
Copy link

tpiros commented Aug 30, 2018

Actually I think I figured it out:

  const newUrl = new URL(final);
  console.log(new Request(newUrl.href, {headers: request.headers})); // this now returns the right URL
  return new Request(newUrl.href, {headers: request.headers});

@jeffposnick
Copy link
Contributor

@tpiros Oops, sorry about the Request construction issues. I do tend to forget which info gets copied depending on how it's constructed. I've updated my original code snippet to match what you're doing.

Glad that things are working, and looking forward to seeing this publicized a bit!

@jeffposnick
Copy link
Contributor

I'm going to close this as https://www.npmjs.com/package/cloudinary-workbox-plugin exists for Cloudinary, and I think that model works best—instead of Workbox's official repo hosting provider-specific plugins, providers can publish their own.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
New Project Idea Ideas for a new, standalone module.
Projects
None yet
Development

No branches or pull requests

4 participants