Git Product home page Git Product logo

ember-responsive-image's Introduction

ember-responsive-image

CI npm version Ember Observer Score

An ember addon to automatically generate resized images at build-time, optimized for the responsive web, and using components to render them easily as <picture> elements.

Key Features

๐ŸŒ‡ Supports basic PNG and JPEG formats, as well as next-gen WebP and AVIF, for best image quality at low file sizes.

๐ŸŽ Super fast image processing, thanks to the awesome sharp library.

๐Ÿ“ฑ Layout modes for fixed sizes (with 1x and 2x image variants) as well as responsive layouts (srcset with optimized image sizes across all devices).

๐ŸŒ Besides processing of local images, it also supports integrating remote images from image CDNs like Cloudinary or imgix using a versatile image provider abstraction

๐Ÿ’ฏ Lazy rendering by default, with optimized content-visibility and decoding settings and optimized markup, to prevent CLS (Cumulative Layout Shift), a core Web Vital and Lighthouse metric.

โณ Supports advanced LQIP (Low Quality Image Placeholder) techniques to show a preview while loading, using different configurable strategies like a blurry low-res image, BlurHash or a simple dominant color.

โœจ Octane/Polaris-based, v2 addon using custom webpack-based loader for local image processing, written in TypeScript including Glint types, supporting FastBoot and Embroider, and fully tested.

๏ธโš™ Flexible configuration options

Advanced optimization techniques inspired amongst others by the blog post Maximally optimizing image loading for the web in 2021.

Compatibility

  • Ember.js v4.8 or above
  • ember-auto-import >= 2.7.1 or Embroider using @embroider/webpack

Preface

The <ResponsiveImage/> component provided with this addon expects to receive image data for the image it is supposed to display. Unlike a simple <img> tag, it will need more data than just the URL to a single image: it needs multiple images in different resolutions and image formats, but also some additional meta data like the aspect ratio or data related to more advanced use cases like Low Quality Image PLaceholders.

This data can come from different sources. The most common one is to let the build plugins provided by @ember-responsive-image/webpack process local images (i.e. static images that you have in your git repo) and provide the necessary data. But you can also have the processed image data come from other sources like Image CDNs, see Image Providers.

For the remainder of this documentation we will assume you will be dealing with local images using @ember-responsive-image/webpack.

Getting started

Installation

In your application's directory:

npm install ember-responsive-image @ember-responsive-image/webpack
// or
yarn add ember-responsive-image @ember-responsive-image/webpack
// or
pnpm add ember-responsive-image @ember-responsive-image/webpack

Setting up Webpack

As explained above, @ember-responsive-image/webpack is provided for the Webpack-native build integration. Webpack is used in Ember apps, but in different ways depending on whether you are using Embroider already or a classic Ember CLI build with ember-auto-import.

In either case we need to tell Webpack which files it needs to process using the addon's custom webpack loaders. We do this by setting up a module rule such as this:

{
  module: {
    rules: [
      {
        resourceQuery: /responsive/,
        use: require('@ember-responsive-image/webpack').setupLoaders(),
      },
    ],
  }
}

This is telling Webpack that any import that contains responsive in its query part such as import image from './path/to/image.jpg?responsive' will be processed by the addon's loaders. You could configure this rule also in different ways, but this way you can use ember-responsive-image without any conflicts with other imports of asset files using Webpack's built-in asset modules.

Embroider

To apply this configuration to an Embroider-powered Ember app, edit your ember-cli-build.js file and pass the Webpack config using the options argument of compatBuild:

const { Webpack } = require('@embroider/webpack');
return require('@embroider/compat').compatBuild(app, Webpack, {
  packagerOptions: {
    webpackConfig: {
      module: {
        rules: [
          {
            resourceQuery: /responsive/,
            use: require('@ember-responsive-image/webpack').setupLoaders(),
          },
        ],
      },
    },
  },
});

For more information on how to configure @ember-responsive-image/webpack and setupLoaders() refer to the @ember-responsive-image/webpack documentation.

Classic build with ember-auto-import

In a classic build with ember-auto-import (make sure you are at least on version 2.7.1!), we pass the Webpack config to the autoImport options:

let app = new EmberApp(defaults, {
  autoImport: {
    allowAppImports: ['images/**/*'],
    webpack: {
      module: {
        rules: [
          {
            resourceQuery: /responsive/,
            use: require('@ember-responsive-image/webpack').setupLoaders(),
          },
        ],
      },
    },
  },
});

For more information on how to configure @ember-responsive-image/webpack and setupLoaders() refer to the @ember-responsive-image/webpack documentation.

Note the use of allowAppImports here, which is a way to make the build use ember-auto-import and thus Webpack to handle the files configured by the glob pattern of this configuration option. You can place the images files in a central subfolder under /app, like app/images as in this example, or even colocate them next to other JavaScript files by targeting specific image extensions instead of certain folders (e.g. **/*/*.jpg). Either way make sure that image files you import for use by ember-responsive-image are correctly covered by at least one glob pattern passed to allowAppImports!

TypeScript usage

Glint

All components and helpers have proper Glint types, which allow you when using TypeScript to get strict type checking in your templates.

Unless you are using strict mode templates (via <template> tag), you need to import the addon's Glint template registry and extend your app's registry declaration as described in the Using Addons documentation:

import '@glint/environment-ember-loose';

import type ResponsiveImageRegistry from 'ember-responsive-image/template-registry';

declare module '@glint/environment-ember-loose/registry' {
  export default interface Registry
    extends ResponsiveImageRegistry /* other addon registries */ {
    // local entries
  }
}

Should you want to manage the registry by yourself, then omit this import, and instead add the entries in your app by explicitly importing the types of the components and helpers from this addon.

Image imports

To make TypeScript understand our image imports, we tag them using a responsive query parameter, that has to come last!

We cannot use something like *.jpg* that works with queries, as TS only supports a single wildcard. See microsoft/TypeScript#38638

Add this declaration to a file, e.g. your app's types/global.d.ts:

declare module '*responsive' {
  import { ImageData } from 'ember-responsive-image';
  const value: ImageData;
  export default value;
}

Basic Usage

Importing images

When Webpack is set up correctly as explained above, we can start to import images that are then being processed by @ember-responsive-image/webpack:

import heroImage from './hero.jpg?responsive';

Again, when following our conventional setup, we need the responsive query param to be passed so that our Webpack loaders are correctly invoked. But advanced Webpack users can also configure this in different ways.

In addition to that, we can also pass query params that affect the actual image processing:

import heroImage from './hero.jpg?lqip=inline&widths=1920,1280,640&responsive';

In this case we are processing this image for only this specific import with different image options than the defaults, as we generate the image variants with specific widths and opt into a Low Quality Image Placeholder technique of inline. This applies only to the image data you get back from this specific import, but does not affect any of the other images or even the same image but with different or just the default image options imported elsewhere!

The <ResponsiveImage/> component

In a template you can use the <ResponsiveImage/> component. The @src argument is required and must contain the necessary image data:

<ResponsiveImage @src={{this.heroImage}} />

Note that with components with separate .js and .hbs files, you would need to assign the image data to the backing component class, so you can access it in your template as in this case as this.heroImage:

import heroImage from './hero.jpg?lqip=inline&widths=1920,1280,640&responsive';

export default class HeroImageComponent extends Component {
  heroImage = heroImage;
}

With <template> tag and .gjs (.gts) components, this becomes much easier:

import heroImage from './hero.jpg?lqip=inline&widths=1920,1280,640&responsive';

<template>
  <ResponsiveImage @src={{heroImage}} />
</template>

Rendering this component will generate an <img> element wrapped in <picture> referencing all the resized images in the different formats, for the browser to decide which image it can support and fits best given the current context (device, screen size, user preferences like low bandwidth etc.):

<picture>
  <source
    srcset="
      /assets/images/hero1920w.avif 1920w,
      /assets/images/hero1280w.avif 1280w,
      /assets/images/hero640w.avif   640w
    "
    type="image/avif"
  />
  <source
    srcset="
      /assets/images/hero1920w.webp 1920w,
      /assets/images/hero1280w.webp 1280w,
      /assets/images/hero640w.webp   640w
    "
    type="image/webp"
  />
  <source
    srcset="
      /assets/images/hero1920w.jpg 1920w,
      /assets/images/hero1280w.jpg 1280w,
      /assets/images/hero640w.jpg   640w
    "
    type="image/jpeg"
  />
  <img
    src="1920"
    height="1280"
    class="eri-responsive"
    loading="lazy"
    decoding="async"
  />
</picture>

The image in the src attribute is calculated by the component and will be used by browsers without <picture> support - which is basically IE11.

If your image width is not 100vw, say 70vw for example, you can specify the @size (only vw is supported as a unit for now):

<ResponsiveImage @src={{heroImage}} @size={{70}} />

This will render the corresponding sizes attribute on all <source> elements. You can also set the attribute like this if your responsive image width is more complicated:

<ResponsiveImage @src={{heroImage}} @sizes='(min-width: 800px) 800px, 100vw' />

Fixed layout

The example above assumed you wanted a responsive image, i.e. one that automatically takes the whole available width of its parent element. This is the default mode, and will automatically add the following CSS to you image:

img {
  width: 100%;
  height: auto;
}

But this addon also supports a fixed layout with fixed image dimensions. Just provide either @width or @height to opt into that mode. Also make sure that the generated image variants have the appropriate sizes:

import logoImage from './hero.jpg?lqip=inline&widths=320,640&responsive';

<ResponsiveImage @src={{logoImage}} @width={{320}} />

It will still render a <img> wrapped in a <picture>, but this time it will provide the image with the optimal width (smallest width which is equal or above the target width), and additionally a 2x variant for devices with high pixel densities:

<picture>
  <source
    srcset="/assets/images/hero320w.avif 1x, /assets/images/hero640w.avif 2x"
    type="image/avif"
  />
  <source
    srcset="/assets/images/hero320w.webp 1x, /assets/images/hero640w.webp 2x"
    type="image/webp"
  />
  <source
    srcset="/assets/images/hero320w.jpg 1x, /assets/images/hero640w.jpg 2x"
    type="image/jpeg"
  />
  <img
    src="/assets/images/hero320w.jpg"
    width="320"
    height="213"
    class="eri-fixed"
    loading="lazy"
    decoding="async"
  />
</picture>

Note it is sufficient to supply either @width or @height, the component will still render the missing attribute according to the image's aspect ratio!

Image formats

Besides the basic PNG and JPEG also the next-gen formats WebP and AVIF are supported. Every modern browser supports WebP. AVIF is also pretty well supported, but takes longer to generate. Given the way multiple formats are supported using this addon as described above, browsers that support one of those will load them, while other will fallback to basic PNG/JPEG.

To give you an idea of the improvements possible with these formats, here are some examples. Note that the images might not show correctly if you are not using a modern browser.

A more detailed analysis can be found on Jake Archibald's blog.

JPEG

Dimensions: 640px ร— 427px File size: 16KB.

a dog near the costline

WebP

Dimensions: 640px ร— 427px File size: 10KB.

a dog near the costline

AVIF

Dimensions: 640px ร— 427px File size: 7KB.

a dog near the costline

Note: Generating AVIF files can take a lot of time, as it is very CPU-intensive. Therefore the format is currently not enabled by default. You would have to opt-in, by defining the formats configuration option to include AVIF.

LQIP

Low Quality Image Placeholder is a technique to give users a preview of the image while it is loading. This addon supports different types, all with their own tradeoffs. Based on the cute dog you saw above, you can see here how these different techniques will look like.

See the Webpack Configuration section for how to configure these.

Color

This is the most basic technique, calculating the dominant color of the image, and setting it as the background color of the images while it loads. The "cost" is basically 7bytes, for the hex code of the color.

dominant color LQIP of a dog image

See the Webpack Configuration section for how to configure this.

Inline

This creates a very small thumbnail of the original image, wraps it into a SVG and applies a blurry filter. This is then set as a base64 encoded data-URL as the background of the image while it loads. The example below consumes 348 bytes (uncompressed).

blurry LQIP of a dog image

See the Webpack Configuration section for how to configure this.

Blurhash

BlurHash is an encoding algorithm and library, dedicated for the very purpose of generating nice looking blurry placeholders, without the overhead of a real image format, which was never optimized for that kind of tiny images. This example consumes just 40 bytes (uncompressed).

blurry LQIP of a dog image

But the tradeoff here is that it needs a runtime library for decoding, which takes about 4.7KB (1.9KB compressed). Therefore it is less suited if you have just a few images, but shines if you need placeholders for a lot!

Image Providers

So far we have only dealt with local images - static images that are commonly part of your app's git repo and get processed by this addon during the build process. But this addon provides even a more versatile abstraction to use any kind of (remote) images: image providers.

All the <ResponsiveImage @src={{imageData}}/> component needs is an ImageData structure, which contains some meta data for a given image, and a function to compute the actual URL for each referenced image, based on its width and type. This is what importing an image using Webpack loaders returns as explained above, but it is not restricted to that. You could pass that data structure as a static POJO, or generate it more dynamically using a simple function (helper).

Simply pass the result of the helper as the @src of the component:

<ResponsiveImage @src={{some-image-provider 'some/image.jpg'}} />

Besides local images, providers allow using also remote images. The most common use case is to load images from an image CDN, that is then used to offload all image processing to the Cloud. Moreover, this allows for dynamic image processing, in cases where your images are not available at build-time. For example you could have an ember-data model refer to the raw (large, unprocessed) image, and use an image CDN as a proxy to scale, optimize and deliver that image as needed, at runtime.

This addon comes with additional packages for these image providers, please refer to their documentation for additional details:

Configuration

The configuration of the main ember-responsive-image addon is optional. To do so, add the configuration in your app's config/addons.js file (create it if not existing yet):

// config/addons.js
'use strict';

module.exports = {
  'ember-responsive-image': {
    deviceWidths: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
  },
};

Configuration options:

  • deviceWidths: an array of widths representing the typical screen widths of your user's devices, used when the available image widths are not known beforehand, like when using an image CDN. Default: [640, 750, 828, 1080, 1200, 1920, 2048, 3840]

The options for configuring the processing of local images are handled by the @ember-responsive-image/webpack package, and other options related to image CDNs or BlurHash-support are handled by their respective sub-packages as well, so please refer to their documentation for detailed configuration instructions:

Advanced Usage

The addon provides a service and a helper for more advances usages if required. This is described in detail in the Advanced Usage documentation.

Contributing

See the Contributing guide for details.

License

This project is licensed under the MIT License.

ember-responsive-image's People

Contributors

abhilashlr avatar dajk avatar dependabot-preview[bot] avatar dependabot[bot] avatar elgordino avatar github-actions[bot] avatar levelbossmike avatar mansona avatar michalbryxi avatar nickschot avatar olafura avatar renovate-bot avatar renovate[bot] avatar scathers avatar simonihmig avatar testrocket 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  avatar  avatar

ember-responsive-image's Issues

Reduce image meta size

Status quo

We write meta data as JSON into a <script> element of index.html. This is needed for the service/component to know which sizes (and now also image formats) of a given image are available, and what the URL for each size/format pair is.

Here is an example of a single image's meta data:

image meta
{
  "assets/images/projects/screenshots/ERGO/ergo_screen_05.png":{
    "images": [
      {
        "image": "/assets/images/projects/screenshots/ERGO/ergo_screen_05640w.webp",
        "type": "webp",
        "width": 640,
        "height": 481
      },
      {
        "image": "/assets/images/projects/screenshots/ERGO/ergo_screen_05640w.avif",
        "type": "avif",
        "width": 640,
        "height": 481
      },
      {
        "image": "/assets/images/projects/screenshots/ERGO/ergo_screen_05640w.png",
        "type": "png",
        "width": 640,
        "height": 481
      },
      {
        "image": "/assets/images/projects/screenshots/ERGO/ergo_screen_05320w.webp",
        "type": "webp",
        "width": 320,
        "height": 241
      },
      {
        "image": "/assets/images/projects/screenshots/ERGO/ergo_screen_05320w.avif",
        "type": "avif",
        "width": 320,
        "height": 241
      },
      {
        "image": "/assets/images/projects/screenshots/ERGO/ergo_screen_05320w.png",
        "type": "png",
        "width": 320,
        "height": 241
      },
      {
        "image": "/assets/images/projects/screenshots/ERGO/ergo_screen_05750w.webp",
        "type": "webp",
        "width": 750,
        "height": 564
      },
      {
        "image": "/assets/images/projects/screenshots/ERGO/ergo_screen_05750w.avif",
        "type": "avif",
        "width": 750,
        "height": 564
      },
      {
        "image": "/assets/images/projects/screenshots/ERGO/ergo_screen_05750w.png",
        "type": "png",
        "width": 750,
        "height": 564
      },
      {
        "image": "/assets/images/projects/screenshots/ERGO/ergo_screen_05280w.webp",
        "type": "webp",
        "width": 280,
        "height": 211
      },
      {
        "image": "/assets/images/projects/screenshots/ERGO/ergo_screen_05280w.avif",
        "type": "avif",
        "width": 280,
        "height": 211
      },
      {
        "image": "/assets/images/projects/screenshots/ERGO/ergo_screen_05280w.png",
        "type": "png",
        "width": 280,
        "height": 211
      },
      {
        "image": "/assets/images/projects/screenshots/ERGO/ergo_screen_05590w.webp",
        "type": "webp",
        "width": 590,
        "height": 444
      },
      {
        "image": "/assets/images/projects/screenshots/ERGO/ergo_screen_05590w.avif",
        "type": "avif",
        "width": 590,
        "height": 444
      },
      {
        "image": "/assets/images/projects/screenshots/ERGO/ergo_screen_05590w.png",
        "type": "png",
        "width": 590,
        "height": 444
      },
      {
        "image": "/assets/images/projects/screenshots/ERGO/ergo_screen_051180w.webp",
        "type": "webp",
        "width": 1180,
        "height": 887
      },
      {
        "image": "/assets/images/projects/screenshots/ERGO/ergo_screen_051180w.avif",
        "type": "avif",
        "width": 1180,
        "height": 887
      },
      {
        "image": "/assets/images/projects/screenshots/ERGO/ergo_screen_051180w.png",
        "type": "png",
        "width": 1180,
        "height": 887
      }
    ],
    "lqip": {
      "type": "blurhash",
      "hash": "g9T9Is?bof%MRj?bRj~qofWBofofofj[t7RjWUt7WBWVj]_3ozofj[ozofj[M{ayj[RjWBayWB",
      "width": 7,
      "height": 5
    }
  }
}

Problem

The meta data size can grow considerably. Especially now with 3 image formats generated by default (jpeg/png, webp, avif). While looking closer at the performance impact the introduction of responsive images to the kaliber5 website (see https://github.com/kaliber5/kaliber5-website/pull/218) had, using our Lighthouse-CI server, it became apparent that for pages with no/few images (no potential for improvement) the change actually yielded a perf regression. Specifically longer times for e.g. TTI and blocking time. See https://lhci.kaliber5.de/app/projects/kaliber5-website/compare/39b4ad0b1ac9

After spending quite a bit of time looking into it, and also introducing some improvements here (#177, #178), I am coming to the conclusion that the meta data is mostly the remaining problem responsible for this, occupying bandwidth that delays the loading of the JS assets.

If you look at https://staging.kaliber5.de/de, the meta data account for ~230KB (uncompressed) / 17KB (gzipped). You see that gzip is quite effective for that textual data, but still that 17KB is quite notable. And it doesn't get better with more sizes or images.

Possible solution

The meta data is highly redundant. Removing/shortening all the repetitive stuff (image paths, keys like width etc.) does not yield any significant improvements, as gzip does exactly that for us already. But we could try to be less explicit, as for example the generated file names are all deterministically created. So instead of the above image meta data, it could look like this:

image meta
{
  "assets/images/projects/screenshots/ERGO/ergo_screen_05.png":{
    "widths": [640, 320, 750, 280, 590, 1180],
    "formats": ["png", "webp", "avif"],
    "lqip": {
      "type": "blurhash",
      "hash": "g9T9Is?bof%MRj?bRj~qofWBofofofj[t7RjWUt7WBWVj]_3ozofj[ozofj[M{ayj[RjWBayWB",
      "width": 7,
      "height": 5
    }
  }
}

This would be enough to deduce all the previous information.

There is a caveat though: this would not work with fingerprinting. Images should be fingerprinted in production, and broccoli-asset-rev will do that, but to let the image meta data point to the final images including their fingerprinting hash, the un-fingerprinted full file path must exist there, so it can rewrite it.

The only solution that comes to my mind is to do our own fingerprinting. The generated images only depend on the original one, and their image processing configuration (e.g. quality setting). Creating a hash for esch generated image is actually not necessary. So we could create a hash based on the original image and its configuration and put it into the above meta data. On the build-time side, the image processor would create the generated images including this hash in their file name. On the run-time side, the service/component would know the hash (given in the meta data) and could deduce all required filenames based on the hash, the available sizes and formats.

We must then make sure that broccoli-asset-rev does not operate on our already fingerprinted files. Hopefully this should be possible, by letting our addon modify the app's config of broccoli-asset-rev, and filling/extending the exclude array with globs generated by the list of images we processed.

Support WebP

When starting to try to work on: ember-learn/ember-website#184

Wanted to try WebP for the image format for responsiveness, because by the team the above issue is resolved, WebP will probably be available in all browsers.

[Feature request] Make it possible to export the image meta data as JSON files

I'm creating a portfolio website which has multiple pages with a lot of images each. It would be nice if I could configure this addon in some way that it outputs JSON files with the image meta data instead of embedding it all in the index.html.

That way I could manually fetch these files and add the data to the service on demand. Combined with FastBoot's Shoebox this would allow me to only include the minimum amount of meta data in the HTML body.

Do you think something like this would be a good addition or is it a niche use case?

Copying of images is pretty slow

160 images copied


Build successful - 97020ms.

Slowest Trees                                 | Total               
----------------------------------------------+---------------------
ImageResizer                                  | 93814ms             

The copyImages method extracts the image meta data for each supported width, although that is always the same (for a given file). Probably moving this gm-stuff (https://github.com/kaliber5/ember-responsive-image/blob/master/broccoli-image-writer.js#L99-L105) out of the forEach loop can help to speed things up!

Add support for background images

We will need this for TLW sooner than later! :)

Could be a helper for simple elements and a mixin for components. Logic similar to that of the current implementation of the src attribute.

Probably makes sense to refactor things a bit, i.e. add a service that contains all the pieces (like the supportedWidths) that the component, helper and mixin can use.

Incorrect image paths on Windows

When I build on Windows, I get broken paths to the images, because the paths have backslashes (presumably because this is the Windows file separator) rather than the forward slashes that are required in URLs. I was able to fix this by replacing the backslashes with forward slashes using a regex like this:
let correctImagePath = originalImagePath.replace(/\\/g, '/');

I believe if this was done with image variable in the line linked below, it would fix the problem. There may also be a better solution using a path.posix method rather than path.join (because path.join uses the system separator rather than always using a forward slash).

https://github.com/kaliber5/ember-responsive-image/blob/7108fe95571296f35d7515a8577dacbd9f4debe3/broccoli-image-writer.js#L129

Allow file paths as sourceDir

It would be very useful to be able to assign a file path to the sourceDir configuration. With the current configuration, you are forced to organize your resources in a way that is amenable to defining the configurations per folder, which can make things a little messy.

For instance, on our homepage, we have a hero image that needs to be resized with a specific set of dimensions. There is also a background image on that page that needs a different set of dimensions. We wish to keep these two images in the same assets folder, but process them differently. In order to achieve this with the current ember-responsive-image we must place each image in a folder by itself so that we can define separate configurations.

This has a slight orthogonal relationship to #50

ImageMagick installation instructions

I installed ImageMagick 7 on Windows. I was getting an error when trying to build until I reinstalled and selected the option to "Install legacy utilities (e.g. convert)". The "Install ImageMagick" instructions should be updated to note that installing legacy utilities is required to make this work if you use the ImageMagick installer.

consider making the computed properties `readOnly`

Just skimmed the add-on, and noticed many computed properties. Most (or all) don't make sense to be changed independent of the dependent keys they declare changing. (lets say via .set or some other accidental way).

It may be a matter of taste, but having debug many related heisenbugs I generally recommend computed properties to be marked as readOnly unless ofcourse they explicit support the set case (which in practice is rare).

The easiest way to do this, is as follows:

propertyName: computed('x','y', function() { /* ... code ... */ }).readOnly()

If you don't think this matters, feel free to disregard this issue. Anyways, cool add-on!

Upgrade sharp

Using the old version of the sharp dependency breaks builds on Windows.

Incompatible library version: libvips-cpp.dylib ...

Here is an exception I get when I run $ ember s:

Referenced from: ../node_modules/ember-responsive-image/node_modules/sharp/vendor/lib/libvips-cpp.42.dylib
Reason: Incompatible library version: libvips-cpp.dylib requires version 52.0.0 or later, but libvips.42.dylib provides version 51.0.0
OS: macOS 10.14.2
Node: 10.15.0
NPM: 6.4.1
Ember-cli: 3.7.1 

Support LQIP technique

Some background: https://jmperezperez.com/medium-image-progressive-loading-placeholder/

As we control the image generation at build time as well as the markup, we are in a good position to support something like this with zero effort for the user. Especially rendering the image with a really low-res image that is added inline with a Data URI should be a nice solution, as no additional request is needed.

This idea needs to be fleshed out a bit more though, like downsides of inlining (increases JS payload for every image) vs. additional request etc.

Still slow copy of images

From our TLW project:

[...]

165 images copied

[...]

4 images copied

Build successful (91877ms) โ€“ Serving on http://localhost:4200/

Slowest Nodes (totalTime => 5% )              | Total (avg)         
----------------------------------------------+---------------------
ImageResizer (2)                              | 29775ms (14887 ms)  

I would guess this is caused by the call to extract the size meta data here.

At least the raw file copy time is neglectable (this is just one third of the number of files, but 3x more is still <0.2 seconds):

time cp -pr public/assets/img/responsive ./xxx

real	0m0.048s
user	0m0.002s
sys	0m0.032s

It seems the only thing used from the meta data call is the height, used to calculate the resized height here. But I am not sure we really need the height? For the srcset only the widths are relevant, and they are static (from the config), and don't depend on the image itself.

Using the screen width rather than the viewport width results in too-large images

Hi, thanks for the addon, it's very useful :)

I might have missed there being some reason for this, but using the screen width as the base for picking the image size (in getDestinationWidthBySize()) seems incorrect. On desktop, the screen size isn't necessarily the same as the browser viewport size (since the browser can be resized), which means the helpers, components, etc. that get the "best fitting" image over-estimate the on-page size of the image if the browser isn't fullscreen.

I think it'd make more sense to use $(window).width() (or document.documentElement.clientWidth).

Allow fingerprinting of generated images

This addon allows resolving the fingerprinted asset path, even when generating the path dynamically: https://github.com/RuslanZavacky/ember-cli-ifa. This could solve the current problem having to turn off fingerprinting for our generated images.

We could either add ifa as a dependency to this addon so it is always available. Or dynamically detect its presence and add support for it when available.

There is an issue with removeSourceDir

So, if I put the content and run building process, for some reason it removes img folder from public/assets/ path, even though it logs only dist/assets/.

This is the config I've created:

var defaultConfig = {
  sourceDir: 'assets/img/',
  destinationDir: 'assets/generated',
  quality: 80,
  supportedWidths: [2048, 1536, 1080, 750, 640],
  removeSourceDir: true,
  justCopy: false
};

The problem could be associated with path I inserted into sourceDir, the last slash after img/ but not really sure to be honest.

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Awaiting Schedule

These updates are awaiting their schedule. Click on a checkbox to get an update now.

  • Lock file maintenance

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Ignored or Blocked

These are blocked by an existing closed PR and will not be recreated unless you click a checkbox below.

Detected dependencies

github-actions
.github/workflows/ci.yml
  • actions/checkout v4
  • wyvox/action-setup-pnpm v3
  • actions/checkout v4
  • wyvox/action-setup-pnpm v3
  • actions/checkout v4
  • wyvox/action-setup-pnpm v3
.github/workflows/release.yml
  • actions/checkout v4
  • wyvox/action-setup-pnpm v3
  • changesets/action v1
npm
package.json
  • postinstall-postinstall ^2.1.0
  • @changesets/changelog-github 0.5.0
  • @changesets/cli 2.27.1
  • concurrently 8.2.2
  • prettier 3.2.5
  • node 20.12.1
  • pnpm 8.15.6
packages/blurhash/package.json
  • @embroider/addon-shim ^1.8.7
  • blurhash ^2.0.4
  • @babel/core 7.24.4
  • @babel/preset-typescript 7.24.1
  • @rollup/plugin-babel 6.0.4
  • @rollup/plugin-node-resolve 15.2.3
  • @rollup/plugin-terser 0.4.4
  • @tsconfig/ember 3.0.6
  • @types/node 20.12.7
  • @types/sharp 0.32.0
  • @typescript-eslint/eslint-plugin 7.6.0
  • @typescript-eslint/parser 7.6.0
  • concurrently 8.2.2
  • eslint 8.57.0
  • eslint-config-prettier 9.1.0
  • eslint-plugin-n 17.1.0
  • eslint-plugin-prettier 5.1.3
  • prettier 3.2.5
  • rollup 4.14.1
  • rollup-plugin-copy 3.5.0
  • rollup-plugin-node-externals 6.1.2
  • sharp 0.33.3
  • typescript 5.4.4
  • webpack 5.91.0
  • @ember-responsive-image/webpack ^1.0.0-beta.0
  • webpack ^5.70.0
packages/cloudinary/package.json
  • @embroider/addon-shim ^1.8.7
  • @embroider/macros ^1.0.0
  • decorator-transforms ^1.0.2
  • ember-addon-config ^0.1.0
  • @babel/core 7.24.4
  • @babel/plugin-transform-typescript 7.24.4
  • @babel/runtime 7.24.4
  • @embroider/addon-dev 4.2.1
  • @glimmer/component 1.1.2
  • @glimmer/tracking 1.1.2
  • @glint/core 1.4.0
  • @glint/environment-ember-loose 1.4.0
  • @glint/template 1.4.0
  • @tsconfig/ember 3.0.6
  • @types/ember 4.0.11
  • @types/ember__application 4.0.11
  • @types/ember__array 4.0.10
  • @types/ember__component 4.0.22
  • @types/ember__controller 4.0.12
  • @types/ember__debug 4.0.8
  • @types/ember__destroyable 4.0.5
  • @types/ember__engine 4.0.11
  • @types/ember__error 4.0.6
  • @types/ember__helper 4.0.7
  • @types/ember__modifier 4.0.9
  • @types/ember__object 4.0.12
  • @types/ember__owner 4.0.9
  • @types/ember__polyfills 4.0.6
  • @types/ember__routing 4.0.22
  • @types/ember__runloop 4.0.10
  • @types/ember__service 4.0.9
  • @types/ember__string 3.16.3
  • @types/ember__template 4.0.7
  • @types/ember__test 4.0.6
  • @types/ember__utils 4.0.7
  • @typescript-eslint/eslint-plugin 7.6.0
  • @typescript-eslint/parser 7.6.0
  • @rollup/plugin-babel 6.0.4
  • babel-plugin-ember-template-compilation 2.2.1
  • concurrently 8.2.2
  • ember-template-lint 5.13.0
  • eslint 8.57.0
  • eslint-config-prettier 9.1.0
  • eslint-plugin-ember 12.0.2
  • eslint-plugin-n 17.1.0
  • eslint-plugin-prettier 5.1.3
  • prettier 3.2.5
  • rollup 4.14.1
  • rollup-plugin-copy 3.5.0
  • typescript 5.4.4
  • @glimmer/component ^1.1.2
  • @glimmer/tracking ^1.1.2
  • ember-responsive-image ^5.0.0-beta.0
packages/ember-responsive-image/package.json
  • @ember/render-modifiers ^2.0.0
  • @embroider/addon-shim ^1.8.7
  • @embroider/macros ^1.0.0
  • blurhash ^2.0.0
  • decorator-transforms ^1.0.2
  • ember-addon-config ^0.1.0
  • ember-style-modifier ^4.0.0
  • @babel/core 7.24.4
  • @babel/plugin-transform-typescript 7.24.4
  • @babel/runtime 7.24.4
  • @embroider/addon-dev 4.2.1
  • @glimmer/component 1.1.2
  • @glimmer/tracking 1.1.2
  • @glint/core 1.4.0
  • @glint/environment-ember-loose 1.4.0
  • @glint/template 1.4.0
  • @tsconfig/ember 3.0.6
  • @types/ember 4.0.11
  • @types/ember__application 4.0.11
  • @types/ember__array 4.0.10
  • @types/ember__component 4.0.22
  • @types/ember__controller 4.0.12
  • @types/ember__debug 4.0.8
  • @types/ember__destroyable 4.0.5
  • @types/ember__engine 4.0.11
  • @types/ember__error 4.0.6
  • @types/ember__helper 4.0.7
  • @types/ember__modifier 4.0.9
  • @types/ember__object 4.0.12
  • @types/ember__owner 4.0.9
  • @types/ember__polyfills 4.0.6
  • @types/ember__routing 4.0.22
  • @types/ember__runloop 4.0.10
  • @types/ember__service 4.0.9
  • @types/ember__string 3.16.3
  • @types/ember__template 4.0.7
  • @types/ember__test 4.0.6
  • @types/ember__utils 4.0.7
  • @typescript-eslint/eslint-plugin 7.6.0
  • @typescript-eslint/parser 7.6.0
  • @rollup/plugin-babel 6.0.4
  • babel-plugin-ember-template-compilation 2.2.1
  • concurrently 8.2.2
  • ember-cached-decorator-polyfill 1.0.2
  • ember-modifier 4.1.0
  • ember-template-lint 5.13.0
  • eslint 8.57.0
  • eslint-config-prettier 9.1.0
  • eslint-plugin-ember 12.0.2
  • eslint-plugin-n 17.1.0
  • eslint-plugin-prettier 5.1.3
  • prettier 3.2.5
  • rollup 4.14.1
  • rollup-plugin-copy 3.5.0
  • typescript 5.4.4
  • @glimmer/component ^1.1.2
  • @glimmer/tracking ^1.1.2
  • @ember-responsive-image/blurhash ^1.0.0-beta.0
packages/imgix/package.json
  • @embroider/addon-shim ^1.8.7
  • @embroider/macros ^1.0.0
  • decorator-transforms ^1.0.2
  • ember-addon-config ^0.1.0
  • @babel/core 7.24.4
  • @babel/plugin-transform-typescript 7.24.4
  • @babel/runtime 7.24.4
  • @embroider/addon-dev 4.2.1
  • @glimmer/component 1.1.2
  • @glimmer/tracking 1.1.2
  • @glint/core 1.4.0
  • @glint/environment-ember-loose 1.4.0
  • @glint/template 1.4.0
  • @tsconfig/ember 3.0.6
  • @types/ember 4.0.11
  • @types/ember__application 4.0.11
  • @types/ember__array 4.0.10
  • @types/ember__component 4.0.22
  • @types/ember__controller 4.0.12
  • @types/ember__debug 4.0.8
  • @types/ember__destroyable 4.0.5
  • @types/ember__engine 4.0.11
  • @types/ember__error 4.0.6
  • @types/ember__helper 4.0.7
  • @types/ember__modifier 4.0.9
  • @types/ember__object 4.0.12
  • @types/ember__owner 4.0.9
  • @types/ember__polyfills 4.0.6
  • @types/ember__routing 4.0.22
  • @types/ember__runloop 4.0.10
  • @types/ember__service 4.0.9
  • @types/ember__string 3.16.3
  • @types/ember__template 4.0.7
  • @types/ember__test 4.0.6
  • @types/ember__utils 4.0.7
  • @typescript-eslint/eslint-plugin 7.6.0
  • @typescript-eslint/parser 7.6.0
  • @rollup/plugin-babel 6.0.4
  • babel-plugin-ember-template-compilation 2.2.1
  • concurrently 8.2.2
  • ember-template-lint 5.13.0
  • eslint 8.57.0
  • eslint-config-prettier 9.1.0
  • eslint-plugin-ember 12.0.2
  • eslint-plugin-n 17.1.0
  • eslint-plugin-prettier 5.1.3
  • prettier 3.2.5
  • rollup 4.14.1
  • rollup-plugin-copy 3.5.0
  • typescript 5.4.4
  • @glimmer/component ^1.1.2
  • @glimmer/tracking ^1.1.2
  • ember-responsive-image ^5.0.0-beta.0
packages/webpack/package.json
  • base-n ^3.0.0
  • loader-utils ^3.2.0
  • sharp ^0.33.0
  • @babel/core 7.24.4
  • @babel/preset-typescript 7.24.1
  • @tsconfig/ember 3.0.6
  • @types/node 20.12.7
  • @types/sharp 0.32.0
  • @typescript-eslint/eslint-plugin 7.6.0
  • @typescript-eslint/parser 7.6.0
  • concurrently 8.2.2
  • eslint 8.57.0
  • eslint-config-prettier 9.1.0
  • eslint-plugin-node 11.1.0
  • eslint-plugin-prettier 5.1.3
  • glob 8.1.0
  • prettier 3.2.5
  • rollup 4.14.1
  • rollup-plugin-copy 3.5.0
  • rollup-plugin-node-externals 6.1.2
  • rollup-plugin-ts 3.4.5
  • typescript 5.4.4
  • webpack 5.91.0
  • @ember-responsive-image/blurhash ^1.0.0-beta.0
  • webpack ^5.70.0
  • node 20.* || >= 22
test-app/package.json
  • @babel/core 7.24.4
  • @ember/optional-features 2.1.0
  • @ember/string 3.1.1
  • @ember/test-helpers 3.3.0
  • @embroider/macros 1.15.0
  • @embroider/test-setup 3.0.3
  • @glimmer/component 1.1.2
  • @glimmer/tracking 1.1.2
  • @glint/core 1.4.0
  • @glint/environment-ember-loose 1.4.0
  • @glint/template 1.4.0
  • @tsconfig/ember 3.0.6
  • @types/ember 4.0.11
  • @types/ember-resolver 9.0.0
  • @types/ember__application 4.0.11
  • @types/ember__array 4.0.10
  • @types/ember__component 4.0.22
  • @types/ember__controller 4.0.12
  • @types/ember__debug 4.0.8
  • @types/ember__destroyable 4.0.5
  • @types/ember__engine 4.0.11
  • @types/ember__error 4.0.6
  • @types/ember__helper 4.0.7
  • @types/ember__modifier 4.0.9
  • @types/ember__object 4.0.12
  • @types/ember__owner 4.0.9
  • @types/ember__polyfills 4.0.6
  • @types/ember__routing 4.0.22
  • @types/ember__runloop 4.0.10
  • @types/ember__service 4.0.9
  • @types/ember__string 3.16.3
  • @types/ember__template 4.0.7
  • @types/ember__test 4.0.6
  • @types/ember__utils 4.0.7
  • @types/qunit 2.19.10
  • @typescript-eslint/eslint-plugin 7.6.0
  • @typescript-eslint/parser 7.6.0
  • broccoli-asset-rev 3.0.0
  • ember-auto-import 2.7.2
  • ember-cli 5.7.0
  • ember-cli-app-version 6.0.1
  • ember-cli-babel 8.2.0
  • ember-cli-dependency-checker 3.3.2
  • ember-cli-fastboot 4.1.2
  • ember-cli-fastboot-testing 0.6.1
  • ember-cli-htmlbars 6.3.0
  • ember-cli-inject-live-reload 2.1.0
  • ember-cli-sri 2.1.1
  • ember-cli-terser 4.0.2
  • ember-disable-prototype-extensions 1.1.3
  • ember-load-initializers 2.1.2
  • ember-page-title 8.2.3
  • ember-qunit 8.0.2
  • ember-resolver 11.0.1
  • ember-source 5.7.0
  • ember-source-channel-url 3.0.0
  • ember-template-lint 5.13.0
  • ember-try 3.0.0
  • eslint 8.57.0
  • eslint-config-prettier 9.1.0
  • eslint-plugin-ember 12.0.2
  • eslint-plugin-n 17.1.0
  • eslint-plugin-prettier 5.1.3
  • eslint-plugin-qunit 8.1.1
  • loader.js 4.7.0
  • npm-run-all 4.1.5
  • prettier 3.2.5
  • qunit 2.20.1
  • qunit-dom 3.1.1
  • typescript 5.4.4
  • webpack 5.91.0
  • node 20.* || >= 22

  • Check this box to trigger a request for Renovate to run again on this repository

Add sprites support

May be related - #172, ember-learn/ember-website#769

Case:

Download list of small images

Current behavour:

Depends on network capacity and images count, images may blink and produce 10+ network requests to show some "near" UI elements.

Why sprites:

If we will be able to glue images of relatively same size into one sprite (adaptive one), I think we can unblock more network capacity and save more IO time.

In theory, sprite size should be samller than summ of all image sizes.

Sprites may be "chuncked" by dimentions, image "paths", image sizes, etc.

Incremental rebuild not working

When I add a new image, this does not trigger resizing of the new image, and a new rebuild of the meta data. Instead this happens:

image

Ideally, it would not rescale all images, but only the changed ones. But even rescaling all images would be better than having to restart the ember server.

Addon doesn't work as a nested dependency

Hey folks ๐Ÿ‘‹ First of all I want to say thanks for a great addon! It's super useful and I'm trying to make it a key addon in a lot of the empress projects ๐ŸŽ‰

I'm having a few issues right now where I want to make this addon work as a dependency of a dependency. There are a few issues that stem from the fact that certain hooks are not called in a nested fashion (like contentFor() ember-cli/ember-cli#8077) but there are other issues that seem to stem from the fact that we seem to have quite a few local variables in the addon that are not part of the general addon spec ๐Ÿค”

You can see my attempts to defer some of the implementation of contentFor() and postProcessTree() to ember-responsive-image here empress/empress-blog-casper-template#37 but as you can see from the Netlify demo apps and build history, I'm not getting very far ๐Ÿ™ˆ

If you have any advice on how to proceed that would be super useful ๐Ÿ‘

Allow scoped file names

When multiple directories (say a and b) are set up in the addons config, having a file foo.jpg in both will not work, as the image is referred to only with its filename. I.e. <ResponsiveImage image="foo.jpg"/> is ambiguous. With many images in different configurations, it might be easily possible to get into name clashes.

We should support referencing files with a path, to add a scope to them.

What about something like this:

// config/environment.js

// ...
'responsive-image': {
  sourceDir: 'assets/images/responsive',
  destinationDir: 'assets/images/generated',
  // ... default image generation settings
  quality: 80,
  supportedWidths: [2048, 1536, 1242, 1080, 750, 640, 320, 150],
  
  overrides: [ // override settings for specific files, similar to eslintrc.js
      {
        dir: 'a',
        supportedWidths: [800, 400],
        // ...
      },
      {
        dir: 'b',
        supportedWidths: [160, 80],
        // ...
      }
  ]
}

This would lead to the following file handling:

  • assets/images/responsive/foo.jpg would be written to assets/images/generated/foo***.jpg and referenced as <ResponsiveImage image="foo.jpg"/>, using the default image settings
  • assets/images/responsive/a/foo.jpg would be written to assets/images/generated/a/foo***.jpg and referenced as <ResponsiveImage image="a/foo.jpg"/>, using the specific image settings
  • assets/images/responsive/b/foo.jpg would be written to assets/images/generated/b/foo***.jpg and referenced as <ResponsiveImage image="b/foo.jpg"/>, using the specific image settings

Instead of assigning configurations by folder, we could also do that by pattern matching. I.e. instead of dir: 'a' in config item, we could do pattern: 'a/*', or any other pattern to allow different settings within a single directory, e.g. pattern: '**/*-small.jpg'.

@andreasschacht thoughts?

Should we change/rename the component API?

Currently the basic invocation of the component looks like <ResponsiveImage @image="some/image.jpg"/>, and that does not change so far in the upcoming v2.

However as there are so many breaking changes (not really hard to fix, but just a bunch of them), we have the opportunity now to add more breaking changes, if we think they are justified.

My current thinking goes towards aligning the API more to match what a plain image tag would look like:

<Image @src="some/image.jpg"/>

vs.

<img src="some/image.jpg"/>

Maybe using Image as the component name could lead to conflicts with other components of user's apps though? Unless we get template imports, component names are still global and static, so that could be bad? (There is still an escape hatch by re-exporting the component in the app under a different name/module though!)

Maybe just rename @image to @src, therefore? I.e. <ResponsiveImage @src="some/image.jpg"/>

Or keep everything as it is now? ๐Ÿค”

@andreasschacht @lolmaus thoughts?

[Windows] imageName lookup fails due to filepath inconsistencies

On a windows machine, the metadata object of images uses backslashes and forward slashes inconsistently, since one path comes from the browser and the other comes from Windows itself. This causes the key lookup to fail here:

https://github.com/kaliber5/ember-responsive-image/blob/master/addon/services/responsive-image-local.ts#L52

image

The likely fix is to add a case to the normalizeImageName method here: https://github.com/kaliber5/ember-responsive-image/blob/f72157adabe0e055e750bd5167882c77bf78d707/addon/services/responsive-image-local.ts#L58

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.