@tgwf/co2 0.9.0 → 0.10.2

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.
Files changed (92) hide show
  1. package/.esbuild.browser.js +11 -0
  2. package/.esbuild.esm.js +25 -0
  3. package/.esbuild.node.js +16 -0
  4. package/.gitpod.yml +8 -0
  5. package/CHANGELOG.md +22 -1
  6. package/README.md +29 -66
  7. package/dist/cjs/1byte.js +52 -0
  8. package/dist/cjs/1byte.js.map +7 -0
  9. package/dist/cjs/1byte.test.js +27 -0
  10. package/dist/cjs/1byte.test.js.map +7 -0
  11. package/dist/cjs/co2.js +121 -0
  12. package/dist/cjs/co2.js.map +7 -0
  13. package/dist/cjs/co2.test.js +258 -0
  14. package/dist/cjs/co2.test.js.map +7 -0
  15. package/dist/cjs/constants/file-size.js +27 -0
  16. package/dist/cjs/constants/file-size.js.map +7 -0
  17. package/dist/cjs/constants/index.js +27 -0
  18. package/dist/cjs/constants/index.js.map +7 -0
  19. package/dist/cjs/helpers/index.js +24 -0
  20. package/dist/cjs/helpers/index.js.map +7 -0
  21. package/dist/cjs/hosting-api.js +64 -0
  22. package/dist/cjs/hosting-api.js.map +7 -0
  23. package/dist/cjs/hosting-api.test.js +47 -0
  24. package/dist/cjs/hosting-api.test.js.map +7 -0
  25. package/dist/cjs/hosting-database.node.test.js +36 -0
  26. package/dist/cjs/hosting-database.node.test.js.map +7 -0
  27. package/dist/cjs/hosting-json.node.js +73 -0
  28. package/dist/cjs/hosting-json.node.js.map +7 -0
  29. package/dist/cjs/hosting-json.node.test.js +43 -0
  30. package/dist/cjs/hosting-json.node.test.js.map +7 -0
  31. package/dist/cjs/hosting-node.js +78 -0
  32. package/dist/cjs/hosting-node.js.map +7 -0
  33. package/dist/cjs/hosting.js +36 -0
  34. package/dist/cjs/hosting.js.map +7 -0
  35. package/dist/cjs/hosting.test.js +74 -0
  36. package/dist/cjs/hosting.test.js.map +7 -0
  37. package/dist/cjs/index-node.js +29 -0
  38. package/dist/cjs/index-node.js.map +7 -0
  39. package/dist/cjs/index.js +31 -0
  40. package/dist/cjs/index.js.map +7 -0
  41. package/dist/cjs/package.json +3 -0
  42. package/dist/cjs/sustainable-web-design.js +134 -0
  43. package/dist/cjs/sustainable-web-design.js.map +7 -0
  44. package/dist/cjs/sustainable-web-design.test.js +79 -0
  45. package/dist/cjs/sustainable-web-design.test.js.map +7 -0
  46. package/dist/esm/1byte.js +32 -0
  47. package/dist/esm/1byte.test.js +11 -0
  48. package/dist/esm/co2.js +98 -0
  49. package/{src → dist/esm}/co2.test.js +47 -94
  50. package/dist/esm/constants/file-size.js +7 -0
  51. package/dist/esm/constants/index.js +4 -0
  52. package/dist/esm/helpers/index.js +4 -0
  53. package/dist/esm/hosting-api.js +41 -0
  54. package/dist/esm/hosting-api.test.js +31 -0
  55. package/{src/hosting-database.test.js → dist/esm/hosting-database.node.test.js} +7 -18
  56. package/{src/hosting-json.test.js → dist/esm/hosting-json.node.test.js} +4 -21
  57. package/dist/esm/hosting.js +13 -0
  58. package/{src → dist/esm}/hosting.test.js +10 -30
  59. package/dist/esm/index.js +8 -0
  60. package/dist/esm/package.json +3 -0
  61. package/dist/esm/sustainable-web-design.js +111 -0
  62. package/{src → dist/esm}/sustainable-web-design.test.js +19 -37
  63. package/dist/iife/index.js +2 -0
  64. package/dist/iife/index.js.map +7 -0
  65. package/fixup +19 -0
  66. package/package.json +38 -17
  67. package/public/index.html +58 -0
  68. package/public/index.js +2 -0
  69. package/src/1byte.js +2 -3
  70. package/src/co2.js +17 -18
  71. package/src/constants/file-size.js +2 -2
  72. package/src/constants/index.js +2 -2
  73. package/src/helpers/index.js +1 -3
  74. package/src/hosting-api.js +23 -43
  75. package/src/{hosting-json.js → hosting-json.node.js} +9 -11
  76. package/src/hosting-node.js +94 -0
  77. package/src/hosting.js +6 -28
  78. package/src/index-node.js +4 -0
  79. package/src/index.js +4 -6
  80. package/src/sustainable-web-design.js +37 -11
  81. package/.eslintrc.json +0 -21
  82. package/.github/workflows/unittests.yml +0 -26
  83. package/data/Lean-ICT-Materials-1byte-Model-2018.xlsx +0 -0
  84. package/data/Lean-ICT-Materials-Forecast-Model-2018.xlsx +0 -0
  85. package/data/fixtures/tgwf.har +0 -6107
  86. package/data/fixtures/url2green.test.db +0 -0
  87. package/data/fixtures/url2green.test.json +0 -1
  88. package/data/fixtures/url2green.test.json.gz +0 -0
  89. package/images/swd-energy-usage.png +0 -0
  90. package/src/1byte.test.js +0 -17
  91. package/src/green-byte.js +0 -26
  92. package/src/hosting-api.test.js +0 -37
@@ -0,0 +1,58 @@
1
+ <html>
2
+
3
+ <head>
4
+ <meta charset=utf-8>
5
+ </head>
6
+
7
+
8
+ <body class="">
9
+
10
+ <h1>This is a minimal demo using the IIFE version of CO2.js.</h1>
11
+
12
+ <p>Add a number in bytes, and we convert it to carbon, using the SWD model</p>
13
+
14
+ <form>
15
+ <p>
16
+ <label for="bytes">Number of bytes</label>
17
+ <input name="bytes" value=0 />
18
+ </p>
19
+ <button>Update</button>
20
+ </form>
21
+
22
+ <h2 class="result">
23
+ (result goes here)
24
+ </h2>
25
+
26
+
27
+ <script src="./index.js"></script>
28
+ <script>
29
+ let emissions = new co2.co2();
30
+
31
+ async function main() {
32
+
33
+ // this is included to demonstrate checking a given domain
34
+ const hosting = co2.hosting
35
+ // is the result green?
36
+ const result = await hosting.check("google.com")
37
+ // should return true or false
38
+ console.log({
39
+ result
40
+ })
41
+
42
+ document.querySelector('form').addEventListener('submit', function (event) {
43
+ event.preventDefault();
44
+ event.stopPropagation();
45
+
46
+ const thisForm = event.target
47
+ const bytes = thisForm.querySelector('input').value
48
+ const result = emissions.perByte(bytes)
49
+
50
+ document.querySelector('.result').textContent = result
51
+ document.querySelector('body').classList = []
52
+ })
53
+ }
54
+ main()
55
+ </script>
56
+ </body>
57
+
58
+ </html>
@@ -0,0 +1,2 @@
1
+ var co2=(()=>{var ae=Object.create;var B=Object.defineProperty;var ue=Object.getOwnPropertyDescriptor;var fe=Object.getOwnPropertyNames;var le=Object.getPrototypeOf,Ce=Object.prototype.hasOwnProperty;var v=(r,t)=>()=>(t||r((t={exports:{}}).exports,t),t.exports),de=(r,t)=>{for(var e in t)B(r,e,{get:t[e],enumerable:!0})},V=(r,t,e,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of fe(t))!Ce.call(r,o)&&o!==e&&B(r,o,{get:()=>t[o],enumerable:!(n=ue(t,o))||n.enumerable});return r};var T=(r,t,e)=>(e=r!=null?ae(le(r)):{},V(t||!r||!r.__esModule?B(e,"default",{value:r,enumerable:!0}):e,r)),pe=r=>V(B({},"__esModule",{value:!0}),r);var $=v((Ye,L)=>{var h=1e3,E=h*60,F=E*60,y=F*24,me=y*7,ye=y*365.25;L.exports=function(r,t){t=t||{};var e=typeof r;if(e==="string"&&r.length>0)return ge(r);if(e==="number"&&isFinite(r))return t.long?Ee(r):he(r);throw new Error("val is not a non-empty string or a valid number. val="+JSON.stringify(r))};function ge(r){if(r=String(r),!(r.length>100)){var t=/^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(r);if(!!t){var e=parseFloat(t[1]),n=(t[2]||"ms").toLowerCase();switch(n){case"years":case"year":case"yrs":case"yr":case"y":return e*ye;case"weeks":case"week":case"w":return e*me;case"days":case"day":case"d":return e*y;case"hours":case"hour":case"hrs":case"hr":case"h":return e*F;case"minutes":case"minute":case"mins":case"min":case"m":return e*E;case"seconds":case"second":case"secs":case"sec":case"s":return e*h;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return e;default:return}}}}function he(r){var t=Math.abs(r);return t>=y?Math.round(r/y)+"d":t>=F?Math.round(r/F)+"h":t>=E?Math.round(r/E)+"m":t>=h?Math.round(r/h)+"s":r+"ms"}function Ee(r){var t=Math.abs(r);return t>=y?N(r,t,y,"day"):t>=F?N(r,t,F,"hour"):t>=E?N(r,t,E,"minute"):t>=h?N(r,t,h,"second"):r+" ms"}function N(r,t,e,n){var o=t>=e*1.5;return Math.round(r/e)+" "+n+(o?"s":"")}});var M=v((Ve,U)=>{function Fe(r){e.debug=e,e.default=e,e.coerce=g,e.disable=s,e.enable=o,e.enabled=a,e.humanize=$(),e.destroy=p,Object.keys(r).forEach(i=>{e[i]=r[i]}),e.names=[],e.skips=[],e.formatters={};function t(i){let c=0;for(let u=0;u<i.length;u++)c=(c<<5)-c+i.charCodeAt(u),c|=0;return e.colors[Math.abs(c)%e.colors.length]}e.selectColor=t;function e(i){let c,u=null,w,z;function d(...l){if(!d.enabled)return;let m=d,R=Number(new Date),se=R-(c||R);m.diff=se,m.prev=c,m.curr=R,c=R,l[0]=e.coerce(l[0]),typeof l[0]!="string"&&l.unshift("%O");let O=0;l[0]=l[0].replace(/%([a-zA-Z%])/g,(P,ie)=>{if(P==="%%")return"%";O++;let Y=e.formatters[ie];if(typeof Y=="function"){let ce=l[O];P=Y.call(m,ce),l.splice(O,1),O--}return P}),e.formatArgs.call(m,l),(m.log||e.log).apply(m,l)}return d.namespace=i,d.useColors=e.useColors(),d.color=e.selectColor(i),d.extend=n,d.destroy=e.destroy,Object.defineProperty(d,"enabled",{enumerable:!0,configurable:!1,get:()=>u!==null?u:(w!==e.namespaces&&(w=e.namespaces,z=e.enabled(i)),z),set:l=>{u=l}}),typeof e.init=="function"&&e.init(d),d}function n(i,c){let u=e(this.namespace+(typeof c>"u"?":":c)+i);return u.log=this.log,u}function o(i){e.save(i),e.namespaces=i,e.names=[],e.skips=[];let c,u=(typeof i=="string"?i:"").split(/[\s,]+/),w=u.length;for(c=0;c<w;c++)!u[c]||(i=u[c].replace(/\*/g,".*?"),i[0]==="-"?e.skips.push(new RegExp("^"+i.slice(1)+"$")):e.names.push(new RegExp("^"+i+"$")))}function s(){let i=[...e.names.map(C),...e.skips.map(C).map(c=>"-"+c)].join(",");return e.enable(""),i}function a(i){if(i[i.length-1]==="*")return!0;let c,u;for(c=0,u=e.skips.length;c<u;c++)if(e.skips[c].test(i))return!1;for(c=0,u=e.names.length;c<u;c++)if(e.names[c].test(i))return!0;return!1}function C(i){return i.toString().substring(2,i.toString().length-2).replace(/\.\*\?$/,"*")}function g(i){return i instanceof Error?i.stack||i.message:i}function p(){console.warn("Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.")}return e.enable(e.load()),e}U.exports=Fe});var x=v((f,I)=>{f.formatArgs=be;f.save=we;f.load=Re;f.useColors=_e;f.storage=Oe();f.destroy=(()=>{let r=!1;return()=>{r||(r=!0,console.warn("Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`."))}})();f.colors=["#0000CC","#0000FF","#0033CC","#0033FF","#0066CC","#0066FF","#0099CC","#0099FF","#00CC00","#00CC33","#00CC66","#00CC99","#00CCCC","#00CCFF","#3300CC","#3300FF","#3333CC","#3333FF","#3366CC","#3366FF","#3399CC","#3399FF","#33CC00","#33CC33","#33CC66","#33CC99","#33CCCC","#33CCFF","#6600CC","#6600FF","#6633CC","#6633FF","#66CC00","#66CC33","#9900CC","#9900FF","#9933CC","#9933FF","#99CC00","#99CC33","#CC0000","#CC0033","#CC0066","#CC0099","#CC00CC","#CC00FF","#CC3300","#CC3333","#CC3366","#CC3399","#CC33CC","#CC33FF","#CC6600","#CC6633","#CC9900","#CC9933","#CCCC00","#CCCC33","#FF0000","#FF0033","#FF0066","#FF0099","#FF00CC","#FF00FF","#FF3300","#FF3333","#FF3366","#FF3399","#FF33CC","#FF33FF","#FF6600","#FF6633","#FF9900","#FF9933","#FFCC00","#FFCC33"];function _e(){return typeof window<"u"&&window.process&&(window.process.type==="renderer"||window.process.__nwjs)?!0:typeof navigator<"u"&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)?!1:typeof document<"u"&&document.documentElement&&document.documentElement.style&&document.documentElement.style.WebkitAppearance||typeof window<"u"&&window.console&&(window.console.firebug||window.console.exception&&window.console.table)||typeof navigator<"u"&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31||typeof navigator<"u"&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)}function be(r){if(r[0]=(this.useColors?"%c":"")+this.namespace+(this.useColors?" %c":" ")+r[0]+(this.useColors?"%c ":" ")+"+"+I.exports.humanize(this.diff),!this.useColors)return;let t="color: "+this.color;r.splice(1,0,t,"color: inherit");let e=0,n=0;r[0].replace(/%[a-zA-Z%]/g,o=>{o!=="%%"&&(e++,o==="%c"&&(n=e))}),r.splice(n,0,t)}f.log=console.debug||console.log||(()=>{});function we(r){try{r?f.storage.setItem("debug",r):f.storage.removeItem("debug")}catch{}}function Re(){let r;try{r=f.storage.getItem("debug")}catch{}return!r&&typeof process<"u"&&"env"in process&&(r=process.env.DEBUG),r}function Oe(){try{return localStorage}catch{}}I.exports=M()(f);var{formatters:Be}=I.exports;Be.j=function(r){try{return JSON.stringify(r)}catch(t){return"[UnexpectedJSONParseError]: "+t.message}}});var De={};de(De,{co2:()=>D,default:()=>We,hosting:()=>j});var A=4883333333333333e-25;var S=class{constructor(t){this.options=t,this.KWH_PER_BYTE_FOR_NETWORK=A}perByte(t,e){if(t<1)return 0;if(e){let o=t*72e-12*0,s=t*A*475;return o+s}let n=72e-12+A;return t*n*519}};var H=S;var ee=T(x());var k={GIGABYTE:1e9};var _=r=>parseFloat(r.toFixed(2));var q=(0,ee.default)("tgwf:sustainable-web-design"),Ne=.81,J=.52,Z=.14,Q=.15,X=.19,b=442,Ie=50,xe=.75,Pe=.25,ve=.02,G=class{constructor(t){this.options=t}energyPerByteByComponent(t){let n=t/k.GIGABYTE*Ne;return{consumerDeviceEnergy:n*J,networkEnergy:n*Z,productionEnergy:n*X,dataCenterEnergy:n*Q}}co2byComponent(t,e=b){let n={};for(let[o,s]of Object.entries(t))o==="dataCenterEnergy"?n[o]=s*e:n[o]=s*b;return n}perByte(t,e=b){let n=this.energyPerByteByComponent(t);if(Boolean(e)===!1&&(e=b),e===!0&&(e=Ie),typeof e!="number")throw new Error(`perByte expects a numeric value or boolean for the carbon intensity value. Received: ${e}`);let o=this.co2byComponent(n,e);return Object.values(o).reduce((a,C)=>a+C)}energyPerByte(t){let e=this.energyPerByteByComponent(t);return Object.values(e).reduce((o,s)=>o+s)}energyPerVisitByComponent(t,e=xe,n=Pe,o=ve){let s=this.energyPerByteByComponent(t),a={};q({energyBycomponent:s});let C=Object.values(s);for(let[g,p]of Object.entries(s))a[`${g} - first`]=p*e,a[`${g} - subsequent`]=p*n*o;return q({cacheAdjustedSegmentEnergy:a}),a}energyPerVisit(t){let e=0,n=0,o=Object.entries(this.energyPerVisitByComponent(t));for(let[s,a]of o)s.indexOf("first")>0&&(e+=a);for(let[s,a]of o)s.indexOf("subsequent")>0&&(n+=a);return e+n}emissionsPerVisitInGrams(t,e=b){return _(t*e)}annualEnergyInKwh(t,e=1e3){return t*e*12}annualEmissionsInGrams(t,e=1e3){return t*e*12}annualSegmentEnergy(t){return{consumerDeviceEnergy:_(t*J),networkEnergy:_(t*Z),dataCenterEnergy:_(t*Q),productionEnergy:_(t*X)}}};var te=G;var W=class{constructor(t){this.options=t,this.model=new H,t&&(t.model==="swd"?this.model=new te:this.model=new t.model)}perByte(t,e){return this.model.perByte(t,e)}perDomain(t,e){let n=[];for(let o of Object.keys(t.domains)){let s;e&&e.indexOf(o)>-1?s=this.perByte(t.domains[o].transferSize,!0):s=this.perByte(t.domains[o].transferSize),n.push({domain:o,co2:s,transferSize:t.domains[o].transferSize})}return n.sort((o,s)=>s.co2-o.co2),n}perPage(t,e){let n=this.perDomain(t,e),o=0;for(let s of n)o+=s.co2;return o}perContentType(t,e){let n={};for(let s of t.assets){let a=new URL(s.url).domain,C=s.transferSize,g=this.perByte(C,e&&e.indexOf(a)>-1),p=s.type;n[p]||(n[p]={co2:0,transferSize:0}),n[p].co2+=g,n[p].transferSize+=C}let o=[];for(let s of Object.keys(n))o.push({type:s,co2:n[s].co2,transferSize:n[s].transferSize});return o.sort((s,a)=>a.co2-s.co2),o}dirtiestResources(t,e){let n=[];for(let o of t.assets){let s=new URL(o.url).domain,a=o.transferSize,C=this.perByte(a,e&&e.indexOf(s)>-1);n.push({url:o.url,co2:C,transferSize:a})}return n.sort((o,s)=>s.co2-o.co2),n.slice(0,n.length>10?10:n.length)}perParty(t,e){let n=0,o=0,s=t.firstPartyRegEx;for(let a of Object.keys(t.domains))a.match(s)?n+=this.perByte(t.domains[a].transferSize,e&&e.indexOf(a)>-1):o+=this.perByte(t.domains[a].transferSize,e&&e.indexOf(a)>-1);return{firstParty:n,thirdParty:o}}};var D=W;var oe=T(x());var re=T(x()),K=(0,re.default)("tgwf:hostingAPI");function Te(r){return typeof r=="string"?Ae(r):Se(r)}async function Ae(r){return(await(await fetch(`https://api.thegreenwebfoundation.org/greencheck/${r}`)).json()).green}async function Se(r){try{let t="https://api.thegreenwebfoundation.org/v2/greencheckmulti",e=JSON.stringify(r),n=await fetch(`${t}/${e}`);K(`${t}/${e}`),K({req:n});let o=await n.text();K({textResult:o});let s=await n.json();return ke(s)}catch{return[]}}function ke(r){return Object.entries(r).filter(([n,o])=>o.green).map(([n,o])=>o.url)}var ne={check:Te};var tt=(0,oe.default)("tgwf:hosting");function Ge(r,t){return ne.check(r)}var j={check:Ge};var We={co2:D,hosting:j};return pe(De);})();
2
+ //# sourceMappingURL=index.js.map
package/src/1byte.js CHANGED
@@ -66,6 +66,5 @@ class OneByte {
66
66
  }
67
67
  }
68
68
 
69
- module.exports = {
70
- OneByte,
71
- };
69
+ export { OneByte };
70
+ export default OneByte;
package/src/co2.js CHANGED
@@ -1,22 +1,26 @@
1
1
  "use strict";
2
2
 
3
- const url = require("url");
4
- const onebyte = require("./1byte.js");
3
+ import OneByte from "./1byte.js";
4
+ import SustainableWebDesign from "./sustainable-web-design.js";
5
5
 
6
6
  class CO2 {
7
7
  constructor(options) {
8
8
  this.options = options;
9
9
 
10
10
  // default model
11
- this.model = new onebyte.OneByte();
11
+ this.model = new OneByte();
12
12
 
13
13
  if (options) {
14
- this.model = new options.model();
14
+ if (options.model === "swd") {
15
+ this.model = new SustainableWebDesign();
16
+ } else {
17
+ // retain the fallback for people developing
18
+ // new models that follow the same API
19
+ this.model = new options.model();
20
+ }
15
21
  }
16
22
  }
17
23
 
18
- //
19
- //
20
24
  /**
21
25
  * Accept a figure in bytes for data transfer, and a boolean for whether
22
26
  * the domain shows as 'green', and return a CO2 figure for energy used to shift the corresponding
@@ -45,9 +49,7 @@ class CO2 {
45
49
  transferSize: pageXray.domains[domain].transferSize,
46
50
  });
47
51
  }
48
- co2PerDomain.sort(function (a, b) {
49
- return b.co2 - a.co2;
50
- });
52
+ co2PerDomain.sort((a, b) => b.co2 - a.co2);
51
53
 
52
54
  return co2PerDomain;
53
55
  }
@@ -71,7 +73,7 @@ class CO2 {
71
73
  perContentType(pageXray, greenDomains) {
72
74
  const co2PerContentType = {};
73
75
  for (let asset of pageXray.assets) {
74
- const domain = url.parse(asset.url).domain;
76
+ const domain = new URL(asset.url).domain;
75
77
  const transferSize = asset.transferSize;
76
78
  const co2ForTransfer = this.perByte(
77
79
  transferSize,
@@ -93,16 +95,14 @@ class CO2 {
93
95
  transferSize: co2PerContentType[type].transferSize,
94
96
  });
95
97
  }
96
- all.sort(function (a, b) {
97
- return b.co2 - a.co2;
98
- });
98
+ all.sort((a, b) => b.co2 - a.co2);
99
99
  return all;
100
100
  }
101
101
 
102
102
  dirtiestResources(pageXray, greenDomains) {
103
103
  const allAssets = [];
104
104
  for (let asset of pageXray.assets) {
105
- const domain = url.parse(asset.url).domain;
105
+ const domain = new URL(asset.url).domain;
106
106
  const transferSize = asset.transferSize;
107
107
  const co2ForTransfer = this.perByte(
108
108
  transferSize,
@@ -110,9 +110,7 @@ class CO2 {
110
110
  );
111
111
  allAssets.push({ url: asset.url, co2: co2ForTransfer, transferSize });
112
112
  }
113
- allAssets.sort(function (a, b) {
114
- return b.co2 - a.co2;
115
- });
113
+ allAssets.sort((a, b) => b.co2 - a.co2);
116
114
 
117
115
  return allAssets.slice(0, allAssets.length > 10 ? 10 : allAssets.length);
118
116
  }
@@ -139,4 +137,5 @@ class CO2 {
139
137
  }
140
138
  }
141
139
 
142
- module.exports = CO2;
140
+ export { CO2 };
141
+ export default CO2;
@@ -1,5 +1,5 @@
1
- const GIGABYTE = 1024 * 1024 * 1024;
1
+ const GIGABYTE = 1000 * 1000 * 1000;
2
2
 
3
- module.exports = {
3
+ export default {
4
4
  GIGABYTE,
5
5
  };
@@ -1,3 +1,3 @@
1
- const fileSize = require("./file-size");
1
+ import fileSize from "./file-size.js";
2
2
 
3
- module.exports = { fileSize };
3
+ export { fileSize };
@@ -1,5 +1,3 @@
1
1
  const formatNumber = (num) => parseFloat(num.toFixed(2));
2
2
 
3
- module.exports = {
4
- formatNumber,
5
- };
3
+ export { formatNumber };
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
 
3
- const log = require("debug")("tgwf:hostingAPI");
4
- const https = require("https");
3
+ import debugFactory from "debug";
4
+ const log = debugFactory("tgwf:hostingAPI");
5
5
 
6
6
  function check(domain) {
7
7
  // is it a single domain or an array of them?
@@ -13,21 +13,30 @@ function check(domain) {
13
13
  }
14
14
 
15
15
  async function checkAgainstAPI(domain) {
16
- const res = JSON.parse(
17
- await getBody(`https://api.thegreenwebfoundation.org/greencheck/${domain}`)
16
+ const req = await fetch(
17
+ `https://api.thegreenwebfoundation.org/greencheck/${domain}`
18
18
  );
19
+ const res = await req.json();
19
20
  return res.green;
20
21
  }
21
22
 
22
23
  async function checkDomainsAgainstAPI(domains) {
23
24
  try {
24
- const allGreenCheckResults = JSON.parse(
25
- await getBody(
26
- `https://api.thegreenwebfoundation.org/v2/greencheckmulti/${JSON.stringify(
27
- domains
28
- )}`
29
- )
30
- );
25
+ const apiPath = "https://api.thegreenwebfoundation.org/v2/greencheckmulti";
26
+ const domainsString = JSON.stringify(domains);
27
+
28
+ const req = await fetch(`${apiPath}/${domainsString}`);
29
+
30
+ // sanity check API result. Is this the library or
31
+ // the actual API request that's the problem?
32
+ // Is nock mocking node-native fetch API calls properly?
33
+ log(`${apiPath}/${domainsString}`);
34
+ log({ req });
35
+ const textResult = await req.text();
36
+ log({ textResult });
37
+
38
+ const allGreenCheckResults = await req.json();
39
+
31
40
  return greenDomainsFromResults(allGreenCheckResults);
32
41
  } catch (e) {
33
42
  return [];
@@ -36,39 +45,10 @@ async function checkDomainsAgainstAPI(domains) {
36
45
 
37
46
  function greenDomainsFromResults(greenResults) {
38
47
  const entries = Object.entries(greenResults);
39
- let greenEntries = entries.filter(function ([key, val]) {
40
- return val.green;
41
- });
42
- return greenEntries.map(function ([key, val]) {
43
- return val.url;
44
- });
45
- }
46
-
47
- async function getBody(url) {
48
- // Return new promise
49
- return new Promise(function (resolve, reject) {
50
- // Do async job
51
- const req = https.get(url, function (res) {
52
- if (res.statusCode < 200 || res.statusCode >= 300) {
53
- log(
54
- "Could not get info from the Green Web Foundation API, %s for %s",
55
- res.statusCode,
56
- url
57
- );
58
- return reject(new Error(`Status Code: ${res.statusCode}`));
59
- }
60
- const data = [];
61
-
62
- res.on("data", (chunk) => {
63
- data.push(chunk);
64
- });
65
-
66
- res.on("end", () => resolve(Buffer.concat(data).toString()));
67
- });
68
- req.end();
69
- });
48
+ const greenEntries = entries.filter(([key, val]) => val.green);
49
+ return greenEntries.map(([key, val]) => val.url);
70
50
  }
71
51
 
72
- module.exports = {
52
+ export default {
73
53
  check,
74
54
  };
@@ -1,13 +1,15 @@
1
1
  "use strict";
2
2
 
3
- const log = require("debug")("tgwf:hostingCache");
4
- const path = require("path");
5
- const fs = require("fs");
6
- const zlib = require("zlib");
7
- const { promisify } = require("util");
3
+ import fs from "fs";
4
+ import zlib from "zlib";
5
+ import { promisify } from "util";
6
+
8
7
  const readFile = promisify(fs.readFile);
9
8
  const gunzip = promisify(zlib.gunzip);
10
9
 
10
+ import debugFactory from "debug";
11
+ const log = debugFactory("tgwf:hostingCache");
12
+
11
13
  async function streamToString(stream) {
12
14
  return new Promise((resolve, reject) => {
13
15
  const chunks = [];
@@ -49,13 +51,9 @@ function checkInJSON(domain, db) {
49
51
 
50
52
  function greenDomainsFromResults(greenResults) {
51
53
  const entries = Object.entries(greenResults);
52
- let greenEntries = entries.filter(function ([key, val]) {
53
- return val.green;
54
- });
54
+ const greenEntries = entries.filter(([key, val]) => val.green);
55
55
 
56
- return greenEntries.map(function ([key, val]) {
57
- return val.url;
58
- });
56
+ return greenEntries.map(([key, val]) => val.url);
59
57
  }
60
58
 
61
59
  function checkDomainsInJSON(domains, db) {
@@ -0,0 +1,94 @@
1
+ /*
2
+
3
+ We have a separate node-specific hosting.js file for node.
4
+ This uses the node-specific APIs for making http requests,
5
+ and doing lookups against local JSON and sqlite databases.
6
+ This is used in the CommonJS build of co2.js
7
+
8
+ This lets us keep the total library small, and dependencies minimal.
9
+ */
10
+
11
+ import https from "https";
12
+
13
+ import debugFactory from "debug";
14
+ const log = debugFactory("tgwf:hosting-node");
15
+
16
+ import hostingJSON from "./hosting-json.node.js";
17
+
18
+ /**
19
+ * Accept a url and perform an http request, returning the body
20
+ * for parsing as JSON.
21
+ *
22
+ * @param {string} url
23
+ * @return {string}
24
+ */
25
+ async function getBody(url) {
26
+ return new Promise(function (resolve, reject) {
27
+ // Do async job
28
+ const req = https.get(url, function (res) {
29
+ if (res.statusCode < 200 || res.statusCode >= 300) {
30
+ log(
31
+ "Could not get info from the Green Web Foundation API, %s for %s",
32
+ res.statusCode,
33
+ url
34
+ );
35
+ return reject(new Error(`Status Code: ${res.statusCode}`));
36
+ }
37
+ const data = [];
38
+
39
+ res.on("data", (chunk) => {
40
+ data.push(chunk);
41
+ });
42
+
43
+ res.on("end", () => resolve(Buffer.concat(data).toString()));
44
+ });
45
+ req.end();
46
+ });
47
+ }
48
+
49
+ function check(domain, db) {
50
+ if (db) {
51
+ return hostingJSON.check(domain, db);
52
+ }
53
+
54
+ // is it a single domain or an array of them?
55
+ if (typeof domain === "string") {
56
+ return checkAgainstAPI(domain);
57
+ } else {
58
+ return checkDomainsAgainstAPI(domain);
59
+ }
60
+ }
61
+
62
+ async function checkAgainstAPI(domain) {
63
+ const res = JSON.parse(
64
+ await getBody(`https://api.thegreenwebfoundation.org/greencheck/${domain}`)
65
+ );
66
+ return res.green;
67
+ }
68
+
69
+ async function checkDomainsAgainstAPI(domains) {
70
+ try {
71
+ const allGreenCheckResults = JSON.parse(
72
+ await getBody(
73
+ `https://api.thegreenwebfoundation.org/v2/greencheckmulti/${JSON.stringify(
74
+ domains
75
+ )}`
76
+ )
77
+ );
78
+ return hostingJSON.greenDomainsFromResults(allGreenCheckResults);
79
+ } catch (e) {
80
+ return [];
81
+ }
82
+ }
83
+
84
+ async function checkPage(pageXray, db) {
85
+ const domains = Object.keys(pageXray.domains);
86
+ return check(domains, db);
87
+ }
88
+
89
+ export default {
90
+ check,
91
+ checkPage,
92
+ greendomains: hostingJSON.greenDomainsFromResults,
93
+ loadJSON: hostingJSON.loadJSON,
94
+ };
package/src/hosting.js CHANGED
@@ -1,36 +1,14 @@
1
1
  "use strict";
2
2
 
3
- const log = require("debug")("tgwf:hosting");
4
- const hostingAPI = require("./hosting-api");
5
- const hostingJSON = require("./hosting-json");
3
+ import debugFactory from "debug";
4
+ const log = debugFactory("tgwf:hosting");
6
5
 
7
- function check(domain, db) {
8
- if (db) {
9
- return hostingJSON.check(domain, db);
10
- } else {
11
- return hostingAPI.check(domain);
12
- }
13
- }
14
-
15
- function greenDomainsFromResults(greenResults) {
16
- const entries = Object.entries(greenResults);
17
- let greenEntries = entries.filter(function ([key, val]) {
18
- return val.green;
19
- });
6
+ import hostingAPI from "./hosting-api.js";
20
7
 
21
- return greenEntries.map(function ([key, val]) {
22
- return val.url;
23
- });
24
- }
25
-
26
- async function checkPage(pageXray, db) {
27
- const domains = Object.keys(pageXray.domains);
28
- return check(domains, db);
8
+ function check(domain, db) {
9
+ return hostingAPI.check(domain);
29
10
  }
30
11
 
31
- module.exports = {
12
+ export default {
32
13
  check,
33
- checkPage,
34
- greenDomains: greenDomainsFromResults,
35
- loadJSON: hostingJSON.loadJSON,
36
14
  };
@@ -0,0 +1,4 @@
1
+ import co2 from "./co2.js";
2
+ import hosting from "./hosting-node.js";
3
+
4
+ export { co2, hosting };
package/src/index.js CHANGED
@@ -1,7 +1,5 @@
1
- const co2 = require("./co2");
2
- const hosting = require("./hosting");
1
+ import co2 from "./co2.js";
2
+ import hosting from "./hosting.js";
3
3
 
4
- module.exports = {
5
- co2,
6
- hosting,
7
- };
4
+ export { co2, hosting };
5
+ export default { co2, hosting };
@@ -8,8 +8,11 @@
8
8
  *
9
9
  *
10
10
  */
11
- const { fileSize } = require("./constants");
12
- const { formatNumber } = require("./helpers");
11
+ import debugFactory from "debug";
12
+ const log = debugFactory("tgwf:sustainable-web-design");
13
+
14
+ import { fileSize } from "./constants/index.js";
15
+ import { formatNumber } from "./helpers/index.js";
13
16
 
14
17
  // this refers to the estimated total energy use for the internet around 2000 TWh,
15
18
  // divided by the total transfer it enables around 2500 exabytes
@@ -29,8 +32,8 @@ const RENEWABLES_INTENSITY = 50;
29
32
 
30
33
  // Taken from: https://gitlab.com/wholegrain/carbon-api-2-0/-/blob/master/includes/carbonapi.php
31
34
 
32
- const FIRST_TIME_VIEWING_PERCENTAGE = 0.25;
33
- const RETURNING_VISITOR_PERCENTAGE = 0.75;
35
+ const FIRST_TIME_VIEWING_PERCENTAGE = 0.75;
36
+ const RETURNING_VISITOR_PERCENTAGE = 0.25;
34
37
  const PERCENTAGE_OF_DATA_LOADED_ON_SUBSEQUENT_LOAD = 0.02;
35
38
 
36
39
  class SustainableWebDesign {
@@ -169,13 +172,19 @@ class SustainableWebDesign {
169
172
  const energyBycomponent = this.energyPerByteByComponent(bytes);
170
173
  const cacheAdjustedSegmentEnergy = {};
171
174
 
175
+ log({ energyBycomponent });
176
+ const energyValues = Object.values(energyBycomponent);
177
+
178
+ // for this, we want
172
179
  for (const [key, value] of Object.entries(energyBycomponent)) {
173
180
  // represent the first load
174
- cacheAdjustedSegmentEnergy[key] = value * firstView;
181
+ cacheAdjustedSegmentEnergy[`${key} - first`] = value * firstView;
175
182
 
176
183
  // then represent the subsequent load
177
- cacheAdjustedSegmentEnergy[key] += value * returnView * dataReloadRatio;
184
+ cacheAdjustedSegmentEnergy[`${key} - subsequent`] =
185
+ value * returnView * dataReloadRatio;
178
186
  }
187
+ log({ cacheAdjustedSegmentEnergy });
179
188
 
180
189
  return cacheAdjustedSegmentEnergy;
181
190
  }
@@ -189,12 +198,28 @@ class SustainableWebDesign {
189
198
  */
190
199
  energyPerVisit(bytes) {
191
200
  // fetch the values using the default caching assumptions
192
- const energyValues = Object.values(this.energyPerVisitByComponent(bytes));
201
+ // const energyValues = Object.values(this.energyPerVisitByComponent(bytes));
193
202
 
194
- // return the summed of the values to return our single number
195
- return energyValues.reduce(
196
- (prevValue, currentValue) => prevValue + currentValue
203
+ let firstVisits = 0;
204
+ let subsequentVisits = 0;
205
+
206
+ const energyBycomponent = Object.entries(
207
+ this.energyPerVisitByComponent(bytes)
197
208
  );
209
+
210
+ for (const [key, val] of energyBycomponent) {
211
+ if (key.indexOf("first") > 0) {
212
+ firstVisits += val;
213
+ }
214
+ }
215
+
216
+ for (const [key, val] of energyBycomponent) {
217
+ if (key.indexOf("subsequent") > 0) {
218
+ subsequentVisits += val;
219
+ }
220
+ }
221
+
222
+ return firstVisits + subsequentVisits;
198
223
  }
199
224
 
200
225
  // TODO: this method looks like it applies the carbon intensity
@@ -221,4 +246,5 @@ class SustainableWebDesign {
221
246
  }
222
247
  }
223
248
 
224
- module.exports = SustainableWebDesign;
249
+ export { SustainableWebDesign };
250
+ export default SustainableWebDesign;
package/.eslintrc.json DELETED
@@ -1,21 +0,0 @@
1
- {
2
- "root": true,
3
- "env": {
4
- "node": true,
5
- "es6": true,
6
- "jest/globals": true
7
- },
8
- "parserOptions": {
9
- "ecmaVersion": 8
10
- },
11
- "plugins": ["prettier", "jest"],
12
- "extends": "eslint:recommended",
13
- "rules": {
14
- "prettier/prettier": [
15
- "error",
16
- {
17
- }
18
- ],
19
- "no-unused-vars": "off"
20
- }
21
- }
@@ -1,26 +0,0 @@
1
- name: Unit tests
2
- on:
3
- push:
4
- branches:
5
- - main
6
- pull_request:
7
- branches:
8
- - main
9
- jobs:
10
- build:
11
- runs-on: ubuntu-latest
12
- strategy:
13
- matrix:
14
- node-version: [12.x, 14.x, 16.x]
15
- steps:
16
- - uses: actions/checkout@v2
17
- - name: Use Node.js ${{ matrix.node-version }}
18
- uses: actions/setup-node@v1
19
- with:
20
- node-version: ${{ matrix.node-version }}
21
- - name: Install
22
- run: npm ci
23
- - name: Verify lint
24
- run: npm run lint
25
- - name: Run unit tests
26
- run: npm test