@automattic/jetpack-image-guide 0.2.1 → 0.4.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,25 +5,37 @@ 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.2.1] - 2023-04-06
8
+ ## [0.4.0] - 2023-06-23
9
+ ### Added
10
+ - Jetpack Boost: add a proxy to Image Guide so it can load remote images. [#31145]
11
+
12
+ ### Fixed
13
+ - Improved image url validation for background image source. [#31410]
14
+
15
+ ## 0.3.0 - 2023-05-11
16
+ ### Changed
17
+ - Set `exports` in package.json. This will break directly requiring files from within the package in environments that respect `exports`. [#30313]
18
+ - Updated package dependencies. [#30264] [#30265] [#30294]
19
+
20
+ ## 0.2.1 - 2023-04-06
9
21
  ### Changed
10
22
  - Updated package dependencies. [#28609]
11
23
 
12
- ## [0.2.0] - 2023-01-19
24
+ ## 0.2.0 - 2023-01-19
13
25
  ### Changed
14
26
  - Bundle the package to make it easy to consume [#28429]
15
27
 
16
28
  ### Fixed
17
29
  - Clean up JavaScript eslint issues. [#28441]
18
30
 
19
- ## [0.1.2] - 2023-01-17
31
+ ## 0.1.2 - 2023-01-17
20
32
  ### Added
21
33
  - Fixed an issue that would break the release process [#28186]
22
34
 
23
35
  ### Fixed
24
36
  - Adding prepare script back to package.json, was needed for an ESlint test [#28103]
25
37
 
26
- ## [0.1.1] - 2022-12-26
38
+ ## 0.1.1 - 2022-12-26
27
39
  ### Added
28
40
  - Turn on auto-publish [#28032]
29
41
 
@@ -37,7 +49,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
37
49
  ### Removed
38
50
  - Minor package.json change - removing private entry.
39
51
 
40
- [0.2.1]: https://github.com/Automattic/jetpack-image-guide/compare/v0.2.0...v0.2.1
41
- [0.2.0]: https://github.com/Automattic/jetpack-image-guide/compare/v0.1.2...v0.2.0
42
- [0.1.2]: https://github.com/Automattic/jetpack-image-guide/compare/v0.1.1...v0.1.2
43
- [0.1.1]: https://github.com/Automattic/jetpack-image-guide/compare/v0.1.0...v0.1.1
52
+ [0.4.0]: https://github.com/Automattic/jetpack-image-guide/compare/v0.3.0...v0.4.0
@@ -1,11 +1,12 @@
1
- export declare type SourceCallbackFn = (node: HTMLElement) => string | null;
2
- export declare type Dimensions = {
1
+ export type SourceCallbackFn = (node: HTMLElement) => string | null;
2
+ export type Dimensions = {
3
3
  width: number;
4
4
  height: number;
5
5
  };
6
- export declare type Weight = {
6
+ export type Weight = {
7
7
  weight: number;
8
8
  };
9
+ type FetchFn = (input: URL | RequestInfo, init?: RequestInit) => Promise<Response>;
9
10
  /**
10
11
  * A class that represents a DOM Element that
11
12
  * has an image that should be measured and
@@ -14,13 +15,15 @@ export declare type Weight = {
14
15
  export declare class MeasurableImage {
15
16
  readonly node: HTMLElement | HTMLImageElement;
16
17
  private getURLCallback;
18
+ fetch: ((input: URL | RequestInfo, init?: RequestInit) => Promise<Response>) & typeof fetch;
17
19
  /**
18
20
  * Constructor.
19
21
  *
20
22
  * @param {HTMLElement | HTMLImageElement} node - The DOM Element that contains the image.
21
23
  * @param {SourceCallbackFn} getURL - A function that takes in the node and returns the URL of the image.
24
+ * @param {FetchFn} fetchFn - A function that fetches a URL and returns a Promise.
22
25
  */
23
- constructor(node: HTMLElement | HTMLImageElement, getURL: SourceCallbackFn);
26
+ constructor(node: HTMLElement | HTMLImageElement, getURL: SourceCallbackFn, fetchFn?: FetchFn);
24
27
  getURL(): string;
25
28
  getSizeOnPage(): {
26
29
  width: number;
@@ -59,3 +62,4 @@ export declare class MeasurableImage {
59
62
  */
60
63
  private fetchFileDimensions;
61
64
  }
65
+ export {};
@@ -25,6 +25,7 @@ export declare function backgroundImageSource(node: HTMLElement): string;
25
25
  * and remove any nodes that can't be measured.
26
26
  *
27
27
  * @param {Element[]} domNodes - A list of nodes to measure
28
+ * @param {(input: string, init?: Array) => Promise<Response>} fetchFn - A function that fetches a URL and returns a Promise.
28
29
  * @returns {MeasurableImage[]} - A list of MeasurableImage objects.
29
30
  */
30
- export declare function getMeasurableImages(domNodes: Element[]): MeasurableImage[];
31
+ export declare function getMeasurableImages(domNodes: Element[], fetchFn?: typeof fetch): MeasurableImage[];
@@ -1 +1 @@
1
- <?php return array('dependencies' => array(), 'version' => 'e6fb4ce0f7b08187a456');
1
+ <?php return array('dependencies' => array(), 'version' => '8823cb077090bda601fc');
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(i){return function(a){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;u;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return u.label++,{value:i[1],done:!1};case 5:u.label++,r=i[1],i=[0];continue;case 7:i=u.ops.pop(),u.trys.pop();continue;default:if(!(o=u.trys,(o=o.length>0&&o[o.length-1])||6!==i[0]&&2!==i[0])){u=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]<o[3])){u.label=i[1];break}if(6===i[0]&&u.label<o[1]){u.label=o[1],o=i;break}if(o&&u.label<o[2]){u.label=o[2],u.ops.push(i);break}o[2]&&u.ops.pop(),u.trys.pop();continue}i=e.call(t,u)}catch(t){i=[6,t],r=0}finally{n=o=0}if(5&i[0])throw i[1];return{value:i[0]?i[1]:void 0,done:!0}}([i,a])}}},i=function(){function t(t,e){this.node=t,this.getURLCallback=e}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,n;return o(this,(function(r){switch(r.label){case 0:return[4,fetch(t,{method:"HEAD",mode:"no-cors"})];case 1:return(e=r.sent()).url?(n=e.headers.get("content-length"))?[2,parseInt(n,10)/1024]:[2,-1]:(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\(.?(.*?).?\)/i);return e&&e[1]&&a(e[1])?e[1]:null}function u(t){return 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):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=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})()));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automattic/jetpack-image-guide",
3
- "version": "0.2.1",
3
+ "version": "0.4.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",
@@ -15,7 +15,6 @@
15
15
  "license": "GPL-2.0-or-later",
16
16
  "author": "Automattic",
17
17
  "scripts": {
18
- "prepare": "[ -e ./build/index.js ] || pnpm build",
19
18
  "build": "pnpm run clean && webpack",
20
19
  "clean": "rm -rf build/",
21
20
  "watch": "pnpm run build && pnpm webpack watch",
@@ -23,14 +22,20 @@
23
22
  },
24
23
  "devDependencies": {
25
24
  "@automattic/jetpack-webpack-config": "workspace:*",
26
- "@typescript-eslint/parser": "5.36.1",
27
- "eslint": "8.33.0",
25
+ "@typescript-eslint/parser": "5.59.0",
26
+ "eslint": "8.39.0",
28
27
  "jest": "29.4.3",
29
28
  "ts-loader": "9.4.2",
30
- "typescript": "4.8.2",
29
+ "typescript": "5.0.4",
31
30
  "webpack": "5.76.0",
32
31
  "webpack-cli": "4.9.1"
33
32
  },
33
+ "exports": {
34
+ ".": {
35
+ "types": "./build/index.d.ts",
36
+ "default": "./build/index.js"
37
+ }
38
+ },
34
39
  "main": "./build/index.js",
35
40
  "types": "./build/index.d.ts"
36
41
  }
@@ -2,6 +2,8 @@ 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 >;
6
+
5
7
  /**
6
8
  * A class that represents a DOM Element that
7
9
  * has an image that should be measured and
@@ -11,15 +13,23 @@ export class MeasurableImage {
11
13
  readonly node: HTMLElement | HTMLImageElement;
12
14
  private getURLCallback: SourceCallbackFn;
13
15
 
16
+ public fetch = window.fetch;
17
+
14
18
  /**
15
19
  * Constructor.
16
20
  *
17
21
  * @param {HTMLElement | HTMLImageElement} node - The DOM Element that contains the image.
18
22
  * @param {SourceCallbackFn} getURL - A function that takes in the node and returns the URL of the image.
23
+ * @param {FetchFn} fetchFn - A function that fetches a URL and returns a Promise.
19
24
  */
20
- constructor( node: HTMLElement | HTMLImageElement, getURL: SourceCallbackFn ) {
25
+ constructor(
26
+ node: HTMLElement | HTMLImageElement,
27
+ getURL: SourceCallbackFn,
28
+ fetchFn: FetchFn = fetch
29
+ ) {
21
30
  this.node = node;
22
31
  this.getURLCallback = getURL;
32
+ this.fetch = fetchFn;
23
33
  }
24
34
 
25
35
  public getURL() {
@@ -83,19 +93,14 @@ export class MeasurableImage {
83
93
  * @param {string} url - string The URL of the image.
84
94
  */
85
95
  private async fetchFileWeight( url: string ) {
86
- const response = await fetch( url, { method: 'HEAD', mode: 'no-cors' } );
96
+ const response = await this.fetch( url );
87
97
  if ( ! response.url ) {
88
98
  // eslint-disable-next-line no-console
89
99
  console.log( `Can't get image size for ${ url } likely due to a CORS error.` );
90
100
  return -1;
91
101
  }
92
102
 
93
- const size = response.headers.get( 'content-length' );
94
- if ( size ) {
95
- return parseInt( size, 10 ) / 1024;
96
- }
97
-
98
- return -1;
103
+ return parseInt( response.headers.get( 'content-length' ), 10 ) / 1024;
99
104
  }
100
105
 
101
106
  /**
@@ -44,9 +44,13 @@ export function imageTagSource( node: HTMLImageElement ) {
44
44
  */
45
45
  export function backgroundImageSource( node: HTMLElement ) {
46
46
  const src = getComputedStyle( node ).backgroundImage;
47
- const url = src.match( /url\(.?(.*?).?\)/i );
48
- if ( url && url[ 1 ] && imageLikeURL( url[ 1 ] ) ) {
49
- return url[ 1 ];
47
+ const url = src.match( /url\(\s*(['"])(.*?)\1\s*\)/i );
48
+
49
+ // If background image is `url('')`, the computed value becomes the current page URL. So we need to check for that.
50
+ const currentUrlWithoutHash = window.location.href.split( '#' )[ 0 ];
51
+
52
+ if ( url && url[ 2 ] && url[ 2 ] !== currentUrlWithoutHash && imageLikeURL( url[ 2 ] ) ) {
53
+ return url[ 2 ];
50
54
  }
51
55
  return null;
52
56
  }
@@ -56,14 +60,15 @@ export function backgroundImageSource( node: HTMLElement ) {
56
60
  * and remove any nodes that can't be measured.
57
61
  *
58
62
  * @param {Element[]} domNodes - A list of nodes to measure
63
+ * @param {(input: string, init?: Array) => Promise<Response>} fetchFn - A function that fetches a URL and returns a Promise.
59
64
  * @returns {MeasurableImage[]} - A list of MeasurableImage objects.
60
65
  */
61
- export function getMeasurableImages( domNodes: Element[] ): MeasurableImage[] {
66
+ export function getMeasurableImages( domNodes: Element[], fetchFn = fetch ): MeasurableImage[] {
62
67
  const nodes = findMeasurableElements( domNodes );
63
68
  return nodes
64
69
  .map( node => {
65
70
  if ( node instanceof HTMLImageElement ) {
66
- return new MeasurableImage( node, imageTagSource );
71
+ return new MeasurableImage( node, imageTagSource, fetchFn );
67
72
  } else if ( node instanceof HTMLElement ) {
68
73
  if ( ! backgroundImageSource( node ) ) {
69
74
  /**