@automattic/jetpack-image-guide 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.5.0] - 2023-07-07
9
+ ### Added
10
+ - Boost: exclude small images from Image Size Analysis [#31504]
11
+
12
+ ### Changed
13
+ - Return an error when an image is missing, instead of -1 by -1 dimensions. [#31632]
14
+
15
+ ### Fixed
16
+ - Check response.url, not response.ok to verify a response worked [#31538]
17
+
8
18
  ## [0.4.0] - 2023-06-23
9
19
  ### Added
10
20
  - Jetpack Boost: add a proxy to Image Guide so it can load remote images. [#31145]
@@ -49,4 +59,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
49
59
  ### Removed
50
60
  - Minor package.json change - removing private entry.
51
61
 
62
+ [0.5.0]: https://github.com/Automattic/jetpack-image-guide/compare/v0.4.0...v0.5.0
52
63
  [0.4.0]: https://github.com/Automattic/jetpack-image-guide/compare/v0.3.0...v0.4.0
@@ -6,7 +6,7 @@ export type Dimensions = {
6
6
  export type Weight = {
7
7
  weight: number;
8
8
  };
9
- type FetchFn = (input: URL | RequestInfo, init?: RequestInit) => Promise<Response>;
9
+ export type FetchFn = (input: URL | RequestInfo, init?: RequestInit) => Promise<Response>;
10
10
  /**
11
11
  * A class that represents a DOM Element that
12
12
  * has an image that should be measured and
@@ -15,7 +15,7 @@ type FetchFn = (input: URL | RequestInfo, init?: RequestInit) => Promise<Respons
15
15
  export declare class MeasurableImage {
16
16
  readonly node: HTMLElement | HTMLImageElement;
17
17
  private getURLCallback;
18
- fetch: ((input: URL | RequestInfo, init?: RequestInit) => Promise<Response>) & typeof fetch;
18
+ fetch: FetchFn;
19
19
  /**
20
20
  * Constructor.
21
21
  *
@@ -23,7 +23,7 @@ export declare class MeasurableImage {
23
23
  * @param {SourceCallbackFn} getURL - A function that takes in the node and returns the URL of the image.
24
24
  * @param {FetchFn} fetchFn - A function that fetches a URL and returns a Promise.
25
25
  */
26
- constructor(node: HTMLElement | HTMLImageElement, getURL: SourceCallbackFn, fetchFn?: FetchFn);
26
+ constructor(node: HTMLElement | HTMLImageElement, getURL: SourceCallbackFn, fetchFn?: FetchFn | null);
27
27
  getURL(): string;
28
28
  getSizeOnPage(): {
29
29
  width: number;
@@ -61,5 +61,11 @@ export declare class MeasurableImage {
61
61
  * @param {string} url - image url
62
62
  */
63
63
  private fetchFileDimensions;
64
+ /**
65
+ * Checks if the image is too small and should be ignored. Will return false on images
66
+ * that don't load at all - we can't establish they're tiny!
67
+ *
68
+ * @returns {boolean} - if the image is smaller than 65 pixels width and height return true
69
+ */
70
+ isImageTiny(): Promise<boolean>;
64
71
  }
65
- export {};
@@ -1,4 +1,4 @@
1
- import { MeasurableImage } from './MeasurableImage';
1
+ import { FetchFn, MeasurableImage } from './MeasurableImage';
2
2
  /**
3
3
  * Get elements that either are image tags or have a background image.
4
4
  *
@@ -28,4 +28,4 @@ export declare function backgroundImageSource(node: HTMLElement): string;
28
28
  * @param {(input: string, init?: Array) => Promise<Response>} fetchFn - A function that fetches a URL and returns a Promise.
29
29
  * @returns {MeasurableImage[]} - A list of MeasurableImage objects.
30
30
  */
31
- export declare function getMeasurableImages(domNodes: Element[], fetchFn?: typeof fetch): MeasurableImage[];
31
+ export declare function getMeasurableImages(domNodes: Element[], fetchFn?: FetchFn | null): Promise<MeasurableImage[]>;
@@ -1 +1 @@
1
- <?php return array('dependencies' => array(), 'version' => '8823cb077090bda601fc');
1
+ <?php return array('dependencies' => array(), 'version' => '001cd98ea4ed0b8489db');
package/build/index.js CHANGED
@@ -1 +1 @@
1
- !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ImageGuideLibrary=e():t.ImageGuideLibrary=e()}(self,(()=>(()=>{"use strict";var t={38:(t,e,n)=>{n.d(e,{z:()=>i});var r=function(t,e,n,r){return new(n||(n=Promise))((function(o,i){function u(t){try{c(r.next(t))}catch(t){i(t)}}function a(t){try{c(r.throw(t))}catch(t){i(t)}}function c(t){var e;t.done?o(t.value):(e=t.value,e instanceof n?e:new n((function(t){t(e)}))).then(u,a)}c((r=r.apply(t,e||[])).next())}))},o=function(t,e){var n,r,o,i,u={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function a(a){return function(c){return function(a){if(n)throw new TypeError("Generator is already executing.");for(;i&&(i=0,a[0]&&(u=0)),u;)try{if(n=1,r&&(o=2&a[0]?r.return:a[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,a[1])).done)return o;switch(r=0,o&&(a=[2&a[0],o.value]),a[0]){case 0:case 1:o=a;break;case 4:return u.label++,{value:a[1],done:!1};case 5:u.label++,r=a[1],a=[0];continue;case 7:a=u.ops.pop(),u.trys.pop();continue;default:if(!(o=u.trys,(o=o.length>0&&o[o.length-1])||6!==a[0]&&2!==a[0])){u=0;continue}if(3===a[0]&&(!o||a[1]>o[0]&&a[1]<o[3])){u.label=a[1];break}if(6===a[0]&&u.label<o[1]){u.label=o[1],o=a;break}if(o&&u.label<o[2]){u.label=o[2],u.ops.push(a);break}o[2]&&u.ops.pop(),u.trys.pop();continue}a=e.call(t,u)}catch(t){a=[6,t],r=0}finally{n=o=0}if(5&a[0])throw a[1];return{value:a[0]?a[1]:void 0,done:!0}}([a,c])}}},i=function(){function t(t,e,n){void 0===n&&(n=fetch),this.fetch=window.fetch,this.node=t,this.getURLCallback=e,this.fetch=n}return t.prototype.getURL=function(){return this.getURLCallback(this.node)},t.prototype.getSizeOnPage=function(){var t=this.node.getBoundingClientRect(),e=t.width,n=t.height;return{width:Math.round(e),height:Math.round(n)}},t.prototype.getFileSize=function(t){return r(this,void 0,void 0,(function(){var e,n,r,i,u;return o(this,(function(o){switch(o.label){case 0:return[4,Promise.all([this.fetchFileWeight(t),this.fetchFileDimensions(t)])];case 1:return e=o.sent(),n=e[0],r=e[1],i=r.width,u=r.height,[2,{width:i,height:u,weight:n}]}}))}))},t.prototype.getPotentialSavings=function(t,e){var n=this.getOversizedRatio(t,e);return n<=1?null:Math.round(t.weight-t.weight/n)},t.prototype.getExpectedSize=function(t){var e=window.devicePixelRatio||1;return{width:Math.round(t.width*e),height:Math.round(t.height*e)}},t.prototype.getOversizedRatio=function(t,e){var n=this.getExpectedSize(e),r=n.width,o=n.height;return t.width*t.height/(r*o)},t.prototype.fetchFileWeight=function(t){return r(this,void 0,void 0,(function(){var e;return o(this,(function(n){switch(n.label){case 0:return[4,this.fetch(t)];case 1:return(e=n.sent()).url?[2,parseInt(e.headers.get("content-length"),10)/1024]:(console.log("Can't get image size for ".concat(t," likely due to a CORS error.")),[2,-1])}}))}))},t.prototype.fetchFileDimensions=function(t){return r(this,void 0,void 0,(function(){var e;return o(this,(function(n){return(e=new Image).src=t,[2,new Promise((function(t){e.onload=function(){t({width:Math.round(e.width),height:Math.round(e.height)})},e.onerror=function(){t({width:-1,height:-1})}}))]}))}))},t}()},705:(t,e,n)=>{n.d(e,{RK:()=>u});var r=n(38);function o(t){return a(t.currentSrc)?t.currentSrc:a(t.src)?t.src:null}function i(t){var e=getComputedStyle(t).backgroundImage.match(/url\(\s*(['"])(.*?)\1\s*\)/i),n=window.location.href.split("#")[0];return e&&e[2]&&e[2]!==n&&a(e[2])?e[2]:null}function u(t,e){return void 0===e&&(e=fetch),function(t){return t.filter((function(t){return t instanceof HTMLImageElement||t instanceof HTMLElement&&"none"!==getComputedStyle(t).backgroundImage}))}(t).map((function(t){return t instanceof HTMLImageElement?new r.z(t,o,e):t instanceof HTMLElement&&i(t)?new r.z(t,i):null})).filter((function(t){return null!==t}))}function a(t){if(t.startsWith("/"))return!t.split("?")[0].endsWith(".svg");try{var e=new URL(t);return"http:"===e.protocol||"https:"===e.protocol}catch(t){return!1}}}},e={};function n(r){var o=e[r];if(void 0!==o)return o.exports;var i=e[r]={exports:{}};return t[r](i,i.exports,n),i.exports}n.d=(t,e)=>{for(var r in e)n.o(e,r)&&!n.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:e[r]})},n.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),n.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})};var r={};return(()=>{n.r(r),n.d(r,{MeasurableImage:()=>t.z,getMeasurableImages:()=>e.RK});var t=n(38),e=n(705)})(),r})()));
1
+ !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ImageGuideLibrary=e():t.ImageGuideLibrary=e()}(self,(()=>(()=>{"use strict";var t={38:(t,e,n)=>{n.d(e,{z:()=>i});var r=function(t,e,n,r){return new(n||(n=Promise))((function(o,i){function u(t){try{c(r.next(t))}catch(t){i(t)}}function a(t){try{c(r.throw(t))}catch(t){i(t)}}function c(t){var e;t.done?o(t.value):(e=t.value,e instanceof n?e:new n((function(t){t(e)}))).then(u,a)}c((r=r.apply(t,e||[])).next())}))},o=function(t,e){var n,r,o,i,u={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function a(a){return function(c){return function(a){if(n)throw new TypeError("Generator is already executing.");for(;i&&(i=0,a[0]&&(u=0)),u;)try{if(n=1,r&&(o=2&a[0]?r.return:a[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,a[1])).done)return o;switch(r=0,o&&(a=[2&a[0],o.value]),a[0]){case 0:case 1:o=a;break;case 4:return u.label++,{value:a[1],done:!1};case 5:u.label++,r=a[1],a=[0];continue;case 7:a=u.ops.pop(),u.trys.pop();continue;default:if(!(o=u.trys,(o=o.length>0&&o[o.length-1])||6!==a[0]&&2!==a[0])){u=0;continue}if(3===a[0]&&(!o||a[1]>o[0]&&a[1]<o[3])){u.label=a[1];break}if(6===a[0]&&u.label<o[1]){u.label=o[1],o=a;break}if(o&&u.label<o[2]){u.label=o[2],u.ops.push(a);break}o[2]&&u.ops.pop(),u.trys.pop();continue}a=e.call(t,u)}catch(t){a=[6,t],r=0}finally{n=o=0}if(5&a[0])throw a[1];return{value:a[0]?a[1]:void 0,done:!0}}([a,c])}}},i=function(){function t(t,e,n){void 0===n&&(n=null),this.node=t,this.getURLCallback=e,this.fetch=null!=n?n:window.fetch.bind(window)}return t.prototype.getURL=function(){return this.getURLCallback(this.node)},t.prototype.getSizeOnPage=function(){var t=this.node.getBoundingClientRect(),e=t.width,n=t.height;return{width:Math.round(e),height:Math.round(n)}},t.prototype.getFileSize=function(t){return r(this,void 0,void 0,(function(){var e,n,r,i,u;return o(this,(function(o){switch(o.label){case 0:return[4,Promise.all([this.fetchFileWeight(t),this.fetchFileDimensions(t)])];case 1:return e=o.sent(),n=e[0],r=e[1],i=r.width,u=r.height,[2,{width:i,height:u,weight:n}]}}))}))},t.prototype.getPotentialSavings=function(t,e){var n=this.getOversizedRatio(t,e);return n<=1?null:Math.round(t.weight-t.weight/n)},t.prototype.getExpectedSize=function(t){var e=window.devicePixelRatio||1;return{width:Math.round(t.width*e),height:Math.round(t.height*e)}},t.prototype.getOversizedRatio=function(t,e){var n=this.getExpectedSize(e),r=n.width,o=n.height;return t.width*t.height/(r*o)},t.prototype.fetchFileWeight=function(t){return r(this,void 0,void 0,(function(){var e;return o(this,(function(n){switch(n.label){case 0:return[4,this.fetch(t)];case 1:return(e=n.sent()).ok?[2,parseInt(e.headers.get("content-length"),10)/1024]:(console.log("Can't get image size for ".concat(t," likely due to a CORS error.")),[2,-1])}}))}))},t.prototype.fetchFileDimensions=function(t){return r(this,void 0,void 0,(function(){var e;return o(this,(function(n){return(e=new Image).src=t,[2,new Promise((function(t,n){e.onload=function(){t({width:Math.round(e.width),height:Math.round(e.height)})},e.onerror=function(){n("Unable to load image.")}}))]}))}))},t.prototype.isImageTiny=function(){return r(this,void 0,void 0,(function(){var t,e;return o(this,(function(n){switch(n.label){case 0:return n.trys.push([0,2,,3]),t=65,[4,this.fetchFileDimensions(this.getURL())];case 1:return[2,(e=n.sent()).width<t||e.height<t];case 2:return n.sent(),[2,!1];case 3:return[2]}}))}))},t}()},705:(t,e,n)=>{n.d(e,{RK:()=>c});var r=n(38),o=function(t,e,n,r){return new(n||(n=Promise))((function(o,i){function u(t){try{c(r.next(t))}catch(t){i(t)}}function a(t){try{c(r.throw(t))}catch(t){i(t)}}function c(t){var e;t.done?o(t.value):(e=t.value,e instanceof n?e:new n((function(t){t(e)}))).then(u,a)}c((r=r.apply(t,e||[])).next())}))},i=function(t,e){var n,r,o,i,u={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function a(a){return function(c){return function(a){if(n)throw new TypeError("Generator is already executing.");for(;i&&(i=0,a[0]&&(u=0)),u;)try{if(n=1,r&&(o=2&a[0]?r.return:a[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,a[1])).done)return o;switch(r=0,o&&(a=[2&a[0],o.value]),a[0]){case 0:case 1:o=a;break;case 4:return u.label++,{value:a[1],done:!1};case 5:u.label++,r=a[1],a=[0];continue;case 7:a=u.ops.pop(),u.trys.pop();continue;default:if(!(o=u.trys,(o=o.length>0&&o[o.length-1])||6!==a[0]&&2!==a[0])){u=0;continue}if(3===a[0]&&(!o||a[1]>o[0]&&a[1]<o[3])){u.label=a[1];break}if(6===a[0]&&u.label<o[1]){u.label=o[1],o=a;break}if(o&&u.label<o[2]){u.label=o[2],u.ops.push(a);break}o[2]&&u.ops.pop(),u.trys.pop();continue}a=e.call(t,u)}catch(t){a=[6,t],r=0}finally{n=o=0}if(5&a[0])throw a[1];return{value:a[0]?a[1]:void 0,done:!0}}([a,c])}}};function u(t){return l(t.currentSrc)?t.currentSrc:l(t.src)?t.src:null}function a(t){var e=getComputedStyle(t).backgroundImage.match(/url\(\s*(['"])(.*?)\1\s*\)/i),n=window.location.href.split("#")[0];return e&&e[2]&&e[2]!==n&&l(e[2])?e[2]:null}function c(t,e){return void 0===e&&(e=null),o(this,void 0,void 0,(function(){var n;return i(this,(function(o){return n=function(t){return t.filter((function(t){return t instanceof HTMLImageElement||t instanceof HTMLElement&&"none"!==getComputedStyle(t).backgroundImage}))}(t),[2,n.map((function(t){return t instanceof HTMLImageElement?new r.z(t,u,e):t instanceof HTMLElement&&a(t)?new r.z(t,a):null}))]}))}))}function l(t){if(t.startsWith("/"))return!t.split("?")[0].endsWith(".svg");try{var e=new URL(t);return"http:"===e.protocol||"https:"===e.protocol}catch(t){return!1}}}},e={};function n(r){var o=e[r];if(void 0!==o)return o.exports;var i=e[r]={exports:{}};return t[r](i,i.exports,n),i.exports}n.d=(t,e)=>{for(var r in e)n.o(e,r)&&!n.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:e[r]})},n.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),n.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})};var r={};return(()=>{n.r(r),n.d(r,{MeasurableImage:()=>t.z,getMeasurableImages:()=>e.RK});var t=n(38),e=n(705)})(),r})()));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automattic/jetpack-image-guide",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Go through the dom to analyze image size on screen vs actual file size.",
5
5
  "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/js-packages/image-guide/#readme",
6
6
  "type": "module",
@@ -2,7 +2,7 @@ export type SourceCallbackFn = ( node: HTMLElement ) => string | null;
2
2
  export type Dimensions = { width: number; height: number };
3
3
  export type Weight = { weight: number };
4
4
 
5
- type FetchFn = ( input: URL | RequestInfo, init?: RequestInit ) => Promise< Response >;
5
+ export type FetchFn = ( input: URL | RequestInfo, init?: RequestInit ) => Promise< Response >;
6
6
 
7
7
  /**
8
8
  * A class that represents a DOM Element that
@@ -13,7 +13,7 @@ export class MeasurableImage {
13
13
  readonly node: HTMLElement | HTMLImageElement;
14
14
  private getURLCallback: SourceCallbackFn;
15
15
 
16
- public fetch = window.fetch;
16
+ public fetch: FetchFn;
17
17
 
18
18
  /**
19
19
  * Constructor.
@@ -25,11 +25,11 @@ export class MeasurableImage {
25
25
  constructor(
26
26
  node: HTMLElement | HTMLImageElement,
27
27
  getURL: SourceCallbackFn,
28
- fetchFn: FetchFn = fetch
28
+ fetchFn: FetchFn | null = null
29
29
  ) {
30
30
  this.node = node;
31
31
  this.getURLCallback = getURL;
32
- this.fetch = fetchFn;
32
+ this.fetch = fetchFn ?? window.fetch.bind( window );
33
33
  }
34
34
 
35
35
  public getURL() {
@@ -94,7 +94,7 @@ export class MeasurableImage {
94
94
  */
95
95
  private async fetchFileWeight( url: string ) {
96
96
  const response = await this.fetch( url );
97
- if ( ! response.url ) {
97
+ if ( ! response.ok ) {
98
98
  // eslint-disable-next-line no-console
99
99
  console.log( `Can't get image size for ${ url } likely due to a CORS error.` );
100
100
  return -1;
@@ -112,13 +112,29 @@ export class MeasurableImage {
112
112
  private async fetchFileDimensions( url: string ) {
113
113
  const img = new Image();
114
114
  img.src = url;
115
- return new Promise< { width: number; height: number } >( resolve => {
115
+ return new Promise< { width: number; height: number } >( ( resolve, reject ) => {
116
116
  img.onload = () => {
117
117
  resolve( { width: Math.round( img.width ), height: Math.round( img.height ) } );
118
118
  };
119
119
  img.onerror = () => {
120
- resolve( { width: -1, height: -1 } );
120
+ reject( 'Unable to load image.' );
121
121
  };
122
122
  } );
123
123
  }
124
+
125
+ /**
126
+ * Checks if the image is too small and should be ignored. Will return false on images
127
+ * that don't load at all - we can't establish they're tiny!
128
+ *
129
+ * @returns {boolean} - if the image is smaller than 65 pixels width and height return true
130
+ */
131
+ public async isImageTiny(): Promise< boolean > {
132
+ try {
133
+ const minSize = 65;
134
+ const dimensions = await this.fetchFileDimensions( this.getURL() );
135
+ return dimensions.width < minSize || dimensions.height < minSize;
136
+ } catch ( err ) {
137
+ return false;
138
+ }
139
+ }
124
140
  }
@@ -1,4 +1,4 @@
1
- import { MeasurableImage } from './MeasurableImage';
1
+ import { FetchFn, MeasurableImage } from './MeasurableImage';
2
2
 
3
3
  /**
4
4
  * Get elements that either are image tags or have a background image.
@@ -63,27 +63,30 @@ export function backgroundImageSource( node: HTMLElement ) {
63
63
  * @param {(input: string, init?: Array) => Promise<Response>} fetchFn - A function that fetches a URL and returns a Promise.
64
64
  * @returns {MeasurableImage[]} - A list of MeasurableImage objects.
65
65
  */
66
- export function getMeasurableImages( domNodes: Element[], fetchFn = fetch ): MeasurableImage[] {
66
+ export async function getMeasurableImages(
67
+ domNodes: Element[],
68
+ fetchFn: FetchFn | null = null
69
+ ): Promise< MeasurableImage[] > {
67
70
  const nodes = findMeasurableElements( domNodes );
68
- return nodes
69
- .map( node => {
70
- if ( node instanceof HTMLImageElement ) {
71
- return new MeasurableImage( node, imageTagSource, fetchFn );
72
- } else if ( node instanceof HTMLElement ) {
73
- if ( ! backgroundImageSource( node ) ) {
74
- /**
75
- * Background elements that have no valid URL
76
- * shouldn't be measured.
77
- */
78
- return null;
79
- }
80
-
81
- return new MeasurableImage( node, backgroundImageSource );
71
+ const images = nodes.map( node => {
72
+ if ( node instanceof HTMLImageElement ) {
73
+ return new MeasurableImage( node, imageTagSource, fetchFn );
74
+ } else if ( node instanceof HTMLElement ) {
75
+ if ( ! backgroundImageSource( node ) ) {
76
+ /**
77
+ * Background elements that have no valid URL
78
+ * shouldn't be measured.
79
+ */
80
+ return null;
82
81
  }
83
82
 
84
- return null;
85
- } )
86
- .filter( image => image !== null );
83
+ return new MeasurableImage( node, backgroundImageSource );
84
+ }
85
+
86
+ return null;
87
+ } );
88
+
89
+ return images;
87
90
  }
88
91
 
89
92
  /**